feat: add AdminLavoraConferma and AdminContratto components for managing service confirmations and contracts
- Implemented AdminLavoraConferma component to handle confirmation processes with document management. - Added AdminContratto component for managing rental contracts and related documents. - Introduced shared components for document selection and display (DocInfo and DocCombo). - Enhanced storage controller with a new function to retrieve storage ID from user storage ID. - Updated database interactions to improve error handling and data retrieval.
This commit is contained in:
parent
17ecc9604f
commit
23525d8f14
19 changed files with 1900 additions and 1482 deletions
|
|
@ -88,7 +88,7 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
||||||
disponibile_da = $47,
|
disponibile_da = $47,
|
||||||
permanenza = $48,
|
permanenza = $48,
|
||||||
persone = $49,
|
persone = $49,
|
||||||
updated_at = NOW()
|
media_updated_at = NOW()
|
||||||
WHERE codice = $46
|
WHERE codice = $46
|
||||||
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
||||||
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
||||||
|
|
@ -155,7 +155,7 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
||||||
disponibile_da,
|
disponibile_da,
|
||||||
permanenza,
|
permanenza,
|
||||||
persone,
|
persone,
|
||||||
updated_at
|
media_updated_at
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, NOW())
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, NOW())
|
||||||
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
||||||
|
|
|
||||||
540
apps/infoalloggi/src/components/servizio/admin_conferma.tsx
Normal file
540
apps/infoalloggi/src/components/servizio/admin_conferma.tsx
Normal file
|
|
@ -0,0 +1,540 @@
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { CircleCheck, ClipboardPaste, Pen, Trash2, Wrench } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import z from "zod";
|
||||||
|
import { formatCurrency } from "~/lib/utils";
|
||||||
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
|
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
import {
|
||||||
|
Credenza,
|
||||||
|
CredenzaBody,
|
||||||
|
CredenzaClose,
|
||||||
|
CredenzaContent,
|
||||||
|
CredenzaDescription,
|
||||||
|
CredenzaFooter,
|
||||||
|
CredenzaHeader,
|
||||||
|
CredenzaTitle,
|
||||||
|
CredenzaTrigger,
|
||||||
|
} from "../custom_ui/credenza";
|
||||||
|
import { LoadingPage } from "../loading";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
|
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Switch } from "../ui/switch";
|
||||||
|
import { UploadModal } from "../upload_modal";
|
||||||
|
import { DocCombo, DocInfo } from "./shared";
|
||||||
|
|
||||||
|
export const AdminLavoraConferma = () => {
|
||||||
|
const { servizio } = useServizio();
|
||||||
|
const { data } = useServizioAnnuncio();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Credenza>
|
||||||
|
<CredenzaTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-label="Open Lavora Conferma Section"
|
||||||
|
className="w-fit"
|
||||||
|
variant={data.hasConfermaAdmin ? "success" : "secondary"}
|
||||||
|
>
|
||||||
|
{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 />
|
||||||
|
<Caparra />
|
||||||
|
{servizio.tipologia === TipologiaPosizioneEnum.Transitorio && (
|
||||||
|
<MotivazioneSection />
|
||||||
|
)}
|
||||||
|
<SendConferma />
|
||||||
|
</div>
|
||||||
|
</CredenzaBody>
|
||||||
|
|
||||||
|
<CredenzaFooter>
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button aria-label="Close Credenza" variant="outline">
|
||||||
|
Chiudi
|
||||||
|
</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
</CredenzaFooter>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DocSection = () => {
|
||||||
|
const { userId } = useServizio();
|
||||||
|
const { data, updateServizioAnnunci } = useServizioAnnuncio();
|
||||||
|
|
||||||
|
const utils = api.useUtils();
|
||||||
|
// const { mutate: updateServizio } = api.servizio.UpdateConfermaDoc.useMutation(
|
||||||
|
// {
|
||||||
|
// onError: (error) => {
|
||||||
|
// toast.error(error.message);
|
||||||
|
// },
|
||||||
|
// onSuccess: async () => {
|
||||||
|
// await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
||||||
|
// await utils.servizio.getServizio.invalidate({
|
||||||
|
// servizioId,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// toast.success("Dati salvati con successo");
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
|
const { data: files, isLoading } = api.storage.getAll.useQuery();
|
||||||
|
|
||||||
|
const { mutate: setUserStorage } =
|
||||||
|
api.storage.addUserStorageEntry.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
await utils.storage.getAll.invalidate();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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={async () => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
doc_conferma_added: false,
|
||||||
|
doc_conferma_ref: null,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
variant="destructive"
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
</DocInfo>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<DocCombo
|
||||||
|
current={data.doc_conferma_ref}
|
||||||
|
files={files}
|
||||||
|
onSelect={async (f) => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
doc_conferma_added: true,
|
||||||
|
doc_conferma_ref: f.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
setUserStorage({
|
||||||
|
storageId: f.id,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<UploadModal isAdmin userId={userId} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CaparraSchema = z.object({
|
||||||
|
caparra_causale: z.string().nullable(),
|
||||||
|
caparra_iban: z.string().nullable(),
|
||||||
|
caparra_importo: z.string().nullable(),
|
||||||
|
caparra_intestazione: z.string().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type CaparraObj = z.infer<typeof CaparraSchema>;
|
||||||
|
|
||||||
|
const parseCaparra = (ingest: string) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
console.error("Invalid caparra format:", parsed.error);
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Caparra = () => {
|
||||||
|
const { data, updateServizioAnnunci } = 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 = async () => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
caparra_causale: caparra?.caparra_causale || null,
|
||||||
|
caparra_iban: caparra?.caparra_iban || null,
|
||||||
|
caparra_importo: caparra?.caparra_importo || null,
|
||||||
|
caparra_intestazione: caparra?.caparra_intestazione || null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
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"
|
||||||
|
onClick={async () => {
|
||||||
|
const clipboardText = await navigator.clipboard.readText();
|
||||||
|
|
||||||
|
setCaparra(parseCaparra(clipboardText));
|
||||||
|
}}
|
||||||
|
variant="info"
|
||||||
|
>
|
||||||
|
<ClipboardPaste className="size-4" />
|
||||||
|
Dal gestionale
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
aria-label="Reset Caparra"
|
||||||
|
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"
|
||||||
|
className="h-6 px-1.5 [&_svg]:size-4"
|
||||||
|
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"
|
||||||
|
className="h-6 px-1.5 [&_svg]:size-4"
|
||||||
|
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"
|
||||||
|
className="h-6 px-1.5 [&_svg]:size-4"
|
||||||
|
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"
|
||||||
|
className="h-6 px-1.5 [&_svg]:size-4"
|
||||||
|
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={async () => {
|
||||||
|
await handleSave();
|
||||||
|
}}
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
Salva
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MotivazioneSection = () => {
|
||||||
|
const { servizioId } = useServizio();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { data: servizio } = api.servizio.getServizio.useQuery({ servizioId });
|
||||||
|
if (!servizio) return null;
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Motivazione di transitorietà</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Informazioni relative alla motivazione di transitorietà inserite
|
||||||
|
dall'utente
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">Motivazione specificata: </span>
|
||||||
|
{servizio.motivazione_transitorio ? (
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
t.preferenze.motivazioni_mappings.find(
|
||||||
|
(m) => m.id === servizio.motivazione_transitorio,
|
||||||
|
)?.title
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-red-500 italic">Non inserita</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">
|
||||||
|
Data scadenza motivazione inserita:{" "}
|
||||||
|
</span>
|
||||||
|
{servizio.scadenza_motivazione_transitoria ? (
|
||||||
|
format(servizio.scadenza_motivazione_transitoria, "dd/MM/yyyy")
|
||||||
|
) : (
|
||||||
|
<span className="text-red-500 italic">Non inserita</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">Documento caricato: </span>
|
||||||
|
{servizio.doc_motivazione_ref ? (
|
||||||
|
<FileLink userStorageId={servizio.doc_motivazione_ref} />
|
||||||
|
) : (
|
||||||
|
<span className="text-red-500 italic">Non caricato</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FileLink = ({
|
||||||
|
userStorageId,
|
||||||
|
}: {
|
||||||
|
userStorageId: UsersStorageUserStorageId;
|
||||||
|
}) => {
|
||||||
|
const { data: storageId } =
|
||||||
|
api.storage.getStorageIdFromUserStorageId.useQuery({
|
||||||
|
userStorageId,
|
||||||
|
});
|
||||||
|
if (!storageId) return null;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className="underline hover:text-primary"
|
||||||
|
href={`/area-riservata/allegato-view/${storageId}`}
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Vedi documento
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SendConferma = () => {
|
||||||
|
const { servizio } = useServizio();
|
||||||
|
const { data } = useServizioAnnuncio();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [check, setCheck] = useState(data.hasConfermaAdmin);
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const { mutate: handleUpdate } =
|
||||||
|
api.servizio.adminUpdateConfermaStatus.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.servizio.getAllServizioAnnunci.invalidate();
|
||||||
|
await utils.servizio.getServizio.invalidate({
|
||||||
|
servizioId: servizio.servizio_id,
|
||||||
|
});
|
||||||
|
toast.success("Status aggiornato con successo");
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(`Errore durante l'aggiornamento: ${error.message}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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 specificata dall'utente:{" "}
|
||||||
|
{servizio.permanenza
|
||||||
|
? t.preferenze.permanenza[servizio.permanenza]
|
||||||
|
: "non inserita"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Permanenza in descrizione: {(() => {
|
||||||
|
const match = data.desc_it?.match(/Permanenza:.*$/m);
|
||||||
|
return match
|
||||||
|
? `"${match[0]}"`
|
||||||
|
: "errore: testo non trovato in descrizione";
|
||||||
|
})()}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>Budget (da preferenze): {formatCurrency(servizio.budget)}</p>
|
||||||
|
<p>
|
||||||
|
Canone Mensile (da descrizione):{" "}
|
||||||
|
{formatCurrency(data.prezzo / 100)}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Switch
|
||||||
|
aria-label="Conferma Lavorata"
|
||||||
|
checked={check}
|
||||||
|
id="conferma"
|
||||||
|
onCheckedChange={(c) => {
|
||||||
|
setCheck(c);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="conferma">Conferma lavorata</Label>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
aria-label="Save Conferma Status"
|
||||||
|
onClick={() => {
|
||||||
|
handleUpdate({
|
||||||
|
servizioId: servizio.servizio_id,
|
||||||
|
annuncioId: data.id,
|
||||||
|
status: check,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
Salva
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
512
apps/infoalloggi/src/components/servizio/admin_contratto.tsx
Normal file
512
apps/infoalloggi/src/components/servizio/admin_contratto.tsx
Normal file
|
|
@ -0,0 +1,512 @@
|
||||||
|
import { CircleCheck, ClipboardPaste, Pen, Trash2, Wrench } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import z from "zod";
|
||||||
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
import {
|
||||||
|
Credenza,
|
||||||
|
CredenzaBody,
|
||||||
|
CredenzaClose,
|
||||||
|
CredenzaContent,
|
||||||
|
CredenzaDescription,
|
||||||
|
CredenzaFooter,
|
||||||
|
CredenzaHeader,
|
||||||
|
CredenzaTitle,
|
||||||
|
CredenzaTrigger,
|
||||||
|
} from "../custom_ui/credenza";
|
||||||
|
import { LoadingPage } from "../loading";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
|
import { Separator } from "../ui/separator";
|
||||||
|
import { UploadModal } from "../upload_modal";
|
||||||
|
import { DocCombo, DocInfo } from "./shared";
|
||||||
|
|
||||||
|
export const AdminContratto = () => {
|
||||||
|
const { data } = useServizioAnnuncio();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Credenza>
|
||||||
|
<CredenzaTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-label="Open Contratto Section"
|
||||||
|
className="w-fit"
|
||||||
|
variant={data.doc_contratto_added ? "success" : "secondary"}
|
||||||
|
>
|
||||||
|
{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 />
|
||||||
|
<Separator />
|
||||||
|
<ContrattoImport />
|
||||||
|
<Separator />
|
||||||
|
<RicevutaSection />
|
||||||
|
</div>
|
||||||
|
</CredenzaBody>
|
||||||
|
|
||||||
|
<CredenzaFooter>
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button aria-label="Close Credenza" variant="outline">
|
||||||
|
Chiudi
|
||||||
|
</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
</CredenzaFooter>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContrattoSection = () => {
|
||||||
|
const { userId } = useServizio();
|
||||||
|
const { data, updateServizioAnnunci } = useServizioAnnuncio();
|
||||||
|
const [selectedDoc, setSelectedDoc] = useState<string | null>(
|
||||||
|
data.doc_contratto_ref,
|
||||||
|
);
|
||||||
|
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const { data: files, isLoading } = api.storage.getAll.useQuery();
|
||||||
|
|
||||||
|
const { mutate: setUserStorage } =
|
||||||
|
api.storage.addUserStorageEntry.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
await utils.storage.getAll.invalidate();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
doc_contratto_added: selectedDoc != null,
|
||||||
|
doc_contratto_ref: selectedDoc,
|
||||||
|
});
|
||||||
|
if (selectedDoc) {
|
||||||
|
setUserStorage({
|
||||||
|
storageId: selectedDoc,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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={async () => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
doc_contratto_added: false,
|
||||||
|
doc_contratto_ref: null,
|
||||||
|
});
|
||||||
|
setSelectedDoc(null);
|
||||||
|
}}
|
||||||
|
variant="destructive"
|
||||||
|
>
|
||||||
|
<Trash2 className="size-6" />
|
||||||
|
</Button>
|
||||||
|
</DocInfo>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DocCombo
|
||||||
|
current={selectedDoc}
|
||||||
|
files={files}
|
||||||
|
onSelect={(f) => {
|
||||||
|
setSelectedDoc(f.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<UploadModal isAdmin userId={userId} />
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
aria-label="Save Contratto"
|
||||||
|
onClick={async () => {
|
||||||
|
await handleSave();
|
||||||
|
}}
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
Salva
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContrattoImportSchema = z.object({
|
||||||
|
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(),
|
||||||
|
gestionale_id: z.number().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ContrattoImportObj
|
||||||
|
extends Omit<
|
||||||
|
z.infer<typeof ContrattoImportSchema>,
|
||||||
|
"contratto_decorrenza" | "contratto_scadenza"
|
||||||
|
> {
|
||||||
|
contratto_decorrenza: Date | null;
|
||||||
|
contratto_scadenza: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseContrattoImport = (ingest: string) => {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
console.error("Invalid format:", parsed.error);
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContrattoImport = () => {
|
||||||
|
const { data, updateServizioAnnunci } = 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"],
|
||||||
|
gestionale_id: data.gestionale_id,
|
||||||
|
});
|
||||||
|
const handleSave = async () => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
contratto_decorrenza: contratto?.contratto_decorrenza || null,
|
||||||
|
contratto_scadenza: contratto?.contratto_scadenza || null,
|
||||||
|
contratto_tipo: contratto?.contratto_tipo || null,
|
||||||
|
gestionale_id: contratto?.gestionale_id || null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
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"
|
||||||
|
onClick={async () => {
|
||||||
|
const clipboardText = await navigator.clipboard.readText();
|
||||||
|
|
||||||
|
setContratto(parseContrattoImport(clipboardText));
|
||||||
|
}}
|
||||||
|
variant="info"
|
||||||
|
>
|
||||||
|
<ClipboardPaste className="size-4" />
|
||||||
|
Clicca qui per importare la stringa dal gestionale
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
aria-label="Reset Contratto"
|
||||||
|
onClick={() => {
|
||||||
|
setContratto({
|
||||||
|
contratto_decorrenza: null,
|
||||||
|
contratto_scadenza: null,
|
||||||
|
contratto_tipo: null,
|
||||||
|
gestionale_id: null,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
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"
|
||||||
|
className="h-6 px-1.5 [&_svg]:size-4"
|
||||||
|
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"
|
||||||
|
className="h-6 px-1.5 [&_svg]:size-4"
|
||||||
|
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"
|
||||||
|
className="h-6 px-1.5 [&_svg]:size-4"
|
||||||
|
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"
|
||||||
|
className="h-6 px-1.5 [&_svg]:size-4"
|
||||||
|
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={async () => {
|
||||||
|
await handleSave();
|
||||||
|
}}
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
Salva
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const RicevutaSection = () => {
|
||||||
|
const { userId } = useServizio();
|
||||||
|
const { data, updateServizioAnnunci } = useServizioAnnuncio();
|
||||||
|
const [selectedDoc, setSelectedDoc] = useState<string | null>(
|
||||||
|
data.doc_registrazione_ref,
|
||||||
|
);
|
||||||
|
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const { data: files, isLoading } = api.storage.getAll.useQuery();
|
||||||
|
|
||||||
|
const { mutate: setUserStorage } =
|
||||||
|
api.storage.addUserStorageEntry.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
await utils.storage.getAll.invalidate();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
doc_registrazione_added: selectedDoc != null,
|
||||||
|
doc_registrazione_ref: selectedDoc,
|
||||||
|
});
|
||||||
|
if (selectedDoc) {
|
||||||
|
setUserStorage({
|
||||||
|
storageId: selectedDoc,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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={async () => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
doc_registrazione_added: false,
|
||||||
|
doc_registrazione_ref: null,
|
||||||
|
});
|
||||||
|
setSelectedDoc(null);
|
||||||
|
}}
|
||||||
|
variant="destructive"
|
||||||
|
>
|
||||||
|
<Trash2 className="size-6" />
|
||||||
|
</Button>
|
||||||
|
</DocInfo>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DocCombo
|
||||||
|
current={selectedDoc}
|
||||||
|
files={files}
|
||||||
|
onSelect={(f) => {
|
||||||
|
setSelectedDoc(f.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<UploadModal isAdmin userId={userId} />
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
aria-label="Save Ricevuta"
|
||||||
|
onClick={async () => {
|
||||||
|
await handleSave();
|
||||||
|
}}
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
Salva
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,28 @@
|
||||||
|
import { CircleCheck, Handshake } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
import { AllegatoIframe } from "~/components/allegato-iframe";
|
import { AllegatoIframe } from "~/components/allegato-iframe";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
|
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
import {
|
||||||
|
Credenza,
|
||||||
|
CredenzaBody,
|
||||||
|
CredenzaClose,
|
||||||
|
CredenzaContent,
|
||||||
|
CredenzaDescription,
|
||||||
|
CredenzaFooter,
|
||||||
|
CredenzaHeader,
|
||||||
|
CredenzaTitle,
|
||||||
|
CredenzaTrigger,
|
||||||
|
} from "../custom_ui/credenza";
|
||||||
|
import LoadingButton from "../custom_ui/loading-button";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { type UploadCallback, UploadModal } from "../upload_modal";
|
||||||
|
|
||||||
export const FileSection = ({ storageId }: { storageId: string }) => {
|
export const FileSection = ({ storageId }: { storageId: string }) => {
|
||||||
const { data: fileInfos, isLoading } = api.storage.getFileInfos.useQuery({
|
const { data: fileInfos, isLoading } = api.storage.getFileInfos.useQuery({
|
||||||
|
|
@ -27,3 +47,236 @@ export const FileSection = ({ storageId }: { storageId: string }) => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ConfermaAnnuncioModal = () => {
|
||||||
|
const { servizio } = useServizio();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const needsDocuments =
|
||||||
|
!servizio.skipDocMotivazione &&
|
||||||
|
!servizio.doc_motivazione &&
|
||||||
|
servizio.tipologia === TipologiaPosizioneEnum.Transitorio;
|
||||||
|
return (
|
||||||
|
<Credenza onOpenChange={setOpen} open={open}>
|
||||||
|
<CredenzaTrigger asChild>
|
||||||
|
<Button aria-label="Conferma immobile" className="w-fit">
|
||||||
|
<Handshake className="size-6" /> Sono interessato a confermare
|
||||||
|
</Button>
|
||||||
|
</CredenzaTrigger>
|
||||||
|
<CredenzaContent className="max-h-[90vh]">
|
||||||
|
<CredenzaHeader>
|
||||||
|
<CredenzaTitle> {t.richieste.conf_title}</CredenzaTitle>
|
||||||
|
<CredenzaDescription className="sr-only">
|
||||||
|
{t.richieste.conf_desc}
|
||||||
|
</CredenzaDescription>
|
||||||
|
</CredenzaHeader>
|
||||||
|
{needsDocuments ? (
|
||||||
|
<DocCheckpoint />
|
||||||
|
) : (
|
||||||
|
<InvioConferma setOpen={setOpen} />
|
||||||
|
)}
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const InvioConferma = ({ setOpen }: { setOpen: (value: boolean) => void }) => {
|
||||||
|
const { userId, servizioId } = useServizio();
|
||||||
|
const { annuncioId } = useServizioAnnuncio();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const { mutate, isPending } = api.servizio.userSendConfermaIntent.useMutation(
|
||||||
|
{
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error(err.message, { id: "updateRichiestaToast" });
|
||||||
|
},
|
||||||
|
onMutate: () => {
|
||||||
|
toast.loading("Conferma in corso", { id: "updateRichiestaToast" });
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
toast.success("Richiesta confermata con successo", {
|
||||||
|
id: "updateRichiestaToast",
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
||||||
|
setOpen(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||||
|
<p>{t.richieste.conferma_txt}</p>
|
||||||
|
</CredenzaBody>
|
||||||
|
<CredenzaFooter>
|
||||||
|
<LoadingButton
|
||||||
|
aria-label="Conferma immobile"
|
||||||
|
loading={isPending}
|
||||||
|
onClick={() => {
|
||||||
|
mutate({
|
||||||
|
annuncioId,
|
||||||
|
servizioId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
{t.conferma}
|
||||||
|
</LoadingButton>
|
||||||
|
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button aria-label="Chiudi modal credenza">Chiudi</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
</CredenzaFooter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DocComponent = ({
|
||||||
|
docInfo,
|
||||||
|
cb,
|
||||||
|
}: {
|
||||||
|
docInfo: {
|
||||||
|
storageId: string;
|
||||||
|
ref: UsersStorageUserStorageId | null;
|
||||||
|
} | null;
|
||||||
|
cb: UploadCallback;
|
||||||
|
}) => {
|
||||||
|
const { isAdmin, userId } = useServizio();
|
||||||
|
|
||||||
|
if (docInfo?.storageId) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 text-green-500">
|
||||||
|
<CircleCheck className="size-5" />
|
||||||
|
<span>Documento caricato</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UploadModal
|
||||||
|
cb_onUpload={cb}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
maxFiles={1}
|
||||||
|
userId={userId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DocCheckpoint = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { userId, servizio } = useServizio();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const { mutate, isPending } = api.servizio.updateServizio.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.servizio.getAllServizioAnnunci.invalidate({
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
await utils.storage.retrieveUserFileData.invalidate({ userId });
|
||||||
|
await utils.servizio.getServizio.invalidate({
|
||||||
|
servizioId: servizio.servizio_id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error(`Errore aggiornamento servizio: ${err.message}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const handleUpload = (id: UsersStorageUserStorageId) => {
|
||||||
|
mutate({
|
||||||
|
servizioId: servizio.servizio_id,
|
||||||
|
data: { doc_motivazione_ref: id },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||||
|
<div className="space-y-5">
|
||||||
|
<p>
|
||||||
|
Devi ancora caricare un tuo documento legato alla permanenza e
|
||||||
|
motivazione di transitorietà
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span>Documento:</span>
|
||||||
|
<DocComponent
|
||||||
|
cb={({ userStorageIds }) =>
|
||||||
|
userStorageIds.length > 0 &&
|
||||||
|
userStorageIds[0] &&
|
||||||
|
handleUpload(userStorageIds[0])
|
||||||
|
}
|
||||||
|
docInfo={servizio.doc_motivazione}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
I tuoi dati saranno conservati solamente per la durata del servizio,
|
||||||
|
in conformità con la nostra{" "}
|
||||||
|
<Link className="underline" href="/privacy-policy" target="_blank">
|
||||||
|
Informativa sulla Privacy
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CredenzaBody>
|
||||||
|
<CredenzaFooter>
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button aria-label="Chiudi" disabled={isPending} variant="outline">
|
||||||
|
{t.chiudi}
|
||||||
|
</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
</CredenzaFooter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ConfermaInLavorazioneModal = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data } = useServizioAnnuncio();
|
||||||
|
return (
|
||||||
|
<Credenza>
|
||||||
|
<CredenzaTrigger asChild>
|
||||||
|
<Button aria-label="Conferma in lavorazione" className="w-fit">
|
||||||
|
<CircleCheck className="size-7" />
|
||||||
|
Conferma in lavorazione
|
||||||
|
</Button>
|
||||||
|
</CredenzaTrigger>
|
||||||
|
<CredenzaContent className="max-h-[90vh]">
|
||||||
|
<CredenzaHeader>
|
||||||
|
<CredenzaTitle>In attesa di conferma</CredenzaTitle>
|
||||||
|
<CredenzaDescription className="sr-only">
|
||||||
|
Consegna status modal
|
||||||
|
</CredenzaDescription>
|
||||||
|
</CredenzaHeader>
|
||||||
|
<CredenzaBody className="prose max-h-[80vh] overflow-auto pb-5">
|
||||||
|
<p>
|
||||||
|
Richiesta di conferma inviata il:{" "}
|
||||||
|
{data.user_confirmed_at &&
|
||||||
|
new Date(data.user_confirmed_at).toLocaleString("it", {
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
La conferma è ancora in lavorazione. Stiamo verificando con i
|
||||||
|
proprietari le condizioni e dettagli della conferma per riferirteli.
|
||||||
|
Attendi una comunicazione.
|
||||||
|
</p>
|
||||||
|
</CredenzaBody>
|
||||||
|
|
||||||
|
<CredenzaFooter>
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button aria-label="Chiudi modal credenza" variant="outline">
|
||||||
|
{t.chiudi}
|
||||||
|
</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
</CredenzaFooter>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,7 @@
|
||||||
import {
|
import { FileBadge, FileText, PackageCheck, Pointer } from "lucide-react";
|
||||||
CircleCheck,
|
|
||||||
FileBadge,
|
|
||||||
FileText,
|
|
||||||
Handshake,
|
|
||||||
PackageCheck,
|
|
||||||
Pointer,
|
|
||||||
} from "lucide-react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import {
|
|
||||||
Credenza,
|
|
||||||
CredenzaBody,
|
|
||||||
CredenzaClose,
|
|
||||||
CredenzaContent,
|
|
||||||
CredenzaDescription,
|
|
||||||
CredenzaFooter,
|
|
||||||
CredenzaHeader,
|
|
||||||
CredenzaTitle,
|
|
||||||
CredenzaTrigger,
|
|
||||||
} from "~/components/custom_ui/credenza";
|
|
||||||
import LoadingButton from "~/components/custom_ui/loading-button";
|
|
||||||
import { Button, type ButtonConfigs } from "~/components/ui/button";
|
import { Button, type ButtonConfigs } from "~/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
|
@ -30,30 +10,44 @@ import {
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "~/components/ui/tooltip";
|
} from "~/components/ui/tooltip";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
|
||||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
import { ConfermaAnnuncioModal, ConfermaInLavorazioneModal } from "./conferma";
|
||||||
import { ContattiProprietarioModal } from "./modale_contatti";
|
import { ContattiProprietarioModal } from "./modale_contatti";
|
||||||
|
|
||||||
export const Interactions = () => {
|
const SezioneConferma = () => {
|
||||||
const { servizio, servizioId } = useServizio();
|
const { servizioId } = useServizio();
|
||||||
const { data, annuncioId } = useServizioAnnuncio();
|
const { data, annuncioId } = useServizioAnnuncio();
|
||||||
|
|
||||||
|
const ContattiSbloccati = data.open_contatti_at !== null;
|
||||||
|
const ConfermaUtente = data.user_confirmed_at !== null;
|
||||||
|
const ConfermaLavorata = data.doc_conferma_added && data.hasConfermaAdmin;
|
||||||
|
const AccettatoConferma = data.accettato_conferma_at !== null;
|
||||||
|
|
||||||
|
if (ContattiSbloccati && !ConfermaUtente) {
|
||||||
|
return <ConfermaAnnuncioModal />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ConfermaUtente && !data.hasConfermaAdmin) {
|
||||||
|
return <ConfermaInLavorazioneModal />;
|
||||||
|
}
|
||||||
|
if (AccettatoConferma) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
aria-label="Conferma immobile"
|
||||||
|
href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`}
|
||||||
|
>
|
||||||
|
<Button className="w-fit" variant="success">
|
||||||
|
<Pointer className="size-6" />
|
||||||
|
Rivedi la conferma
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (ConfermaLavorata) {
|
||||||
|
if (data.doc_conferma_ref) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<ContattiProprietarioModal />
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
{data.open_contatti_at !== null && data.user_confirmed_at == null && (
|
|
||||||
<AnnuncioConfirm />
|
|
||||||
)}
|
|
||||||
{data.user_confirmed_at && !data.hasConfermaAdmin && (
|
|
||||||
<ConfermaLavorazioneModal />
|
|
||||||
)}
|
|
||||||
{data.doc_conferma_added &&
|
|
||||||
data.accettato_conferma_at == null &&
|
|
||||||
data.hasConfermaAdmin &&
|
|
||||||
(data.doc_conferma_ref ? (
|
|
||||||
<Link
|
<Link
|
||||||
aria-label="Conferma immobile"
|
aria-label="Conferma immobile"
|
||||||
href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`}
|
href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`}
|
||||||
|
|
@ -63,7 +57,9 @@ export const Interactions = () => {
|
||||||
Confermato - Procedi ora
|
Confermato - Procedi ora
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|
@ -77,37 +73,35 @@ export const Interactions = () => {
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Il documento non è più disponibile</p>
|
<p>Il documento non è più disponibile</p>
|
||||||
<p>
|
<p>Se hai bisogno di una copia, contattaci via email o chat.</p>
|
||||||
Se hai bisogno di una copia, contattaci via email o chat.
|
|
||||||
</p>
|
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
))}
|
);
|
||||||
{data.accettato_conferma_at != null && (
|
}
|
||||||
<Link
|
|
||||||
aria-label="Conferma immobile"
|
return null;
|
||||||
href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`}
|
};
|
||||||
>
|
|
||||||
<Button className="w-fit" variant="success">
|
const PostConfermaSection = () => {
|
||||||
<Pointer className="size-6" />
|
const { servizio, servizioId } = useServizio();
|
||||||
Rivedi la conferma
|
const { data, annuncioId } = useServizioAnnuncio();
|
||||||
</Button>
|
|
||||||
</Link>
|
const saldoPaid = servizio.isOkSaldo;
|
||||||
)}
|
const consulenzaPaid = servizio.isOkConsulenza;
|
||||||
</div>
|
|
||||||
{!servizio.isOkSaldo && data.accettato_conferma_at != null && (
|
if (!saldoPaid) {
|
||||||
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="font-semibold">
|
<h3 className="font-semibold">
|
||||||
Questo annuncio è stato confermato, procedi al saldo:
|
Questo annuncio è stato confermato, procedi al saldo:
|
||||||
</h3>
|
</h3>
|
||||||
<SaldoButton annuncioId={annuncioId} />
|
<SaldoButton annuncioId={annuncioId} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
|
}
|
||||||
{servizio.isOkSaldo &&
|
if (!data.doc_contratto_added) {
|
||||||
data.accettato_conferma_at != null &&
|
return (
|
||||||
data.doc_contratto_added == null && (
|
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="font-semibold text-green-500">
|
<h3 className="font-semibold text-green-500">
|
||||||
Ci siamo quasi! Stiamo preparando il contratto per te.
|
Ci siamo quasi! Stiamo preparando il contratto per te.
|
||||||
|
|
@ -116,13 +110,13 @@ export const Interactions = () => {
|
||||||
Una volta pronto, riceverai una notifica via email.
|
Una volta pronto, riceverai una notifica via email.
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
{servizio.isOkSaldo &&
|
}
|
||||||
data.accettato_conferma_at != null &&
|
|
||||||
data.doc_contratto_added && (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h3 className="font-semibold">
|
<h3 className="font-semibold">
|
||||||
{servizio.isOkConsulenza
|
{consulenzaPaid
|
||||||
? "Il contratto è pronto, puoi visualizzarlo:"
|
? "Il contratto è pronto, puoi visualizzarlo:"
|
||||||
: "Il contratto è pronto, puoi visualizzarlo e procedere al saldo:"}
|
: "Il contratto è pronto, puoi visualizzarlo e procedere al saldo:"}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -149,18 +143,13 @@ export const Interactions = () => {
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Il documento non è più disponibile</p>
|
<p>Il documento non è più disponibile</p>
|
||||||
<p>
|
<p>Se hai bisogno di una copia, contattaci via email o chat.</p>
|
||||||
Se hai bisogno di una copia, contattaci via email o
|
|
||||||
chat.
|
|
||||||
</p>
|
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!servizio.isOkConsulenza && data.contratto_tipo && (
|
{!consulenzaPaid && data.contratto_tipo && <SaldoConsulenzaButton />}
|
||||||
<SaldoConsulenzaButton />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{data.doc_registrazione_added &&
|
{data.doc_registrazione_added &&
|
||||||
(data.doc_registrazione_ref ? (
|
(data.doc_registrazione_ref ? (
|
||||||
|
|
@ -186,8 +175,7 @@ export const Interactions = () => {
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Il documento non è più disponibile</p>
|
<p>Il documento non è più disponibile</p>
|
||||||
<p>
|
<p>
|
||||||
Se hai bisogno di una copia, contattaci via email o
|
Se hai bisogno di una copia, contattaci via email o chat.
|
||||||
chat.
|
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -195,7 +183,19 @@ export const Interactions = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Interactions = () => {
|
||||||
|
const { data } = useServizioAnnuncio();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<ContattiProprietarioModal />
|
||||||
|
|
||||||
|
<SezioneConferma />
|
||||||
|
|
||||||
|
{data.accettato_conferma_at != null && <PostConfermaSection />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -282,113 +282,3 @@ const SaldoConsulenzaButton = ({
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AnnuncioConfirm = () => {
|
|
||||||
const { userId, servizioId } = useServizio();
|
|
||||||
const { annuncioId } = useServizioAnnuncio();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const { mutate, isPending } = api.servizio.userConfirmImmobile.useMutation({
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(err.message, { id: "updateRichiestaToast" });
|
|
||||||
},
|
|
||||||
onMutate: () => {
|
|
||||||
toast.loading("Conferma in corso", { id: "updateRichiestaToast" });
|
|
||||||
},
|
|
||||||
onSuccess: async () => {
|
|
||||||
toast.success("Richiesta confermata con successo", {
|
|
||||||
id: "updateRichiestaToast",
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
setOpen(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Credenza onOpenChange={setOpen} open={open}>
|
|
||||||
<CredenzaTrigger asChild>
|
|
||||||
<Button aria-label="Conferma immobile" className="w-fit">
|
|
||||||
<Handshake className="size-6" /> Conferma immobile
|
|
||||||
</Button>
|
|
||||||
</CredenzaTrigger>
|
|
||||||
<CredenzaContent className="max-h-[90vh]">
|
|
||||||
<CredenzaHeader>
|
|
||||||
<CredenzaTitle> {t.richieste.conf_title}</CredenzaTitle>
|
|
||||||
<CredenzaDescription className="sr-only">
|
|
||||||
{t.richieste.conf_desc}
|
|
||||||
</CredenzaDescription>
|
|
||||||
</CredenzaHeader>
|
|
||||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
|
||||||
<p>{t.richieste.conferma_txt}</p>
|
|
||||||
</CredenzaBody>
|
|
||||||
<CredenzaFooter>
|
|
||||||
<LoadingButton
|
|
||||||
aria-label="Conferma immobile"
|
|
||||||
loading={isPending}
|
|
||||||
onClick={() => {
|
|
||||||
mutate({
|
|
||||||
annuncioId,
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
variant="success"
|
|
||||||
>
|
|
||||||
{t.conferma}
|
|
||||||
</LoadingButton>
|
|
||||||
<CredenzaClose asChild>
|
|
||||||
<Button aria-label="Chiudi modal credenza">Chiudi</Button>
|
|
||||||
</CredenzaClose>
|
|
||||||
</CredenzaFooter>
|
|
||||||
</CredenzaContent>
|
|
||||||
</Credenza>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const ConfermaLavorazioneModal = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { data } = useServizioAnnuncio();
|
|
||||||
return (
|
|
||||||
<Credenza>
|
|
||||||
<CredenzaTrigger asChild>
|
|
||||||
<Button aria-label="Conferma in lavorazione" className="w-fit">
|
|
||||||
<CircleCheck className="size-7" />
|
|
||||||
Conferma in lavorazione
|
|
||||||
</Button>
|
|
||||||
</CredenzaTrigger>
|
|
||||||
<CredenzaContent className="max-h-[90vh]">
|
|
||||||
<CredenzaHeader>
|
|
||||||
<CredenzaTitle>In attesa di conferma</CredenzaTitle>
|
|
||||||
<CredenzaDescription className="sr-only">
|
|
||||||
Consegna status modal
|
|
||||||
</CredenzaDescription>
|
|
||||||
</CredenzaHeader>
|
|
||||||
<CredenzaBody className="prose max-h-[80vh] overflow-auto pb-5">
|
|
||||||
<p>
|
|
||||||
Richiesta di conferma inviata il:{" "}
|
|
||||||
{data.user_confirmed_at &&
|
|
||||||
new Date(data.user_confirmed_at).toLocaleString("it", {
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
month: "2-digit",
|
|
||||||
year: "numeric",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
La conferma è ancora in lavorazione. Stiamo verificando con i
|
|
||||||
proprietari le condizioni e dettagli della conferma per riferirteli.
|
|
||||||
Attendi una comunicazione.
|
|
||||||
</p>
|
|
||||||
</CredenzaBody>
|
|
||||||
|
|
||||||
<CredenzaFooter>
|
|
||||||
<CredenzaClose asChild>
|
|
||||||
<Button aria-label="Chiudi modal credenza" variant="outline">
|
|
||||||
{t.chiudi}
|
|
||||||
</Button>
|
|
||||||
</CredenzaClose>
|
|
||||||
</CredenzaFooter>
|
|
||||||
</CredenzaContent>
|
|
||||||
</Credenza>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -171,14 +171,19 @@ export const ServizioContent = () => {
|
||||||
(a) => a.accettato_conferma_at !== null,
|
(a) => a.accettato_conferma_at !== null,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (servizio.isOkSaldo) {
|
if (servizio.isOkSaldo && annuncioConfermato.length > 0) {
|
||||||
return (
|
return (
|
||||||
<ServizioCard
|
<ServizioCard
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
{annuncioConfermato.map((data) => {
|
{annuncioConfermato.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
<ServizioAnnuncioProvider
|
||||||
|
data={data}
|
||||||
|
key={data.id}
|
||||||
|
servizioId={servizio.servizio_id}
|
||||||
|
userId={userId}
|
||||||
|
>
|
||||||
<AnnuncioCard />
|
<AnnuncioCard />
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
|
|
@ -194,6 +199,10 @@ export const ServizioContent = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const annunciPreContatti = servizio.annunci.filter(
|
||||||
|
(a) => a.open_contatti_at === null && a.user_confirmed_at === null,
|
||||||
|
);
|
||||||
|
|
||||||
const openContattiAnnunci = servizio.annunci.filter(
|
const openContattiAnnunci = servizio.annunci.filter(
|
||||||
(a) => a.open_contatti_at !== null && a.user_confirmed_at === null,
|
(a) => a.open_contatti_at !== null && a.user_confirmed_at === null,
|
||||||
);
|
);
|
||||||
|
|
@ -275,14 +284,19 @@ export const ServizioContent = () => {
|
||||||
<div className="flex flex-col gap-2 overflow-y-auto">
|
<div className="flex flex-col gap-2 overflow-y-auto">
|
||||||
{annunciInConferma.map((data) => {
|
{annunciInConferma.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
<ServizioAnnuncioProvider
|
||||||
|
data={data}
|
||||||
|
key={data.id}
|
||||||
|
servizioId={servizio.servizio_id}
|
||||||
|
userId={userId}
|
||||||
|
>
|
||||||
<AnnuncioCard className="border-2 border-green-500" />
|
<AnnuncioCard className="border-2 border-green-500" />
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AnnunciSelezionatiAccordion annunci={annunciNotInConferma} />
|
<AnnunciSelezionatiAccordion annunci={annunciPreContatti} />
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
<AnnunciCompatibili />
|
<AnnunciCompatibili />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ const DocCheckpoint = () => {
|
||||||
});
|
});
|
||||||
const handleUpload = (
|
const handleUpload = (
|
||||||
field: "doc_personale_fronte_ref" | "doc_personale_retro_ref",
|
field: "doc_personale_fronte_ref" | "doc_personale_retro_ref",
|
||||||
id: string,
|
id: UsersStorageUserStorageId,
|
||||||
) => {
|
) => {
|
||||||
mutate({
|
mutate({
|
||||||
servizioId: servizio.servizio_id,
|
servizioId: servizio.servizio_id,
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ export const AddAnnuncio = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const InterruzioneServizio = () => {
|
const InterruzioneServizio = () => {
|
||||||
const { servizio, userId } = useServizio();
|
const { servizio, userId, isAdmin } = useServizio();
|
||||||
|
|
||||||
const beforeInterruzioneLimit =
|
const beforeInterruzioneLimit =
|
||||||
servizio.decorrenza != null &&
|
servizio.decorrenza != null &&
|
||||||
|
|
@ -223,9 +223,14 @@ const InterruzioneServizio = () => {
|
||||||
<Button
|
<Button
|
||||||
aria-label="Request Interruzione"
|
aria-label="Request Interruzione"
|
||||||
className=""
|
className=""
|
||||||
disabled={!beforeInterruzioneLimit}
|
disabled={
|
||||||
|
!isAdmin &&
|
||||||
|
(!beforeInterruzioneLimit ||
|
||||||
|
servizio.isInterrotto ||
|
||||||
|
servizio.annunci.some((a) => a.user_confirmed_at !== null))
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ClockAlert /> <span> Richiedi Interruzione Servizio</span>
|
<ClockAlert /> <span>Interruzione Servizio</span>
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
|
|
@ -255,7 +260,7 @@ const InterruzioneServizio = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditPreferenze = () => {
|
const EditPreferenze = () => {
|
||||||
const { servizioId, userId, isAdmin } = useServizio();
|
const { servizioId, userId, isAdmin, servizio } = useServizio();
|
||||||
const [openEditPreferenze, setOpenEditPreferenze] = useState(false);
|
const [openEditPreferenze, setOpenEditPreferenze] = useState(false);
|
||||||
const { data, isLoading } = api.servizio.getServizio.useQuery({ servizioId });
|
const { data, isLoading } = api.servizio.getServizio.useQuery({ servizioId });
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
@ -287,7 +292,15 @@ const EditPreferenze = () => {
|
||||||
return (
|
return (
|
||||||
<Credenza onOpenChange={setOpenEditPreferenze} open={openEditPreferenze}>
|
<Credenza onOpenChange={setOpenEditPreferenze} open={openEditPreferenze}>
|
||||||
<CredenzaTrigger asChild>
|
<CredenzaTrigger asChild>
|
||||||
<Button aria-label="Preferenze di ricerca" className="">
|
<Button
|
||||||
|
aria-label="Preferenze di ricerca"
|
||||||
|
className=""
|
||||||
|
disabled={
|
||||||
|
!isAdmin &&
|
||||||
|
(servizio.isInterrotto ||
|
||||||
|
servizio.annunci.some((a) => a.hasConfermaAdmin))
|
||||||
|
}
|
||||||
|
>
|
||||||
<Calculator />
|
<Calculator />
|
||||||
Modifica Preferenze
|
Modifica Preferenze
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ export const AnnunciInConfermaDialog = ({
|
||||||
}: {
|
}: {
|
||||||
annunci: ServizioData["annunci"];
|
annunci: ServizioData["annunci"];
|
||||||
}) => {
|
}) => {
|
||||||
|
const { servizioId, userId } = useServizio();
|
||||||
return (
|
return (
|
||||||
<DialogServizio
|
<DialogServizio
|
||||||
title="Annunci in conferma"
|
title="Annunci in conferma"
|
||||||
|
|
@ -84,7 +85,12 @@ export const AnnunciInConfermaDialog = ({
|
||||||
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
|
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
|
||||||
{annunci.map((data) => {
|
{annunci.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
<ServizioAnnuncioProvider
|
||||||
|
data={data}
|
||||||
|
key={data.id}
|
||||||
|
servizioId={servizioId}
|
||||||
|
userId={userId}
|
||||||
|
>
|
||||||
<AnnuncioCard className="border-2 border-green-500" />
|
<AnnuncioCard className="border-2 border-green-500" />
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
|
|
@ -99,6 +105,7 @@ export const AnnunciSelezionatiDialog = ({
|
||||||
}: {
|
}: {
|
||||||
annunci: ServizioData["annunci"];
|
annunci: ServizioData["annunci"];
|
||||||
}) => {
|
}) => {
|
||||||
|
const { servizioId, userId } = useServizio();
|
||||||
return (
|
return (
|
||||||
<DialogServizio
|
<DialogServizio
|
||||||
title="Annunci selezionati"
|
title="Annunci selezionati"
|
||||||
|
|
@ -122,7 +129,12 @@ export const AnnunciSelezionatiDialog = ({
|
||||||
{annunci.length > 0 ? (
|
{annunci.length > 0 ? (
|
||||||
annunci.map((data) => {
|
annunci.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
<ServizioAnnuncioProvider
|
||||||
|
data={data}
|
||||||
|
key={data.id}
|
||||||
|
servizioId={servizioId}
|
||||||
|
userId={userId}
|
||||||
|
>
|
||||||
<AnnuncioCard className="border-2 border-sky-500" />
|
<AnnuncioCard className="border-2 border-sky-500" />
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
|
|
@ -152,6 +164,7 @@ export const AnnunciInConfermaAccordion = ({
|
||||||
}: {
|
}: {
|
||||||
annunci: ServizioData["annunci"];
|
annunci: ServizioData["annunci"];
|
||||||
}) => {
|
}) => {
|
||||||
|
const { servizioId, userId } = useServizio();
|
||||||
if (annunci.length === 0) {
|
if (annunci.length === 0) {
|
||||||
return null; // If there are no announcements in confirmation, do not render this section
|
return null; // If there are no announcements in confirmation, do not render this section
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +179,12 @@ export const AnnunciInConfermaAccordion = ({
|
||||||
<div className="flex flex-col flex-wrap gap-5">
|
<div className="flex flex-col flex-wrap gap-5">
|
||||||
{annunci.map((data) => {
|
{annunci.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
<ServizioAnnuncioProvider
|
||||||
|
data={data}
|
||||||
|
key={data.id}
|
||||||
|
servizioId={servizioId}
|
||||||
|
userId={userId}
|
||||||
|
>
|
||||||
<AnnuncioCard className="border-2 border-green-500" />
|
<AnnuncioCard className="border-2 border-green-500" />
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
|
|
@ -183,6 +201,7 @@ export const AnnunciSelezionatiAccordion = ({
|
||||||
}: {
|
}: {
|
||||||
annunci: ServizioData["annunci"];
|
annunci: ServizioData["annunci"];
|
||||||
}) => {
|
}) => {
|
||||||
|
const { servizioId, userId } = useServizio();
|
||||||
return (
|
return (
|
||||||
<Accordion collapsible defaultValue="open" type="single">
|
<Accordion collapsible defaultValue="open" type="single">
|
||||||
<AccordionItem className="space-y-4" value="open">
|
<AccordionItem className="space-y-4" value="open">
|
||||||
|
|
@ -194,7 +213,12 @@ export const AnnunciSelezionatiAccordion = ({
|
||||||
{annunci.length > 0 ? (
|
{annunci.length > 0 ? (
|
||||||
annunci.map((data) => {
|
annunci.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
<ServizioAnnuncioProvider
|
||||||
|
data={data}
|
||||||
|
key={data.id}
|
||||||
|
servizioId={servizioId}
|
||||||
|
userId={userId}
|
||||||
|
>
|
||||||
<AnnuncioCard className="border-2 border-sky-500" />
|
<AnnuncioCard className="border-2 border-sky-500" />
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
|
|
@ -210,7 +234,8 @@ export const AnnunciSelezionatiAccordion = ({
|
||||||
>
|
>
|
||||||
Annunci
|
Annunci
|
||||||
<ExternalLink className="size-4" />
|
<ExternalLink className="size-4" />
|
||||||
</Link>
|
</Link>{" "}
|
||||||
|
o tra gli annunci compatibili qui sotto.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
116
apps/infoalloggi/src/components/servizio/shared.tsx
Normal file
116
apps/infoalloggi/src/components/servizio/shared.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { Check, ChevronsUpDown, ExternalLink } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
import { ExtIcon } from "../area-riservata/allegati";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "../ui/command";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
|
|
||||||
|
export const DocInfo = ({
|
||||||
|
storageId,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
storageId: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
const { data: file_info } = api.storage.getFileInfos.useQuery({
|
||||||
|
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"
|
||||||
|
href={`/area-riservata/allegato-view/${storageId}`}
|
||||||
|
id="selected-file"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<Button className="w-fit grow" variant="outline">
|
||||||
|
<ExtIcon mimeType={file_info.mimeType} />
|
||||||
|
<span className="max-w-40 truncate sm:max-w-80">
|
||||||
|
{file_info.originalName}
|
||||||
|
</span>
|
||||||
|
<ExternalLink className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocCombo = ({
|
||||||
|
files,
|
||||||
|
onSelect,
|
||||||
|
current,
|
||||||
|
}: {
|
||||||
|
files: FileMetadataWithAdmin[];
|
||||||
|
onSelect: (file: FileMetadataWithAdmin) => void;
|
||||||
|
current: string | null;
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover onOpenChange={setOpen} open={open}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-expanded={open}
|
||||||
|
aria-label="Select Existing File"
|
||||||
|
className="w-auto justify-between"
|
||||||
|
role="combobox"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
value={file.id}
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<ExtIcon mimeType={file.mimeType} />
|
||||||
|
<span className="block font-medium">
|
||||||
|
{file.originalName}
|
||||||
|
</span>
|
||||||
|
{current === file.id && (
|
||||||
|
<Check className="size-4 text-accent-foreground" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -182,6 +182,7 @@ export const UploadComponent = ({
|
||||||
userId,
|
userId,
|
||||||
from_admin,
|
from_admin,
|
||||||
});
|
});
|
||||||
|
if (!res) continue;
|
||||||
userStorageIds.push(res.user_storage_id);
|
userStorageIds.push(res.user_storage_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,11 @@ const FormNewServizioSchema = z.object({
|
||||||
reddito: z.string().nullable(),
|
reddito: z.string().nullable(),
|
||||||
sede_legale: z.string().nullable(),
|
sede_legale: z.string().nullable(),
|
||||||
sesso: z.string(),
|
sesso: z.string(),
|
||||||
skipOrdine: z.boolean(),
|
|
||||||
telefono: z.string(),
|
telefono: z.string(),
|
||||||
terrazzo: z.boolean(),
|
terrazzo: z.boolean(),
|
||||||
tipologia: z.custom<TipologiaPosizioneEnum>(),
|
tipologia: z.custom<TipologiaPosizioneEnum>(),
|
||||||
scadenza_motivazione_transitoria: z.date().nullable(),
|
scadenza_motivazione_transitoria: z.date().nullable(),
|
||||||
|
skipPayment: z.boolean(),
|
||||||
});
|
});
|
||||||
type FormNewServizioValues = z.infer<typeof FormNewServizioSchema>;
|
type FormNewServizioValues = z.infer<typeof FormNewServizioSchema>;
|
||||||
|
|
||||||
|
|
@ -308,7 +308,6 @@ export const FormNewServizioAcquisto = ({
|
||||||
sede_legale: initialData.anagrafica?.sede_legale || "",
|
sede_legale: initialData.anagrafica?.sede_legale || "",
|
||||||
sesso: initialData.anagrafica?.sesso || "M",
|
sesso: initialData.anagrafica?.sesso || "M",
|
||||||
...initialData.servizio,
|
...initialData.servizio,
|
||||||
skipOrdine: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { locale, t } = useTranslation();
|
const { locale, t } = useTranslation();
|
||||||
|
|
@ -337,7 +336,7 @@ export const FormNewServizioAcquisto = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
if (data) {
|
if (data.ordineIdForPayment) {
|
||||||
await router.push(`/area-riservata/admin/user-view/ordini/${userId}`);
|
await router.push(`/area-riservata/admin/user-view/ordini/${userId}`);
|
||||||
} else {
|
} else {
|
||||||
await router.push(
|
await router.push(
|
||||||
|
|
@ -345,7 +344,11 @@ export const FormNewServizioAcquisto = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await router.push(`/servizio/pagamento/${data}`);
|
if (data.ordineIdForPayment) {
|
||||||
|
await router.push(`/servizio/pagamento/${data.ordineIdForPayment}`);
|
||||||
|
} else {
|
||||||
|
await router.push("/area-riservata/dashboard");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -391,9 +394,9 @@ export const FormNewServizioAcquisto = ({
|
||||||
reddito: fields.reddito,
|
reddito: fields.reddito,
|
||||||
terrazzo: fields.terrazzo,
|
terrazzo: fields.terrazzo,
|
||||||
tipologia: fields.tipologia,
|
tipologia: fields.tipologia,
|
||||||
|
skipPayment: fields.skipPayment,
|
||||||
},
|
},
|
||||||
servizioId: initialData.servizio.servizio_id,
|
servizioId: initialData.servizio.servizio_id,
|
||||||
skipOrdine: isAdmin ? fields.skipOrdine : false,
|
|
||||||
user: {
|
user: {
|
||||||
cognome: fields.cognome,
|
cognome: fields.cognome,
|
||||||
email: fields.email,
|
email: fields.email,
|
||||||
|
|
@ -1427,11 +1430,11 @@ export const FormNewServizioAcquisto = ({
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="skipOrdine"
|
name="skipPayment"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="py-2">
|
<FormItem className="py-2">
|
||||||
<div className="flex items-center justify-start gap-x-4">
|
<div className="flex items-center justify-start gap-x-4">
|
||||||
<FormLabel htmlFor="skipOrdine">
|
<FormLabel htmlFor="skipPayment">
|
||||||
Vuoi saltare la creazione dell'ordine e impostare il
|
Vuoi saltare la creazione dell'ordine e impostare il
|
||||||
servizio come attivo?
|
servizio come attivo?
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
|
|
@ -1439,7 +1442,7 @@ export const FormNewServizioAcquisto = ({
|
||||||
<Switch
|
<Switch
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
className="data-[state=checked]:bg-neutral-700"
|
className="data-[state=checked]:bg-neutral-700"
|
||||||
id="skipOrdine"
|
id="skipPayment"
|
||||||
onCheckedChange={(v) => {
|
onCheckedChange={(v) => {
|
||||||
field.onChange(v);
|
field.onChange(v);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -83,14 +83,15 @@ const ConfirmSection = ({
|
||||||
const [letto, setLetto] = useState(false);
|
const [letto, setLetto] = useState(false);
|
||||||
const [accettato, setAccettato] = useState(false);
|
const [accettato, setAccettato] = useState(false);
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate: conferma } = api.servizio.updateServizioAnnunci.useMutation({
|
const { mutate: conferma } =
|
||||||
|
api.servizio.userAcceptConfermaImmobile.useMutation({
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
const toastId = toast.loading(t.caricamento, {
|
const toastId = toast.loading(t.caricamento, {
|
||||||
icon: "🔄",
|
icon: "🔄",
|
||||||
});
|
});
|
||||||
return { toastId };
|
return { toastId };
|
||||||
},
|
},
|
||||||
onSuccess: async (_error, _variables, context) => {
|
onSuccess: async (_data, _variables, context) => {
|
||||||
await utils.storage.retrieveUserFileData.invalidate();
|
await utils.storage.retrieveUserFileData.invalidate();
|
||||||
|
|
||||||
toast.success("Confermato", {
|
toast.success("Confermato", {
|
||||||
|
|
@ -147,9 +148,6 @@ const ConfirmSection = ({
|
||||||
// Handle confirmation logic here
|
// Handle confirmation logic here
|
||||||
conferma({
|
conferma({
|
||||||
annuncioId,
|
annuncioId,
|
||||||
data: {
|
|
||||||
accettato_conferma_at: new Date(),
|
|
||||||
},
|
|
||||||
servizioId,
|
servizioId,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
|
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
|
type ServizioProviderProps = {
|
||||||
|
isAdmin: boolean;
|
||||||
|
userId: UsersId;
|
||||||
|
servizioId: ServizioServizioId;
|
||||||
|
servizio: ServizioData;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
type ServizioContextProps = {
|
type ServizioContextProps = {
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
userId: UsersId;
|
userId: UsersId;
|
||||||
|
|
@ -14,20 +24,11 @@ type ServizioContextProps = {
|
||||||
const ServizioContext = createContext<ServizioContextProps | null>(null);
|
const ServizioContext = createContext<ServizioContextProps | null>(null);
|
||||||
|
|
||||||
export const ServizioProvider = ({
|
export const ServizioProvider = ({
|
||||||
isAdmin,
|
|
||||||
userId,
|
|
||||||
servizioId,
|
|
||||||
servizio,
|
|
||||||
children,
|
children,
|
||||||
}: {
|
...props
|
||||||
isAdmin: boolean;
|
}: ServizioProviderProps) => {
|
||||||
userId: UsersId;
|
|
||||||
servizioId: ServizioServizioId;
|
|
||||||
servizio: ServizioData;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<ServizioContext.Provider value={{ isAdmin, servizio, servizioId, userId }}>
|
<ServizioContext.Provider value={{ ...props }}>
|
||||||
{children}
|
{children}
|
||||||
</ServizioContext.Provider>
|
</ServizioContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
@ -41,23 +42,54 @@ export const useServizio = () => {
|
||||||
return servizioContext;
|
return servizioContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ServizioAnnuncioProviderProps = {
|
||||||
|
data: ServizioData["annunci"][number];
|
||||||
|
servizioId: ServizioServizioId;
|
||||||
|
userId: UsersId;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
type ServizioAnnuncioContextProps = {
|
type ServizioAnnuncioContextProps = {
|
||||||
data: ServizioData["annunci"][number];
|
data: ServizioData["annunci"][number];
|
||||||
annuncioId: AnnunciId;
|
annuncioId: AnnunciId;
|
||||||
|
updateServizioAnnunci: (data: ServizioAnnunciUpdate) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ServizioAnnuncioContext =
|
const ServizioAnnuncioContext =
|
||||||
createContext<ServizioAnnuncioContextProps | null>(null);
|
createContext<ServizioAnnuncioContextProps | null>(null);
|
||||||
export const ServizioAnnuncioProvider = ({
|
export const ServizioAnnuncioProvider = ({
|
||||||
data,
|
data,
|
||||||
|
servizioId,
|
||||||
|
userId,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: ServizioAnnuncioProviderProps) => {
|
||||||
data: ServizioData["annunci"][number];
|
const utils = api.useUtils();
|
||||||
children: React.ReactNode;
|
const { mutateAsync } = api.servizio.updateServizioAnnunci.useMutation({
|
||||||
}) => {
|
onError: (error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.servizio.getAllServizioAnnunci.invalidate({
|
||||||
|
userId: userId,
|
||||||
|
});
|
||||||
|
await utils.servizio.getServizio.invalidate({
|
||||||
|
servizioId,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("Dati salvati con successo");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const updateServizioAnnunci = async (upd: ServizioAnnunciUpdate) => {
|
||||||
|
await mutateAsync({
|
||||||
|
annuncioId: data.id,
|
||||||
|
servizioId,
|
||||||
|
data: upd,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioContext.Provider
|
<ServizioAnnuncioContext.Provider
|
||||||
value={{ annuncioId: data.annunci_id, data }}
|
value={{ annuncioId: data.annunci_id, data, updateServizioAnnunci }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ServizioAnnuncioContext.Provider>
|
</ServizioAnnuncioContext.Provider>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import {
|
||||||
import {
|
import {
|
||||||
addServizio,
|
addServizio,
|
||||||
addServizioAnnunci,
|
addServizioAnnunci,
|
||||||
|
adminUpdateConfermaStatus,
|
||||||
deleteServizio,
|
deleteServizio,
|
||||||
deleteServizioAnnuncio,
|
deleteServizioAnnuncio,
|
||||||
getAllPagamenti,
|
getAllPagamenti,
|
||||||
|
|
@ -47,7 +48,8 @@ import {
|
||||||
updatePagamento,
|
updatePagamento,
|
||||||
updateServizio,
|
updateServizio,
|
||||||
updateServizioAnnuncio,
|
updateServizioAnnuncio,
|
||||||
userConfirmImmobile,
|
userAccettaConferma,
|
||||||
|
userSendConfermaIntent,
|
||||||
} from "~/server/controllers/servizio.controller";
|
} from "~/server/controllers/servizio.controller";
|
||||||
import {
|
import {
|
||||||
zAnnuncioId,
|
zAnnuncioId,
|
||||||
|
|
@ -256,7 +258,6 @@ export const servizioRouter = createTRPCRouter({
|
||||||
anagrafica: z.custom<UsersAnagraficaUpdate>(),
|
anagrafica: z.custom<UsersAnagraficaUpdate>(),
|
||||||
servizio: z.custom<ServizioUpdate>(),
|
servizio: z.custom<ServizioUpdate>(),
|
||||||
servizioId: zServizioId,
|
servizioId: zServizioId,
|
||||||
skipOrdine: z.boolean().optional(),
|
|
||||||
user: z.custom<
|
user: z.custom<
|
||||||
Pick<Users, "nome" | "cognome" | "email" | "telefono" | "username">
|
Pick<Users, "nome" | "cognome" | "email" | "telefono" | "username">
|
||||||
>(),
|
>(),
|
||||||
|
|
@ -420,7 +421,22 @@ export const servizioRouter = createTRPCRouter({
|
||||||
servizioId: input.servizioId,
|
servizioId: input.servizioId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
userConfirmImmobile: protectedProcedure
|
adminUpdateConfermaStatus: adminProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
annuncioId: zAnnuncioId,
|
||||||
|
servizioId: zServizioId,
|
||||||
|
status: z.boolean(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
return await adminUpdateConfermaStatus({
|
||||||
|
annuncioId: input.annuncioId,
|
||||||
|
servizioId: input.servizioId,
|
||||||
|
status: input.status,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
userSendConfermaIntent: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
annuncioId: zAnnuncioId,
|
annuncioId: zAnnuncioId,
|
||||||
|
|
@ -428,7 +444,20 @@ export const servizioRouter = createTRPCRouter({
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await userConfirmImmobile({
|
return await userSendConfermaIntent({
|
||||||
|
annuncioId: input.annuncioId,
|
||||||
|
servizioId: input.servizioId,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
userAcceptConfermaImmobile: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
annuncioId: zAnnuncioId,
|
||||||
|
servizioId: zServizioId,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
return await userAccettaConferma({
|
||||||
annuncioId: input.annuncioId,
|
annuncioId: input.annuncioId,
|
||||||
servizioId: input.servizioId,
|
servizioId: input.servizioId,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
import type {
|
||||||
|
NewUsersStorage,
|
||||||
|
UsersStorageUserStorageId,
|
||||||
|
} from "~/schemas/public/UsersStorage";
|
||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
|
|
@ -11,6 +14,7 @@ import {
|
||||||
deleteUserStorageEntry,
|
deleteUserStorageEntry,
|
||||||
getAllWithFromAdmin,
|
getAllWithFromAdmin,
|
||||||
getMultipleWithRelations,
|
getMultipleWithRelations,
|
||||||
|
getStorageIdFromUserStorageId,
|
||||||
purgeFileFromUserStorages,
|
purgeFileFromUserStorages,
|
||||||
retrieveUserFiles,
|
retrieveUserFiles,
|
||||||
} from "~/server/controllers/storage.controller";
|
} from "~/server/controllers/storage.controller";
|
||||||
|
|
@ -83,4 +87,11 @@ export const storageRouter = createTRPCRouter({
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await editFile(input.fileId, input.name);
|
return await editFile(input.fileId, input.name);
|
||||||
}),
|
}),
|
||||||
|
getStorageIdFromUserStorageId: protectedProcedure
|
||||||
|
.input(z.object({ userStorageId: z.custom<UsersStorageUserStorageId>() }))
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
return await getStorageIdFromUserStorageId({
|
||||||
|
userStorageId: input.userStorageId,
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -237,15 +237,13 @@ export const postAcquistoDataHandler = async ({
|
||||||
user,
|
user,
|
||||||
anagrafica,
|
anagrafica,
|
||||||
servizio,
|
servizio,
|
||||||
skipOrdine = false,
|
|
||||||
}: {
|
}: {
|
||||||
servizioId: ServizioServizioId;
|
servizioId: ServizioServizioId;
|
||||||
userId: UsersId;
|
userId: UsersId;
|
||||||
user: Pick<Users, "nome" | "cognome" | "email" | "telefono">;
|
user: Pick<Users, "nome" | "cognome" | "email" | "telefono">;
|
||||||
anagrafica: UsersAnagraficaUpdate;
|
anagrafica: UsersAnagraficaUpdate;
|
||||||
servizio: ServizioUpdate;
|
servizio: ServizioUpdate;
|
||||||
skipOrdine?: boolean;
|
}): Promise<{ ordineIdForPayment: OrdiniOrdineId | null }> => {
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
const servizioData = await db.transaction().execute(async (tx) => {
|
const servizioData = await db.transaction().execute(async (tx) => {
|
||||||
// Update user and anagrafica data
|
// Update user and anagrafica data
|
||||||
|
|
@ -285,16 +283,18 @@ export const postAcquistoDataHandler = async ({
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (skipOrdine) {
|
if (servizioData.skipPayment) {
|
||||||
// If skipping order, set servizio as active with decorrenza today
|
// If skipping order, set servizio as active with decorrenza today
|
||||||
await updateServizio({
|
await updateServizio({
|
||||||
data: {
|
data: {
|
||||||
decorrenza: new Date(),
|
decorrenza: new Date(),
|
||||||
isOkAcconto: true,
|
isOkAcconto: true,
|
||||||
|
isOkSaldo: true,
|
||||||
|
isOkConsulenza: true,
|
||||||
},
|
},
|
||||||
servizioId,
|
servizioId,
|
||||||
});
|
});
|
||||||
return null;
|
return { ordineIdForPayment: null };
|
||||||
}
|
}
|
||||||
//cleanup unused payments
|
//cleanup unused payments
|
||||||
|
|
||||||
|
|
@ -350,7 +350,7 @@ export const postAcquistoDataHandler = async ({
|
||||||
.where("ordini.isActive", "=", false)
|
.where("ordini.isActive", "=", false)
|
||||||
.where("ordine_id", "!=", existingOrder.ordine_id)
|
.where("ordine_id", "!=", existingOrder.ordine_id)
|
||||||
.execute();
|
.execute();
|
||||||
return existingOrder.ordine_id;
|
return { ordineIdForPayment: existingOrder.ordine_id };
|
||||||
}
|
}
|
||||||
|
|
||||||
const order = await db
|
const order = await db
|
||||||
|
|
@ -364,7 +364,7 @@ export const postAcquistoDataHandler = async ({
|
||||||
.returning("ordine_id")
|
.returning("ordine_id")
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
return order.ordine_id;
|
return { ordineIdForPayment: order.ordine_id };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
@ -790,44 +790,6 @@ export const updateServizioAnnuncio = async ({
|
||||||
.where("annunci_id", "=", annuncioId)
|
.where("annunci_id", "=", annuncioId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const servizio = await getServizioById(servizioId);
|
|
||||||
const utente = await getUser({ db, userId: servizio.user_id });
|
|
||||||
//ADMIN CONFERMA LAVORATA EMAIL
|
|
||||||
if (data.hasConfermaAdmin) {
|
|
||||||
await NewMail({
|
|
||||||
mailType: "generic",
|
|
||||||
props: {
|
|
||||||
link: {
|
|
||||||
href: `${env.BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`,
|
|
||||||
label: "Vai al servizio",
|
|
||||||
},
|
|
||||||
noreply: true,
|
|
||||||
testo: `La conferma è stata lavorata con successo. Puoi procedere con le prossime azioni.`,
|
|
||||||
title: "Conferma lavorata per il servizio di ricerca affitto",
|
|
||||||
},
|
|
||||||
subject: "Conferma lavorata per il servizio di ricerca affitto",
|
|
||||||
to: utente.email,
|
|
||||||
userId: utente.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// USER ACCETTA CONFERMA EMAIL
|
|
||||||
if (data.accettato_conferma_at) {
|
|
||||||
await NewMail({
|
|
||||||
mailType: "generic",
|
|
||||||
props: {
|
|
||||||
link: {
|
|
||||||
href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`,
|
|
||||||
label: "Vai al servizio",
|
|
||||||
},
|
|
||||||
noreply: true,
|
|
||||||
testo: `L'utente ha accettato le condizioni di conferma per la locazione. Puoi procedere con le prossime azioni.`,
|
|
||||||
title: `Utente ${utente.username} ha accettato le condizioni di conferma`,
|
|
||||||
},
|
|
||||||
subject: `Utente ${utente.username} ha accettato le condizioni di conferma`,
|
|
||||||
to: "web@infoalloggi.it",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|
@ -1241,7 +1203,7 @@ export const sendServizioEmail = async ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userConfirmImmobile = async ({
|
export const userSendConfermaIntent = async ({
|
||||||
servizioId,
|
servizioId,
|
||||||
annuncioId,
|
annuncioId,
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -1314,6 +1276,101 @@ export const userConfirmImmobile = async ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const adminUpdateConfermaStatus = async ({
|
||||||
|
servizioId,
|
||||||
|
annuncioId,
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
servizioId: ServizioServizioId;
|
||||||
|
annuncioId: AnnunciId;
|
||||||
|
status: boolean;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.updateTable("servizio_annunci")
|
||||||
|
.set({
|
||||||
|
hasConfermaAdmin: status,
|
||||||
|
})
|
||||||
|
.where("servizio_id", "=", servizioId)
|
||||||
|
.where("annunci_id", "=", annuncioId)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
const servizio = await getServizioById(servizioId);
|
||||||
|
const utente = await getUser({ db, userId: servizio.user_id });
|
||||||
|
//ADMIN CONFERMA LAVORATA EMAIL
|
||||||
|
if (status) {
|
||||||
|
await NewMail({
|
||||||
|
mailType: "generic",
|
||||||
|
props: {
|
||||||
|
link: {
|
||||||
|
href: `${env.BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`,
|
||||||
|
label: "Vai al servizio",
|
||||||
|
},
|
||||||
|
noreply: true,
|
||||||
|
testo: `La conferma è stata lavorata con successo. Puoi procedere con le prossime azioni.`,
|
||||||
|
title: "Conferma lavorata per il servizio di ricerca affitto",
|
||||||
|
},
|
||||||
|
subject: "Conferma lavorata per il servizio di ricerca affitto",
|
||||||
|
to: utente.email,
|
||||||
|
userId: utente.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Error updating conferma status from annuncio: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userAccettaConferma = async ({
|
||||||
|
servizioId,
|
||||||
|
annuncioId,
|
||||||
|
}: {
|
||||||
|
servizioId: ServizioServizioId;
|
||||||
|
annuncioId: AnnunciId;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.updateTable("servizio_annunci")
|
||||||
|
.set({
|
||||||
|
accettato_conferma_at: new Date(),
|
||||||
|
})
|
||||||
|
.where("servizio_id", "=", servizioId)
|
||||||
|
.where("annunci_id", "=", annuncioId)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
const servizio = await getServizioById(servizioId);
|
||||||
|
const utente = await getUser({ db, userId: servizio.user_id });
|
||||||
|
|
||||||
|
// USER ACCETTA CONFERMA EMAIL
|
||||||
|
|
||||||
|
await NewMail({
|
||||||
|
mailType: "generic",
|
||||||
|
props: {
|
||||||
|
link: {
|
||||||
|
href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`,
|
||||||
|
label: "Vai al servizio",
|
||||||
|
},
|
||||||
|
noreply: true,
|
||||||
|
testo: `L'utente ha accettato le condizioni di conferma per la locazione. Puoi procedere con le prossime azioni.`,
|
||||||
|
title: `Utente ${utente.username} ha accettato le condizioni di conferma`,
|
||||||
|
},
|
||||||
|
subject: `Utente ${utente.username} ha accettato le condizioni di conferma`,
|
||||||
|
to: "web@infoalloggi.it",
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Error accepting conferma from annuncio: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
export const isOrdineAwaitingPayment = async (ordineId: OrdiniOrdineId) => {
|
export const isOrdineAwaitingPayment = async (ordineId: OrdiniOrdineId) => {
|
||||||
try {
|
try {
|
||||||
const ordine = await db
|
const ordine = await db
|
||||||
|
|
@ -1529,6 +1586,7 @@ export const getCompatibileAnnunci = async (
|
||||||
|
|
||||||
let qry = db
|
let qry = db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
|
.distinctOn("annunci.id")
|
||||||
.select((eb) => [
|
.select((eb) => [
|
||||||
"id",
|
"id",
|
||||||
"codice",
|
"codice",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
import type {
|
||||||
|
NewUsersStorage,
|
||||||
|
UsersStorageUserStorageId,
|
||||||
|
} from "~/schemas/public/UsersStorage";
|
||||||
import { db, type Querier } from "~/server/db";
|
import { db, type Querier } from "~/server/db";
|
||||||
import { fetchFiles, retriveFilesSafely } from "../services/storage.service";
|
import { fetchFiles, retriveFilesSafely } from "../services/storage.service";
|
||||||
|
|
||||||
|
|
@ -83,6 +86,26 @@ export const retrieveUserFiles = async ({ userId }: { userId: UsersId }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getStorageIdFromUserStorageId = async ({
|
||||||
|
userStorageId,
|
||||||
|
}: {
|
||||||
|
userStorageId: UsersStorageUserStorageId;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const storage = await db
|
||||||
|
.selectFrom("users_storage")
|
||||||
|
.select("storageId")
|
||||||
|
.where("user_storage_id", "=", userStorageId)
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
return storage.storageId;
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Error while getting storage ID from user storage ID: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getAllWithFromAdmin = async () => {
|
export const getAllWithFromAdmin = async () => {
|
||||||
try {
|
try {
|
||||||
const files = await db
|
const files = await db
|
||||||
|
|
@ -115,7 +138,7 @@ export const addUserStorage = async (data: NewUsersStorage) => {
|
||||||
.values(data)
|
.values(data)
|
||||||
.onConflict((oc) => oc.columns(["userId", "storageId"]).doNothing())
|
.onConflict((oc) => oc.columns(["userId", "storageId"]).doNothing())
|
||||||
.returning("user_storage_id")
|
.returning("user_storage_id")
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirst();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue