2025-08-04 17:45:44 +02:00
|
|
|
import { Download, Pen, Trash } from "lucide-react";
|
|
|
|
|
import type { GetServerSideProps } from "next";
|
|
|
|
|
import Head from "next/head";
|
|
|
|
|
import { useState, type FormEvent } from "react";
|
|
|
|
|
import { AreaRiservataLayout } from "~/components/Layout";
|
|
|
|
|
import { LoadingPage } from "~/components/loading";
|
|
|
|
|
import { Button } from "~/components/ui/button";
|
|
|
|
|
import type { NextPageWithLayout } from "~/pages/_app";
|
2025-08-20 14:04:23 +02:00
|
|
|
import { useEnforcedSession } from "~/providers/SessionProvider";
|
2025-08-04 17:45:44 +02:00
|
|
|
import type { StorageindexId } from "~/schemas/public/Storageindex";
|
|
|
|
|
import { api } from "~/utils/api";
|
|
|
|
|
import toast from "react-hot-toast";
|
|
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogTrigger,
|
|
|
|
|
} from "~/components/ui/dialog";
|
|
|
|
|
import { useTranslation } from "~/providers/I18nProvider";
|
|
|
|
|
import { Input } from "~/components/ui/input";
|
|
|
|
|
import { Confirm } from "~/components/confirm";
|
|
|
|
|
import { useRouter } from "next/router";
|
|
|
|
|
import { AllegatoIframe } from "~/components/allegato-iframe";
|
|
|
|
|
import { handleDownload } from "~/hooks/filesHooks";
|
|
|
|
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
|
|
|
|
import { zStorageIndexId } from "~/server/utils/zod_types";
|
|
|
|
|
import { DocNotFoundPage } from "~/components/doc_not_found";
|
|
|
|
|
|
|
|
|
|
type AllegatoViewProps = {
|
|
|
|
|
allegatoId: StorageindexId;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
|
|
|
|
allegatoId,
|
|
|
|
|
}: AllegatoViewProps) => {
|
|
|
|
|
const { t } = useTranslation();
|
2025-08-20 14:04:23 +02:00
|
|
|
const session = useEnforcedSession();
|
2025-08-04 17:45:44 +02:00
|
|
|
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
|
|
|
|
|
const { data: fileInfos, isLoading } = api.storage.getStorageFromId.useQuery({
|
|
|
|
|
storageId: allegatoId,
|
|
|
|
|
});
|
|
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
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.getUserStorage.invalidate();
|
|
|
|
|
|
|
|
|
|
toast.success(t.allegati.allegato_rimosso, {
|
|
|
|
|
icon: "🗑️",
|
|
|
|
|
id: context?.toastId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await router.push("/area-riservata/admin/storage");
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-20 14:04:23 +02:00
|
|
|
if (isLoading) return <LoadingPage />;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
if (!fileInfos) return <DocNotFoundPage />;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Head>
|
|
|
|
|
<title>Infoalloggi.it | {fileInfos.filename}</title>
|
|
|
|
|
</Head>
|
|
|
|
|
|
|
|
|
|
<div className="flex h-full w-full grow flex-col md:flex-row">
|
|
|
|
|
<div className="flex grow flex-col justify-center space-y-2 p-4">
|
|
|
|
|
<div className="mx-auto flex flex-wrap items-center gap-4">
|
|
|
|
|
<span>{fileInfos.filename}</span>
|
|
|
|
|
<span>caricato il {fileInfos.created_at.toLocaleString("it")}</span>
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() =>
|
|
|
|
|
handleDownload({
|
|
|
|
|
file: fileInfos,
|
|
|
|
|
getToken: async () => await getToken(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
className="flex gap-2"
|
|
|
|
|
>
|
|
|
|
|
<Download size={16} />
|
|
|
|
|
Download
|
|
|
|
|
</Button>
|
|
|
|
|
<RinominaDialog
|
|
|
|
|
allegatoId={allegatoId}
|
|
|
|
|
filename={fileInfos.filename}
|
|
|
|
|
/>
|
|
|
|
|
{session.user.isAdmin && (
|
|
|
|
|
<Confirm
|
|
|
|
|
onConfirm={() => deleteFile({ storageId: allegatoId })}
|
|
|
|
|
title="Elimina allegato"
|
|
|
|
|
description="Sei sicuro di voler eliminare questo allegato?"
|
|
|
|
|
>
|
|
|
|
|
<Button variant="destructive" className="flex gap-2">
|
|
|
|
|
<Trash size={16} />
|
|
|
|
|
Elimina
|
|
|
|
|
</Button>
|
|
|
|
|
</Confirm>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<AllegatoIframe allegato={fileInfos.id + fileInfos.ext} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AllegatoView.getLayout = function getLayout(page) {
|
|
|
|
|
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getServerSideProps = (async (context) => {
|
|
|
|
|
const allegatoId = context.params?.allegatoId;
|
|
|
|
|
if (!allegatoId || typeof allegatoId !== "string") {
|
|
|
|
|
return {
|
|
|
|
|
notFound: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parsedStorageId = zStorageIndexId.safeParse(allegatoId);
|
|
|
|
|
|
|
|
|
|
if (!parsedStorageId.success) {
|
|
|
|
|
return {
|
|
|
|
|
notFound: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const access_token = context.req.cookies.access_token;
|
|
|
|
|
|
|
|
|
|
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
|
|
|
|
if (helper) {
|
|
|
|
|
await helper.trpc.storage.getStorageFromId.prefetch({
|
|
|
|
|
storageId: parsedStorageId.data,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
props: {
|
|
|
|
|
allegatoId: parsedStorageId.data,
|
|
|
|
|
trpcState: helper.trpc.dehydrate(),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
redirect: {
|
|
|
|
|
destination: "/500",
|
|
|
|
|
permanent: false,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}) satisfies GetServerSideProps<AllegatoViewProps>;
|
|
|
|
|
|
|
|
|
|
export default AllegatoView;
|
|
|
|
|
|
|
|
|
|
const RinominaDialog = ({
|
|
|
|
|
allegatoId,
|
|
|
|
|
filename,
|
|
|
|
|
}: {
|
|
|
|
|
allegatoId: StorageindexId;
|
|
|
|
|
filename: string | null;
|
|
|
|
|
}) => {
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const [editOpen, setEditOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
const { mutate: renameFile } = api.storage.renameFile.useMutation({
|
|
|
|
|
onMutate: () => {
|
|
|
|
|
setEditOpen(false);
|
|
|
|
|
const toastId = toast.loading(t.caricamento, {
|
|
|
|
|
icon: "🪛",
|
|
|
|
|
});
|
|
|
|
|
return { toastId };
|
|
|
|
|
},
|
|
|
|
|
onSuccess: async (_error, _variables, context) => {
|
|
|
|
|
await utils.storage.getStorageFromId.invalidate({
|
|
|
|
|
storageId: allegatoId,
|
|
|
|
|
});
|
|
|
|
|
toast.success(t.successo, {
|
|
|
|
|
icon: "👍",
|
|
|
|
|
id: context?.toastId,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const handleEditSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const form = e.currentTarget;
|
|
|
|
|
const formData = new FormData(form);
|
|
|
|
|
if (!formData.get("filename")) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
renameFile({
|
|
|
|
|
fileId: allegatoId,
|
|
|
|
|
name: formData.get("filename") as string,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
return (
|
|
|
|
|
<Dialog open={editOpen} onOpenChange={setEditOpen}>
|
|
|
|
|
<DialogTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="info"
|
|
|
|
|
onClick={() => console.log("rinomina")}
|
|
|
|
|
className="flex gap-2"
|
|
|
|
|
>
|
|
|
|
|
<Pen size={16} />
|
|
|
|
|
Rinomina
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogTrigger>
|
|
|
|
|
<DialogContent>
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>{t.modifica}</DialogTitle>
|
|
|
|
|
<DialogDescription className="sr-only">modifica</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
<form onSubmit={handleEditSubmit} method="post">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<Input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="Modifica messaggio"
|
|
|
|
|
className="mt-0"
|
|
|
|
|
name="filename"
|
|
|
|
|
defaultValue={filename || undefined}
|
|
|
|
|
minLength={5}
|
|
|
|
|
/>
|
|
|
|
|
<Button type="submit">{t.salva}</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
};
|