refactor: update confirmation handling in service announcements
- Changed the confirmation status checks from `accettato_conferma_at` to `status_conferma` in various components and controllers. - Introduced a new `StatusConfermaEnum` to standardize confirmation statuses. - Removed deprecated confirmation-related fields from the `ServizioAnnunci` schema. - Deleted unused confirmation-related API methods and adjusted related logic in the service controller. - Updated the search parameters schema to use the new confirmation status. - Removed the `conferma-immobile` and `riapri-conferma` pages as they are no longer needed.
This commit is contained in:
parent
dc28e42baa
commit
ed7359aa16
17 changed files with 185 additions and 2010 deletions
53
apps/db/migrations/43_conferma_enum.up.sql
Normal file
53
apps/db/migrations/43_conferma_enum.up.sql
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE public."StatusConfermaEnum" AS ENUM (
|
||||
'Inviato conferma',
|
||||
'Conferma accettata',
|
||||
'Caparra versata'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
ADD COLUMN IF NOT EXISTS status_conferma public."StatusConfermaEnum";
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'servizio_annunci'
|
||||
AND column_name = 'hasConfermaAdmin'
|
||||
) THEN
|
||||
UPDATE public.servizio_annunci
|
||||
SET status_conferma = 'Caparra versata'
|
||||
WHERE "hasConfermaAdmin" = true;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS doc_conferma_ref;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS user_confirmed_at;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS accettato_conferma_at;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS caparra_causale;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS caparra_iban;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS caparra_importo;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS caparra_intestazione;
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS "hasConfermaAdmin";
|
||||
|
||||
ALTER TABLE IF EXISTS public.servizio_annunci
|
||||
DROP COLUMN IF EXISTS doc_conferma_added;
|
||||
|
|
@ -1,893 +0,0 @@
|
|||
import { format } from "date-fns";
|
||||
import {
|
||||
CircleAlert,
|
||||
CircleCheck,
|
||||
ClipboardPaste,
|
||||
FlaskConical,
|
||||
Pen,
|
||||
Trash2,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useDebounce } from "react-use";
|
||||
import z from "zod";
|
||||
import { it } from "~/i18n/it";
|
||||
import { handleConsegna } from "~/lib/annuncio_details";
|
||||
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 { Confirm } from "../confirm";
|
||||
import { Counter } from "../counter";
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
CredenzaClose,
|
||||
CredenzaContent,
|
||||
CredenzaDescription,
|
||||
CredenzaFooter,
|
||||
CredenzaHeader,
|
||||
CredenzaTitle,
|
||||
CredenzaTrigger,
|
||||
} from "../custom_ui/credenza";
|
||||
import { NumberInput } from "../custom_ui/InputNumber";
|
||||
import {
|
||||
type ListOption,
|
||||
MultiSelect,
|
||||
UnpackOptions,
|
||||
} from "../custom_ui/multiselect";
|
||||
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 { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||
import { UploadModal } from "../upload_modal";
|
||||
import { ServizioPacksInfos } from "./servizio_actions";
|
||||
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"
|
||||
size="sm"
|
||||
variant={data.hasConfermaAdmin ? "success" : "orange"}
|
||||
>
|
||||
{data.hasConfermaAdmin ? <CircleCheck /> : <Wrench />}
|
||||
<span>Conferma</span>
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh] w-full px-3 sm:max-w-6xl">
|
||||
<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-4">
|
||||
<DocSection />
|
||||
<Caparra />
|
||||
{servizio.tipologia === TipologiaPosizioneEnum.Transitorio && (
|
||||
<MotivazioneSection />
|
||||
)}
|
||||
<SendConferma />
|
||||
</div>
|
||||
</CredenzaBody>
|
||||
|
||||
<CredenzaFooter>
|
||||
<DebugActions />
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Close Credenza" variant="outline">
|
||||
Chiudi
|
||||
</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
};
|
||||
|
||||
const DebugActions = () => {
|
||||
const { servizioId } = useServizio();
|
||||
const { annuncioId } = useServizioAnnuncio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate } = api.servizio.updateServizioAnnunci.useMutation({
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await utils.servizio.invalidate();
|
||||
|
||||
toast.success("Dati salvati con successo");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button>
|
||||
<FlaskConical />
|
||||
<span>Debug</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="flex w-fit flex-col items-center gap-4"
|
||||
>
|
||||
<Confirm
|
||||
description="Sei sicuro di voler eliminare l'annuncio?"
|
||||
onConfirm={() => {
|
||||
mutate({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
data: {
|
||||
user_confirmed_at: null,
|
||||
},
|
||||
});
|
||||
}}
|
||||
title="Elimina annuncio"
|
||||
>
|
||||
<Button
|
||||
aria-label="Delete Annuncio"
|
||||
className="w-full"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
<span>Elimina Conferma</span>
|
||||
</Button>
|
||||
</Confirm>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
const DocSection = () => {
|
||||
const { userId } = useServizio();
|
||||
const { data, updateServizioAnnunci } = useServizioAnnuncio();
|
||||
const initialDoc = data.doc_conferma_ref;
|
||||
const [selectedDoc, setSelectedDoc] = useState<string | null>(
|
||||
data.doc_conferma_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_conferma_added: selectedDoc != null,
|
||||
doc_conferma_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 di Conferma</CardTitle>
|
||||
<CardDescription>
|
||||
documento che l'utente vedra alla conferma
|
||||
</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_conferma_added: false,
|
||||
doc_conferma_ref: null,
|
||||
});
|
||||
setSelectedDoc(null);
|
||||
}}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 className="size-6" />
|
||||
</Button>
|
||||
</DocInfo>
|
||||
</div>
|
||||
)}
|
||||
<DocCombo
|
||||
current={selectedDoc}
|
||||
files={files}
|
||||
onSelect={(f) => {
|
||||
setSelectedDoc(f.id);
|
||||
}}
|
||||
/>
|
||||
<UploadModal
|
||||
cb_onUpload={({ storageIds }) => {
|
||||
if (storageIds.length === 1 && storageIds[0]) {
|
||||
setSelectedDoc(storageIds[0]);
|
||||
}
|
||||
}}
|
||||
isAdmin
|
||||
userId={userId}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="gap-4">
|
||||
<Button
|
||||
aria-label="Save Contratto"
|
||||
disabled={selectedDoc === initialDoc}
|
||||
onClick={async () => {
|
||||
await handleSave();
|
||||
}}
|
||||
variant="success"
|
||||
>
|
||||
Salva
|
||||
</Button>
|
||||
{selectedDoc !== initialDoc ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<CircleAlert className="size-5 text-orange-500" />
|
||||
<span className="text-orange-500">Salva modifiche</span>
|
||||
</div>
|
||||
) : (
|
||||
initialDoc &&
|
||||
selectedDoc === initialDoc && (
|
||||
<div className="flex items-center gap-2">
|
||||
<CircleCheck className="size-5 text-green-500" />
|
||||
<span className="text-green-500">Dati salvati</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</CardFooter>
|
||||
</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 initialCaparra = {
|
||||
caparra_causale: data.caparra_causale,
|
||||
caparra_iban: data.caparra_iban,
|
||||
caparra_importo: data.caparra_importo,
|
||||
caparra_intestazione: data.caparra_intestazione,
|
||||
};
|
||||
|
||||
const [caparra, setCaparra] = useState<CaparraObj | undefined>(
|
||||
initialCaparra,
|
||||
);
|
||||
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 className="gap-4">
|
||||
<Button
|
||||
aria-label="Save Caparra"
|
||||
disabled={JSON.stringify(caparra) === JSON.stringify(initialCaparra)}
|
||||
onClick={async () => {
|
||||
await handleSave();
|
||||
}}
|
||||
variant="success"
|
||||
>
|
||||
Salva
|
||||
</Button>
|
||||
{JSON.stringify(caparra) !== JSON.stringify(initialCaparra) ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<CircleAlert className="size-5 text-orange-500" />
|
||||
<span className="text-orange-500">Salva modifiche</span>
|
||||
</div>
|
||||
) : (
|
||||
Object.values(initialCaparra).every((v) => v !== null) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<CircleCheck className="size-5 text-green-500" />
|
||||
<span className="text-green-500">Dati salvati</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const MotivazioneSection = () => {
|
||||
const { servizio } = useServizio();
|
||||
const { t } = useTranslation();
|
||||
|
||||
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.parametri.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>
|
||||
{!servizio.skipDocMotivazione && (
|
||||
<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 TransitorioSection = () => {
|
||||
const { servizio } = useServizio();
|
||||
const { data } = useServizioAnnuncio();
|
||||
const { t } = useTranslation();
|
||||
const { servizioId } = useServizio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
toast.success("Servizio modificato con successo");
|
||||
await utils.servizio.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const handlePermanenzaChange = (value: ListOption) => {
|
||||
update({
|
||||
servizioId,
|
||||
data: {
|
||||
permanenza: value.value !== "" ? parseInt(value.value) : null,
|
||||
},
|
||||
});
|
||||
};
|
||||
const preferenzeDescrizione = data.desc_it?.match(/Permanenza:.*$/m);
|
||||
|
||||
const permanenzaAnnuncio =
|
||||
data.permanenza !== null
|
||||
? `${data.permanenza.join(" - ")} mesi`
|
||||
: (preferenzeDescrizione?.[0] ??
|
||||
"errore: testo non trovato in descrizione");
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-4 rounded-md border border-border bg-muted p-2 sm:flex-row">
|
||||
<div className="w-full">
|
||||
<Label className="text-base" htmlFor="permanenza">
|
||||
Permanenza (da parametri servizio):
|
||||
</Label>
|
||||
|
||||
<MultiSelect
|
||||
defaultValue={
|
||||
servizio.permanenza !== null
|
||||
? {
|
||||
label: t.parametri.permanenza[servizio.permanenza] || "",
|
||||
value: servizio.permanenza.toString(),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
elementId="permanenza"
|
||||
elementName="permanenza"
|
||||
isMulti={false}
|
||||
onValueChange={handlePermanenzaChange}
|
||||
options={UnpackOptions({
|
||||
options: t.parametri.permanenza,
|
||||
})}
|
||||
placeholder="Seleziona..."
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full space-y-2">
|
||||
<p className="font-medium">Permanenza in annuncio:</p>
|
||||
<p>{permanenzaAnnuncio}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StabileSection = () => {
|
||||
const { servizio, servizioId } = useServizio();
|
||||
const { data } = useServizioAnnuncio();
|
||||
const [value, setValue] = useState(servizio.budget);
|
||||
const utils = api.useUtils();
|
||||
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
toast.success("Servizio modificato con successo");
|
||||
await utils.servizio.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
if (value === servizio.budget) return;
|
||||
update({
|
||||
servizioId,
|
||||
data: {
|
||||
budget: value,
|
||||
},
|
||||
});
|
||||
},
|
||||
1000,
|
||||
[value],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 rounded-md border border-border bg-muted p-2 sm:flex-row">
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<span className="font-semibold">Budget (da preferenze servizio):</span>
|
||||
<NumberInput
|
||||
allowNegative={false}
|
||||
defaultValue={value}
|
||||
min={0}
|
||||
onValueChange={(val) => {
|
||||
if (val !== undefined) {
|
||||
setValue(val);
|
||||
}
|
||||
}}
|
||||
stepper={50}
|
||||
suffix=" €"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full space-y-2">
|
||||
<p className="font-medium">Prezzo in descrizione:</p>
|
||||
<p>{formatCurrency(data.prezzo / 100)}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GeneralSection = () => {
|
||||
const { data } = useServizioAnnuncio();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { servizio, servizioId } = useServizio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
toast.success("Servizio modificato con successo");
|
||||
await utils.servizio.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const handleDisponibilitàChange = (value: ListOption) => {
|
||||
update({
|
||||
servizioId,
|
||||
data: {
|
||||
entromese: value.value !== "" ? parseInt(value.value) : null,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleAdultiChange = (value: number) => {
|
||||
update({
|
||||
servizioId,
|
||||
data: {
|
||||
n_adulti: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
const handleMinoriChange = (value: number) => {
|
||||
update({
|
||||
servizioId,
|
||||
data: {
|
||||
n_minori: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const personeDescrizione = data.desc_it?.match(/Preferenza:.*$/m);
|
||||
|
||||
const personeAnnuncio = data.persone
|
||||
? data.persone.join(", ").toUpperCase()
|
||||
: (personeDescrizione?.[0] ?? "errore: testo non trovato in descrizione");
|
||||
|
||||
const dispAnnuncio = data.disponibile_da
|
||||
? format(new Date(data.disponibile_da), "dd/MM/yyyy")
|
||||
: handleConsegna({
|
||||
consegna: data.consegna,
|
||||
aggiornamento: it.card.in_aggiornamento,
|
||||
|
||||
consegna_da: it.card.consegna_da,
|
||||
mesi: it.parametri.mesi,
|
||||
subito: it.card.consegna_subito,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-x-4 gap-y-2 rounded-md border border-border bg-muted p-2 sm:flex-row">
|
||||
<div className="w-full space-y-2">
|
||||
<span className="font-semibold">Persone:</span>
|
||||
<div className="flex w-full gap-4">
|
||||
<div className="flex w-1/2 flex-col gap-2">
|
||||
<Label htmlFor="n_adulti">{t.parametri.nadulti_label}</Label>
|
||||
<Counter
|
||||
name="n_adulti"
|
||||
onlyPositive
|
||||
setValue={(v) => handleAdultiChange(Number(v))}
|
||||
value={servizio.n_adulti || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex w-1/2 flex-col gap-2">
|
||||
<Label htmlFor="n_minori">{t.parametri.nminori_label}</Label>
|
||||
<Counter
|
||||
name="n_minori"
|
||||
onlyPositive
|
||||
setValue={(v) => handleMinoriChange(Number(v))}
|
||||
value={servizio.n_minori || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full space-y-2">
|
||||
<p className="font-medium">Persone in annuncio:</p>
|
||||
<p>{personeAnnuncio}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-x-4 gap-y-2 rounded-md border border-border bg-muted p-2 sm:flex-row">
|
||||
<div className="w-full">
|
||||
<Label className="text-base" htmlFor="select-mesi">
|
||||
Entro mese (da parametri servizio):
|
||||
</Label>
|
||||
<MultiSelect
|
||||
defaultValue={
|
||||
servizio.entromese !== null
|
||||
? {
|
||||
// biome-ignore lint/style/noNonNullAssertion: <known size>
|
||||
label: t.parametri.mesi[servizio.entromese]!,
|
||||
value: String(servizio.entromese),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
elementId="select-mesi"
|
||||
elementName="entro_mese"
|
||||
isMulti={false}
|
||||
onValueChange={handleDisponibilitàChange}
|
||||
options={UnpackOptions({
|
||||
options: t.parametri.mesi,
|
||||
})}
|
||||
placeholder="Seleziona..."
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full space-y-2">
|
||||
<p className="font-medium">Disponibilità in annuncio:</p>
|
||||
<p>{dispAnnuncio}</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SendConferma = () => {
|
||||
const { servizio } = useServizio();
|
||||
const { data } = useServizioAnnuncio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate: handleUpdate } =
|
||||
api.servizio.adminUpdateConfermaStatus.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.servizio.invalidate();
|
||||
|
||||
toast.success("Status aggiornato con successo");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Errore durante l'aggiornamento: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const docOk = data.doc_conferma_added && data.doc_conferma_ref;
|
||||
const caparraOk =
|
||||
data.caparra_iban &&
|
||||
data.caparra_importo &&
|
||||
data.caparra_intestazione &&
|
||||
data.caparra_causale;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Lavorazione Conferma</CardTitle>
|
||||
<CardDescription>
|
||||
controlla i parametri di ricerca prima!
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-10">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p>
|
||||
<span className="font-semibold">Tipologia servizio: </span>
|
||||
{servizio.tipologia}
|
||||
</p>
|
||||
{servizio.tipologia === TipologiaPosizioneEnum.Transitorio ? (
|
||||
<TransitorioSection />
|
||||
) : (
|
||||
<StabileSection />
|
||||
)}
|
||||
<GeneralSection />
|
||||
<ServizioPacksInfos />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{!docOk && (
|
||||
<p className="text-red-500">
|
||||
Attenzione: il documento di conferma non è stato ancora caricato
|
||||
</p>
|
||||
)}
|
||||
{!caparraOk && (
|
||||
<p className="text-red-500">
|
||||
Attenzione: i dati per il pagamento della caparra non sono
|
||||
completi
|
||||
</p>
|
||||
)}
|
||||
<p className="font-semibold text-lg">
|
||||
ATTENZIONE: Il saldo verrà aggiunto (se necessario), in base ai
|
||||
parametri, all'invio della conferma
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex items-center gap-3">
|
||||
<Button
|
||||
aria-label="Send Conferma"
|
||||
disabled={!docOk || !caparraOk || data.hasConfermaAdmin}
|
||||
onClick={() => {
|
||||
handleUpdate({
|
||||
servizioId: servizio.servizio_id,
|
||||
annuncioId: data.id,
|
||||
status: true,
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
variant="orange"
|
||||
>
|
||||
<CircleAlert className="size-5" />
|
||||
<span>Invia la conferma</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
import {
|
||||
Circle,
|
||||
CircleCheck,
|
||||
FileBadge,
|
||||
FileText,
|
||||
FolderKanban,
|
||||
Info,
|
||||
PackageCheck,
|
||||
Pointer,
|
||||
Printer,
|
||||
Trash2,
|
||||
Workflow,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
|
@ -22,6 +24,14 @@ import {
|
|||
AlertDialogTrigger,
|
||||
} from "~/components/ui/alert-dialog";
|
||||
import { Button, type ButtonConfigs } from "~/components/ui/button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverHeader,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "~/components/ui/popover";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
|
|
@ -33,11 +43,10 @@ import { useTranslation } from "~/providers/I18nProvider";
|
|||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||
import StatusConfermaEnum from "~/schemas/public/StatusConfermaEnum";
|
||||
import { api } from "~/utils/api";
|
||||
import { Confirm } from "../confirm";
|
||||
import { AdminLavoraConferma } from "./admin_conferma";
|
||||
import { AdminContratto } from "./admin_contratto";
|
||||
import { ConfermaAnnuncioModal, ConfermaInLavorazioneModal } from "./conferma";
|
||||
import { ContattiProprietarioModal } from "./modale_contatti";
|
||||
|
||||
export const AdminActions = () => {
|
||||
|
|
@ -62,8 +71,9 @@ export const AdminActions = () => {
|
|||
<h3 className="ml-1 font-semibold">Azioni Admin:</h3>
|
||||
<div className="flex w-full max-w-fit flex-wrap justify-start gap-3">
|
||||
<AdminAnnuncioContatti />
|
||||
{data.user_confirmed_at != null && <AdminLavoraConferma />}
|
||||
{data.accettato_conferma_at != null && (
|
||||
<AdminAnnuncioConfermaPopover />
|
||||
|
||||
{data.status_conferma != null && (
|
||||
<>
|
||||
<AdminContratto />
|
||||
{data.gestionale_id && (
|
||||
|
|
@ -134,86 +144,79 @@ export const AdminActions = () => {
|
|||
};
|
||||
|
||||
export const UserActions = () => {
|
||||
const { servizioId } = useServizio();
|
||||
const { data, annuncioId } = useServizioAnnuncio();
|
||||
const { locale } = useTranslation();
|
||||
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;
|
||||
const { data } = useServizioAnnuncio();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full flex-col flex-wrap gap-2 sm:flex-row">
|
||||
<ContattiProprietarioModal />
|
||||
{ConfermaUtente
|
||||
? !data.hasConfermaAdmin && <ConfermaInLavorazioneModal />
|
||||
: ContattiSbloccati && <ConfermaAnnuncioModal />}
|
||||
|
||||
{AccettatoConferma ? (
|
||||
<Link
|
||||
aria-label={
|
||||
locale === "it" ? "Rivedi la conferma" : "Review the confirmation"
|
||||
}
|
||||
href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`}
|
||||
>
|
||||
<Button className="w-full sm:w-fit" variant="success">
|
||||
<Pointer />
|
||||
{locale === "it"
|
||||
? "Rivedi la conferma"
|
||||
: "Review the confirmation"}
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
ConfermaLavorata &&
|
||||
(data.doc_conferma_ref ? (
|
||||
<Link
|
||||
aria-label={locale === "it" ? "Confermato" : "Confirmed"}
|
||||
href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`}
|
||||
>
|
||||
<Button className="w-full sm:w-fit" variant="success">
|
||||
<Pointer />
|
||||
{locale === "it"
|
||||
? "Confermato - Procedi ora"
|
||||
: "Confirmed - Proceed now"}
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
className="w-full cursor-not-allowed opacity-50 sm:w-fit"
|
||||
variant="success"
|
||||
>
|
||||
<Pointer />
|
||||
{locale === "it"
|
||||
? "Confermato - Procedi ora"
|
||||
: "Confirmed - Proceed now"}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
{locale === "it"
|
||||
? "Il documento non è più disponibile"
|
||||
: "The document is no longer available"}
|
||||
</p>
|
||||
<p>
|
||||
{locale === "it"
|
||||
? "Se hai bisogno di una copia, contattaci via email o chat."
|
||||
: "If you need a copy, contact us via email or chat."}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{data.accettato_conferma_at != null && <PostConfermaSection />}
|
||||
{data.status_conferma != null && <PostConfermaSection />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AdminAnnuncioConfermaPopover = () => {
|
||||
const { servizioId } = useServizio();
|
||||
const { data } = useServizioAnnuncio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate } = api.servizio.updateServizioAnnunci.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.servizio.invalidate();
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
toast.error("Errore durante l'aggiornamento dell'annuncio");
|
||||
},
|
||||
});
|
||||
const handleStatusChange = (status: StatusConfermaEnum | null) => {
|
||||
mutate({
|
||||
annuncioId: data.id,
|
||||
servizioId,
|
||||
data: { status_conferma: status },
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button size="sm" variant="orange">
|
||||
<Workflow /> {data.status_conferma || "Non in conferma"}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverHeader>
|
||||
<PopoverTitle>Status Conferma:</PopoverTitle>
|
||||
<PopoverDescription className="sr-only">
|
||||
Description
|
||||
</PopoverDescription>
|
||||
</PopoverHeader>
|
||||
|
||||
<div className="flex flex-col divide-y rounded-md border border-border">
|
||||
<Button
|
||||
className="justify-start rounded-b-none"
|
||||
onClick={() => handleStatusChange(null)}
|
||||
variant={data.status_conferma === null ? "default" : "outline"}
|
||||
>
|
||||
{data.status_conferma === null ? <CircleCheck /> : <Circle />}
|
||||
Non in conferma
|
||||
</Button>
|
||||
{Object.values(StatusConfermaEnum).map((status) => (
|
||||
<Button
|
||||
className="justify-start rounded-none last:rounded-b-md"
|
||||
key={status}
|
||||
onClick={() => handleStatusChange(status)}
|
||||
variant={data.status_conferma === status ? "default" : "outline"}
|
||||
>
|
||||
{data.status_conferma === status ? <CircleCheck /> : <Circle />}
|
||||
{status}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
const AdminAnnuncioContatti = () => {
|
||||
const { data } = useServizioAnnuncio();
|
||||
const { data: annuncio, isLoading } = api.annunci.getAnnuncioFull.useQuery({
|
||||
|
|
|
|||
|
|
@ -1,268 +0,0 @@
|
|||
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 { getStorageUrl } from "~/lib/storage_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 { DocumentiUploadSection } from "../area-riservata/documenti_personali";
|
||||
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";
|
||||
|
||||
export const FileSection = ({ storageId }: { storageId: string }) => {
|
||||
const { data: fileInfos, isLoading } = api.storage.getFileInfos.useQuery({
|
||||
storageId,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
if (isLoading) return <LoadingPage />;
|
||||
if (!fileInfos) return <p>{t.file_section.error}</p>;
|
||||
return (
|
||||
<>
|
||||
<AllegatoIframe
|
||||
allegato={fileInfos}
|
||||
className="h-full max-h-[70vh] min-h-[60vh]"
|
||||
/>
|
||||
|
||||
<Link
|
||||
className="underline underline-offset-1 after:content-['_↗']"
|
||||
href={getStorageUrl({
|
||||
storageId: fileInfos.id,
|
||||
params: { mode: "download" },
|
||||
})}
|
||||
>
|
||||
{t.file_section.download}
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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-full sm:w-fit">
|
||||
<Handshake /> {t.richieste.cta}
|
||||
</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 closeModal={() => setOpen(false)} />
|
||||
)}
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const InvioConferma = ({ closeModal }: { closeModal: () => void }) => {
|
||||
const { 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.invalidate();
|
||||
closeModal();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredenzaBody className="flex flex-col gap-4">
|
||||
<p className="text-center font-semibold">
|
||||
Hai fatto la visita? Se no contatta il proprietario!
|
||||
</p>
|
||||
<p>{t.richieste.conferma_txt}</p>
|
||||
|
||||
<p className="text-sm">{t.richieste.conf_alert}</p>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<LoadingButton
|
||||
aria-label="Conferma immobile"
|
||||
loading={isPending}
|
||||
onClick={() => {
|
||||
mutate({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
});
|
||||
}}
|
||||
variant="info"
|
||||
>
|
||||
{t.procedi}
|
||||
</LoadingButton>
|
||||
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi modal credenza">{t.chiudi}</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DocCheckpoint = () => {
|
||||
const { t, locale } = useTranslation();
|
||||
const { userId, servizio, isAdmin } = useServizio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate, isPending } = api.servizio.updateServizio.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.servizio.invalidate();
|
||||
},
|
||||
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>
|
||||
{locale === "it"
|
||||
? "Devi ancora caricare un tuo documento legato alla permanenza e motivazione di transitorietà"
|
||||
: "You still need to upload a document related to your stay and reason for transience"}
|
||||
</p>
|
||||
|
||||
<DocumentiUploadSection
|
||||
caricatoNote
|
||||
document={servizio.doc_motivazione}
|
||||
isAdmin={isAdmin}
|
||||
isPending={isPending}
|
||||
onUpload={handleUpload}
|
||||
title={locale === "it" ? "Documento:" : "Document:"}
|
||||
userId={userId}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{locale === "it"
|
||||
? "I tuoi dati saranno conservati solamente per la durata del servizio, in conformità con la nostra "
|
||||
: "Your data will be stored only for the duration of the service, in accordance with our "}
|
||||
<Link className="underline" href="/privacy-policy" target="_blank">
|
||||
{locale === "it" ? "Informativa sulla Privacy" : "Privacy Policy"}
|
||||
</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, locale } = useTranslation();
|
||||
const { data } = useServizioAnnuncio();
|
||||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
aria-label="Conferma in lavorazione"
|
||||
className="w-full sm:w-fit"
|
||||
>
|
||||
<CircleCheck />
|
||||
{locale === "it"
|
||||
? "Conferma in lavorazione"
|
||||
: "Confirmation in progress"}
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh]">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>
|
||||
{locale === "it"
|
||||
? "In attesa di conferma"
|
||||
: "Awaiting confirmation"}
|
||||
</CredenzaTitle>
|
||||
<CredenzaDescription className="sr-only">
|
||||
Consegna status modal
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody className="prose dark:prose-invert max-h-[80vh] overflow-auto pb-5">
|
||||
<p>
|
||||
{locale === "it"
|
||||
? "Richiesta di conferma inviata il:"
|
||||
: "Confirmation request sent on:"}{" "}
|
||||
{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>
|
||||
{locale === "it"
|
||||
? "La conferma è ancora in lavorazione. Stiamo verificando con i proprietari le condizioni e dettagli della conferma per riferirteli. Attendi una comunicazione."
|
||||
: "The confirmation is still in progress. We are checking with the owners the conditions and details of the confirmation to report them to you. Please wait for a communication."}
|
||||
</p>
|
||||
</CredenzaBody>
|
||||
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi modal credenza" variant="outline">
|
||||
{t.chiudi}
|
||||
</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
};
|
||||
|
|
@ -195,7 +195,7 @@ export const InterruzioneServizio = () => {
|
|||
!servizio.isInterrotto &&
|
||||
(isAdmin ||
|
||||
(isBefore(new Date(), fineInterruzione) &&
|
||||
servizio.annunci.every((a) => a.accettato_conferma_at === null)));
|
||||
servizio.annunci.every((a) => a.status_conferma === null)));
|
||||
|
||||
return (
|
||||
<AlertDialog>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ const Schema = z
|
|||
pianoterra: z.boolean(),
|
||||
reddito: z.string().nullable(),
|
||||
terrazzo: z.boolean(),
|
||||
tipologia: z.custom<TipologiaPosizioneEnum>(),
|
||||
tipologia: z.enum(TipologiaPosizioneEnum),
|
||||
scadenza_motivazione_transitoria: z.date().nullable(),
|
||||
skipPayment: z.boolean(),
|
||||
skipControlloDoc: z.boolean(),
|
||||
|
|
@ -832,7 +832,7 @@ export const EditServizioModal = () => {
|
|||
disabled={
|
||||
!isAdmin &&
|
||||
(servizio.isInterrotto ||
|
||||
servizio.annunci.some((a) => a.hasConfermaAdmin))
|
||||
servizio.annunci.some((a) => a.status_conferma !== null))
|
||||
}
|
||||
variant="secondary"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -567,15 +567,13 @@ const Main = () => {
|
|||
);
|
||||
}
|
||||
|
||||
const annuncioConfermato = servizio.annunci.filter(
|
||||
(a) => a.accettato_conferma_at !== null,
|
||||
);
|
||||
const InConferma = servizio.annunci.filter((a) => a.status_conferma !== null);
|
||||
|
||||
// in conferma, mostra solo annunci confermati
|
||||
if (servizio.isOkSaldo && annuncioConfermato.length > 0) {
|
||||
if (servizio.isOkSaldo && InConferma.length > 0) {
|
||||
return (
|
||||
<>
|
||||
{annuncioConfermato.map((data) => {
|
||||
{InConferma.map((data) => {
|
||||
return (
|
||||
<ServizioAnnuncioProvider
|
||||
data={data}
|
||||
|
|
@ -591,7 +589,7 @@ const Main = () => {
|
|||
<h4 className="text-lg">Altri annunci:</h4>
|
||||
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
|
||||
{servizio.annunci
|
||||
.filter((a) => !a.accettato_conferma_at)
|
||||
.filter((a) => a.status_conferma === null)
|
||||
.map((data) => {
|
||||
return (
|
||||
<ServizioAnnuncioProvider
|
||||
|
|
@ -633,28 +631,18 @@ const Main = () => {
|
|||
// normal case
|
||||
return (
|
||||
<>
|
||||
{annuncioConfermato.length > 0 && (
|
||||
{InConferma.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<PackageCheck className="size-6 text-primary" />
|
||||
<span className="font-medium">{t.servizio.saldi_in_attesa}</span>
|
||||
</div>
|
||||
{annuncioConfermato.map((a) => (
|
||||
{InConferma.map((a) => (
|
||||
<div
|
||||
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
|
||||
key={a.annunci_id}
|
||||
>
|
||||
<h3>
|
||||
{a.codice} -{" "}
|
||||
{a.accettato_conferma_at &&
|
||||
new Date(a.accettato_conferma_at).toLocaleDateString("it", {
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
})}
|
||||
</h3>
|
||||
<h3 className="font-semibold text-white">{a.codice} </h3>
|
||||
<SaldoButton
|
||||
annuncioId={a.annunci_id}
|
||||
className="border-none"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
Bug,
|
||||
Check,
|
||||
ChevronDown,
|
||||
Cog,
|
||||
Euro,
|
||||
ExternalLink,
|
||||
|
|
@ -369,101 +368,6 @@ const AddOrderDialogContent = ({ swapBack }: { swapBack: () => void }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const ServizioPacksInfos = () => {
|
||||
const { servizio } = useServizio();
|
||||
const { t } = useTranslation();
|
||||
const { data: packs } = api.servizio.getPacksSolution.useQuery({
|
||||
servizioId: servizio.servizio_id,
|
||||
});
|
||||
if (!packs) return null;
|
||||
|
||||
return (
|
||||
<Collapsible className="flex flex-col gap-2">
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button
|
||||
className="h-fit w-full flex-wrap items-center justify-start py-1 sm:w-fit sm:justify-center"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
<Euro className="size-5" />
|
||||
|
||||
<h3 className="font-semibold text-lg">{t.servizio.prezzi.title}</h3>
|
||||
|
||||
<ChevronDown className="ml-auto size-4 sm:ml-0" />
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="flex flex-col gap-2">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{t.servizio.prezzi.sub}
|
||||
</span>
|
||||
<div className="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
|
||||
{packs.acconto.success ? (
|
||||
<>
|
||||
<span className="font-medium">
|
||||
{packs.acconto.pack.nome_it}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{packs.acconto.pack.desc_it}
|
||||
</span>
|
||||
<span className="font-semibold text-lg">
|
||||
{formatCurrency(packs.acconto.pack.prezzo_cent / 100)}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
COD: {packs.acconto.pack.idprezziario}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="font-medium">{packs.acconto.message}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
|
||||
{packs.saldo.success ? (
|
||||
<>
|
||||
<span className="font-medium">{packs.saldo.pack.nome_it}</span>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{packs.saldo.pack.desc_it}
|
||||
</span>
|
||||
<span className="font-semibold text-lg">
|
||||
{formatCurrency(packs.saldo.pack.prezzo_cent / 100)}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
COD: {packs.saldo.pack.idprezziario}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="font-medium">{packs.saldo.message}</span>
|
||||
)}
|
||||
</div>
|
||||
{servizio.tipologia === TipologiaPosizioneEnum.Stabile && (
|
||||
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
|
||||
<>
|
||||
<span className="font-medium">
|
||||
{t.servizio.prezzi.consulenza}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{t.servizio.prezzi.consulenza_desc}
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
<Link
|
||||
className="flex items-center gap-2 underline underline-offset-2"
|
||||
href={"/prezzi#contratti"}
|
||||
target="_blank"
|
||||
>
|
||||
<span>{t.servizio.prezzi.gotoprezzi}</span>
|
||||
<ExternalLink className="size-4 stroke-foreground" />
|
||||
</Link>
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
|
||||
const AdminActivationUfficio = () => {
|
||||
const { servizio } = useServizio();
|
||||
const utils = api.useUtils();
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import { UserAvatar } from "~/components/user_avatar";
|
|||
import { MutationPageRestore } from "~/lib/tableUtils";
|
||||
import { cn } from "~/lib/utils";
|
||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||
import StatusConfermaEnum from "~/schemas/public/StatusConfermaEnum";
|
||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||
import type { RichiesteData } from "~/server/controllers/servizio.controller";
|
||||
import { api } from "~/utils/api";
|
||||
|
|
@ -383,10 +384,10 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
}
|
||||
}
|
||||
|
||||
const annuncioConfermato = row.original.annunci_servizio.filter(
|
||||
(a) => a.accettato_conferma_at !== null,
|
||||
const annuncioCaparraVersata = row.original.annunci_servizio.filter(
|
||||
(a) => a.status_conferma === StatusConfermaEnum["Caparra versata"],
|
||||
);
|
||||
if (annuncioConfermato.length > 0) {
|
||||
if (annuncioCaparraVersata.length > 0) {
|
||||
if (row.original.isOkSaldo) {
|
||||
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
||||
contents = (
|
||||
|
|
@ -407,9 +408,24 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
|
||||
return;
|
||||
}
|
||||
const annuncioConfermato = row.original.annunci_servizio.filter(
|
||||
(a) =>
|
||||
a.status_conferma === StatusConfermaEnum["Conferma accettata"],
|
||||
);
|
||||
if (annuncioConfermato.length > 0) {
|
||||
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
||||
contents = (
|
||||
<>
|
||||
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
|
||||
Attesa di caparra
|
||||
</>
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const annuncioAttesaConferma = row.original.annunci_servizio.filter(
|
||||
(a) => a.accettato_conferma_at == null && a.doc_conferma_added,
|
||||
(a) => a.status_conferma === StatusConfermaEnum["Inviato conferma"],
|
||||
);
|
||||
if (annuncioAttesaConferma.length > 0) {
|
||||
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
||||
|
|
|
|||
|
|
@ -1,219 +0,0 @@
|
|||
import type { GetServerSideProps } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { DocNotFoundPage } from "~/components/doc_not_found";
|
||||
import { AreaRiservataLayout } from "~/components/Layout";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
import { FileSection } from "~/components/servizio/conferma";
|
||||
import { Status500 } from "~/components/status-page";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import { redirectTo500 } from "~/lib/utils";
|
||||
import type { NextPageWithLayout } from "~/pages/_app";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import { useEnforcedSession } from "~/providers/SessionProvider";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||
import { zAnnuncioId, zServizioId } from "~/server/utils/zod_types";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
type ConfermaImmobileProps = {
|
||||
servizioId: ServizioServizioId;
|
||||
annuncioId: AnnunciId;
|
||||
};
|
||||
/**
|
||||
* Pagina di conferma immobile: /servizio/conferma-immobile/[servizioId]/[annuncioId]
|
||||
*/
|
||||
const ConfermaImmobile: NextPageWithLayout<ConfermaImmobileProps> = ({
|
||||
servizioId,
|
||||
annuncioId,
|
||||
}: ConfermaImmobileProps) => {
|
||||
const { data, isLoading } = api.servizio.getServizioAnnuncio.useQuery({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
if (isLoading) return <LoadingPage />;
|
||||
if (!data) return <Status500 />;
|
||||
if (!data.doc_conferma_ref) return <DocNotFoundPage />;
|
||||
return (
|
||||
<div className="mx-2 flex flex-1 overflow-auto">
|
||||
<div className="mx-auto flex grow sm:grow-0">
|
||||
<div className="flex flex-col items-center space-y-4 py-4">
|
||||
<h1 className="font-bold text-xl">{t.servizio.conferma.titolo}</h1>
|
||||
<p className="text-center">{t.servizio.conferma.descrizione}</p>
|
||||
|
||||
<FileSection storageId={data.doc_conferma_ref} />
|
||||
|
||||
<div className="flex max-w-3xl flex-col gap-2 rounded-md bg-green-400 p-4 text-left">
|
||||
<p className="font-semibold text-xl">{t.servizio.conferma.cta}</p>
|
||||
<p>{t.servizio.conferma.coordinate}</p>
|
||||
<p>
|
||||
{t.servizio.conferma.intestazione}{" "}
|
||||
{data.caparra_intestazione || ""}
|
||||
</p>
|
||||
<p>
|
||||
{t.servizio.conferma.iban} {data.caparra_iban || ""}
|
||||
</p>
|
||||
<p>
|
||||
{t.servizio.conferma.importo} {data.caparra_importo || ""}
|
||||
</p>
|
||||
<p>
|
||||
{t.servizio.conferma.causale} {data.caparra_causale || ""}
|
||||
</p>
|
||||
<p className="font-semibold text-xl">
|
||||
{t.servizio.conferma.coord_footer}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ConfirmSection annuncioId={annuncioId} servizioId={servizioId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ConfirmSection = ({
|
||||
servizioId,
|
||||
annuncioId,
|
||||
}: {
|
||||
servizioId: ServizioServizioId;
|
||||
annuncioId: AnnunciId;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [letto, setLetto] = useState(false);
|
||||
const [accettato, setAccettato] = useState(false);
|
||||
const { user } = useEnforcedSession();
|
||||
const utils = api.useUtils();
|
||||
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();
|
||||
await utils.servizio.invalidate();
|
||||
|
||||
toast.success("Confermato", {
|
||||
icon: "👍",
|
||||
id: context?.toastId,
|
||||
});
|
||||
if (user.isAdmin) {
|
||||
await router.push(
|
||||
`/area-riservata/admin/user-view/servizio/${data.userId}/${servizioId}`,
|
||||
);
|
||||
} else {
|
||||
await router.push(`/area-riservata/servizio/${servizioId}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-start space-y-3 px-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={letto}
|
||||
className="size-6"
|
||||
id="letto"
|
||||
onCheckedChange={(v) => v !== "indeterminate" && setLetto(v)}
|
||||
/>
|
||||
<div className="grid gap-1.5 leading-none">
|
||||
<label
|
||||
className="font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
htmlFor="letto"
|
||||
>
|
||||
{t.servizio.conferma.ho_letto}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={accettato}
|
||||
className="size-6"
|
||||
id="termini"
|
||||
onCheckedChange={(v) => v !== "indeterminate" && setAccettato(v)}
|
||||
/>
|
||||
<div className="grid gap-1.5 leading-none">
|
||||
<label
|
||||
className="font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
htmlFor="termini"
|
||||
>
|
||||
{t.servizio.conferma.accetto}
|
||||
</label>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t.servizio.conferma.confermo_termini}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="h-fit whitespace-normal text-pretty"
|
||||
disabled={!letto || !accettato}
|
||||
onClick={() => {
|
||||
// Handle confirmation logic here
|
||||
conferma({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t.servizio.conferma.conferma}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ConfermaImmobile.getLayout = function getLayout(page) {
|
||||
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
||||
};
|
||||
|
||||
export default ConfermaImmobile;
|
||||
|
||||
export const getServerSideProps = (async (context) => {
|
||||
const slug = context.params?.slug;
|
||||
if (!slug || slug.length !== 2) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const [servizioId, annuncioId] = slug;
|
||||
if (!servizioId || !annuncioId) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedServizioId = zServizioId.safeParse(servizioId);
|
||||
const parsedAnnuncioId = zAnnuncioId.safeParse(parseInt(annuncioId));
|
||||
|
||||
if (!parsedServizioId.success || !parsedAnnuncioId.success) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const access_token = context.req.cookies.access_token;
|
||||
|
||||
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
||||
if (helper) {
|
||||
await helper.trpc.servizio.getServizioAnnuncio.prefetch({
|
||||
annuncioId: parsedAnnuncioId.data,
|
||||
servizioId: parsedServizioId.data,
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
annuncioId: parsedAnnuncioId.data,
|
||||
servizioId: parsedServizioId.data,
|
||||
trpcState: helper.trpc.dehydrate(),
|
||||
},
|
||||
};
|
||||
}
|
||||
return redirectTo500;
|
||||
}) satisfies GetServerSideProps<ConfermaImmobileProps>;
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
import { ArrowLeft } from "lucide-react";
|
||||
import type { GetServerSideProps } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { DocNotFoundPage } from "~/components/doc_not_found";
|
||||
import { AreaRiservataLayout } from "~/components/Layout";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
import { FileSection } from "~/components/servizio/conferma";
|
||||
import { Status500 } from "~/components/status-page";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { redirectTo500 } from "~/lib/utils";
|
||||
import type { NextPageWithLayout } from "~/pages/_app";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||
import { zAnnuncioId, zServizioId } from "~/server/utils/zod_types";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
type RiapriConfermaProps = {
|
||||
servizioId: ServizioServizioId;
|
||||
annuncioId: AnnunciId;
|
||||
};
|
||||
/**
|
||||
* Pagina di conferma di riapertura servizio: /servizio/riapri-conferma/[servizioId]/[annuncioId]
|
||||
*/
|
||||
const RiapriConferma: NextPageWithLayout<RiapriConfermaProps> = ({
|
||||
servizioId,
|
||||
annuncioId,
|
||||
}: RiapriConfermaProps) => {
|
||||
const { data, isLoading } = api.servizio.getServizioAnnuncio.useQuery({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
});
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (isLoading) return <LoadingPage />;
|
||||
if (!data) return <Status500 />;
|
||||
if (!data.doc_conferma_ref) return <DocNotFoundPage />;
|
||||
return (
|
||||
<div className="mx-2 flex flex-1 overflow-auto">
|
||||
<div className="mx-auto flex grow sm:grow-0">
|
||||
<div className="flex flex-col items-center space-y-4 py-4">
|
||||
<h1 className="font-bold text-xl">{t.servizio.conferma.titolo}</h1>
|
||||
<p className="text-center">{t.servizio.conferma.descrizione}</p>
|
||||
|
||||
<FileSection storageId={data.doc_conferma_ref} />
|
||||
|
||||
<div className="flex max-w-3xl flex-col gap-2 rounded-md bg-green-400 p-4 text-left">
|
||||
<p className="font-semibold text-xl">{t.servizio.conferma.cta}</p>
|
||||
<p>{t.servizio.conferma.coordinate}</p>
|
||||
<p>
|
||||
{t.servizio.conferma.intestazione}{" "}
|
||||
{data.caparra_intestazione || ""}
|
||||
</p>
|
||||
<p>
|
||||
{t.servizio.conferma.iban} {data.caparra_iban || ""}
|
||||
</p>
|
||||
<p>
|
||||
{t.servizio.conferma.importo} {data.caparra_importo || ""}
|
||||
</p>
|
||||
<p>
|
||||
{t.servizio.conferma.causale} {data.caparra_causale || ""}
|
||||
</p>
|
||||
<p className="font-semibold text-xl">
|
||||
{t.servizio.conferma.coord_footer}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="mx-auto mb-4"
|
||||
onClick={() => router.back()}
|
||||
variant="secondary"
|
||||
>
|
||||
<ArrowLeft />
|
||||
{t.indietro}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RiapriConferma.getLayout = function getLayout(page) {
|
||||
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
||||
};
|
||||
|
||||
export default RiapriConferma;
|
||||
|
||||
export const getServerSideProps = (async (context) => {
|
||||
const slug = context.params?.slug;
|
||||
if (!slug || slug.length !== 2) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const [servizioId, annuncioId] = slug;
|
||||
if (!servizioId || !annuncioId) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedServizioId = zServizioId.safeParse(servizioId);
|
||||
const parsedAnnuncioId = zAnnuncioId.safeParse(parseInt(annuncioId));
|
||||
|
||||
if (!parsedServizioId.success || !parsedAnnuncioId.success) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const access_token = context.req.cookies.access_token;
|
||||
|
||||
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
||||
if (helper) {
|
||||
await helper.trpc.servizio.getServizioAnnuncio.prefetch({
|
||||
annuncioId: parsedAnnuncioId.data,
|
||||
servizioId: parsedServizioId.data,
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
annuncioId: parsedAnnuncioId.data,
|
||||
servizioId: parsedServizioId.data,
|
||||
trpcState: helper.trpc.dehydrate(),
|
||||
},
|
||||
};
|
||||
}
|
||||
return redirectTo500;
|
||||
}) satisfies GetServerSideProps<RiapriConfermaProps>;
|
||||
|
|
@ -10,18 +10,18 @@ import { LoadingPage } from "~/components/loading";
|
|||
import type { Session, SessionStatus } from "~/server/api/trpc";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export type ValidSession = {
|
||||
type ValidSession = {
|
||||
user: Session;
|
||||
status: Extract<SessionStatus, "AUTHENTICATED">;
|
||||
};
|
||||
|
||||
type InvalidStatus = Exclude<SessionStatus, "AUTHENTICATED"> | "LOADING";
|
||||
export type InvalidSession = {
|
||||
type InvalidSession = {
|
||||
status: InvalidStatus;
|
||||
user: null;
|
||||
};
|
||||
|
||||
export type SessionContextType = ValidSession | InvalidSession;
|
||||
type SessionContextType = ValidSession | InvalidSession;
|
||||
|
||||
const SessionContext = createContext<SessionContextType>({
|
||||
status: "LOADING",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import type { ServizioServizioId } from './Servizio';
|
||||
import type { AnnunciId } from './Annunci';
|
||||
import type { default as StatusConfermaEnum } from './StatusConfermaEnum';
|
||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||
|
||||
/** Identifier type for public.servizio_annunci */
|
||||
|
|
@ -20,22 +21,6 @@ export default interface ServizioAnnunciTable {
|
|||
|
||||
open_contatti_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||
|
||||
doc_conferma_ref: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
user_confirmed_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||
|
||||
accettato_conferma_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||
|
||||
caparra_causale: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
caparra_iban: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
caparra_importo: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
caparra_intestazione: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
hasConfermaAdmin: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
doc_contratto_ref: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
gestionale_id: ColumnType<number | null, number | null, number | null>;
|
||||
|
|
@ -48,13 +33,13 @@ export default interface ServizioAnnunciTable {
|
|||
|
||||
doc_registrazione_ref: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
doc_conferma_added: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
doc_contratto_added: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
doc_registrazione_added: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
note: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
status_conferma: ColumnType<StatusConfermaEnum | null, StatusConfermaEnum | null, StatusConfermaEnum | null>;
|
||||
}
|
||||
|
||||
export type ServizioAnnunci = Selectable<ServizioAnnunciTable>;
|
||||
|
|
|
|||
11
apps/infoalloggi/src/schemas/public/StatusConfermaEnum.ts
Normal file
11
apps/infoalloggi/src/schemas/public/StatusConfermaEnum.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// @generated
|
||||
// This file is automatically generated by Kanel. Do not modify manually.
|
||||
|
||||
/** Represents the enum public.StatusConfermaEnum */
|
||||
enum StatusConfermaEnum {
|
||||
'Inviato conferma' = 'Inviato conferma',
|
||||
'Conferma accettata' = 'Conferma accettata',
|
||||
'Caparra versata' = 'Caparra versata',
|
||||
};
|
||||
|
||||
export default StatusConfermaEnum;
|
||||
|
|
@ -18,22 +18,18 @@ import { getPacksPerServizio } from "~/server/controllers/pagamenti.controller";
|
|||
import {
|
||||
AdminAttivaServizio_Ufficio,
|
||||
addServizioAnnunci,
|
||||
adminUpdateConfermaStatus,
|
||||
attivaServizio_SaltaPagamentoAcconto,
|
||||
getAllServizioAnnunci,
|
||||
getAnnunciAvailableToAdd,
|
||||
getCompatibileAnnunci,
|
||||
getDataPerAcquisto,
|
||||
getRichieste,
|
||||
getServiziByUserId,
|
||||
getServizioDataById,
|
||||
InteractionLogicTipologia,
|
||||
interruzioneServizio,
|
||||
processServizioOnboard,
|
||||
SbloccaContatti,
|
||||
type ServizioData,
|
||||
userAccettaConferma,
|
||||
userSendConfermaIntent,
|
||||
} from "~/server/controllers/servizio.controller";
|
||||
import {
|
||||
createOrdine,
|
||||
|
|
@ -376,47 +372,7 @@ export const servizioRouter = createTRPCRouter({
|
|||
servizioId: input.servizioId,
|
||||
});
|
||||
}),
|
||||
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,
|
||||
servizioId: zServizioId,
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
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,
|
||||
});
|
||||
}),
|
||||
|
||||
getAllServiziData: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { TRPCError } from "@trpc/server";
|
||||
import z from "zod";
|
||||
import type {
|
||||
NewUsersStorage,
|
||||
UsersStorageUserStorageId,
|
||||
} from "~/schemas/public/UsersStorage";
|
||||
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
||||
import {
|
||||
adminProcedure,
|
||||
createTRPCRouter,
|
||||
|
|
@ -14,7 +11,6 @@ import {
|
|||
deleteUserStorageEntry,
|
||||
getAllWithFromAdmin,
|
||||
getMultipleWithRelations,
|
||||
getStorageIdFromUserStorageId,
|
||||
purgeFileFromUserStorages,
|
||||
retrieveUserFiles,
|
||||
} from "~/server/controllers/storage.controller";
|
||||
|
|
@ -87,11 +83,4 @@ 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,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ export const getAllServizioAnnunci = async (
|
|||
//ignora annunci che non sono disponibili
|
||||
//.where("annunci.web", "=", true)
|
||||
//.where("annunci.stato", "!=", "Sospeso")
|
||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||
.orderBy("servizio_annunci.status_conferma", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||
|
|
@ -593,7 +593,7 @@ export const getServizioDataById = async (
|
|||
//ignora annunci che non sono disponibili
|
||||
//.where("annunci.web", "=", true)
|
||||
//.where("annunci.stato", "!=", "Sospeso")
|
||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||
.orderBy("servizio_annunci.status_conferma", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||
|
|
@ -631,10 +631,7 @@ export const getServizioDataById = async (
|
|||
...annuncio,
|
||||
created_at: new Date(annuncio.created_at),
|
||||
open_contatti_at: parseNullableDateString(annuncio.open_contatti_at),
|
||||
user_confirmed_at: parseNullableDateString(annuncio.user_confirmed_at),
|
||||
accettato_conferma_at: parseNullableDateString(
|
||||
annuncio.accettato_conferma_at,
|
||||
),
|
||||
|
||||
contratto_decorrenza: parseNullableDateString(
|
||||
annuncio.contratto_decorrenza,
|
||||
),
|
||||
|
|
@ -1006,222 +1003,6 @@ export const InteractionLogicTipologia = async ({
|
|||
};
|
||||
};
|
||||
|
||||
export const userSendConfermaIntent = async ({
|
||||
servizioId,
|
||||
annuncioId,
|
||||
}: {
|
||||
servizioId: ServizioServizioId;
|
||||
annuncioId: AnnunciId;
|
||||
}) => {
|
||||
try {
|
||||
const servizio = await getServizioById(servizioId);
|
||||
if (
|
||||
!servizio ||
|
||||
servizio.isInterrotto ||
|
||||
!servizio.isOkAcconto ||
|
||||
servizio.decorrenza == null
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Servizio not found",
|
||||
});
|
||||
}
|
||||
await db
|
||||
.updateTable("servizio_annunci")
|
||||
.set({
|
||||
user_confirmed_at: new Date(),
|
||||
})
|
||||
.where("servizio_id", "=", servizioId)
|
||||
.where("annunci_id", "=", annuncioId)
|
||||
.execute();
|
||||
|
||||
// NOTIFICHE EMAIL CONFERMA IMMOBILE
|
||||
|
||||
const utente = await getUser({ db, userId: servizio.user_id });
|
||||
if (!utente.comms_enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const annuncio_codice = await db
|
||||
.selectFrom("annunci")
|
||||
.select("codice")
|
||||
.where("id", "=", annuncioId)
|
||||
.executeTakeFirstOrThrow(
|
||||
() =>
|
||||
new Error(
|
||||
`Annuncio not found - servizioId: ${servizioId}, annunciId: ${annuncioId}`,
|
||||
),
|
||||
);
|
||||
|
||||
await NewMail({
|
||||
template: {
|
||||
mailType: "generic",
|
||||
props: {
|
||||
link: {
|
||||
href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}`,
|
||||
label: "Visualizza Annuncio",
|
||||
},
|
||||
noreply: true,
|
||||
testo: `L'utente ${utente.username} ha notificato interesse per l'immobile con codice ${annuncio_codice.codice}.`,
|
||||
title: `Nuovo interesse conferma immobile ${annuncio_codice.codice}`,
|
||||
},
|
||||
},
|
||||
mail: {
|
||||
subject: `Nuovo interesse conferma immobile ${annuncio_codice.codice}`,
|
||||
to: "web@infoalloggi.it",
|
||||
},
|
||||
});
|
||||
|
||||
await NewMail({
|
||||
template: {
|
||||
mailType: "generic",
|
||||
props: {
|
||||
noreply: true,
|
||||
testo: `La tua notifica di interesse per l'immobile con codice ${annuncio_codice.codice} è stata inviata con successo. Contatteremo i proprietari per raccogliere le informazioni necessarie. Riceverai una notifica via email per procedere.`,
|
||||
title: `Notifica interesse immobile ${annuncio_codice.codice} inviata con successo`,
|
||||
},
|
||||
},
|
||||
mail: {
|
||||
subject: `Notifica interesse immobile ${annuncio_codice.codice} inviata con successo`,
|
||||
to: utente.email,
|
||||
},
|
||||
userId: utente.id,
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Error confirming immobile: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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 });
|
||||
|
||||
const saldo = await SaldoSolver({
|
||||
tipologia: servizio.tipologia,
|
||||
permanenza: servizio.permanenza,
|
||||
budget: servizio.budget,
|
||||
});
|
||||
if (saldo.success) {
|
||||
await createOrdine({
|
||||
servizio_id: servizioId,
|
||||
type: OrderTypeEnum.Saldo,
|
||||
amount_cent: saldo.pack.prezzo_cent,
|
||||
packid: saldo.pack.idprezziario,
|
||||
userid: servizio.user_id,
|
||||
});
|
||||
} else {
|
||||
console.error(
|
||||
`Errore nel calcolo del saldo per il servizio ${servizioId}: ${saldo.message}, procedura invio conferma`,
|
||||
);
|
||||
}
|
||||
|
||||
//ADMIN CONFERMA LAVORATA EMAIL
|
||||
if (!utente.comms_enabled) {
|
||||
return true;
|
||||
}
|
||||
if (status) {
|
||||
await NewMail({
|
||||
template: {
|
||||
mailType: "generic",
|
||||
props: {
|
||||
link: {
|
||||
href: `${env.BASE_URL}/area-riservata/servizio/${servizioId}`,
|
||||
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",
|
||||
},
|
||||
},
|
||||
mail: {
|
||||
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({
|
||||
template: {
|
||||
mailType: "generic",
|
||||
props: {
|
||||
link: {
|
||||
href: `${env.BASE_URL}/area-riservata/admin/user-view/servizio/${servizio.user_id}/${servizioId}`,
|
||||
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`,
|
||||
},
|
||||
},
|
||||
mail: {
|
||||
subject: `Utente ${utente.username} ha accettato le condizioni di conferma`,
|
||||
to: "web@infoalloggi.it",
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, userId: utente.id };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Error accepting conferma from annuncio: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const UtenteInteressatoAdAnnuncioEmail = async ({
|
||||
codice,
|
||||
nome,
|
||||
|
|
@ -1414,7 +1195,7 @@ export const getRichieste = async (): Promise<RichiesteData[]> => {
|
|||
withImages(),
|
||||
withVideos(),
|
||||
])
|
||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||
.orderBy("servizio_annunci.status_conferma", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue