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

789 lines
21 KiB
TypeScript
Raw Normal View History

import { add, addBusinessDays, isBefore } from "date-fns";
import {
Bug,
ChevronDown,
ClockAlert,
Cog,
Euro,
ExternalLink,
Logs,
Plus,
Search,
SlidersHorizontal,
Trash2,
} from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
2025-08-28 18:27:07 +02:00
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 {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "~/components/ui/tooltip";
2025-08-28 18:27:07 +02:00
import {
type FormValues as EditFormValues,
FormEditServizio,
} from "~/forms/FormEditServizioAdmin";
import { FormNewOrder } from "~/forms/FormNewOrdine";
import { FormServizio, type FormValues } from "~/forms/FormServizio";
import { formatCurrency } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider";
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 TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import type { UsersId } from "~/schemas/public/Users";
2025-08-28 18:27:07 +02:00
import { api } from "~/utils/api";
import { Confirm } from "../confirm";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../ui/table";
export const ServizioInfos = () => {
return (
<div className="grid w-full grid-cols-2 gap-2 sm:grid-cols-4">
<ServizioPrezzo />
<UserActions />
<ServizioOrders />
</div>
);
};
2025-08-04 17:45:44 +02:00
export const AdminServizioActions = () => {
2025-08-28 18:27:07 +02:00
const { servizio, isAdmin, userId } = useServizio();
if (!isAdmin) return null;
2025-08-28 18:27:07 +02:00
return (
<div className="flex flex-wrap items-center gap-3">
<AddAnnuncio servizioId={servizio.servizio_id} userId={userId} />
<EditServizioAdmin />
2025-08-28 18:27:07 +02:00
</div>
);
};
const UserActions = () => {
const { servizio, isAdmin } = useServizio();
const canUserEdit = servizio.isOkAcconto && servizio.decorrenza !== null;
/*&&
new Date() <= add(new Date(servizio.decorrenza), { days: 60 }) &&
!servizio.annunci.some((a) => a.user_confirmed_at !== null) &&
!servizio.isInterrotto &&
!servizio.annunci.some((a) => a.user_confirmed_at !== null);*/
if (isAdmin || canUserEdit) {
return (
<>
<EditParametri />
<InterruzioneServizio />
</>
);
}
return null;
};
const ServizioPrezzo = () => {
const { servizio } = useServizio();
const { t } = useTranslation();
const { data: packs } = api.servizio.getPacksSolution.useQuery({
servizioId: servizio.servizio_id,
});
if (!packs) return null;
return (
<Dialog>
<DialogTrigger asChild>
<Button className="flex-1" variant="outline">
<Euro />
<span>Prezzi</span>
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle>Prezzi Servizio</DialogTitle>
<DialogDescription className="sr-only">
Prezzi disponibili per questo servizio
</DialogDescription>
</DialogHeader>
<div 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>
</div>
</DialogContent>
</Dialog>
);
};
const ServizioOrders = () => {
const { isAdmin, servizio } = useServizio();
const [openOrders, setOpenOrders] = useState(false);
const [openEdit, setOpenEdit] = useState(false);
const swapToEdit = () => {
setOpenOrders(false);
setOpenEdit(true);
};
const swapToOrders = () => {
setOpenEdit(false);
setOpenOrders(true);
};
return (
<>
<Dialog onOpenChange={setOpenOrders} open={openOrders}>
<DialogTrigger asChild>
<Button className="flex-1" variant="outline">
<Logs />
<span>Ordini</span>
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-2xl">
<DialogHeader className="flex-row items-center gap-4">
<DialogTitle>Ordini Servizio</DialogTitle>
<DialogDescription className="sr-only">
Ordini disponibili per questo servizio
</DialogDescription>
{isAdmin && (
<Button
className="w-fit"
onClick={swapToEdit}
size="sm"
variant="outline"
>
<Plus />
</Button>
)}
</DialogHeader>
<div className="flex flex-col items-center gap-4 overflow-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Data</TableHead>
<TableHead>ID</TableHead>
<TableHead>Importo</TableHead>
<TableHead>Status</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{servizio.ordini.length === 0 && (
<tr>
<td className="py-4 text-center" colSpan={4}>
<span>Nessun ordine disponibile</span>
</td>
</tr>
)}
{servizio.ordini.map((ordine) => (
<TableRow key={ordine.ordine_id}>
<TableCell>
{ordine.created_at.toLocaleDateString("it-IT")}
</TableCell>
<TableCell className="font-medium">
{ordine.packid}
</TableCell>
<TableCell>
{formatCurrency(ordine.amount_cent / 100)}
</TableCell>
<TableCell>
{ordine.isActive ? "Attivo" : "Non attivo"}
</TableCell>
<TableCell>
<Link
href={`/area-riservata/${isAdmin ? "admin/user-view/" : ""}ordini${isAdmin ? `/${ordine.userid}` : ""}?ordineId=${ordine.ordine_id}`}
target="_blank"
>
<Button size="sm" variant="outline">
<Search />
</Button>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</DialogContent>
</Dialog>
<Dialog onOpenChange={setOpenEdit} open={openEdit}>
<DialogContent>
<DialogHeader>
<DialogTitle>Nuovo Ordine</DialogTitle>
<DialogDescription className="sr-only">nuovo</DialogDescription>
</DialogHeader>
<AddOrderDialogContent swapBack={swapToOrders} />
</DialogContent>
</Dialog>
</>
);
};
const AddOrderDialogContent = ({ swapBack }: { swapBack: () => void }) => {
const { servizioId, userId } = useServizio();
const { data, isLoading } = api.prezziario.getPrezziario.useQuery();
if (isLoading) return <LoadingPage />;
if (!data)
return (
<>
<span>Errore nel caricamento del prezziario.</span>
</>
);
return (
<FormNewOrder
prezziario={data}
servizioId={servizioId}
setOpen={swapBack}
userId={userId}
/>
);
};
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 AddAnnuncio = ({
2025-08-28 18:27:07 +02:00
servizioId,
}: {
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.invalidate();
2025-08-28 18:27:07 +02:00
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"
className=""
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-2xl"
2025-08-28 18:27:07 +02:00
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} - ${cod.titolo_it}`,
2025-08-29 16:18:32 +02:00
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 = () => {
const { servizio, isAdmin } = useServizio();
const { t } = useTranslation();
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.invalidate();
2025-08-29 16:18:32 +02:00
toast.success("Interruzione richiesta");
},
2025-08-28 18:27:07 +02:00
});
if (!servizio.decorrenza) return null;
const fineInterruzione = add(servizio.decorrenza, {
days: 14,
});
const canInterrompere =
!servizio.isInterrotto &&
(isAdmin ||
(isBefore(new Date(), fineInterruzione) &&
servizio.annunci.every((a) => a.accettato_conferma_at === null)));
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
<AlertDialog>
<AlertDialogTrigger asChild>
{canInterrompere ? (
<Button
aria-label="Request Interruzione"
className="w-full"
disabled={!canInterrompere}
>
<ClockAlert /> <span>{t.servizio.interruzione.btn}</span>
</Button>
) : (
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<span className="inline-block w-full">
<Button
aria-label="Request Interruzione"
className="w-full"
disabled={!canInterrompere}
>
<ClockAlert /> <span>{t.servizio.interruzione.btn}</span>
</Button>
</span>
</TooltipTrigger>
<TooltipContent>
<p>Periodo di interruzione scaduto, contattaci.</p>
</TooltipContent>
</Tooltip>
)}
2025-08-28 18:27:07 +02:00
</AlertDialogTrigger>
2025-08-28 18:27:07 +02:00
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t.servizio.interruzione.title}</AlertDialogTitle>
<AlertDialogDescription className="space-y-2">
<p>
<b>
Fine periodo di interruzione:{" "}
{fineInterruzione.toLocaleDateString("it-IT")}
</b>
</p>
<p className="whitespace-pre-line">
{t.servizio.interruzione.description}
</p>
2025-08-28 18:27:07 +02:00
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t.annulla}</AlertDialogCancel>
2025-08-28 18:27:07 +02:00
<AlertDialogAction
aria-label="Confirm Interruzione"
onClick={() => mutate({ servizioId: servizio.servizio_id })}
>
{t.conferma}
2025-08-28 18:27:07 +02:00
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
2025-08-04 17:45:44 +02:00
};
const EditParametri = () => {
const { servizioId, isAdmin, servizio } = useServizio();
const { t } = useTranslation();
const [openEditParametri, setOpenEditParametri] = useState(false);
2025-08-28 18:27:07 +02:00
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.invalidate();
setOpenEditParametri(false);
2025-08-28 18:27:07 +02:00
},
});
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 (
<Credenza onOpenChange={setOpenEditParametri} open={openEditParametri}>
2025-08-28 18:27:07 +02:00
<CredenzaTrigger asChild>
<Button
aria-label="Parametri di ricerca"
className="w-full"
disabled={
!isAdmin &&
(servizio.isInterrotto ||
servizio.annunci.some((a) => a.hasConfermaAdmin))
}
variant="secondary"
>
<SlidersHorizontal />
{t.servizio.parametri.btn}
2025-08-28 18:27:07 +02:00
</Button>
</CredenzaTrigger>
<CredenzaContent className="max-h-[90vh] w-full sm:max-w-5xl">
<CredenzaHeader>
<CredenzaTitle>{t.servizio.parametri.title}</CredenzaTitle>
2025-08-28 18:27:07 +02:00
<CredenzaDescription className="sr-only">
{t.servizio.parametri.title}
2025-08-28 18:27:07 +02:00
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody className="max-h-[80vh] w-full max-w-3xl overflow-y-auto pb-5 sm:max-w-5xl">
<FormServizio
initialData={servizio}
isLimited={!isAdmin}
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 utils = api.useUtils();
const router = useRouter();
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.invalidate();
2025-08-28 18:27:07 +02:00
setOpen(false);
},
});
const { mutate: remove } = api.servizio.deleteServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onMutate: async () => {
await router.push(`/area-riservata/admin/user-view/ricerca/${userId}`);
},
onSuccess: async () => {
toast.success("Servizio rimosso con successo");
await utils.servizio.invalidate();
},
});
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">
<Cog />
Modifica
2025-08-28 18:27:07 +02:00
</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">
<FormEditServizio initialData={servizio} onSubmit={onSubmit} />
2025-08-28 18:27:07 +02:00
<Collapsible>
<CollapsibleTrigger asChild>
<Button variant="warning">
2025-08-28 18:27:07 +02:00
<Bug /> Debug Info Servizio
</Button>
</CollapsibleTrigger>
<CollapsibleContent>
{(() => {
const { annunci, ...rest } = servizio;
return <pre>{JSON.stringify(rest, null, 2)}</pre>;
})()}
</CollapsibleContent>
</Collapsible>
<Confirm
description="Sei sicuro di voler eliminare questo servizio? L'azione è irreversibile."
onConfirm={() => {
remove({ servizioId });
}}
title="Elimina servizio"
>
<Button aria-label="Elimina Servizio" variant="destructive">
<Trash2 className="size-6" />
Elimina Servizio
</Button>
</Confirm>
2025-08-28 18:27:07 +02:00
</CredenzaBody>
</CredenzaContent>
</Credenza>
);
};