2026-03-27 18:10:17 +01:00
|
|
|
import type { ColumnDef, Table } from "@tanstack/react-table";
|
2026-03-30 16:26:03 +02:00
|
|
|
import {
|
|
|
|
|
add,
|
|
|
|
|
format,
|
|
|
|
|
formatDuration,
|
|
|
|
|
intervalToDuration,
|
|
|
|
|
isBefore,
|
|
|
|
|
} from "date-fns";
|
2026-03-27 18:10:17 +01:00
|
|
|
import { it } from "date-fns/locale";
|
|
|
|
|
|
2026-03-25 17:30:33 +01:00
|
|
|
import {
|
|
|
|
|
CircleCheck,
|
|
|
|
|
CircleCheckBig,
|
|
|
|
|
ClockFading,
|
|
|
|
|
ExternalLink,
|
2026-03-30 17:36:35 +02:00
|
|
|
Logs,
|
2026-03-25 17:30:33 +01:00
|
|
|
SearchX,
|
2026-03-30 16:26:03 +02:00
|
|
|
UserCog,
|
2026-03-25 17:30:33 +01:00
|
|
|
} from "lucide-react";
|
2026-03-11 12:07:19 +01:00
|
|
|
import Link from "next/link";
|
2026-03-27 18:10:17 +01:00
|
|
|
import { useRef } from "react";
|
2026-03-26 17:12:33 +01:00
|
|
|
import toast from "react-hot-toast";
|
2026-03-27 18:10:17 +01:00
|
|
|
import { NotePopover } from "~/components/notePopover";
|
|
|
|
|
import { AnnuncioCardRichieste } from "~/components/servizio/annuncio_card";
|
2026-03-30 17:36:35 +02:00
|
|
|
import { OrdersModal } from "~/components/servizio/servizio_actions";
|
2026-03-30 16:26:03 +02:00
|
|
|
import { WhatsAppIcon2 } from "~/components/svgs";
|
2026-03-25 17:30:33 +01:00
|
|
|
import { Badge } from "~/components/ui/badge";
|
2026-03-19 10:24:32 +01:00
|
|
|
import { Button } from "~/components/ui/button";
|
2026-03-30 16:26:03 +02:00
|
|
|
import {
|
|
|
|
|
ContextMenu,
|
|
|
|
|
ContextMenuContent,
|
|
|
|
|
ContextMenuItem,
|
|
|
|
|
ContextMenuLabel,
|
2026-04-13 12:45:37 +02:00
|
|
|
ContextMenuSub,
|
|
|
|
|
ContextMenuSubContent,
|
|
|
|
|
ContextMenuSubTrigger,
|
2026-03-30 16:26:03 +02:00
|
|
|
ContextMenuTrigger,
|
|
|
|
|
} from "~/components/ui/context-menu";
|
2026-03-19 10:24:32 +01:00
|
|
|
import { DataTable } from "~/components/ui/data-table";
|
|
|
|
|
import { DataTableColumnHeader } from "~/components/ui/dataTable-header";
|
2026-03-11 12:07:19 +01:00
|
|
|
import type {
|
|
|
|
|
PinnedFiltro,
|
|
|
|
|
SearchFiltro,
|
2026-03-19 10:24:32 +01:00
|
|
|
} from "~/components/ui/dataTable-toolbar";
|
2026-03-25 17:30:33 +01:00
|
|
|
import {
|
2026-03-30 11:49:39 +02:00
|
|
|
Sheet,
|
|
|
|
|
SheetContent,
|
|
|
|
|
SheetDescription,
|
|
|
|
|
SheetHeader,
|
|
|
|
|
SheetTitle,
|
|
|
|
|
SheetTrigger,
|
|
|
|
|
} from "~/components/ui/sheet";
|
2026-03-25 17:30:33 +01:00
|
|
|
import {
|
|
|
|
|
Tooltip,
|
|
|
|
|
TooltipContent,
|
|
|
|
|
TooltipTrigger,
|
|
|
|
|
} from "~/components/ui/tooltip";
|
|
|
|
|
import { UserAvatar } from "~/components/user_avatar";
|
2026-03-27 18:10:17 +01:00
|
|
|
import { MutationPageRestore } from "~/lib/tableUtils";
|
2026-03-25 17:30:33 +01:00
|
|
|
import { cn } from "~/lib/utils";
|
2026-04-28 20:32:19 +02:00
|
|
|
import { orderTypeEnum } from "~/schemas/public/OrderTypeEnum";
|
|
|
|
|
import { statusConfermaEnum } from "~/schemas/public/StatusConfermaEnum";
|
|
|
|
|
import { tipologiaPosizioneEnum } from "~/schemas/public/TipologiaPosizioneEnum";
|
2026-03-11 12:07:19 +01:00
|
|
|
import type { RichiesteData } from "~/server/controllers/servizio.controller";
|
2026-03-26 17:12:33 +01:00
|
|
|
import { api } from "~/utils/api";
|
2026-03-30 16:26:03 +02:00
|
|
|
import { DEFAULT_SERVIZIO_DURATION_DAYS } from "~/utils/config";
|
2026-03-11 12:07:19 +01:00
|
|
|
|
|
|
|
|
type RichiesteTableProps = {
|
|
|
|
|
data: RichiesteData[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
2026-03-30 11:54:42 +02:00
|
|
|
const tabledata = data;
|
2026-03-11 12:07:19 +01:00
|
|
|
const columns_titles = {
|
|
|
|
|
id: "ID Ordine",
|
|
|
|
|
username: "Utente",
|
2026-03-26 17:12:33 +01:00
|
|
|
note: "Note",
|
2026-03-25 17:30:33 +01:00
|
|
|
decorrenza: "Decorrenza",
|
2026-03-11 12:07:19 +01:00
|
|
|
status: "Status",
|
2026-03-25 17:30:33 +01:00
|
|
|
tipologia: "Tipo",
|
2026-03-11 12:07:19 +01:00
|
|
|
annunci: "Annunci",
|
2026-03-30 17:36:35 +02:00
|
|
|
ordini: "Ordini",
|
2026-03-27 18:10:17 +01:00
|
|
|
interruzione_expires_at: "Scad. interr.",
|
2026-03-11 12:07:19 +01:00
|
|
|
actions: "Azioni",
|
|
|
|
|
};
|
|
|
|
|
const statusOptions = [
|
|
|
|
|
{ label: "Attesa di attivazione", value: "attesa_attivazione" },
|
|
|
|
|
{ label: "Acconto pagato", value: "acconto_pagato" },
|
|
|
|
|
{ label: "Saldo pagato", value: "saldo_pagato" },
|
|
|
|
|
{ label: "Conferma in corso", value: "conferma" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const type_options = [
|
2026-04-28 20:32:19 +02:00
|
|
|
{ label: "Transitorio", value: tipologiaPosizioneEnum.enum.Transitorio },
|
|
|
|
|
{ label: "Stabile", value: tipologiaPosizioneEnum.enum.Stabile },
|
2026-03-11 12:07:19 +01:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
type Column = (typeof tabledata)[number];
|
2026-03-27 18:10:17 +01:00
|
|
|
const tableRef = useRef<Table<Column> | null>(null);
|
2026-03-26 17:12:33 +01:00
|
|
|
const utils = api.useUtils();
|
2026-03-27 18:10:17 +01:00
|
|
|
|
|
|
|
|
const { onMutate, onSettled } = MutationPageRestore(tableRef);
|
2026-03-26 17:12:33 +01:00
|
|
|
const { mutate, isPending } = api.users.editUser.useMutation({
|
2026-03-27 18:10:17 +01:00
|
|
|
onMutate,
|
|
|
|
|
onSettled,
|
2026-03-26 17:12:33 +01:00
|
|
|
onSuccess: async () => {
|
|
|
|
|
toast.success("Note aggiornate con successo");
|
|
|
|
|
await utils.users.invalidate();
|
|
|
|
|
await utils.servizio.invalidate();
|
|
|
|
|
},
|
|
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(error.message);
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-04-01 10:51:13 +02:00
|
|
|
const searchFiltro: SearchFiltro = {
|
|
|
|
|
columnName: "username",
|
|
|
|
|
placeholder: "Cerca...",
|
|
|
|
|
};
|
2026-03-26 17:12:33 +01:00
|
|
|
|
2026-03-11 12:07:19 +01:00
|
|
|
const columns: ColumnDef<Column>[] = [
|
|
|
|
|
{
|
|
|
|
|
accessorKey: "id",
|
|
|
|
|
cell: () => null,
|
|
|
|
|
enableHiding: false,
|
|
|
|
|
header: () => null,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
accessorKey: "username",
|
|
|
|
|
cell: ({ row }) => {
|
2026-03-30 16:26:03 +02:00
|
|
|
const data = row.original;
|
2026-03-11 12:07:19 +01:00
|
|
|
return (
|
2026-03-30 16:26:03 +02:00
|
|
|
<ContextMenu>
|
|
|
|
|
<ContextMenuTrigger asChild>
|
|
|
|
|
<Link
|
|
|
|
|
className="flex items-center gap-2"
|
|
|
|
|
href={`/area-riservata/admin/user-view/ricerca/${data.user_id}`}
|
|
|
|
|
>
|
|
|
|
|
<UserAvatar
|
|
|
|
|
className="size-8"
|
|
|
|
|
userId={data.user_id}
|
|
|
|
|
username={data.username}
|
|
|
|
|
/>
|
|
|
|
|
<span className="font-semibold text-sm">{data.username}</span>
|
|
|
|
|
</Link>
|
|
|
|
|
</ContextMenuTrigger>
|
|
|
|
|
<ContextMenuContent>
|
|
|
|
|
<ContextMenuLabel className="flex h-8 items-center gap-2">
|
|
|
|
|
<UserAvatar
|
|
|
|
|
className="size-5"
|
|
|
|
|
style={{ height: "1.25rem", width: "1.25rem" }}
|
|
|
|
|
userId={data.user_id}
|
|
|
|
|
username={data.username}
|
|
|
|
|
/>
|
|
|
|
|
<span className="font-semibold text-sm">{data.username}</span>
|
|
|
|
|
</ContextMenuLabel>
|
|
|
|
|
|
|
|
|
|
<ContextMenuItem>
|
|
|
|
|
<Link
|
|
|
|
|
aria-label="Visualizza Profilo"
|
|
|
|
|
className="flex w-full items-center gap-2"
|
|
|
|
|
href={`/area-riservata/admin/user-view/edit-user/${data.user_id}`}
|
|
|
|
|
>
|
|
|
|
|
<UserCog className="text-muted-foreground" /> Profilo
|
|
|
|
|
</Link>
|
|
|
|
|
</ContextMenuItem>
|
2026-04-13 12:45:37 +02:00
|
|
|
{data.extra_telefono_numero ? (
|
|
|
|
|
<ContextMenuSub>
|
|
|
|
|
<ContextMenuSubTrigger className="flex w-full items-center gap-2">
|
|
|
|
|
<WhatsAppIcon2 className="size-4 fill-muted-foreground stroke-0" />
|
|
|
|
|
WhatsApp
|
|
|
|
|
</ContextMenuSubTrigger>
|
|
|
|
|
<ContextMenuSubContent>
|
|
|
|
|
<ContextMenuItem>
|
|
|
|
|
<Link
|
|
|
|
|
className="flex w-full items-center gap-2"
|
|
|
|
|
href={`https://wa.me/${data.telefono}`}
|
|
|
|
|
target="_blank"
|
|
|
|
|
>
|
|
|
|
|
<WhatsAppIcon2 className="size-4 fill-muted-foreground stroke-0" />
|
|
|
|
|
WhatsApp ({data.nome})
|
|
|
|
|
</Link>
|
|
|
|
|
</ContextMenuItem>
|
|
|
|
|
<ContextMenuItem>
|
|
|
|
|
<Link
|
|
|
|
|
className="flex w-full items-center gap-2"
|
|
|
|
|
href={`https://wa.me/${data.extra_telefono_numero}`}
|
|
|
|
|
target="_blank"
|
|
|
|
|
>
|
|
|
|
|
<WhatsAppIcon2 className="size-4 fill-muted-foreground stroke-0" />
|
|
|
|
|
WhatsApp ({data.extra_telefono_titolo})
|
|
|
|
|
</Link>
|
|
|
|
|
</ContextMenuItem>
|
|
|
|
|
</ContextMenuSubContent>
|
|
|
|
|
</ContextMenuSub>
|
|
|
|
|
) : (
|
|
|
|
|
<ContextMenuItem>
|
|
|
|
|
<Link
|
|
|
|
|
className="flex w-full items-center gap-2"
|
|
|
|
|
href={`https://wa.me/${data.telefono}`}
|
|
|
|
|
target="_blank"
|
|
|
|
|
>
|
|
|
|
|
<WhatsAppIcon2 className="size-4 fill-muted-foreground stroke-0" />
|
|
|
|
|
WhatsApp
|
|
|
|
|
</Link>
|
|
|
|
|
</ContextMenuItem>
|
|
|
|
|
)}
|
2026-03-30 16:26:03 +02:00
|
|
|
</ContextMenuContent>
|
|
|
|
|
</ContextMenu>
|
2026-03-11 12:07:19 +01:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
enableHiding: false,
|
|
|
|
|
header: ({ column }) => (
|
|
|
|
|
<DataTableColumnHeader
|
|
|
|
|
column={column}
|
|
|
|
|
title={columns_titles.username}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
},
|
2026-03-26 17:12:33 +01:00
|
|
|
{
|
|
|
|
|
accessorKey: "note",
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
const note = row.original.note;
|
|
|
|
|
return (
|
|
|
|
|
<NotePopover
|
|
|
|
|
isPending={isPending}
|
|
|
|
|
mutate={mutate}
|
|
|
|
|
note={note}
|
|
|
|
|
userId={row.original.user_id}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
header: ({ column }) => (
|
|
|
|
|
<DataTableColumnHeader column={column} title={columns_titles.note} />
|
|
|
|
|
),
|
|
|
|
|
enableSorting: false,
|
|
|
|
|
},
|
2026-03-11 12:07:19 +01:00
|
|
|
{
|
2026-03-25 17:30:33 +01:00
|
|
|
accessorKey: "decorrenza",
|
2026-03-11 12:07:19 +01:00
|
|
|
cell: ({ row }) => {
|
2026-03-25 17:30:33 +01:00
|
|
|
let decorrenzaColor = "";
|
|
|
|
|
let decorrenzaTooltip = "";
|
|
|
|
|
if (!row.original.isOkAcconto || !row.original.decorrenza) {
|
|
|
|
|
decorrenzaColor = "text-muted-foreground";
|
|
|
|
|
decorrenzaTooltip = "Attesa di attivazione";
|
|
|
|
|
} else if (row.original.isInterrotto) {
|
|
|
|
|
decorrenzaColor = "text-destructive";
|
|
|
|
|
decorrenzaTooltip = "Servizio interrotto";
|
|
|
|
|
} else if (row.original.isOkAcconto && !row.original.isOkSaldo) {
|
|
|
|
|
decorrenzaColor = "text-warning";
|
|
|
|
|
decorrenzaTooltip = "Attesa di saldo";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger className={decorrenzaColor}>
|
|
|
|
|
{row.original.decorrenza
|
|
|
|
|
? format(row.original.decorrenza, "dd/MM")
|
|
|
|
|
: "---"}
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<p>{decorrenzaTooltip}</p>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
2026-03-11 12:07:19 +01:00
|
|
|
},
|
|
|
|
|
enableHiding: false,
|
|
|
|
|
header: ({ column }) => (
|
|
|
|
|
<DataTableColumnHeader
|
|
|
|
|
column={column}
|
2026-03-25 17:30:33 +01:00
|
|
|
title={columns_titles.decorrenza}
|
2026-03-11 12:07:19 +01:00
|
|
|
/>
|
|
|
|
|
),
|
2026-04-01 10:51:13 +02:00
|
|
|
|
2026-03-11 12:07:19 +01:00
|
|
|
sortingFn: "datetime",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
accessorKey: "tipologia",
|
|
|
|
|
cell: ({ row }) => {
|
2026-03-25 17:30:33 +01:00
|
|
|
const { tipologia } = row.original;
|
|
|
|
|
return (
|
|
|
|
|
<Badge
|
|
|
|
|
className={cn(
|
|
|
|
|
"px-1.5",
|
2026-04-28 20:32:19 +02:00
|
|
|
tipologia === tipologiaPosizioneEnum.enum.Transitorio
|
2026-03-25 17:30:33 +01:00
|
|
|
? "border-transitorio bg-transitorio/15"
|
|
|
|
|
: "border-stabile bg-stabile/15",
|
|
|
|
|
)}
|
|
|
|
|
variant="outline"
|
|
|
|
|
>
|
|
|
|
|
{tipologia.toString()}
|
|
|
|
|
</Badge>
|
|
|
|
|
);
|
2026-03-11 12:07:19 +01:00
|
|
|
},
|
|
|
|
|
filterFn: (row, id, value) => {
|
|
|
|
|
return value.includes(row.getValue(id));
|
|
|
|
|
},
|
|
|
|
|
header: ({ column }) => (
|
2026-03-25 17:30:33 +01:00
|
|
|
<DataTableColumnHeader
|
|
|
|
|
column={column}
|
|
|
|
|
title={columns_titles.tipologia}
|
|
|
|
|
/>
|
2026-03-11 12:07:19 +01:00
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
accessorKey: "status",
|
|
|
|
|
cell: ({ row }) => {
|
2026-03-25 17:30:33 +01:00
|
|
|
let badgeClass = "";
|
|
|
|
|
let contents: React.ReactNode;
|
2026-03-30 16:26:03 +02:00
|
|
|
|
|
|
|
|
const resolveStatus = () => {
|
|
|
|
|
if (row.original.isInterrotto) {
|
|
|
|
|
badgeClass =
|
|
|
|
|
"border-destructive bg-destructive/10 text-destructive";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<SearchX className="stroke-red-500 dark:stroke-red-400" />
|
|
|
|
|
Interrotto
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!row.original.onboardOk) {
|
|
|
|
|
badgeClass = "border-pink-500 bg-pink-500/10 text-pink-600";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<ClockFading className="stroke-pink-500 dark:stroke-pink-400" />
|
|
|
|
|
Onboarding
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!row.original.isOkAcconto || !row.original.decorrenza) {
|
|
|
|
|
const acconti = row.original.ordini_servizio.filter(
|
2026-04-28 20:32:19 +02:00
|
|
|
(o) => o.type === orderTypeEnum.enum.Acconto,
|
2026-03-30 16:26:03 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const activeAcconto = acconti.find((a) => a.isActive);
|
|
|
|
|
|
2026-04-10 12:08:33 +02:00
|
|
|
//hasActiveAcconto
|
2026-03-30 16:26:03 +02:00
|
|
|
if (activeAcconto) {
|
|
|
|
|
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
|
|
|
|
|
Attesa attivazione
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
|
2026-03-30 17:36:35 +02:00
|
|
|
Attesa pag. acconto
|
2026-03-30 16:26:03 +02:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
isBefore(
|
|
|
|
|
add(row.original.decorrenza, {
|
|
|
|
|
days: DEFAULT_SERVIZIO_DURATION_DAYS,
|
|
|
|
|
}),
|
|
|
|
|
new Date(),
|
|
|
|
|
) &&
|
|
|
|
|
!row.original.isOkSaldo
|
|
|
|
|
) {
|
|
|
|
|
badgeClass = "border-red-500 bg-red-500/10 text-red-500";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<SearchX className="stroke-red-500 dark:stroke-red-400" />
|
|
|
|
|
Scaduto
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const contrattoInserito = row.original.annunci_servizio.filter(
|
|
|
|
|
(a) => a.doc_contratto_added,
|
2026-03-25 17:30:33 +01:00
|
|
|
);
|
2026-03-30 16:26:03 +02:00
|
|
|
if (contrattoInserito.length > 0) {
|
|
|
|
|
if (row.original.isOkSaldo) {
|
|
|
|
|
if (row.original.isOkConsulenza) {
|
|
|
|
|
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<CircleCheckBig className="stroke-blue-500 dark:stroke-blue-400" />
|
|
|
|
|
Confermato
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (
|
2026-04-10 12:08:33 +02:00
|
|
|
//hasConsulanzaDaPagare
|
2026-03-30 16:26:03 +02:00
|
|
|
row.original.ordini_servizio.some(
|
2026-04-28 20:32:19 +02:00
|
|
|
(o) =>
|
|
|
|
|
o.type === orderTypeEnum.enum.Consulenza && !o.isActive,
|
2026-03-30 16:26:03 +02:00
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
badgeClass =
|
|
|
|
|
"border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
|
|
|
|
|
Consulenza da pagare
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
|
|
|
|
|
Consulenza da inserire
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 16:17:24 +02:00
|
|
|
const annuncioCaparraVersata = row.original.annunci_servizio.filter(
|
2026-04-28 20:32:19 +02:00
|
|
|
(a) =>
|
|
|
|
|
a.status_conferma === statusConfermaEnum.enum["Caparra versata"],
|
2026-03-25 17:30:33 +01:00
|
|
|
);
|
2026-04-09 16:17:24 +02:00
|
|
|
if (annuncioCaparraVersata.length > 0) {
|
2026-03-30 16:26:03 +02:00
|
|
|
if (row.original.isOkSaldo) {
|
|
|
|
|
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<CircleCheckBig className="stroke-blue-500 dark:stroke-blue-400" />
|
|
|
|
|
Confermato
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
|
|
|
|
|
Attesa saldo
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-04-09 16:17:24 +02:00
|
|
|
const annuncioConfermato = row.original.annunci_servizio.filter(
|
|
|
|
|
(a) =>
|
2026-04-28 20:32:19 +02:00
|
|
|
a.status_conferma ===
|
|
|
|
|
statusConfermaEnum.enum["Conferma accettata"],
|
2026-04-09 16:17:24 +02:00
|
|
|
);
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-03-30 16:26:03 +02:00
|
|
|
|
|
|
|
|
const annuncioAttesaConferma = row.original.annunci_servizio.filter(
|
2026-04-28 20:32:19 +02:00
|
|
|
(a) =>
|
|
|
|
|
a.status_conferma === statusConfermaEnum.enum["Inviato conferma"],
|
2026-03-25 17:30:33 +01:00
|
|
|
);
|
2026-03-30 16:26:03 +02:00
|
|
|
if (annuncioAttesaConferma.length > 0) {
|
|
|
|
|
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<CircleCheckBig className="stroke-blue-500 dark:stroke-blue-400" />
|
2026-03-30 17:36:35 +02:00
|
|
|
Attesa accett. conferma
|
2026-03-30 16:26:03 +02:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 17:30:33 +01:00
|
|
|
badgeClass = "border-green-500 bg-green-500/10 text-green-500";
|
|
|
|
|
contents = (
|
|
|
|
|
<>
|
|
|
|
|
<CircleCheck className="stroke-green-500 dark:stroke-green-400" />
|
|
|
|
|
Attivo
|
|
|
|
|
</>
|
|
|
|
|
);
|
2026-03-30 16:26:03 +02:00
|
|
|
};
|
|
|
|
|
resolveStatus();
|
2026-03-25 17:30:33 +01:00
|
|
|
return (
|
|
|
|
|
<Badge
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center px-1.5 text-muted-foreground [&>svg]:size-4",
|
|
|
|
|
badgeClass,
|
|
|
|
|
)}
|
|
|
|
|
variant="outline"
|
|
|
|
|
>
|
|
|
|
|
{contents}
|
|
|
|
|
</Badge>
|
|
|
|
|
);
|
2026-03-11 12:07:19 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
filterFn: (row, id, value) => {
|
|
|
|
|
return value.includes(row.getValue(id));
|
|
|
|
|
},
|
|
|
|
|
header: ({ column }) => (
|
|
|
|
|
<DataTableColumnHeader column={column} title={columns_titles.status} />
|
|
|
|
|
),
|
|
|
|
|
},
|
2026-03-27 18:10:17 +01:00
|
|
|
{
|
|
|
|
|
accessorKey: "interruzione_expires_at",
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
if (row.original.isInterrotto) return "---";
|
|
|
|
|
if (!row.original.decorrenza) return "---";
|
|
|
|
|
const expiresAt = add(row.original.decorrenza, {
|
|
|
|
|
days: row.original.interruzioneDays,
|
|
|
|
|
});
|
|
|
|
|
const timeLeft = intervalToDuration({
|
|
|
|
|
start: new Date(),
|
|
|
|
|
end: expiresAt,
|
|
|
|
|
});
|
|
|
|
|
const daysLeft = timeLeft.days || 0;
|
|
|
|
|
let badgeColor = "";
|
|
|
|
|
if (daysLeft > 5) {
|
|
|
|
|
badgeColor = "border-green-500 bg-green-500/10 text-green-500";
|
|
|
|
|
} else if (daysLeft > 0) {
|
|
|
|
|
badgeColor = "border-yellow-500 bg-yellow-500/10 text-yellow-500";
|
|
|
|
|
} else {
|
|
|
|
|
badgeColor = "border-red-500 bg-red-500/10 text-red-500";
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger>
|
|
|
|
|
<Badge className={cn("px-1.5", badgeColor)} variant="outline">
|
|
|
|
|
{format(expiresAt, "dd/MM")}
|
|
|
|
|
</Badge>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
{daysLeft > 0 ? (
|
|
|
|
|
<p>
|
|
|
|
|
{formatDuration(timeLeft, { locale: it, format: ["days"] })}{" "}
|
|
|
|
|
rimanenti
|
|
|
|
|
</p>
|
|
|
|
|
) : (
|
|
|
|
|
<p>Interruzione scaduta</p>
|
|
|
|
|
)}
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-30 17:36:35 +02:00
|
|
|
enableSorting: false,
|
2026-03-27 18:10:17 +01:00
|
|
|
header: ({ column }) => (
|
|
|
|
|
<DataTableColumnHeader
|
|
|
|
|
column={column}
|
|
|
|
|
title={columns_titles.interruzione_expires_at}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
},
|
2026-03-11 12:07:19 +01:00
|
|
|
{
|
|
|
|
|
accessorKey: "annunci_servizio",
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
const annunci = row.original.annunci_servizio;
|
2026-03-25 17:30:33 +01:00
|
|
|
return (
|
2026-03-30 11:49:39 +02:00
|
|
|
<Sheet>
|
|
|
|
|
<SheetTrigger asChild>
|
2026-03-30 11:30:35 +02:00
|
|
|
<Button
|
|
|
|
|
className="data-[state=open]:border-primary data-[state=open]:bg-primary/10"
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="outline"
|
|
|
|
|
>
|
2026-03-30 17:36:35 +02:00
|
|
|
{annunci.length}
|
2026-03-25 17:30:33 +01:00
|
|
|
</Button>
|
2026-03-30 11:49:39 +02:00
|
|
|
</SheetTrigger>
|
|
|
|
|
<SheetContent className="w-4/5 sm:max-w-3xl">
|
|
|
|
|
<SheetHeader>
|
|
|
|
|
<SheetTitle>Annunci Inseriti</SheetTitle>
|
|
|
|
|
<SheetDescription className="sr-only">Desc</SheetDescription>
|
|
|
|
|
</SheetHeader>
|
|
|
|
|
<div className="flex flex-col gap-4 divide-y overflow-y-auto">
|
2026-03-25 17:30:33 +01:00
|
|
|
{annunci.map((a) => (
|
2026-03-27 18:10:17 +01:00
|
|
|
<AnnuncioCardRichieste
|
2026-03-25 17:30:33 +01:00
|
|
|
data={a}
|
|
|
|
|
key={a.annunci_id}
|
2026-03-27 18:10:17 +01:00
|
|
|
tableRef={tableRef}
|
2026-03-25 17:30:33 +01:00
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-03-30 11:49:39 +02:00
|
|
|
</SheetContent>
|
|
|
|
|
</Sheet>
|
2026-03-25 17:30:33 +01:00
|
|
|
);
|
2026-03-11 12:07:19 +01:00
|
|
|
},
|
|
|
|
|
header: ({ column }) => (
|
|
|
|
|
<DataTableColumnHeader column={column} title={columns_titles.annunci} />
|
|
|
|
|
),
|
|
|
|
|
enableSorting: false,
|
|
|
|
|
},
|
2026-03-30 17:36:35 +02:00
|
|
|
{
|
|
|
|
|
accessorKey: "ordini",
|
|
|
|
|
cell: ({ row }) => {
|
2026-04-10 12:08:33 +02:00
|
|
|
//paidOrdiniCount
|
2026-04-03 18:57:39 +02:00
|
|
|
const paidOrders = row.original.ordini_servizio.filter(
|
|
|
|
|
(o) => o.isActive,
|
|
|
|
|
).length;
|
2026-04-10 12:08:33 +02:00
|
|
|
//unpaidOrdiniCount
|
2026-04-03 18:57:39 +02:00
|
|
|
const unpaidOrders = row.original.ordini_servizio.length - paidOrders;
|
2026-03-30 17:36:35 +02:00
|
|
|
return (
|
|
|
|
|
<OrdersModal isAdmin={true} ordini={row.original.ordini_servizio}>
|
2026-04-03 18:57:39 +02:00
|
|
|
<div className="relative inline-flex">
|
|
|
|
|
<Button className="flex-1" size="sm" variant="outline">
|
|
|
|
|
<Logs />
|
|
|
|
|
</Button>
|
|
|
|
|
{unpaidOrders > 0 && (
|
|
|
|
|
<span className="absolute -top-1.5 -right-1.5 flex size-3.5 items-center justify-center rounded-full bg-red-600 text-white text-xs">
|
|
|
|
|
{unpaidOrders}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{paidOrders > 0 && (
|
|
|
|
|
<span className="absolute -top-1.5 -left-1.5 flex size-3.5 items-center justify-center rounded-full bg-green-600 text-white text-xs">
|
|
|
|
|
{paidOrders}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-03-30 17:36:35 +02:00
|
|
|
</OrdersModal>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
header: ({ column }) => (
|
|
|
|
|
<DataTableColumnHeader column={column} title={columns_titles.ordini} />
|
|
|
|
|
),
|
|
|
|
|
enableSorting: false,
|
|
|
|
|
},
|
2026-03-11 12:07:19 +01:00
|
|
|
{
|
2026-03-25 17:30:33 +01:00
|
|
|
cell: ({ row }) => {
|
2026-03-11 12:07:19 +01:00
|
|
|
return (
|
2026-03-25 17:30:33 +01:00
|
|
|
<Link
|
|
|
|
|
href={`/area-riservata/admin/user-view/servizio/${row.original.user_id}/${row.original.servizio_id}`}
|
|
|
|
|
target="_blank"
|
2026-03-11 12:07:19 +01:00
|
|
|
>
|
2026-03-25 17:30:33 +01:00
|
|
|
<Button aria-label="Apri servizio" size="sm" variant="outline">
|
|
|
|
|
<ExternalLink className="size-5" />
|
|
|
|
|
</Button>
|
|
|
|
|
</Link>
|
2026-03-11 12:07:19 +01:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
enableHiding: false,
|
|
|
|
|
id: "actions",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const pinnedFiltri: PinnedFiltro[] = [
|
|
|
|
|
{
|
|
|
|
|
columnName: "status",
|
|
|
|
|
|
|
|
|
|
options: statusOptions,
|
|
|
|
|
title: "Stato Ordine",
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-03-25 17:30:33 +01:00
|
|
|
columnName: "tipologia",
|
2026-03-11 12:07:19 +01:00
|
|
|
options: type_options,
|
|
|
|
|
title: "Tipo Ordine",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="mx-auto w-full">
|
|
|
|
|
<DataTable
|
|
|
|
|
columns={columns}
|
|
|
|
|
columns_titles={columns_titles}
|
|
|
|
|
data={tabledata}
|
2026-03-25 17:30:33 +01:00
|
|
|
defaultSort={[{ desc: true, id: "decorrenza" }]}
|
2026-03-27 18:10:17 +01:00
|
|
|
onTableInit={(t) => (tableRef.current = t)}
|
2026-03-11 12:07:19 +01:00
|
|
|
pinnedFiltri={pinnedFiltri}
|
|
|
|
|
searchColumn={searchFiltro}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|