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 {
|
import {
|
||||||
|
Circle,
|
||||||
|
CircleCheck,
|
||||||
FileBadge,
|
FileBadge,
|
||||||
FileText,
|
FileText,
|
||||||
FolderKanban,
|
FolderKanban,
|
||||||
Info,
|
Info,
|
||||||
PackageCheck,
|
PackageCheck,
|
||||||
Pointer,
|
|
||||||
Printer,
|
Printer,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
Workflow,
|
||||||
Wrench,
|
Wrench,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
@ -22,6 +24,14 @@ import {
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "~/components/ui/alert-dialog";
|
} from "~/components/ui/alert-dialog";
|
||||||
import { Button, type ButtonConfigs } from "~/components/ui/button";
|
import { Button, type ButtonConfigs } from "~/components/ui/button";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverDescription,
|
||||||
|
PopoverHeader,
|
||||||
|
PopoverTitle,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "~/components/ui/popover";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
|
|
@ -33,11 +43,10 @@ import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
|
import StatusConfermaEnum from "~/schemas/public/StatusConfermaEnum";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { Confirm } from "../confirm";
|
import { Confirm } from "../confirm";
|
||||||
import { AdminLavoraConferma } from "./admin_conferma";
|
|
||||||
import { AdminContratto } from "./admin_contratto";
|
import { AdminContratto } from "./admin_contratto";
|
||||||
import { ConfermaAnnuncioModal, ConfermaInLavorazioneModal } from "./conferma";
|
|
||||||
import { ContattiProprietarioModal } from "./modale_contatti";
|
import { ContattiProprietarioModal } from "./modale_contatti";
|
||||||
|
|
||||||
export const AdminActions = () => {
|
export const AdminActions = () => {
|
||||||
|
|
@ -62,8 +71,9 @@ export const AdminActions = () => {
|
||||||
<h3 className="ml-1 font-semibold">Azioni Admin:</h3>
|
<h3 className="ml-1 font-semibold">Azioni Admin:</h3>
|
||||||
<div className="flex w-full max-w-fit flex-wrap justify-start gap-3">
|
<div className="flex w-full max-w-fit flex-wrap justify-start gap-3">
|
||||||
<AdminAnnuncioContatti />
|
<AdminAnnuncioContatti />
|
||||||
{data.user_confirmed_at != null && <AdminLavoraConferma />}
|
<AdminAnnuncioConfermaPopover />
|
||||||
{data.accettato_conferma_at != null && (
|
|
||||||
|
{data.status_conferma != null && (
|
||||||
<>
|
<>
|
||||||
<AdminContratto />
|
<AdminContratto />
|
||||||
{data.gestionale_id && (
|
{data.gestionale_id && (
|
||||||
|
|
@ -134,86 +144,79 @@ export const AdminActions = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserActions = () => {
|
export const UserActions = () => {
|
||||||
const { servizioId } = useServizio();
|
const { data } = useServizioAnnuncio();
|
||||||
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;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex w-full flex-col flex-wrap gap-2 sm:flex-row">
|
<div className="flex w-full flex-col flex-wrap gap-2 sm:flex-row">
|
||||||
<ContattiProprietarioModal />
|
<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>
|
</div>
|
||||||
|
{data.status_conferma != null && <PostConfermaSection />}
|
||||||
{data.accettato_conferma_at != 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 AdminAnnuncioContatti = () => {
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
const { data: annuncio, isLoading } = api.annunci.getAnnuncioFull.useQuery({
|
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 &&
|
!servizio.isInterrotto &&
|
||||||
(isAdmin ||
|
(isAdmin ||
|
||||||
(isBefore(new Date(), fineInterruzione) &&
|
(isBefore(new Date(), fineInterruzione) &&
|
||||||
servizio.annunci.every((a) => a.accettato_conferma_at === null)));
|
servizio.annunci.every((a) => a.status_conferma === null)));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ const Schema = z
|
||||||
pianoterra: z.boolean(),
|
pianoterra: z.boolean(),
|
||||||
reddito: z.string().nullable(),
|
reddito: z.string().nullable(),
|
||||||
terrazzo: z.boolean(),
|
terrazzo: z.boolean(),
|
||||||
tipologia: z.custom<TipologiaPosizioneEnum>(),
|
tipologia: z.enum(TipologiaPosizioneEnum),
|
||||||
scadenza_motivazione_transitoria: z.date().nullable(),
|
scadenza_motivazione_transitoria: z.date().nullable(),
|
||||||
skipPayment: z.boolean(),
|
skipPayment: z.boolean(),
|
||||||
skipControlloDoc: z.boolean(),
|
skipControlloDoc: z.boolean(),
|
||||||
|
|
@ -832,7 +832,7 @@ export const EditServizioModal = () => {
|
||||||
disabled={
|
disabled={
|
||||||
!isAdmin &&
|
!isAdmin &&
|
||||||
(servizio.isInterrotto ||
|
(servizio.isInterrotto ||
|
||||||
servizio.annunci.some((a) => a.hasConfermaAdmin))
|
servizio.annunci.some((a) => a.status_conferma !== null))
|
||||||
}
|
}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -567,15 +567,13 @@ const Main = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const annuncioConfermato = servizio.annunci.filter(
|
const InConferma = servizio.annunci.filter((a) => a.status_conferma !== null);
|
||||||
(a) => a.accettato_conferma_at !== null,
|
|
||||||
);
|
|
||||||
|
|
||||||
// in conferma, mostra solo annunci confermati
|
// in conferma, mostra solo annunci confermati
|
||||||
if (servizio.isOkSaldo && annuncioConfermato.length > 0) {
|
if (servizio.isOkSaldo && InConferma.length > 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{annuncioConfermato.map((data) => {
|
{InConferma.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider
|
<ServizioAnnuncioProvider
|
||||||
data={data}
|
data={data}
|
||||||
|
|
@ -591,7 +589,7 @@ const Main = () => {
|
||||||
<h4 className="text-lg">Altri annunci:</h4>
|
<h4 className="text-lg">Altri annunci:</h4>
|
||||||
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
|
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
|
||||||
{servizio.annunci
|
{servizio.annunci
|
||||||
.filter((a) => !a.accettato_conferma_at)
|
.filter((a) => a.status_conferma === null)
|
||||||
.map((data) => {
|
.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider
|
<ServizioAnnuncioProvider
|
||||||
|
|
@ -633,28 +631,18 @@ const Main = () => {
|
||||||
// normal case
|
// normal case
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{annuncioConfermato.length > 0 && (
|
{InConferma.length > 0 && (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<PackageCheck className="size-6 text-primary" />
|
<PackageCheck className="size-6 text-primary" />
|
||||||
<span className="font-medium">{t.servizio.saldi_in_attesa}</span>
|
<span className="font-medium">{t.servizio.saldi_in_attesa}</span>
|
||||||
</div>
|
</div>
|
||||||
{annuncioConfermato.map((a) => (
|
{InConferma.map((a) => (
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
|
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
|
||||||
key={a.annunci_id}
|
key={a.annunci_id}
|
||||||
>
|
>
|
||||||
<h3>
|
<h3 className="font-semibold text-white">{a.codice} </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>
|
|
||||||
<SaldoButton
|
<SaldoButton
|
||||||
annuncioId={a.annunci_id}
|
annuncioId={a.annunci_id}
|
||||||
className="border-none"
|
className="border-none"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Bug,
|
Bug,
|
||||||
Check,
|
Check,
|
||||||
ChevronDown,
|
|
||||||
Cog,
|
Cog,
|
||||||
Euro,
|
Euro,
|
||||||
ExternalLink,
|
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 AdminActivationUfficio = () => {
|
||||||
const { servizio } = useServizio();
|
const { servizio } = useServizio();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ import { UserAvatar } from "~/components/user_avatar";
|
||||||
import { MutationPageRestore } from "~/lib/tableUtils";
|
import { MutationPageRestore } from "~/lib/tableUtils";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
|
import StatusConfermaEnum from "~/schemas/public/StatusConfermaEnum";
|
||||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import type { RichiesteData } from "~/server/controllers/servizio.controller";
|
import type { RichiesteData } from "~/server/controllers/servizio.controller";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
@ -383,10 +384,10 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const annuncioConfermato = row.original.annunci_servizio.filter(
|
const annuncioCaparraVersata = row.original.annunci_servizio.filter(
|
||||||
(a) => a.accettato_conferma_at !== null,
|
(a) => a.status_conferma === StatusConfermaEnum["Caparra versata"],
|
||||||
);
|
);
|
||||||
if (annuncioConfermato.length > 0) {
|
if (annuncioCaparraVersata.length > 0) {
|
||||||
if (row.original.isOkSaldo) {
|
if (row.original.isOkSaldo) {
|
||||||
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
||||||
contents = (
|
contents = (
|
||||||
|
|
@ -407,9 +408,24 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
|
|
||||||
return;
|
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(
|
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) {
|
if (annuncioAttesaConferma.length > 0) {
|
||||||
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
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 type { Session, SessionStatus } from "~/server/api/trpc";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export type ValidSession = {
|
type ValidSession = {
|
||||||
user: Session;
|
user: Session;
|
||||||
status: Extract<SessionStatus, "AUTHENTICATED">;
|
status: Extract<SessionStatus, "AUTHENTICATED">;
|
||||||
};
|
};
|
||||||
|
|
||||||
type InvalidStatus = Exclude<SessionStatus, "AUTHENTICATED"> | "LOADING";
|
type InvalidStatus = Exclude<SessionStatus, "AUTHENTICATED"> | "LOADING";
|
||||||
export type InvalidSession = {
|
type InvalidSession = {
|
||||||
status: InvalidStatus;
|
status: InvalidStatus;
|
||||||
user: null;
|
user: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SessionContextType = ValidSession | InvalidSession;
|
type SessionContextType = ValidSession | InvalidSession;
|
||||||
|
|
||||||
const SessionContext = createContext<SessionContextType>({
|
const SessionContext = createContext<SessionContextType>({
|
||||||
status: "LOADING",
|
status: "LOADING",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import type { ServizioServizioId } from './Servizio';
|
import type { ServizioServizioId } from './Servizio';
|
||||||
import type { AnnunciId } from './Annunci';
|
import type { AnnunciId } from './Annunci';
|
||||||
|
import type { default as StatusConfermaEnum } from './StatusConfermaEnum';
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.servizio_annunci */
|
/** 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>;
|
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>;
|
doc_contratto_ref: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
gestionale_id: ColumnType<number | null, number | null, number | 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_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_contratto_added: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
doc_registrazione_added: ColumnType<boolean, boolean | undefined, boolean>;
|
doc_registrazione_added: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
note: ColumnType<string | null, string | null, string | null>;
|
note: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
|
status_conferma: ColumnType<StatusConfermaEnum | null, StatusConfermaEnum | null, StatusConfermaEnum | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServizioAnnunci = Selectable<ServizioAnnunciTable>;
|
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 {
|
import {
|
||||||
AdminAttivaServizio_Ufficio,
|
AdminAttivaServizio_Ufficio,
|
||||||
addServizioAnnunci,
|
addServizioAnnunci,
|
||||||
adminUpdateConfermaStatus,
|
|
||||||
attivaServizio_SaltaPagamentoAcconto,
|
attivaServizio_SaltaPagamentoAcconto,
|
||||||
getAllServizioAnnunci,
|
getAllServizioAnnunci,
|
||||||
getAnnunciAvailableToAdd,
|
getAnnunciAvailableToAdd,
|
||||||
getCompatibileAnnunci,
|
getCompatibileAnnunci,
|
||||||
getDataPerAcquisto,
|
getDataPerAcquisto,
|
||||||
getRichieste,
|
getRichieste,
|
||||||
getServiziByUserId,
|
|
||||||
getServizioDataById,
|
getServizioDataById,
|
||||||
InteractionLogicTipologia,
|
InteractionLogicTipologia,
|
||||||
interruzioneServizio,
|
interruzioneServizio,
|
||||||
processServizioOnboard,
|
processServizioOnboard,
|
||||||
SbloccaContatti,
|
SbloccaContatti,
|
||||||
type ServizioData,
|
type ServizioData,
|
||||||
userAccettaConferma,
|
|
||||||
userSendConfermaIntent,
|
|
||||||
} from "~/server/controllers/servizio.controller";
|
} from "~/server/controllers/servizio.controller";
|
||||||
import {
|
import {
|
||||||
createOrdine,
|
createOrdine,
|
||||||
|
|
@ -376,47 +372,7 @@ export const servizioRouter = createTRPCRouter({
|
||||||
servizioId: input.servizioId,
|
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
|
getAllServiziData: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import type {
|
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
||||||
NewUsersStorage,
|
|
||||||
UsersStorageUserStorageId,
|
|
||||||
} from "~/schemas/public/UsersStorage";
|
|
||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
|
|
@ -14,7 +11,6 @@ import {
|
||||||
deleteUserStorageEntry,
|
deleteUserStorageEntry,
|
||||||
getAllWithFromAdmin,
|
getAllWithFromAdmin,
|
||||||
getMultipleWithRelations,
|
getMultipleWithRelations,
|
||||||
getStorageIdFromUserStorageId,
|
|
||||||
purgeFileFromUserStorages,
|
purgeFileFromUserStorages,
|
||||||
retrieveUserFiles,
|
retrieveUserFiles,
|
||||||
} from "~/server/controllers/storage.controller";
|
} from "~/server/controllers/storage.controller";
|
||||||
|
|
@ -87,11 +83,4 @@ export const storageRouter = createTRPCRouter({
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await editFile(input.fileId, input.name);
|
return await editFile(input.fileId, input.name);
|
||||||
}),
|
}),
|
||||||
getStorageIdFromUserStorageId: protectedProcedure
|
|
||||||
.input(z.object({ userStorageId: z.custom<UsersStorageUserStorageId>() }))
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getStorageIdFromUserStorageId({
|
|
||||||
userStorageId: input.userStorageId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -455,7 +455,7 @@ export const getAllServizioAnnunci = async (
|
||||||
//ignora annunci che non sono disponibili
|
//ignora annunci che non sono disponibili
|
||||||
//.where("annunci.web", "=", true)
|
//.where("annunci.web", "=", true)
|
||||||
//.where("annunci.stato", "!=", "Sospeso")
|
//.where("annunci.stato", "!=", "Sospeso")
|
||||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
.orderBy("servizio_annunci.status_conferma", (ob) =>
|
||||||
ob.asc().nullsLast(),
|
ob.asc().nullsLast(),
|
||||||
)
|
)
|
||||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||||
|
|
@ -593,7 +593,7 @@ export const getServizioDataById = async (
|
||||||
//ignora annunci che non sono disponibili
|
//ignora annunci che non sono disponibili
|
||||||
//.where("annunci.web", "=", true)
|
//.where("annunci.web", "=", true)
|
||||||
//.where("annunci.stato", "!=", "Sospeso")
|
//.where("annunci.stato", "!=", "Sospeso")
|
||||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
.orderBy("servizio_annunci.status_conferma", (ob) =>
|
||||||
ob.asc().nullsLast(),
|
ob.asc().nullsLast(),
|
||||||
)
|
)
|
||||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||||
|
|
@ -631,10 +631,7 @@ export const getServizioDataById = async (
|
||||||
...annuncio,
|
...annuncio,
|
||||||
created_at: new Date(annuncio.created_at),
|
created_at: new Date(annuncio.created_at),
|
||||||
open_contatti_at: parseNullableDateString(annuncio.open_contatti_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(
|
contratto_decorrenza: parseNullableDateString(
|
||||||
annuncio.contratto_decorrenza,
|
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 ({
|
export const UtenteInteressatoAdAnnuncioEmail = async ({
|
||||||
codice,
|
codice,
|
||||||
nome,
|
nome,
|
||||||
|
|
@ -1414,7 +1195,7 @@ export const getRichieste = async (): Promise<RichiesteData[]> => {
|
||||||
withImages(),
|
withImages(),
|
||||||
withVideos(),
|
withVideos(),
|
||||||
])
|
])
|
||||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
.orderBy("servizio_annunci.status_conferma", (ob) =>
|
||||||
ob.asc().nullsLast(),
|
ob.asc().nullsLast(),
|
||||||
)
|
)
|
||||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue