2025-08-28 18:27:07 +02:00
|
|
|
import { ChevronsUpDown, RefreshCcw } from "lucide-react";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { useState } from "react";
|
2025-08-28 18:27:07 +02:00
|
|
|
import toast from "react-hot-toast";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { AreaRiservataLayout } from "~/components/Layout";
|
|
|
|
|
import { LoadingPage } from "~/components/loading";
|
|
|
|
|
import { Status500 } from "~/components/status-page";
|
|
|
|
|
import { StorageTable } from "~/components/tables/storage-table";
|
|
|
|
|
import { Button } from "~/components/ui/button";
|
|
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Command,
|
|
|
|
|
CommandEmpty,
|
|
|
|
|
CommandGroup,
|
|
|
|
|
CommandInput,
|
|
|
|
|
CommandItem,
|
|
|
|
|
CommandList,
|
2025-08-04 17:45:44 +02:00
|
|
|
} from "~/components/ui/command";
|
|
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
} from "~/components/ui/dialog";
|
2025-08-04 17:45:44 +02:00
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Popover,
|
|
|
|
|
PopoverContent,
|
|
|
|
|
PopoverTrigger,
|
|
|
|
|
} from "~/components/ui/popover";
|
|
|
|
|
import { Separator } from "~/components/ui/separator";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { UploadModal } from "~/components/upload_modal";
|
|
|
|
|
import { UserAvatar } from "~/components/user_avatar";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { cn } from "~/lib/utils";
|
|
|
|
|
import type { NextPageWithLayout } from "~/pages/_app";
|
|
|
|
|
import { useTranslation } from "~/providers/I18nProvider";
|
|
|
|
|
import {
|
|
|
|
|
StorageTableProvider,
|
|
|
|
|
type StorageTable as StorageTableType,
|
|
|
|
|
useStorageTable,
|
|
|
|
|
} from "~/providers/StorageTableProvider";
|
|
|
|
|
import type {
|
|
|
|
|
Storageindex,
|
|
|
|
|
StorageindexId,
|
|
|
|
|
} from "~/schemas/public/Storageindex";
|
|
|
|
|
import type { Users } from "~/schemas/public/Users";
|
|
|
|
|
import { api } from "~/utils/api";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
const AdminStorage: NextPageWithLayout = () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { data, isLoading, refetch } = api.storage.getAll.useQuery();
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const { mutate: deleteFile } = api.storage.deleteStorage.useMutation({
|
|
|
|
|
onMutate: () => {
|
|
|
|
|
const toastId = toast.loading(t.caricamento, {
|
|
|
|
|
icon: "🪛",
|
|
|
|
|
});
|
|
|
|
|
return { toastId };
|
|
|
|
|
},
|
|
|
|
|
onSuccess: async (_error, _variables, context) => {
|
|
|
|
|
await utils.storage.getAll.invalidate();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
toast.success(t.allegati.allegato_rimosso, {
|
|
|
|
|
icon: "🗑️",
|
|
|
|
|
id: context?.toastId,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const handleDelete = (id: StorageindexId) => {
|
|
|
|
|
deleteFile({ storageId: id });
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const handleRefetch = async () => {
|
|
|
|
|
await refetch();
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (isLoading) return <LoadingPage />;
|
|
|
|
|
if (!data) return <Status500 />;
|
|
|
|
|
//magari query amche file ownership per poter filtrare in tab se da admin o da utente o una lista di utenti a cui è visibile il file
|
|
|
|
|
return (
|
|
|
|
|
<StorageTableProvider>
|
|
|
|
|
<div className="mx-1 flex-1 overflow-auto">
|
|
|
|
|
<div className="mx-auto py-8">
|
|
|
|
|
<StorageTableHeader
|
|
|
|
|
handleDelete={handleDelete}
|
2025-08-29 16:18:32 +02:00
|
|
|
handleRefetch={handleRefetch}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<StorageTable data={data} handleDelete={handleDelete} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</StorageTableProvider>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
export default AdminStorage;
|
|
|
|
|
|
|
|
|
|
const StorageTableHeader = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
handleRefetch,
|
|
|
|
|
handleDelete,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
handleRefetch: () => Promise<void>;
|
|
|
|
|
handleDelete: (id: StorageindexId) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { table } = useStorageTable();
|
|
|
|
|
if (!table) return null;
|
|
|
|
|
return (
|
|
|
|
|
<div className="mx-3 flex flex-wrap items-center justify-between gap-3">
|
|
|
|
|
<div className="flex max-w-lg items-center gap-3">
|
2025-10-10 16:18:43 +02:00
|
|
|
<h3 className="font-bold text-accent-foreground text-xl sm:text-2xl">
|
2025-08-28 18:27:07 +02:00
|
|
|
Impostazioni
|
|
|
|
|
</h3>
|
|
|
|
|
<button
|
2025-08-29 16:18:32 +02:00
|
|
|
className="cursor-pointer hover:scale-110"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={async () => {
|
|
|
|
|
await handleRefetch();
|
|
|
|
|
table.resetRowSelection();
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
type="button"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<RefreshCcw className="size-6" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex max-w-lg flex-wrap items-center gap-3">
|
|
|
|
|
<UploadModal
|
|
|
|
|
cb_onUpload={() => {
|
|
|
|
|
table.setRowSelection({});
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<Actions handleDelete={handleDelete} table={table} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AdminStorage.getLayout = function getLayout(page) {
|
2025-08-28 18:27:07 +02:00
|
|
|
return <AreaRiservataLayout>{page}</AreaRiservataLayout>;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Actions = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
handleDelete,
|
|
|
|
|
table,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
handleDelete: (id: StorageindexId) => void;
|
|
|
|
|
table: StorageTableType;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { selectedRows } = useStorageTable();
|
|
|
|
|
const [visbOpen, setVisbOpen] = useState(false);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const deleteSelected = () => {
|
|
|
|
|
selectedRows.forEach((row) => handleDelete(row.id));
|
|
|
|
|
table.resetRowSelection();
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<div className="flex items-center gap-4 md:mt-0">
|
|
|
|
|
<Button
|
|
|
|
|
className="transition-all duration-100"
|
2025-08-29 16:18:32 +02:00
|
|
|
disabled={selectedRows.length === 0}
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => setVisbOpen(true)}
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="info"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
Modifica Visibilità
|
|
|
|
|
</Button>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<Button
|
2025-08-29 16:18:32 +02:00
|
|
|
className="transition-all duration-100"
|
2025-08-28 18:27:07 +02:00
|
|
|
disabled={selectedRows.length === 0}
|
|
|
|
|
onClick={() => deleteSelected()}
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="destructive"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
Elimina
|
|
|
|
|
</Button>
|
|
|
|
|
{visbOpen && (
|
|
|
|
|
<VisibComponent
|
|
|
|
|
allegati={selectedRows}
|
|
|
|
|
open={visbOpen}
|
|
|
|
|
setOpen={setVisbOpen}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const VisibComponent = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
allegati,
|
|
|
|
|
open,
|
|
|
|
|
setOpen,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
allegati: Storageindex[];
|
|
|
|
|
open: boolean;
|
|
|
|
|
setOpen: (v: boolean) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const { data, isLoading } = api.storage.getStorageVisibility.useQuery({
|
|
|
|
|
storageIds: allegati.map((a) => a.id),
|
|
|
|
|
});
|
|
|
|
|
const { data: users } = api.users.getUsers.useQuery();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { mutate: remove } = api.storage.removeStorageVisibility.useMutation({
|
|
|
|
|
onMutate: () => {
|
|
|
|
|
const toastId = toast.loading("Rimozione utente in corso", {
|
|
|
|
|
icon: "🪛",
|
|
|
|
|
});
|
|
|
|
|
return { toastId };
|
|
|
|
|
},
|
|
|
|
|
onSuccess: async (_error, _variables, context) => {
|
|
|
|
|
await utils.storage.getStorageVisibility.invalidate();
|
|
|
|
|
toast.success("Utente rimosso", {
|
|
|
|
|
icon: "🗑️",
|
|
|
|
|
id: context?.toastId,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const { mutate: add } = api.storage.addStorageVisibility.useMutation({
|
|
|
|
|
onMutate: () => {
|
|
|
|
|
const toastId = toast.loading("Aggiunta utente in corso", {
|
|
|
|
|
icon: "🪛",
|
|
|
|
|
});
|
|
|
|
|
return { toastId };
|
|
|
|
|
},
|
|
|
|
|
onSuccess: async (_error, _variables, context) => {
|
|
|
|
|
await utils.storage.getStorageVisibility.invalidate();
|
|
|
|
|
toast.success("Utente aggiunto", {
|
|
|
|
|
icon: "👍",
|
|
|
|
|
id: context?.toastId,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { mutate: reset } = api.storage.resetStorageVisibility.useMutation({
|
|
|
|
|
onMutate: () => {
|
|
|
|
|
const toastId = toast.loading("Reset visibilità in corso", {
|
|
|
|
|
icon: "🪛",
|
|
|
|
|
});
|
|
|
|
|
return { toastId };
|
|
|
|
|
},
|
|
|
|
|
onSuccess: async (_error, _variables, context) => {
|
|
|
|
|
await utils.storage.getStorageVisibility.invalidate();
|
|
|
|
|
toast.success("Reset effettuato", {
|
|
|
|
|
icon: "👍",
|
|
|
|
|
id: context?.toastId,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<>
|
2025-08-29 16:18:32 +02:00
|
|
|
<Dialog onOpenChange={setOpen} open={open}>
|
2025-08-28 18:27:07 +02:00
|
|
|
<DialogContent className="max-w-2xl">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>Modifica Visibilità</DialogTitle>
|
|
|
|
|
<DialogDescription className="sr-only">
|
|
|
|
|
Modifica la visibilità degli allegati selezionati
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
{isLoading ? (
|
|
|
|
|
<LoadingPage />
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<p>Seleziona utente:</p>
|
|
|
|
|
<UtentiCombo
|
|
|
|
|
onSelect={(user) => {
|
|
|
|
|
for (const allegato of allegati) {
|
2025-08-29 16:18:32 +02:00
|
|
|
add({ storageId: allegato.id, userId: user.id });
|
2025-08-28 18:27:07 +02:00
|
|
|
}
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
users={users || []}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
<Button
|
|
|
|
|
className="ml-auto"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
reset({ storageId: allegati.map((a) => a.id) })
|
|
|
|
|
}
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="destructive"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
Reset
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
{data?.map((allegato, i) => (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex flex-col gap-3 rounded-md p-2",
|
|
|
|
|
i % 2 === 0 && "bg-gray-200",
|
|
|
|
|
)}
|
2025-08-29 16:18:32 +02:00
|
|
|
key={allegato.id}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
2025-10-10 16:18:43 +02:00
|
|
|
<p className="font-semibold text-lg">
|
2025-08-28 18:27:07 +02:00
|
|
|
File: {allegato.filename}
|
|
|
|
|
</p>
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<p>Visibile a:</p>
|
|
|
|
|
{allegato.users_storages.length === 0 ? (
|
|
|
|
|
<p>Nessuno</p>
|
|
|
|
|
) : (
|
|
|
|
|
<ul className="flex gap-2">
|
|
|
|
|
{allegato.users_storages.map((user) => (
|
|
|
|
|
<li key={user.userid}>
|
|
|
|
|
<Button
|
|
|
|
|
className="after:content-['X']"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
remove({
|
|
|
|
|
storageId: allegato.id,
|
2025-08-29 16:18:32 +02:00
|
|
|
userId: user.userid,
|
2025-08-28 18:27:07 +02:00
|
|
|
})
|
|
|
|
|
}
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="outline"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
{user.username}
|
|
|
|
|
</Button>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
<Separator />
|
|
|
|
|
</div>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const UtentiCombo = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
users,
|
|
|
|
|
onSelect,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
users: Users[];
|
|
|
|
|
onSelect: (user: Users) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const [open, setOpen] = useState(false);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
2025-08-29 16:18:32 +02:00
|
|
|
<Popover onOpenChange={setOpen} open={open}>
|
2025-08-28 18:27:07 +02:00
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
aria-expanded={open}
|
|
|
|
|
className="w-auto justify-between"
|
2025-08-29 16:18:32 +02:00
|
|
|
role="combobox"
|
|
|
|
|
variant="outline"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
Seleziona utente...
|
|
|
|
|
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
|
|
|
|
</Button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className="w-auto p-0">
|
|
|
|
|
<Command>
|
|
|
|
|
<CommandInput placeholder="Cerca Utente..." />
|
|
|
|
|
<CommandList>
|
|
|
|
|
<CommandEmpty>Nessun utente.</CommandEmpty>
|
|
|
|
|
<CommandGroup>
|
|
|
|
|
{users.map((user) => (
|
|
|
|
|
<CommandItem
|
|
|
|
|
key={user.id}
|
|
|
|
|
onSelect={(currentValue) => {
|
|
|
|
|
// biome-ignore lint/style/noNonNullAssertion: <known lenght>
|
|
|
|
|
onSelect(users.find((u) => u.id === currentValue)!);
|
|
|
|
|
setOpen(false);
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
value={user.id}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<span className="flex items-center gap-2">
|
|
|
|
|
<UserAvatar
|
|
|
|
|
className="size-8"
|
|
|
|
|
userId={user.id}
|
|
|
|
|
username={user.username}
|
|
|
|
|
/>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<span>
|
|
|
|
|
<span className="block font-medium">{user.username}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</CommandItem>
|
|
|
|
|
))}
|
|
|
|
|
</CommandGroup>
|
|
|
|
|
</CommandList>
|
|
|
|
|
</Command>
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|