infoalloggi-monorepo/apps/infoalloggi/src/components/servizio/servizio_actions.tsx

395 lines
10 KiB
TypeScript
Raw Normal View History

2025-08-28 18:27:07 +02:00
import { add } from "date-fns";
import { Bug, Calculator, ClockAlert, Plus, Wrench } from "lucide-react";
import { useState } from "react";
2025-08-04 17:45:44 +02:00
import toast from "react-hot-toast";
import {
2025-08-28 18:27:07 +02:00
Credenza,
CredenzaBody,
CredenzaContent,
CredenzaDescription,
CredenzaHeader,
CredenzaTitle,
CredenzaTrigger,
} from "~/components/custom_ui/credenza";
2025-08-04 17:45:44 +02:00
import { MultiSelect } from "~/components/custom_ui/multiselect";
2025-08-28 18:27:07 +02:00
import { LoadingPage } from "~/components/loading";
2025-08-04 17:45:44 +02:00
import {
2025-08-28 18:27:07 +02:00
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
2025-08-04 17:45:44 +02:00
} from "~/components/ui/alert-dialog";
2025-08-28 18:27:07 +02:00
import { Button } from "~/components/ui/button";
2025-08-04 17:45:44 +02:00
import {
2025-08-28 18:27:07 +02:00
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "~/components/ui/collapsible";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import {
type FormValues as EditFormValues,
FormEditServizio,
} from "~/forms/FormEditServizioAdmin";
import { FormNewServizio, type FormValues } from "~/forms/FormNewServizio";
2025-08-04 17:45:44 +02:00
import { useServizio } from "~/providers/ServizioProvider";
2025-08-28 18:27:07 +02:00
import type { AnnunciId } from "~/schemas/public/Annunci";
import type { ServizioServizioId } from "~/schemas/public/Servizio";
import type { UsersId } from "~/schemas/public/Users";
2025-08-28 18:27:07 +02:00
import { api } from "~/utils/api";
2025-08-04 17:45:44 +02:00
export const ServizioActions2 = () => {
2025-08-28 18:27:07 +02:00
const { servizio, isAdmin, userId } = useServizio();
2025-08-28 18:27:07 +02:00
const canUserEdit =
servizio.isOkAcconto &&
servizio.decorrenza !== null &&
!servizio.annunci.some((a) => a.user_confirmed_at !== null);
2025-08-28 18:27:07 +02:00
if (!isAdmin && !canUserEdit) {
return null; // If the user is not an admin and cannot edit, do not render the actions
}
return (
<div className="flex w-full flex-col flex-wrap justify-between gap-3 sm:flex-row">
{(isAdmin || canUserEdit) && (
<div className="flex items-center gap-3">
<span className="hidden sm:block">Azioni: </span>
<EditPreferenze />
<InterruzioneServizio />
</div>
)}
{isAdmin && (
<div className="flex items-center gap-3">
<AddAnnuncio servizioId={servizio.servizio_id} userId={userId} />
<EditServizioAdmin />
</div>
)}
</div>
);
};
export const AddAnnuncio = ({
2025-08-28 18:27:07 +02:00
servizioId,
userId,
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
userId: UsersId;
}) => {
2025-08-28 18:27:07 +02:00
const { data: codici, isLoading: loading } =
api.servizio.getAnnunciAvailableToAdd.useQuery({
servizioId,
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const [selectedAnnunci, setSelectedAnnunci] = useState<
{ value: AnnunciId; label: string }[]
>([]);
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const [openModal, setOpenModal] = useState(false);
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const utils = api.useUtils();
const { mutate } = api.servizio.addServizioAnnunci.useMutation({
2025-08-29 16:18:32 +02:00
onError: (error) => {
console.error(error);
toast.error("Errore durante l'aggiunta dell'annuncio");
},
2025-08-28 18:27:07 +02:00
onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getAnnunciAvailableToAdd.invalidate({
servizioId,
});
await utils.servizio.getCompatibileAnnunci.invalidate({
servizioId,
});
await utils.servizio.getUserServizi.invalidate({ userId });
toast.success("Annuncio aggiunto");
setOpenModal(false);
setSelectedAnnunci([]);
},
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
2025-08-29 16:18:32 +02:00
<Dialog onOpenChange={setOpenModal} open={openModal}>
2025-08-28 18:27:07 +02:00
<DialogTrigger asChild>
<Button
aria-label="Add Annuncio"
2025-08-29 16:18:32 +02:00
className="items-center gap-2"
2025-08-28 18:27:07 +02:00
onClick={() => {
setSelectedAnnunci([]);
}}
2025-08-29 16:18:32 +02:00
variant="outline"
2025-08-28 18:27:07 +02:00
>
<Plus />
Annuncio
</Button>
</DialogTrigger>
<DialogContent
className="sm:max-w-[425px]"
onInteractOutside={(e) => {
if (selectedAnnunci.length !== 0) {
e.preventDefault(); // Prevent closing the dialog if there are selected announcements
}
}}
>
<DialogHeader>
<DialogTitle>Aggiungi annuncio</DialogTitle>
<DialogDescription>
Salva un annuncio nella ricerca dell&apos;utente
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
{loading ? (
<LoadingPage />
) : (
<MultiSelect
2025-08-29 16:18:32 +02:00
defaultValue={undefined}
2025-08-28 18:27:07 +02:00
elementId="select-annuncio"
elementName="annuncio"
isMulti={true}
onValueChange={(value) => {
setSelectedAnnunci(
value.map((v) => ({
label: v.label,
2025-08-29 16:18:32 +02:00
value: parseInt(v.value) as AnnunciId,
2025-08-28 18:27:07 +02:00
})),
);
}}
2025-08-29 16:18:32 +02:00
options={
codici?.map((cod) => ({
label: cod.codice,
value: cod.id.toString(),
})) || []
}
placeholder=""
2025-08-28 18:27:07 +02:00
/>
)}
</div>
<DialogFooter>
<Button
aria-label="Cancel Add Annuncio"
2025-08-29 16:18:32 +02:00
disabled={!selectedAnnunci.length}
2025-08-28 18:27:07 +02:00
onClick={() => {
if (!selectedAnnunci.length) return;
mutate({
annunci: selectedAnnunci.map((annuncio) => annuncio.value),
2025-08-29 16:18:32 +02:00
servizioId,
2025-08-28 18:27:07 +02:00
});
}}
2025-08-29 16:18:32 +02:00
type="button"
2025-08-28 18:27:07 +02:00
>
Aggiungi
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
2025-08-04 17:45:44 +02:00
};
2025-08-04 17:45:44 +02:00
const InterruzioneServizio = () => {
2025-08-28 18:27:07 +02:00
const { servizio, userId } = useServizio();
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const beforeInterruzioneLimit =
servizio.decorrenza != null &&
new Date() <=
add(servizio.decorrenza, {
days: 14,
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const utils = api.useUtils();
const { mutate } = api.servizio.interruzioneServizio.useMutation({
onError: (error) => {
console.error(error);
toast.error("Errore durante la richiesta di interruzione");
},
2025-08-29 16:18:32 +02:00
onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
toast.success("Interruzione richiesta");
},
2025-08-28 18:27:07 +02:00
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
aria-label="Request Interruzione"
className="items-center gap-2"
disabled={!beforeInterruzioneLimit}
2025-08-29 16:18:32 +02:00
variant="outline"
2025-08-28 18:27:07 +02:00
>
<ClockAlert /> <span>Interruzione</span>
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Sei sicuro di voler richiedere l&apos;interruzione?
</AlertDialogTitle>
<AlertDialogDescription>
Questa azione è irreversibile e comporta la disattivazione dei
servizi attivati. Confermando l&apos;azione recedi dal servizio
secondo le condizioni contrattuali. Verrai notificato via email
della conferma dell&apos;avvenuta interruzione.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Annulla</AlertDialogCancel>
<AlertDialogAction
aria-label="Confirm Interruzione"
onClick={() => mutate({ servizioId: servizio.servizio_id })}
>
Continua
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
2025-08-04 17:45:44 +02:00
};
const EditPreferenze = () => {
2025-08-28 18:27:07 +02:00
const { servizioId, userId, isAdmin } = useServizio();
const [openEditPreferenze, setOpenEditPreferenze] = useState(false);
const { data, isLoading } = api.servizio.getServizio.useQuery({ servizioId });
const utils = api.useUtils();
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const { mutate: update } = api.servizio.updateServizio.useMutation({
2025-08-29 16:18:32 +02:00
onError: (error) => {
toast.error(error.message);
},
2025-08-28 18:27:07 +02:00
onSuccess: async () => {
toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getServizio.invalidate({ servizioId });
await utils.servizio.getCompatibileAnnunci.invalidate({
servizioId,
});
setOpenEditPreferenze(false);
},
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
function onSubmit(fields: FormValues) {
update({
data: {
...fields,
},
2025-08-29 16:18:32 +02:00
servizioId,
2025-08-28 18:27:07 +02:00
});
}
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
2025-08-29 16:18:32 +02:00
<Credenza onOpenChange={setOpenEditPreferenze} open={openEditPreferenze}>
2025-08-28 18:27:07 +02:00
<CredenzaTrigger asChild>
<Button
aria-label="Preferenze di ricerca"
className="items-center gap-2"
>
<Calculator />
Preferenze
</Button>
</CredenzaTrigger>
<CredenzaContent className="max-h-[90vh] w-full sm:max-w-5xl">
<CredenzaHeader>
<CredenzaTitle>Preferenze di ricerca</CredenzaTitle>
<CredenzaDescription className="sr-only">
Preferenze
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody className="max-h-[80vh] w-full max-w-3xl overflow-y-auto pb-5">
2025-08-28 18:27:07 +02:00
{isLoading ? (
<LoadingPage />
) : (
<FormNewServizio
initialData={data}
isLimited={!isAdmin}
2025-08-29 16:18:32 +02:00
onSubmit={onSubmit}
2025-08-28 18:27:07 +02:00
/>
)}
</CredenzaBody>
</CredenzaContent>
</Credenza>
);
2025-08-04 17:45:44 +02:00
};
const EditServizioAdmin = () => {
2025-08-28 18:27:07 +02:00
const { servizioId, userId, servizio } = useServizio();
const [open, setOpen] = useState(false);
const { data, isLoading } = api.servizio.getServizio.useQuery({ servizioId });
const utils = api.useUtils();
2025-08-28 18:27:07 +02:00
const { mutate: update } = api.servizio.updateServizio.useMutation({
2025-08-29 16:18:32 +02:00
onError: (error) => {
toast.error(error.message);
},
2025-08-28 18:27:07 +02:00
onSuccess: async () => {
toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getServizio.invalidate({ servizioId });
await utils.servizio.getCompatibileAnnunci.invalidate({
servizioId,
});
setOpen(false);
},
});
2025-08-28 18:27:07 +02:00
function onSubmit(fields: EditFormValues) {
update({
data: {
...fields,
},
2025-08-29 16:18:32 +02:00
servizioId,
2025-08-28 18:27:07 +02:00
});
}
2025-08-28 18:27:07 +02:00
return (
2025-08-29 16:18:32 +02:00
<Credenza onOpenChange={setOpen} open={open}>
2025-08-28 18:27:07 +02:00
<CredenzaTrigger asChild>
<Button
aria-label="Modifica Servizio Admin"
className="items-center gap-2"
>
<Wrench />
Admin
</Button>
</CredenzaTrigger>
<CredenzaContent className="max-h-[90vh] w-full sm:max-w-5xl">
<CredenzaHeader>
<CredenzaTitle>Modifica Servizio Admin</CredenzaTitle>
<CredenzaDescription className="sr-only">
Modifica
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody className="max-h-[80vh] w-full space-y-2 overflow-y-auto pb-5">
{isLoading ? (
<LoadingPage />
) : (
<FormEditServizio initialData={data} onSubmit={onSubmit} />
)}
<Collapsible>
<CollapsibleTrigger asChild>
2025-08-29 16:18:32 +02:00
<Button className="items-center gap-2" variant="warning">
2025-08-28 18:27:07 +02:00
<Bug /> Debug Info Servizio
</Button>
</CollapsibleTrigger>
<CollapsibleContent>
{(() => {
// biome-ignore lint/correctness/noUnusedVariables: <need to extract>
const { annunci, ...rest } = servizio;
return <pre>{JSON.stringify(rest, null, 2)}</pre>;
})()}
</CollapsibleContent>
</Collapsible>
</CredenzaBody>
</CredenzaContent>
</Credenza>
);
};