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,
|
||||
permanenza = $48,
|
||||
persone = $49,
|
||||
updated_at = NOW()
|
||||
media_updated_at = NOW()
|
||||
WHERE codice = $46
|
||||
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
||||
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
||||
|
|
@ -155,7 +155,7 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
|||
disponibile_da,
|
||||
permanenza,
|
||||
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())
|
||||
`, 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 { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { AllegatoIframe } from "~/components/allegato-iframe";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
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 LoadingButton from "../custom_ui/loading-button";
|
||||
import { Button } from "../ui/button";
|
||||
import { type UploadCallback, UploadModal } from "../upload_modal";
|
||||
|
||||
export const FileSection = ({ storageId }: { storageId: string }) => {
|
||||
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 {
|
||||
CircleCheck,
|
||||
FileBadge,
|
||||
FileText,
|
||||
Handshake,
|
||||
PackageCheck,
|
||||
Pointer,
|
||||
} from "lucide-react";
|
||||
import { FileBadge, FileText, PackageCheck, Pointer } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
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 {
|
||||
Tooltip,
|
||||
|
|
@ -30,49 +10,166 @@ import {
|
|||
TooltipTrigger,
|
||||
} from "~/components/ui/tooltip";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import { api } from "~/utils/api";
|
||||
import { ConfermaAnnuncioModal, ConfermaInLavorazioneModal } from "./conferma";
|
||||
import { ContattiProprietarioModal } from "./modale_contatti";
|
||||
|
||||
export const Interactions = () => {
|
||||
const SezioneConferma = () => {
|
||||
const { servizioId } = useServizio();
|
||||
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 (
|
||||
<Link
|
||||
aria-label="Conferma immobile"
|
||||
href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`}
|
||||
>
|
||||
<Button className="w-fit" variant="success">
|
||||
<Pointer className="size-6" />
|
||||
Confermato - Procedi ora
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
className="w-fit cursor-not-allowed opacity-50"
|
||||
variant="success"
|
||||
>
|
||||
<Pointer className="size-6" />
|
||||
Confermato - Procedi ora
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Il documento non è più disponibile</p>
|
||||
<p>Se hai bisogno di una copia, contattaci via email o chat.</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const PostConfermaSection = () => {
|
||||
const { servizio, servizioId } = useServizio();
|
||||
const { data, annuncioId } = useServizioAnnuncio();
|
||||
|
||||
const saldoPaid = servizio.isOkSaldo;
|
||||
const consulenzaPaid = servizio.isOkConsulenza;
|
||||
|
||||
if (!saldoPaid) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className="font-semibold">
|
||||
Questo annuncio è stato confermato, procedi al saldo:
|
||||
</h3>
|
||||
<SaldoButton annuncioId={annuncioId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!data.doc_contratto_added) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className="font-semibold text-green-500">
|
||||
Ci siamo quasi! Stiamo preparando il contratto per te.
|
||||
</h3>
|
||||
<h3 className="font-semibold">
|
||||
Una volta pronto, riceverai una notifica via email.
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 />
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-semibold">
|
||||
{consulenzaPaid
|
||||
? "Il contratto è pronto, puoi visualizzarlo:"
|
||||
: "Il contratto è pronto, puoi visualizzarlo e procedere al saldo:"}
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{data.doc_contratto_ref ? (
|
||||
<Link
|
||||
aria-label="Visualizza contratto"
|
||||
href={`/servizio/contratto/${servizioId}/${annuncioId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button className="w-fit">
|
||||
<FileText className="size-6" />
|
||||
Visualizza il contratto
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button className="w-fit cursor-not-allowed opacity-50">
|
||||
<FileText className="size-6" />
|
||||
Visualizza il contratto
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Il documento non è più disponibile</p>
|
||||
<p>Se hai bisogno di una copia, contattaci via email o chat.</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
{data.user_confirmed_at && !data.hasConfermaAdmin && (
|
||||
<ConfermaLavorazioneModal />
|
||||
)}
|
||||
{data.doc_conferma_added &&
|
||||
data.accettato_conferma_at == null &&
|
||||
data.hasConfermaAdmin &&
|
||||
(data.doc_conferma_ref ? (
|
||||
|
||||
{!consulenzaPaid && data.contratto_tipo && <SaldoConsulenzaButton />}
|
||||
|
||||
{data.doc_registrazione_added &&
|
||||
(data.doc_registrazione_ref ? (
|
||||
<Link
|
||||
aria-label="Conferma immobile"
|
||||
href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`}
|
||||
aria-label="Visualizza registrazione"
|
||||
href={`/servizio/contratto-registrazione/${servizioId}/${annuncioId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button className="w-fit" variant="success">
|
||||
<Pointer className="size-6" />
|
||||
Confermato - Procedi ora
|
||||
<Button className="w-fit">
|
||||
<FileBadge className="size-6" />
|
||||
Visualizza la registrazione
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
className="w-fit cursor-not-allowed opacity-50"
|
||||
variant="success"
|
||||
>
|
||||
<Pointer className="size-6" />
|
||||
Confermato - Procedi ora
|
||||
<Button className="w-fit cursor-not-allowed opacity-50">
|
||||
<FileBadge className="size-6" />
|
||||
Visualizza la registrazione
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
|
@ -84,118 +181,21 @@ export const Interactions = () => {
|
|||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
))}
|
||||
{data.accettato_conferma_at != null && (
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
{!servizio.isOkSaldo && data.accettato_conferma_at != null && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className="font-semibold">
|
||||
Questo annuncio è stato confermato, procedi al saldo:
|
||||
</h3>
|
||||
<SaldoButton annuncioId={annuncioId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{servizio.isOkSaldo &&
|
||||
data.accettato_conferma_at != null &&
|
||||
data.doc_contratto_added == null && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className="font-semibold text-green-500">
|
||||
Ci siamo quasi! Stiamo preparando il contratto per te.
|
||||
</h3>
|
||||
<h3 className="font-semibold">
|
||||
Una volta pronto, riceverai una notifica via email.
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
{servizio.isOkSaldo &&
|
||||
data.accettato_conferma_at != null &&
|
||||
data.doc_contratto_added && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-semibold">
|
||||
{servizio.isOkConsulenza
|
||||
? "Il contratto è pronto, puoi visualizzarlo:"
|
||||
: "Il contratto è pronto, puoi visualizzarlo e procedere al saldo:"}
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{data.doc_contratto_ref ? (
|
||||
<Link
|
||||
aria-label="Visualizza contratto"
|
||||
href={`/servizio/contratto/${servizioId}/${annuncioId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button className="w-fit">
|
||||
<FileText className="size-6" />
|
||||
Visualizza il contratto
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button className="w-fit cursor-not-allowed opacity-50">
|
||||
<FileText className="size-6" />
|
||||
Visualizza il contratto
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Il documento non è più disponibile</p>
|
||||
<p>
|
||||
Se hai bisogno di una copia, contattaci via email o
|
||||
chat.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
export const Interactions = () => {
|
||||
const { data } = useServizioAnnuncio();
|
||||
|
||||
{!servizio.isOkConsulenza && data.contratto_tipo && (
|
||||
<SaldoConsulenzaButton />
|
||||
)}
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<ContattiProprietarioModal />
|
||||
|
||||
{data.doc_registrazione_added &&
|
||||
(data.doc_registrazione_ref ? (
|
||||
<Link
|
||||
aria-label="Visualizza registrazione"
|
||||
href={`/servizio/contratto-registrazione/${servizioId}/${annuncioId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button className="w-fit">
|
||||
<FileBadge className="size-6" />
|
||||
Visualizza la registrazione
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button className="w-fit cursor-not-allowed opacity-50">
|
||||
<FileBadge className="size-6" />
|
||||
Visualizza la registrazione
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Il documento non è più disponibile</p>
|
||||
<p>
|
||||
Se hai bisogno di una copia, contattaci via email o
|
||||
chat.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<SezioneConferma />
|
||||
|
||||
{data.accettato_conferma_at != null && <PostConfermaSection />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -282,113 +282,3 @@ const SaldoConsulenzaButton = ({
|
|||
</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,
|
||||
);
|
||||
|
||||
if (servizio.isOkSaldo) {
|
||||
if (servizio.isOkSaldo && annuncioConfermato.length > 0) {
|
||||
return (
|
||||
<ServizioCard
|
||||
content={
|
||||
<>
|
||||
{annuncioConfermato.map((data) => {
|
||||
return (
|
||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
||||
<ServizioAnnuncioProvider
|
||||
data={data}
|
||||
key={data.id}
|
||||
servizioId={servizio.servizio_id}
|
||||
userId={userId}
|
||||
>
|
||||
<AnnuncioCard />
|
||||
</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(
|
||||
(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">
|
||||
{annunciInConferma.map((data) => {
|
||||
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" />
|
||||
</ServizioAnnuncioProvider>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<AnnunciSelezionatiAccordion annunci={annunciNotInConferma} />
|
||||
<AnnunciSelezionatiAccordion annunci={annunciPreContatti} />
|
||||
<div className="flex items-center justify-center">
|
||||
<AnnunciCompatibili />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ const DocCheckpoint = () => {
|
|||
});
|
||||
const handleUpload = (
|
||||
field: "doc_personale_fronte_ref" | "doc_personale_retro_ref",
|
||||
id: string,
|
||||
id: UsersStorageUserStorageId,
|
||||
) => {
|
||||
mutate({
|
||||
servizioId: servizio.servizio_id,
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export const AddAnnuncio = ({
|
|||
};
|
||||
|
||||
const InterruzioneServizio = () => {
|
||||
const { servizio, userId } = useServizio();
|
||||
const { servizio, userId, isAdmin } = useServizio();
|
||||
|
||||
const beforeInterruzioneLimit =
|
||||
servizio.decorrenza != null &&
|
||||
|
|
@ -223,9 +223,14 @@ const InterruzioneServizio = () => {
|
|||
<Button
|
||||
aria-label="Request Interruzione"
|
||||
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>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
|
|
@ -255,7 +260,7 @@ const InterruzioneServizio = () => {
|
|||
};
|
||||
|
||||
const EditPreferenze = () => {
|
||||
const { servizioId, userId, isAdmin } = useServizio();
|
||||
const { servizioId, userId, isAdmin, servizio } = useServizio();
|
||||
const [openEditPreferenze, setOpenEditPreferenze] = useState(false);
|
||||
const { data, isLoading } = api.servizio.getServizio.useQuery({ servizioId });
|
||||
const utils = api.useUtils();
|
||||
|
|
@ -287,7 +292,15 @@ const EditPreferenze = () => {
|
|||
return (
|
||||
<Credenza onOpenChange={setOpenEditPreferenze} open={openEditPreferenze}>
|
||||
<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 />
|
||||
Modifica Preferenze
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ export const AnnunciInConfermaDialog = ({
|
|||
}: {
|
||||
annunci: ServizioData["annunci"];
|
||||
}) => {
|
||||
const { servizioId, userId } = useServizio();
|
||||
return (
|
||||
<DialogServizio
|
||||
title="Annunci in conferma"
|
||||
|
|
@ -84,7 +85,12 @@ export const AnnunciInConfermaDialog = ({
|
|||
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
|
||||
{annunci.map((data) => {
|
||||
return (
|
||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
||||
<ServizioAnnuncioProvider
|
||||
data={data}
|
||||
key={data.id}
|
||||
servizioId={servizioId}
|
||||
userId={userId}
|
||||
>
|
||||
<AnnuncioCard className="border-2 border-green-500" />
|
||||
</ServizioAnnuncioProvider>
|
||||
);
|
||||
|
|
@ -99,6 +105,7 @@ export const AnnunciSelezionatiDialog = ({
|
|||
}: {
|
||||
annunci: ServizioData["annunci"];
|
||||
}) => {
|
||||
const { servizioId, userId } = useServizio();
|
||||
return (
|
||||
<DialogServizio
|
||||
title="Annunci selezionati"
|
||||
|
|
@ -122,7 +129,12 @@ export const AnnunciSelezionatiDialog = ({
|
|||
{annunci.length > 0 ? (
|
||||
annunci.map((data) => {
|
||||
return (
|
||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
||||
<ServizioAnnuncioProvider
|
||||
data={data}
|
||||
key={data.id}
|
||||
servizioId={servizioId}
|
||||
userId={userId}
|
||||
>
|
||||
<AnnuncioCard className="border-2 border-sky-500" />
|
||||
</ServizioAnnuncioProvider>
|
||||
);
|
||||
|
|
@ -152,6 +164,7 @@ export const AnnunciInConfermaAccordion = ({
|
|||
}: {
|
||||
annunci: ServizioData["annunci"];
|
||||
}) => {
|
||||
const { servizioId, userId } = useServizio();
|
||||
if (annunci.length === 0) {
|
||||
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">
|
||||
{annunci.map((data) => {
|
||||
return (
|
||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
||||
<ServizioAnnuncioProvider
|
||||
data={data}
|
||||
key={data.id}
|
||||
servizioId={servizioId}
|
||||
userId={userId}
|
||||
>
|
||||
<AnnuncioCard className="border-2 border-green-500" />
|
||||
</ServizioAnnuncioProvider>
|
||||
);
|
||||
|
|
@ -183,6 +201,7 @@ export const AnnunciSelezionatiAccordion = ({
|
|||
}: {
|
||||
annunci: ServizioData["annunci"];
|
||||
}) => {
|
||||
const { servizioId, userId } = useServizio();
|
||||
return (
|
||||
<Accordion collapsible defaultValue="open" type="single">
|
||||
<AccordionItem className="space-y-4" value="open">
|
||||
|
|
@ -194,7 +213,12 @@ export const AnnunciSelezionatiAccordion = ({
|
|||
{annunci.length > 0 ? (
|
||||
annunci.map((data) => {
|
||||
return (
|
||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
||||
<ServizioAnnuncioProvider
|
||||
data={data}
|
||||
key={data.id}
|
||||
servizioId={servizioId}
|
||||
userId={userId}
|
||||
>
|
||||
<AnnuncioCard className="border-2 border-sky-500" />
|
||||
</ServizioAnnuncioProvider>
|
||||
);
|
||||
|
|
@ -210,7 +234,8 @@ export const AnnunciSelezionatiAccordion = ({
|
|||
>
|
||||
Annunci
|
||||
<ExternalLink className="size-4" />
|
||||
</Link>
|
||||
</Link>{" "}
|
||||
o tra gli annunci compatibili qui sotto.
|
||||
</span>
|
||||
</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,
|
||||
from_admin,
|
||||
});
|
||||
if (!res) continue;
|
||||
userStorageIds.push(res.user_storage_id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,11 +108,11 @@ const FormNewServizioSchema = z.object({
|
|||
reddito: z.string().nullable(),
|
||||
sede_legale: z.string().nullable(),
|
||||
sesso: z.string(),
|
||||
skipOrdine: z.boolean(),
|
||||
telefono: z.string(),
|
||||
terrazzo: z.boolean(),
|
||||
tipologia: z.custom<TipologiaPosizioneEnum>(),
|
||||
scadenza_motivazione_transitoria: z.date().nullable(),
|
||||
skipPayment: z.boolean(),
|
||||
});
|
||||
type FormNewServizioValues = z.infer<typeof FormNewServizioSchema>;
|
||||
|
||||
|
|
@ -308,7 +308,6 @@ export const FormNewServizioAcquisto = ({
|
|||
sede_legale: initialData.anagrafica?.sede_legale || "",
|
||||
sesso: initialData.anagrafica?.sesso || "M",
|
||||
...initialData.servizio,
|
||||
skipOrdine: false,
|
||||
};
|
||||
|
||||
const { locale, t } = useTranslation();
|
||||
|
|
@ -337,7 +336,7 @@ export const FormNewServizioAcquisto = ({
|
|||
});
|
||||
|
||||
if (isAdmin) {
|
||||
if (data) {
|
||||
if (data.ordineIdForPayment) {
|
||||
await router.push(`/area-riservata/admin/user-view/ordini/${userId}`);
|
||||
} else {
|
||||
await router.push(
|
||||
|
|
@ -345,7 +344,11 @@ export const FormNewServizioAcquisto = ({
|
|||
);
|
||||
}
|
||||
} 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,
|
||||
terrazzo: fields.terrazzo,
|
||||
tipologia: fields.tipologia,
|
||||
skipPayment: fields.skipPayment,
|
||||
},
|
||||
servizioId: initialData.servizio.servizio_id,
|
||||
skipOrdine: isAdmin ? fields.skipOrdine : false,
|
||||
user: {
|
||||
cognome: fields.cognome,
|
||||
email: fields.email,
|
||||
|
|
@ -1427,11 +1430,11 @@ export const FormNewServizioAcquisto = ({
|
|||
{isAdmin && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="skipOrdine"
|
||||
name="skipPayment"
|
||||
render={({ field }) => (
|
||||
<FormItem className="py-2">
|
||||
<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
|
||||
servizio come attivo?
|
||||
</FormLabel>
|
||||
|
|
@ -1439,7 +1442,7 @@ export const FormNewServizioAcquisto = ({
|
|||
<Switch
|
||||
checked={field.value}
|
||||
className="data-[state=checked]:bg-neutral-700"
|
||||
id="skipOrdine"
|
||||
id="skipPayment"
|
||||
onCheckedChange={(v) => {
|
||||
field.onChange(v);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -83,24 +83,25 @@ const ConfirmSection = ({
|
|||
const [letto, setLetto] = useState(false);
|
||||
const [accettato, setAccettato] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
const { mutate: conferma } = api.servizio.updateServizioAnnunci.useMutation({
|
||||
onMutate: () => {
|
||||
const toastId = toast.loading(t.caricamento, {
|
||||
icon: "🔄",
|
||||
});
|
||||
return { toastId };
|
||||
},
|
||||
onSuccess: async (_error, _variables, context) => {
|
||||
await utils.storage.retrieveUserFileData.invalidate();
|
||||
const { mutate: conferma } =
|
||||
api.servizio.userAcceptConfermaImmobile.useMutation({
|
||||
onMutate: () => {
|
||||
const toastId = toast.loading(t.caricamento, {
|
||||
icon: "🔄",
|
||||
});
|
||||
return { toastId };
|
||||
},
|
||||
onSuccess: async (_data, _variables, context) => {
|
||||
await utils.storage.retrieveUserFileData.invalidate();
|
||||
|
||||
toast.success("Confermato", {
|
||||
icon: "👍",
|
||||
id: context?.toastId,
|
||||
});
|
||||
toast.success("Confermato", {
|
||||
icon: "👍",
|
||||
id: context?.toastId,
|
||||
});
|
||||
|
||||
await router.push("/area-riservata/dashboard");
|
||||
},
|
||||
});
|
||||
await router.push("/area-riservata/dashboard");
|
||||
},
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-start space-y-3 px-2">
|
||||
|
|
@ -147,9 +148,6 @@ const ConfirmSection = ({
|
|||
// Handle confirmation logic here
|
||||
conferma({
|
||||
annuncioId,
|
||||
data: {
|
||||
accettato_conferma_at: new Date(),
|
||||
},
|
||||
servizioId,
|
||||
});
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
import { createContext, useContext } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
|
||||
import type { UsersId } from "~/schemas/public/Users";
|
||||
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 = {
|
||||
isAdmin: boolean;
|
||||
userId: UsersId;
|
||||
|
|
@ -14,20 +24,11 @@ type ServizioContextProps = {
|
|||
const ServizioContext = createContext<ServizioContextProps | null>(null);
|
||||
|
||||
export const ServizioProvider = ({
|
||||
isAdmin,
|
||||
userId,
|
||||
servizioId,
|
||||
servizio,
|
||||
children,
|
||||
}: {
|
||||
isAdmin: boolean;
|
||||
userId: UsersId;
|
||||
servizioId: ServizioServizioId;
|
||||
servizio: ServizioData;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
...props
|
||||
}: ServizioProviderProps) => {
|
||||
return (
|
||||
<ServizioContext.Provider value={{ isAdmin, servizio, servizioId, userId }}>
|
||||
<ServizioContext.Provider value={{ ...props }}>
|
||||
{children}
|
||||
</ServizioContext.Provider>
|
||||
);
|
||||
|
|
@ -41,23 +42,54 @@ export const useServizio = () => {
|
|||
return servizioContext;
|
||||
};
|
||||
|
||||
type ServizioAnnuncioProviderProps = {
|
||||
data: ServizioData["annunci"][number];
|
||||
servizioId: ServizioServizioId;
|
||||
userId: UsersId;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
type ServizioAnnuncioContextProps = {
|
||||
data: ServizioData["annunci"][number];
|
||||
annuncioId: AnnunciId;
|
||||
updateServizioAnnunci: (data: ServizioAnnunciUpdate) => Promise<void>;
|
||||
};
|
||||
|
||||
const ServizioAnnuncioContext =
|
||||
createContext<ServizioAnnuncioContextProps | null>(null);
|
||||
export const ServizioAnnuncioProvider = ({
|
||||
data,
|
||||
servizioId,
|
||||
userId,
|
||||
children,
|
||||
}: {
|
||||
data: ServizioData["annunci"][number];
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
}: ServizioAnnuncioProviderProps) => {
|
||||
const utils = api.useUtils();
|
||||
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 (
|
||||
<ServizioAnnuncioContext.Provider
|
||||
value={{ annuncioId: data.annunci_id, data }}
|
||||
value={{ annuncioId: data.annunci_id, data, updateServizioAnnunci }}
|
||||
>
|
||||
{children}
|
||||
</ServizioAnnuncioContext.Provider>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
import {
|
||||
addServizio,
|
||||
addServizioAnnunci,
|
||||
adminUpdateConfermaStatus,
|
||||
deleteServizio,
|
||||
deleteServizioAnnuncio,
|
||||
getAllPagamenti,
|
||||
|
|
@ -47,7 +48,8 @@ import {
|
|||
updatePagamento,
|
||||
updateServizio,
|
||||
updateServizioAnnuncio,
|
||||
userConfirmImmobile,
|
||||
userAccettaConferma,
|
||||
userSendConfermaIntent,
|
||||
} from "~/server/controllers/servizio.controller";
|
||||
import {
|
||||
zAnnuncioId,
|
||||
|
|
@ -256,7 +258,6 @@ export const servizioRouter = createTRPCRouter({
|
|||
anagrafica: z.custom<UsersAnagraficaUpdate>(),
|
||||
servizio: z.custom<ServizioUpdate>(),
|
||||
servizioId: zServizioId,
|
||||
skipOrdine: z.boolean().optional(),
|
||||
user: z.custom<
|
||||
Pick<Users, "nome" | "cognome" | "email" | "telefono" | "username">
|
||||
>(),
|
||||
|
|
@ -420,7 +421,22 @@ export const servizioRouter = createTRPCRouter({
|
|||
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(
|
||||
z.object({
|
||||
annuncioId: zAnnuncioId,
|
||||
|
|
@ -428,7 +444,20 @@ export const servizioRouter = createTRPCRouter({
|
|||
}),
|
||||
)
|
||||
.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,
|
||||
servizioId: input.servizioId,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { TRPCError } from "@trpc/server";
|
||||
import z from "zod";
|
||||
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
||||
import type {
|
||||
NewUsersStorage,
|
||||
UsersStorageUserStorageId,
|
||||
} from "~/schemas/public/UsersStorage";
|
||||
import {
|
||||
adminProcedure,
|
||||
createTRPCRouter,
|
||||
|
|
@ -11,6 +14,7 @@ import {
|
|||
deleteUserStorageEntry,
|
||||
getAllWithFromAdmin,
|
||||
getMultipleWithRelations,
|
||||
getStorageIdFromUserStorageId,
|
||||
purgeFileFromUserStorages,
|
||||
retrieveUserFiles,
|
||||
} from "~/server/controllers/storage.controller";
|
||||
|
|
@ -83,4 +87,11 @@ export const storageRouter = createTRPCRouter({
|
|||
.mutation(async ({ input }) => {
|
||||
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,
|
||||
anagrafica,
|
||||
servizio,
|
||||
skipOrdine = false,
|
||||
}: {
|
||||
servizioId: ServizioServizioId;
|
||||
userId: UsersId;
|
||||
user: Pick<Users, "nome" | "cognome" | "email" | "telefono">;
|
||||
anagrafica: UsersAnagraficaUpdate;
|
||||
servizio: ServizioUpdate;
|
||||
skipOrdine?: boolean;
|
||||
}) => {
|
||||
}): Promise<{ ordineIdForPayment: OrdiniOrdineId | null }> => {
|
||||
try {
|
||||
const servizioData = await db.transaction().execute(async (tx) => {
|
||||
// Update user and anagrafica data
|
||||
|
|
@ -285,16 +283,18 @@ export const postAcquistoDataHandler = async ({
|
|||
.executeTakeFirstOrThrow();
|
||||
});
|
||||
|
||||
if (skipOrdine) {
|
||||
if (servizioData.skipPayment) {
|
||||
// If skipping order, set servizio as active with decorrenza today
|
||||
await updateServizio({
|
||||
data: {
|
||||
decorrenza: new Date(),
|
||||
isOkAcconto: true,
|
||||
isOkSaldo: true,
|
||||
isOkConsulenza: true,
|
||||
},
|
||||
servizioId,
|
||||
});
|
||||
return null;
|
||||
return { ordineIdForPayment: null };
|
||||
}
|
||||
//cleanup unused payments
|
||||
|
||||
|
|
@ -350,7 +350,7 @@ export const postAcquistoDataHandler = async ({
|
|||
.where("ordini.isActive", "=", false)
|
||||
.where("ordine_id", "!=", existingOrder.ordine_id)
|
||||
.execute();
|
||||
return existingOrder.ordine_id;
|
||||
return { ordineIdForPayment: existingOrder.ordine_id };
|
||||
}
|
||||
|
||||
const order = await db
|
||||
|
|
@ -364,7 +364,7 @@ export const postAcquistoDataHandler = async ({
|
|||
.returning("ordine_id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return order.ordine_id;
|
||||
return { ordineIdForPayment: order.ordine_id };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
|
|
@ -790,44 +790,6 @@ export const updateServizioAnnuncio = async ({
|
|||
.where("annunci_id", "=", annuncioId)
|
||||
.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;
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
|
|
@ -1241,7 +1203,7 @@ export const sendServizioEmail = async ({
|
|||
}
|
||||
};
|
||||
|
||||
export const userConfirmImmobile = async ({
|
||||
export const userSendConfermaIntent = async ({
|
||||
servizioId,
|
||||
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) => {
|
||||
try {
|
||||
const ordine = await db
|
||||
|
|
@ -1529,6 +1586,7 @@ export const getCompatibileAnnunci = async (
|
|||
|
||||
let qry = db
|
||||
.selectFrom("annunci")
|
||||
.distinctOn("annunci.id")
|
||||
.select((eb) => [
|
||||
"id",
|
||||
"codice",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { TRPCError } from "@trpc/server";
|
||||
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 { 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 () => {
|
||||
try {
|
||||
const files = await db
|
||||
|
|
@ -115,7 +138,7 @@ export const addUserStorage = async (data: NewUsersStorage) => {
|
|||
.values(data)
|
||||
.onConflict((oc) => oc.columns(["userId", "storageId"]).doNothing())
|
||||
.returning("user_storage_id")
|
||||
.executeTakeFirstOrThrow();
|
||||
.executeTakeFirst();
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue