2025-08-04 17:45:44 +02:00
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Check,
|
|
|
|
|
ChevronsUpDown,
|
|
|
|
|
CircleCheck,
|
|
|
|
|
ClipboardPaste,
|
|
|
|
|
ExternalLink,
|
|
|
|
|
FolderKanban,
|
|
|
|
|
Pen,
|
|
|
|
|
Plus,
|
|
|
|
|
Trash2,
|
|
|
|
|
Wrench,
|
2025-08-04 17:45:44 +02:00
|
|
|
} from "lucide-react";
|
2025-08-28 18:27:07 +02:00
|
|
|
import Link from "next/link";
|
|
|
|
|
import { useRef, useState } from "react";
|
|
|
|
|
import toast from "react-hot-toast";
|
|
|
|
|
import { z } from "zod/v4";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { ExtIcon } from "~/components/area-riservata/allegati";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { Confirm } from "~/components/confirm";
|
2025-08-04 17:45:44 +02:00
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
AnimatedButton,
|
|
|
|
|
type AnimatedButtonRef,
|
|
|
|
|
} from "~/components/custom_ui/animated-button";
|
2025-08-04 17:45:44 +02:00
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Credenza,
|
|
|
|
|
CredenzaBody,
|
|
|
|
|
CredenzaClose,
|
|
|
|
|
CredenzaContent,
|
|
|
|
|
CredenzaDescription,
|
|
|
|
|
CredenzaFooter,
|
|
|
|
|
CredenzaHeader,
|
|
|
|
|
CredenzaTitle,
|
|
|
|
|
CredenzaTrigger,
|
2025-08-04 17:45:44 +02:00
|
|
|
} from "~/components/custom_ui/credenza";
|
|
|
|
|
import { LoadingPage } from "~/components/loading";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { Button, buttonVariants } from "~/components/ui/button";
|
2025-08-04 17:45:44 +02:00
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardFooter,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
2025-08-04 17:45:44 +02:00
|
|
|
} from "~/components/ui/card";
|
2025-08-28 18:27:07 +02:00
|
|
|
import {
|
|
|
|
|
Command,
|
|
|
|
|
CommandEmpty,
|
|
|
|
|
CommandGroup,
|
|
|
|
|
CommandInput,
|
|
|
|
|
CommandItem,
|
|
|
|
|
CommandList,
|
|
|
|
|
} from "~/components/ui/command";
|
|
|
|
|
import { Label } from "~/components/ui/label";
|
|
|
|
|
import {
|
|
|
|
|
Popover,
|
|
|
|
|
PopoverContent,
|
|
|
|
|
PopoverTrigger,
|
|
|
|
|
} from "~/components/ui/popover";
|
|
|
|
|
import { Separator } from "~/components/ui/separator";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { Switch } from "~/components/ui/switch";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { UploadModal } from "~/components/upload_modal";
|
|
|
|
|
import { cn, formatCurrency } from "~/lib/utils";
|
|
|
|
|
import { useTranslation } from "~/providers/I18nProvider";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
|
|
|
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
2025-08-28 18:27:07 +02:00
|
|
|
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
|
2025-08-19 19:14:27 +02:00
|
|
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
2025-10-24 16:10:59 +02:00
|
|
|
import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { api } from "~/utils/api";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
export const AnnuncioActions = () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { servizioId, userId, isAdmin } = useServizio();
|
|
|
|
|
const { data, annuncioId } = useServizioAnnuncio();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const { mutate } = api.servizio.removeAnnuncioServizio.useMutation({
|
2025-08-29 16:18:32 +02:00
|
|
|
onError: (error) => {
|
|
|
|
|
console.error(error);
|
|
|
|
|
toast.error("Errore durante la rimozione dell'annuncio");
|
|
|
|
|
},
|
2025-08-28 18:27:07 +02:00
|
|
|
onSuccess: async () => {
|
|
|
|
|
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
|
|
|
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
|
|
|
servizioId,
|
|
|
|
|
});
|
|
|
|
|
toast.success("Annuncio rimosso");
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if ((!isAdmin && data.open_contatti_at == null) || isAdmin) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex w-full max-w-fit flex-wrap justify-start gap-3 sm:justify-end">
|
|
|
|
|
{isAdmin && data.user_confirmed_at != null && <AdminLavoraConferma />}
|
|
|
|
|
{isAdmin && data.accettato_conferma_at != null && (
|
|
|
|
|
<>
|
|
|
|
|
<AdminContratto />
|
|
|
|
|
{data.gestionale_id && (
|
|
|
|
|
<Link
|
|
|
|
|
aria-label="Open Gestionale"
|
|
|
|
|
href={`https://win-jk822fggr6b:480/id/${data.gestionale_id}`}
|
|
|
|
|
target="_blank"
|
|
|
|
|
>
|
|
|
|
|
<Button className="flex items-center gap-2">
|
|
|
|
|
<FolderKanban className="size-5" /> Gestionale
|
|
|
|
|
</Button>
|
|
|
|
|
</Link>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{isAdmin && (
|
|
|
|
|
<>
|
|
|
|
|
<Link
|
|
|
|
|
href={`/area-riservata/admin/edit-annuncio/${annuncioId}`}
|
|
|
|
|
target="_blank"
|
|
|
|
|
>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit Annuncio"
|
|
|
|
|
className="flex w-full items-center gap-2"
|
|
|
|
|
size="sm"
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="secondary"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<Wrench className="size-6" />
|
|
|
|
|
</Button>
|
|
|
|
|
</Link>
|
2025-08-10 17:54:00 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<Confirm
|
|
|
|
|
description="Sei sicuro di voler eliminare l'annuncio?"
|
|
|
|
|
onConfirm={() => {
|
|
|
|
|
mutate({
|
|
|
|
|
annuncioId,
|
2025-08-29 16:18:32 +02:00
|
|
|
servizioId,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
title="Elimina annuncio"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Delete Annuncio"
|
|
|
|
|
className="flex items-center gap-2"
|
|
|
|
|
size="sm"
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="destructive"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<Trash2 className="size-6" />
|
|
|
|
|
</Button>
|
|
|
|
|
</Confirm>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ContrattoImportSchema = z.object({
|
2025-08-28 18:27:07 +02:00
|
|
|
contratto_decorrenza: z.string().nullable(),
|
|
|
|
|
contratto_scadenza: z.string().nullable(),
|
|
|
|
|
contratto_tipo: z
|
|
|
|
|
.enum([
|
|
|
|
|
"Transitorio",
|
|
|
|
|
"Agevolato",
|
|
|
|
|
"4 + 4",
|
|
|
|
|
"30 Giorni",
|
|
|
|
|
"Transi.Age",
|
|
|
|
|
"Foresteria",
|
|
|
|
|
"Commerciale",
|
|
|
|
|
])
|
|
|
|
|
.nullable(),
|
2025-08-29 16:18:32 +02:00
|
|
|
gestionale_id: z.number().nullable(),
|
2025-08-04 17:45:44 +02:00
|
|
|
});
|
|
|
|
|
|
2025-08-10 17:54:00 +02:00
|
|
|
interface ContrattoImportObj
|
2025-08-28 18:27:07 +02:00
|
|
|
extends Omit<
|
|
|
|
|
z.infer<typeof ContrattoImportSchema>,
|
|
|
|
|
"contratto_decorrenza" | "contratto_scadenza"
|
|
|
|
|
> {
|
|
|
|
|
contratto_decorrenza: Date | null;
|
|
|
|
|
contratto_scadenza: Date | null;
|
2025-08-10 17:54:00 +02:00
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
const parseContrattoImport = (ingest: string) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
let parsedJson: unknown;
|
|
|
|
|
try {
|
|
|
|
|
parsedJson = JSON.parse(ingest);
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error(
|
|
|
|
|
'Stringa non valida, clicca "copia stringa" nel gestionale e poi questo pulsante',
|
|
|
|
|
);
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
const parsed = ContrattoImportSchema.safeParse(parsedJson);
|
|
|
|
|
if (parsed.success) {
|
|
|
|
|
return {
|
|
|
|
|
...parsed.data,
|
|
|
|
|
contratto_decorrenza: parsed.data.contratto_decorrenza
|
|
|
|
|
? new Date(parsed.data.contratto_decorrenza)
|
|
|
|
|
: null,
|
|
|
|
|
contratto_scadenza: parsed.data.contratto_scadenza
|
|
|
|
|
? new Date(parsed.data.contratto_scadenza)
|
|
|
|
|
: null,
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-09-01 16:48:03 +02:00
|
|
|
console.error("Invalid format:", parsed.error);
|
|
|
|
|
return undefined;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ContrattoImport = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate: (upd: ServizioAnnunciUpdate) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { data } = useServizioAnnuncio();
|
|
|
|
|
const [contratto, setContratto] = useState<ContrattoImportObj | undefined>({
|
|
|
|
|
contratto_decorrenza: data.contratto_decorrenza
|
|
|
|
|
? new Date(data.contratto_decorrenza)
|
|
|
|
|
: null,
|
|
|
|
|
contratto_scadenza: data.contratto_scadenza
|
|
|
|
|
? new Date(data.contratto_scadenza)
|
|
|
|
|
: null,
|
|
|
|
|
contratto_tipo: data.contratto_tipo as ContrattoImportObj["contratto_tipo"],
|
2025-08-29 16:18:32 +02:00
|
|
|
gestionale_id: data.gestionale_id,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
const handleSave = () => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
...contratto,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Dati Contratto</CardTitle>
|
|
|
|
|
<CardDescription className="sr-only">desc</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Import Contratto"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="flex items-center gap-2"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={async () => {
|
|
|
|
|
const clipboardText = await navigator.clipboard.readText();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
setContratto(parseContrattoImport(clipboardText));
|
|
|
|
|
}}
|
|
|
|
|
variant="info"
|
|
|
|
|
>
|
|
|
|
|
<ClipboardPaste className="size-4" />
|
|
|
|
|
Clicca qui per importare la stringa dal gestionale
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Reset Contratto"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="flex items-center gap-2"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={async () => {
|
|
|
|
|
setContratto({
|
|
|
|
|
contratto_decorrenza: null,
|
|
|
|
|
contratto_scadenza: null,
|
|
|
|
|
contratto_tipo: null,
|
2025-08-29 16:18:32 +02:00
|
|
|
gestionale_id: null,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
variant="destructive"
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="size-4" />
|
|
|
|
|
Reset
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
{contratto ? (
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<p>
|
|
|
|
|
<span className="font-semibold">ID Gestionale: </span>
|
|
|
|
|
{contratto.gestionale_id}{" "}
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit Gestionale ID"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="h-6 px-1.5 [&_svg]:size-4"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
const newValue = prompt(
|
|
|
|
|
"Modifica l'ID gestionale del contratto:",
|
|
|
|
|
contratto.gestionale_id?.toString() || "",
|
|
|
|
|
);
|
|
|
|
|
if (newValue !== null) {
|
|
|
|
|
setContratto((prev) =>
|
|
|
|
|
prev
|
|
|
|
|
? { ...prev, gestionale_id: parseInt(newValue) }
|
|
|
|
|
: undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
<Pen />
|
|
|
|
|
</Button>
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<span className="font-semibold">Decorrenza: </span>
|
|
|
|
|
{contratto.contratto_decorrenza &&
|
|
|
|
|
new Date(contratto.contratto_decorrenza).toLocaleDateString(
|
|
|
|
|
"it-IT",
|
|
|
|
|
)}{" "}
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit Decorrenza"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="h-6 px-1.5 [&_svg]:size-4"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
const newValue = prompt(
|
|
|
|
|
"Modifica la decorrenza del contratto (DD/MM/YYYY):",
|
|
|
|
|
contratto.contratto_decorrenza
|
|
|
|
|
? new Date(
|
|
|
|
|
contratto.contratto_decorrenza,
|
|
|
|
|
).toLocaleDateString("it-IT")
|
|
|
|
|
: "",
|
|
|
|
|
);
|
|
|
|
|
if (newValue !== null) {
|
|
|
|
|
setContratto((prev) =>
|
|
|
|
|
prev
|
|
|
|
|
? {
|
|
|
|
|
...prev,
|
|
|
|
|
contratto_decorrenza: new Date(newValue),
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
<Pen />
|
|
|
|
|
</Button>
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<span className="font-semibold">Scadenza: </span>
|
|
|
|
|
{contratto.contratto_scadenza &&
|
|
|
|
|
new Date(contratto.contratto_scadenza).toLocaleDateString(
|
|
|
|
|
"it-IT",
|
|
|
|
|
)}{" "}
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit Scadenza"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="h-6 px-1.5 [&_svg]:size-4"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
const newValue = prompt(
|
|
|
|
|
"Modifica la scadenza del contratto (DD/MM/YYYY):",
|
|
|
|
|
contratto.contratto_scadenza
|
|
|
|
|
? new Date(
|
|
|
|
|
contratto.contratto_scadenza,
|
|
|
|
|
).toLocaleDateString("it-IT")
|
|
|
|
|
: "",
|
|
|
|
|
);
|
|
|
|
|
if (newValue !== null) {
|
|
|
|
|
setContratto((prev) =>
|
|
|
|
|
prev
|
|
|
|
|
? {
|
|
|
|
|
...prev,
|
|
|
|
|
contratto_scadenza: new Date(newValue),
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
<Pen />
|
|
|
|
|
</Button>
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<span className="font-semibold">Tipo di contratto: </span>
|
|
|
|
|
{contratto.contratto_tipo}{" "}
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit Tipo di contratto"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="h-6 px-1.5 [&_svg]:size-4"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
const newValue = prompt(
|
|
|
|
|
"Modifica il tipo di contratto:",
|
|
|
|
|
contratto.contratto_tipo || "",
|
|
|
|
|
);
|
|
|
|
|
if (
|
|
|
|
|
newValue !== null &&
|
|
|
|
|
ContrattoImportSchema.shape.contratto_tipo.safeParse(
|
|
|
|
|
newValue,
|
|
|
|
|
).success
|
|
|
|
|
) {
|
|
|
|
|
setContratto((prev) =>
|
|
|
|
|
prev
|
|
|
|
|
? {
|
|
|
|
|
...prev,
|
|
|
|
|
contratto_tipo:
|
|
|
|
|
newValue as ContrattoImportObj["contratto_tipo"],
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
<Pen />
|
|
|
|
|
</Button>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<p>Inserisci la stringa dal gestionale e elabora</p>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
<CardFooter>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Save Contratto"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleSave();
|
|
|
|
|
}}
|
|
|
|
|
variant="success"
|
|
|
|
|
>
|
|
|
|
|
Salva
|
|
|
|
|
</Button>
|
|
|
|
|
</CardFooter>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AdminContratto = () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { userId, servizioId } = useServizio();
|
|
|
|
|
const { data, annuncioId } = useServizioAnnuncio();
|
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
|
|
|
const utils = api.useUtils();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { mutate: updateServizio } =
|
|
|
|
|
api.servizio.updateServizioAnnunci.useMutation({
|
2025-08-29 16:18:32 +02:00
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(error.message);
|
|
|
|
|
},
|
2025-08-28 18:27:07 +02:00
|
|
|
onSuccess: async () => {
|
|
|
|
|
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
|
|
|
await utils.servizio.getServizio.invalidate({
|
|
|
|
|
servizioId,
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
toast.success("Dati salvati con successo");
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const handleUpdate = (upd: ServizioAnnunciUpdate) => {
|
|
|
|
|
updateServizio({
|
|
|
|
|
annuncioId,
|
|
|
|
|
data: {
|
|
|
|
|
...upd,
|
|
|
|
|
},
|
2025-08-29 16:18:32 +02:00
|
|
|
servizioId,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
};
|
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
|
|
|
<Credenza onOpenChange={setOpen} open={open}>
|
2025-08-28 18:27:07 +02:00
|
|
|
<CredenzaTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Open Contratto Section"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="flex w-fit items-center gap-2"
|
|
|
|
|
variant={data.doc_contratto_added ? "success" : "secondary"}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
{data.doc_contratto_added ? (
|
|
|
|
|
<CircleCheck className="size-6" />
|
|
|
|
|
) : (
|
|
|
|
|
<Wrench className="size-6" />
|
|
|
|
|
)}
|
|
|
|
|
Contratto
|
|
|
|
|
</Button>
|
|
|
|
|
</CredenzaTrigger>
|
|
|
|
|
<CredenzaContent className="max-h-[90vh] max-w-4xl px-3 sm:max-w-5xl">
|
|
|
|
|
<CredenzaHeader>
|
|
|
|
|
<CredenzaTitle>Sezione Contratto</CredenzaTitle>
|
|
|
|
|
<CredenzaDescription className="sr-only">
|
|
|
|
|
Contratto modal
|
|
|
|
|
</CredenzaDescription>
|
|
|
|
|
</CredenzaHeader>
|
|
|
|
|
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
|
|
|
|
<div className="flex flex-col gap-8">
|
|
|
|
|
<ContrattoSection handleUpdate={handleUpdate} />
|
|
|
|
|
<Separator />
|
|
|
|
|
<ContrattoImport handleUpdate={handleUpdate} />
|
|
|
|
|
<Separator />
|
|
|
|
|
<RicevutaSection handleUpdate={handleUpdate} />
|
|
|
|
|
</div>
|
|
|
|
|
</CredenzaBody>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<CredenzaFooter>
|
|
|
|
|
<CredenzaClose asChild>
|
2025-08-29 16:18:32 +02:00
|
|
|
<Button aria-label="Close Credenza" variant="outline">
|
2025-08-28 18:27:07 +02:00
|
|
|
Chiudi
|
|
|
|
|
</Button>
|
|
|
|
|
</CredenzaClose>
|
|
|
|
|
</CredenzaFooter>
|
|
|
|
|
</CredenzaContent>
|
|
|
|
|
</Credenza>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ContrattoSection = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate: (upd: ServizioAnnunciUpdate) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { userId } = useServizio();
|
|
|
|
|
const { data } = useServizioAnnuncio();
|
2025-10-24 16:10:59 +02:00
|
|
|
const [selectedDoc, setSelectedDoc] = useState<string | null>(
|
2025-08-28 18:27:07 +02:00
|
|
|
data.doc_contratto_ref,
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const utils = api.useUtils();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { data: files, isLoading } = api.storage.getAll.useQuery();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
|
|
|
|
onSuccess: async () => {
|
2025-10-24 16:10:59 +02:00
|
|
|
await utils.storage.retrieveUserFileData.invalidate({
|
2025-08-28 18:27:07 +02:00
|
|
|
userId,
|
|
|
|
|
});
|
|
|
|
|
await utils.storage.getAll.invalidate();
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const handleSave = () => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
doc_contratto_added: selectedDoc != null,
|
2025-08-29 16:18:32 +02:00
|
|
|
doc_contratto_ref: selectedDoc,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
if (selectedDoc) {
|
|
|
|
|
setUserStorage({
|
2025-10-24 16:10:59 +02:00
|
|
|
storageId: selectedDoc,
|
2025-08-29 16:18:32 +02:00
|
|
|
userId,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (isLoading) return <LoadingPage />;
|
|
|
|
|
if (files === undefined) {
|
|
|
|
|
return <p>Errore nel caricamento dei documenti</p>;
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Documento del Contratto</CardTitle>
|
|
|
|
|
<CardDescription>contratto di locazione</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
{selectedDoc && (
|
|
|
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
|
|
|
<DocInfo storageId={selectedDoc}>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Reset Contratto"
|
|
|
|
|
className="flex items-center gap-3"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
doc_contratto_added: false,
|
2025-08-29 16:18:32 +02:00
|
|
|
doc_contratto_ref: null,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
setSelectedDoc(null);
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="destructive"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<Trash2 className="size-6" />
|
|
|
|
|
</Button>
|
|
|
|
|
</DocInfo>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<DocCombo
|
2025-08-29 16:18:32 +02:00
|
|
|
current={selectedDoc}
|
2025-08-28 18:27:07 +02:00
|
|
|
files={files}
|
|
|
|
|
onSelect={(f) => {
|
|
|
|
|
setSelectedDoc(f.id);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<UploadModal isAdmin userId={userId} />
|
|
|
|
|
</CardContent>
|
|
|
|
|
<CardFooter>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Save Contratto"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleSave();
|
|
|
|
|
}}
|
|
|
|
|
variant="success"
|
|
|
|
|
>
|
|
|
|
|
Salva
|
|
|
|
|
</Button>
|
|
|
|
|
</CardFooter>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const RicevutaSection = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate: (upd: ServizioAnnunciUpdate) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { userId } = useServizio();
|
|
|
|
|
const { data } = useServizioAnnuncio();
|
2025-10-24 16:10:59 +02:00
|
|
|
const [selectedDoc, setSelectedDoc] = useState<string | null>(
|
2025-08-28 18:27:07 +02:00
|
|
|
data.doc_registrazione_ref,
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const utils = api.useUtils();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { data: files, isLoading } = api.storage.getAll.useQuery();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
|
|
|
|
onSuccess: async () => {
|
2025-10-24 16:10:59 +02:00
|
|
|
await utils.storage.retrieveUserFileData.invalidate({
|
2025-08-28 18:27:07 +02:00
|
|
|
userId,
|
|
|
|
|
});
|
|
|
|
|
await utils.storage.getAll.invalidate();
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const handleSave = () => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
doc_registrazione_added: selectedDoc != null,
|
2025-08-29 16:18:32 +02:00
|
|
|
doc_registrazione_ref: selectedDoc,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
if (selectedDoc) {
|
|
|
|
|
setUserStorage({
|
2025-10-24 16:10:59 +02:00
|
|
|
storageId: selectedDoc,
|
2025-08-29 16:18:32 +02:00
|
|
|
userId,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (isLoading) return <LoadingPage />;
|
|
|
|
|
if (files === undefined) {
|
|
|
|
|
return <p>Errore nel caricamento dei documenti</p>;
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Ricevuta di Registrazione</CardTitle>
|
|
|
|
|
<CardDescription className="sr-only">desc</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
{selectedDoc && (
|
|
|
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
|
|
|
<DocInfo storageId={selectedDoc}>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Reset Ricevuta"
|
|
|
|
|
className="flex items-center gap-3"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
doc_registrazione_added: false,
|
2025-08-29 16:18:32 +02:00
|
|
|
doc_registrazione_ref: null,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
setSelectedDoc(null);
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="destructive"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<Trash2 className="size-6" />
|
|
|
|
|
</Button>
|
|
|
|
|
</DocInfo>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<DocCombo
|
2025-08-29 16:18:32 +02:00
|
|
|
current={selectedDoc}
|
2025-08-28 18:27:07 +02:00
|
|
|
files={files}
|
|
|
|
|
onSelect={(f) => {
|
|
|
|
|
setSelectedDoc(f.id);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<UploadModal isAdmin userId={userId} />
|
|
|
|
|
</CardContent>
|
|
|
|
|
<CardFooter>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Save Ricevuta"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleSave();
|
|
|
|
|
}}
|
|
|
|
|
variant="success"
|
|
|
|
|
>
|
|
|
|
|
Salva
|
|
|
|
|
</Button>
|
|
|
|
|
</CardFooter>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AdminLavoraConferma = () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { userId, servizioId } = useServizio();
|
|
|
|
|
const { data, annuncioId } = useServizioAnnuncio();
|
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
|
|
|
const utils = api.useUtils();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { mutate: updateServizio } =
|
|
|
|
|
api.servizio.updateServizioAnnunci.useMutation({
|
2025-08-29 16:18:32 +02:00
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(error.message);
|
|
|
|
|
},
|
2025-08-28 18:27:07 +02:00
|
|
|
onSuccess: async () => {
|
|
|
|
|
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
|
|
|
await utils.servizio.getServizio.invalidate({
|
|
|
|
|
servizioId,
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
toast.success("Dati salvati con successo");
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const handleUpdate = (upd: ServizioAnnunciUpdate) => {
|
|
|
|
|
updateServizio({
|
|
|
|
|
annuncioId,
|
|
|
|
|
data: {
|
|
|
|
|
...upd,
|
|
|
|
|
},
|
2025-08-29 16:18:32 +02:00
|
|
|
servizioId,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
};
|
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
|
|
|
<Credenza onOpenChange={setOpen} open={open}>
|
2025-08-28 18:27:07 +02:00
|
|
|
<CredenzaTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Open Lavora Conferma Section"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="flex w-fit items-center gap-2"
|
|
|
|
|
variant={data.hasConfermaAdmin ? "success" : "secondary"}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
{data.hasConfermaAdmin ? (
|
|
|
|
|
<CircleCheck className="size-6" />
|
|
|
|
|
) : (
|
|
|
|
|
<Wrench className="size-6" />
|
|
|
|
|
)}
|
|
|
|
|
Conferma
|
|
|
|
|
</Button>
|
|
|
|
|
</CredenzaTrigger>
|
|
|
|
|
<CredenzaContent className="max-h-[90vh] w-full px-3 sm:max-w-4xl">
|
|
|
|
|
<CredenzaHeader>
|
|
|
|
|
<CredenzaTitle>Lavorazione conferma</CredenzaTitle>
|
|
|
|
|
<CredenzaDescription className="sr-only">
|
|
|
|
|
Consegna status modal
|
|
|
|
|
</CredenzaDescription>
|
|
|
|
|
</CredenzaHeader>
|
|
|
|
|
<CredenzaBody className="max-h-[80vh] w-full overflow-y-auto pb-5">
|
|
|
|
|
<div className="flex flex-col gap-8">
|
|
|
|
|
<DocSection handleUpdate={handleUpdate} />
|
|
|
|
|
<Caparra handleUpdate={handleUpdate} />
|
|
|
|
|
<SendConferma handleUpdate={handleUpdate} />
|
|
|
|
|
</div>
|
|
|
|
|
</CredenzaBody>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<CredenzaFooter>
|
|
|
|
|
<CredenzaClose asChild>
|
2025-08-29 16:18:32 +02:00
|
|
|
<Button aria-label="Close Credenza" variant="outline">
|
2025-08-28 18:27:07 +02:00
|
|
|
Chiudi
|
|
|
|
|
</Button>
|
|
|
|
|
</CredenzaClose>
|
|
|
|
|
</CredenzaFooter>
|
|
|
|
|
</CredenzaContent>
|
|
|
|
|
</Credenza>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const DocSection = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate: (upd: ServizioAnnunciUpdate) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { userId } = useServizio();
|
|
|
|
|
const { data } = useServizioAnnuncio();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const utils = api.useUtils();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { data: files, isLoading } = api.storage.getAll.useQuery();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
|
|
|
|
onSuccess: async () => {
|
2025-10-24 16:10:59 +02:00
|
|
|
await utils.storage.retrieveUserFileData.invalidate({
|
2025-08-28 18:27:07 +02:00
|
|
|
userId,
|
|
|
|
|
});
|
|
|
|
|
await utils.storage.getAll.invalidate();
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (isLoading) return <LoadingPage />;
|
|
|
|
|
if (files === undefined) {
|
|
|
|
|
return <p>Errore nel caricamento dei documenti</p>;
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Documento di Conferma</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
documento che l'utente vedra alla conferma
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
{data.doc_conferma_ref && (
|
|
|
|
|
<div className="flex flex-wrap items-start gap-3">
|
|
|
|
|
<DocInfo storageId={data.doc_conferma_ref}>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Reset Conferma"
|
|
|
|
|
className="flex items-center gap-3"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
doc_conferma_added: false,
|
2025-08-29 16:18:32 +02:00
|
|
|
doc_conferma_ref: null,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
variant="destructive"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<Trash2 />
|
|
|
|
|
</Button>
|
|
|
|
|
</DocInfo>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
|
|
|
<DocCombo
|
2025-08-29 16:18:32 +02:00
|
|
|
current={data.doc_conferma_ref}
|
2025-08-28 18:27:07 +02:00
|
|
|
files={files}
|
|
|
|
|
onSelect={(f) => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
doc_conferma_added: true,
|
2025-08-29 16:18:32 +02:00
|
|
|
doc_conferma_ref: f.id,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
2025-08-19 19:14:27 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
setUserStorage({
|
2025-10-24 16:10:59 +02:00
|
|
|
storageId: f.id,
|
2025-08-29 16:18:32 +02:00
|
|
|
userId,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<UploadModal isAdmin userId={userId} />
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-19 19:14:27 +02:00
|
|
|
const DocInfo = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
storageId,
|
|
|
|
|
children,
|
2025-08-19 19:14:27 +02:00
|
|
|
}: {
|
2025-10-24 16:10:59 +02:00
|
|
|
storageId: string;
|
2025-08-28 18:27:07 +02:00
|
|
|
children: React.ReactNode;
|
2025-08-19 19:14:27 +02:00
|
|
|
}) => {
|
2025-10-24 16:10:59 +02:00
|
|
|
const { data: file_info } = api.storage.getFileInfos.useQuery({
|
2025-08-28 18:27:07 +02:00
|
|
|
storageId,
|
|
|
|
|
});
|
|
|
|
|
if (!file_info) return null;
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex w-full flex-col gap-2">
|
|
|
|
|
<Label htmlFor="selected-file">File Selezionato: </Label>
|
|
|
|
|
<div className="flex w-full items-center gap-2 sm:max-w-[30rem]">
|
|
|
|
|
<div className="flex w-full flex-1">
|
|
|
|
|
<Link
|
|
|
|
|
aria-label="Open Selected File"
|
|
|
|
|
className={cn(
|
|
|
|
|
buttonVariants({
|
|
|
|
|
variant: "outline",
|
|
|
|
|
}),
|
|
|
|
|
"flex w-fit grow items-center gap-2",
|
|
|
|
|
)}
|
2025-08-29 16:18:32 +02:00
|
|
|
href={`/area-riservata/allegato-view/${storageId}`}
|
2025-08-28 18:27:07 +02:00
|
|
|
id="selected-file"
|
2025-08-29 16:18:32 +02:00
|
|
|
target="_blank"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
2025-10-24 16:10:59 +02:00
|
|
|
<ExtIcon mimeType={file_info.mimeType} />
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="max-w-40 truncate sm:max-w-80">
|
2025-10-24 16:10:59 +02:00
|
|
|
{file_info.originalName}
|
2025-08-28 18:27:07 +02:00
|
|
|
</span>
|
|
|
|
|
<ExternalLink className="size-4" />
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const DocCombo = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
files,
|
|
|
|
|
onSelect,
|
|
|
|
|
current,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-10-24 16:10:59 +02:00
|
|
|
files: FileMetadataWithAdmin[];
|
|
|
|
|
onSelect: (file: FileMetadataWithAdmin) => void;
|
|
|
|
|
current: string | null;
|
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}
|
2025-08-29 16:18:32 +02:00
|
|
|
aria-label="Select Existing File"
|
2025-08-28 18:27:07 +02:00
|
|
|
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 file esistente...
|
|
|
|
|
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
|
|
|
|
</Button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className="w-auto p-0">
|
|
|
|
|
<Command>
|
|
|
|
|
<CommandInput placeholder="Cerca Documento..." />
|
|
|
|
|
<CommandList>
|
|
|
|
|
<CommandEmpty>Nessun file.</CommandEmpty>
|
|
|
|
|
<CommandGroup>
|
|
|
|
|
{files.map((file) => (
|
|
|
|
|
<CommandItem
|
|
|
|
|
key={file.id}
|
|
|
|
|
onSelect={(currentValue) => {
|
|
|
|
|
if (currentValue !== current) {
|
|
|
|
|
// biome-ignore lint/style/noNonNullAssertion: <exists>
|
|
|
|
|
onSelect(files.find((f) => f.id === currentValue)!);
|
|
|
|
|
}
|
|
|
|
|
setOpen(false);
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
value={file.id}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<span className="flex items-center gap-2">
|
2025-10-24 16:10:59 +02:00
|
|
|
<ExtIcon mimeType={file.mimeType} />
|
|
|
|
|
<span className="block font-medium">
|
|
|
|
|
{file.originalName}
|
|
|
|
|
</span>
|
2025-08-28 18:27:07 +02:00
|
|
|
{current === file.id && (
|
2025-10-10 16:18:43 +02:00
|
|
|
<Check className="size-4 text-accent-foreground" />
|
2025-08-28 18:27:07 +02:00
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
</CommandItem>
|
|
|
|
|
))}
|
|
|
|
|
</CommandGroup>
|
|
|
|
|
</CommandList>
|
|
|
|
|
</Command>
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const CaparraSchema = z.object({
|
2025-08-28 18:27:07 +02:00
|
|
|
caparra_causale: z.string().nullable(),
|
|
|
|
|
caparra_iban: z.string().nullable(),
|
|
|
|
|
caparra_importo: z.string().nullable(),
|
|
|
|
|
caparra_intestazione: z.string().nullable(),
|
2025-08-04 17:45:44 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
type CaparraObj = z.infer<typeof CaparraSchema>;
|
|
|
|
|
|
|
|
|
|
const parseCaparra = (ingest: string) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
let parsedJson: unknown;
|
|
|
|
|
try {
|
|
|
|
|
parsedJson = JSON.parse(ingest);
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error(
|
|
|
|
|
'Stringa non valida, clicca "copia stringa" nel gestionale e poi questo pulsante',
|
|
|
|
|
);
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
const parsed = CaparraSchema.safeParse(parsedJson);
|
|
|
|
|
if (parsed.success) {
|
|
|
|
|
return parsed.data;
|
|
|
|
|
}
|
2025-09-01 16:48:03 +02:00
|
|
|
console.error("Invalid caparra format:", parsed.error);
|
|
|
|
|
return undefined;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Caparra = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate: (upd: ServizioAnnunciUpdate) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { data } = useServizioAnnuncio();
|
|
|
|
|
const [caparra, setCaparra] = useState<CaparraObj | undefined>({
|
|
|
|
|
caparra_causale: data.caparra_causale,
|
|
|
|
|
caparra_iban: data.caparra_iban,
|
|
|
|
|
caparra_importo: data.caparra_importo,
|
|
|
|
|
caparra_intestazione: data.caparra_intestazione,
|
|
|
|
|
});
|
|
|
|
|
const handleSave = () => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
...caparra,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Dati Pagamento Caparra</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
dati per il pagamento della caparra, da mostrare all'utente
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Import Caparra"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="flex items-center gap-2"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={async () => {
|
|
|
|
|
const clipboardText = await navigator.clipboard.readText();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
setCaparra(parseCaparra(clipboardText));
|
|
|
|
|
}}
|
|
|
|
|
variant="info"
|
|
|
|
|
>
|
|
|
|
|
<ClipboardPaste className="size-4" />
|
|
|
|
|
Dal gestionale
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Reset Caparra"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="flex items-center gap-2"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={async () => {
|
|
|
|
|
setCaparra({
|
|
|
|
|
caparra_causale: null,
|
|
|
|
|
caparra_iban: null,
|
|
|
|
|
caparra_importo: null,
|
|
|
|
|
caparra_intestazione: null,
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
variant="destructive"
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="size-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
{caparra ? (
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<p>
|
|
|
|
|
<span className="font-semibold">Intestazione: </span>
|
|
|
|
|
{caparra.caparra_intestazione}{" "}
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit Intestazione"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="h-6 px-1.5 [&_svg]:size-4"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
const newValue = prompt(
|
|
|
|
|
"Modifica l'intestazione della caparra:",
|
|
|
|
|
caparra.caparra_intestazione || "",
|
|
|
|
|
);
|
|
|
|
|
if (newValue !== null) {
|
|
|
|
|
setCaparra((prev) =>
|
|
|
|
|
prev
|
|
|
|
|
? { ...prev, caparra_intestazione: newValue }
|
|
|
|
|
: undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
<Pen />
|
|
|
|
|
</Button>
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<span className="font-semibold">IBAN: </span>
|
|
|
|
|
{caparra.caparra_iban}{" "}
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit IBAN"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="h-6 px-1.5 [&_svg]:size-4"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
const newValue = prompt(
|
|
|
|
|
"Modifica l'IBAN della caparra:",
|
|
|
|
|
caparra.caparra_iban || "",
|
|
|
|
|
);
|
|
|
|
|
if (newValue !== null) {
|
|
|
|
|
setCaparra((prev) =>
|
|
|
|
|
prev ? { ...prev, caparra_iban: newValue } : undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
<Pen />
|
|
|
|
|
</Button>
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<span className="font-semibold">Importo: </span>
|
|
|
|
|
{caparra.caparra_importo}{" "}
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit Importo"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="h-6 px-1.5 [&_svg]:size-4"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
const newValue = prompt(
|
|
|
|
|
"Modifica l'importo della caparra:",
|
|
|
|
|
caparra.caparra_importo || "",
|
|
|
|
|
);
|
|
|
|
|
if (newValue !== null) {
|
|
|
|
|
setCaparra((prev) =>
|
|
|
|
|
prev ? { ...prev, caparra_importo: newValue } : undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
<Pen />
|
|
|
|
|
</Button>
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<span className="font-semibold">Causale: </span>
|
|
|
|
|
{caparra.caparra_causale}{" "}
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Edit Causale"
|
2025-08-29 16:18:32 +02:00
|
|
|
className="h-6 px-1.5 [&_svg]:size-4"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
const newValue = prompt(
|
|
|
|
|
"Modifica la causale della caparra:",
|
|
|
|
|
caparra.caparra_causale || "",
|
|
|
|
|
);
|
|
|
|
|
if (newValue !== null) {
|
|
|
|
|
setCaparra((prev) =>
|
|
|
|
|
prev ? { ...prev, caparra_causale: newValue } : undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
>
|
|
|
|
|
<Pen />
|
|
|
|
|
</Button>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<p>Inserisci la stringa dal gestionale e elabora</p>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
<CardFooter>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Save Caparra"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleSave();
|
|
|
|
|
}}
|
|
|
|
|
variant="success"
|
|
|
|
|
>
|
|
|
|
|
Salva
|
|
|
|
|
</Button>
|
|
|
|
|
</CardFooter>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const SendConferma = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
handleUpdate: (upd: ServizioAnnunciUpdate) => void;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { servizio } = useServizio();
|
|
|
|
|
const { data } = useServizioAnnuncio();
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const [check, setCheck] = useState(data.hasConfermaAdmin);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Lavorazione Conferma</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
controlla i parametri di ricerca prima!
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<p>Tipologia servizio: {servizio.tipologia}</p>
|
|
|
|
|
{(() => {
|
|
|
|
|
if (servizio.tipologia === TipologiaPosizioneEnum.Transitorio) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<p>
|
|
|
|
|
Permanenza (da preferenze):{" "}
|
|
|
|
|
{servizio.permanenza
|
|
|
|
|
? t.preferenze.permanenza[servizio.permanenza]
|
|
|
|
|
: "non inserita"}
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
Permanenza (da descrizione): "
|
|
|
|
|
{(() => {
|
|
|
|
|
const match = data.desc_it?.match(/Permanenza:.*$/m);
|
|
|
|
|
return match ? match[0] : "";
|
|
|
|
|
})()}
|
|
|
|
|
"
|
|
|
|
|
</p>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-01 16:48:03 +02:00
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<p>Budget (da preferenze): {formatCurrency(servizio.budget)}</p>
|
|
|
|
|
<p>
|
|
|
|
|
Canone Mensile (da descrizione):{" "}
|
|
|
|
|
{formatCurrency(data.prezzo / 100)}
|
|
|
|
|
</p>
|
|
|
|
|
</>
|
|
|
|
|
);
|
2025-08-28 18:27:07 +02:00
|
|
|
})()}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<Switch
|
|
|
|
|
aria-label="Conferma Lavorata"
|
|
|
|
|
checked={check}
|
2025-08-29 16:18:32 +02:00
|
|
|
id="conferma"
|
2025-08-28 18:27:07 +02:00
|
|
|
onCheckedChange={(c) => {
|
|
|
|
|
setCheck(c);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<Label htmlFor="conferma">Conferma lavorata</Label>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
<CardFooter>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="Save Conferma Status"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleUpdate({
|
|
|
|
|
hasConfermaAdmin: check,
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
variant="success"
|
|
|
|
|
>
|
|
|
|
|
Salva
|
|
|
|
|
</Button>
|
|
|
|
|
</CardFooter>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const AddButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { servizioId, userId } = useServizio();
|
|
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const { mutate } = api.servizio.addServizioAnnunci.useMutation({
|
2025-08-29 16:18:32 +02:00
|
|
|
onError: (error) => {
|
|
|
|
|
console.error(error);
|
|
|
|
|
toast.error("Errore durante l'aggiunta dell'annuncio");
|
|
|
|
|
},
|
2025-08-28 18:27:07 +02:00
|
|
|
onSuccess: async () => {
|
|
|
|
|
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
|
|
|
await utils.servizio.getAnnunciAvailableToAdd.invalidate({
|
|
|
|
|
servizioId,
|
|
|
|
|
});
|
|
|
|
|
await utils.servizio.AnnuncioServizioTipologiaMatch.invalidate({
|
|
|
|
|
userId,
|
|
|
|
|
});
|
|
|
|
|
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
|
|
|
servizioId,
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
toast.success("Annuncio aggiunto alla ricerca");
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const buttonRef = useRef<AnimatedButtonRef>(null);
|
|
|
|
|
return (
|
|
|
|
|
<AnimatedButton
|
2025-08-29 16:18:32 +02:00
|
|
|
className="group flex w-full gap-2"
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={async () => {
|
|
|
|
|
buttonRef.current?.sparkle();
|
|
|
|
|
//wait for the animation to finish
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
2025-08-29 16:18:32 +02:00
|
|
|
mutate({ annunci: [annuncioId], servizioId });
|
2025-08-28 18:27:07 +02:00
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
ref={buttonRef}
|
|
|
|
|
sparklesClassName="fill-yellow-500 stroke-transparent"
|
|
|
|
|
sparklesIcon="thumbs-up"
|
|
|
|
|
sparklesNumber={15}
|
|
|
|
|
sparklesRadius={60}
|
|
|
|
|
sparklesScale={[1, 1.5]}
|
2025-08-28 18:27:07 +02:00
|
|
|
variant="info"
|
|
|
|
|
>
|
|
|
|
|
<Plus className="size-5" />
|
|
|
|
|
<span>Aggiungi agli annunci da contattare</span>
|
|
|
|
|
</AnimatedButton>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|