infoalloggi-monorepo/apps/infoalloggi/src/components/tables/richieste-table.tsx

436 lines
11 KiB
TypeScript
Raw Normal View History

import type { ColumnDef } from "@tanstack/react-table";
import { format } from "date-fns";
import {
CircleCheck,
CircleCheckBig,
CircleEllipsis,
ClockFading,
ExternalLink,
NotebookPen,
SearchX,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import toast from "react-hot-toast";
import LoadingButton from "~/components/custom_ui/loading-button";
import { Textarea } from "~/components/custom_ui/textarea";
import { AnnuncioCardContent } from "~/components/servizio/annuncio_card";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { DataTable } from "~/components/ui/data-table";
import { DataTableColumnHeader } from "~/components/ui/dataTable-header";
import type {
PinnedFiltro,
SearchFiltro,
} from "~/components/ui/dataTable-toolbar";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "~/components/ui/popover";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "~/components/ui/tooltip";
import { UserAvatar } from "~/components/user_avatar";
import { cn } from "~/lib/utils";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import type { UsersId } from "~/schemas/public/Users";
import type { RichiesteData } from "~/server/controllers/servizio.controller";
import { api } from "~/utils/api";
type RichiesteTableProps = {
data: RichiesteData[];
};
export const RichiesteTable = ({ data }: RichiesteTableProps) => {
const tabledata = data;
const columns_titles = {
id: "ID Ordine",
username: "Utente",
note: "Note",
decorrenza: "Decorrenza",
status: "Status",
tipologia: "Tipo",
annunci: "Annunci",
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 = [
{ label: "Transitorio", value: TipologiaPosizioneEnum.Transitorio },
{ label: "Stabile", value: TipologiaPosizioneEnum.Stabile },
];
type Column = (typeof tabledata)[number];
const utils = api.useUtils();
const { mutate, isPending } = api.users.editUser.useMutation({
onSuccess: async () => {
toast.success("Note aggiornate con successo");
await utils.users.invalidate();
await utils.servizio.invalidate();
},
onError: (error) => {
toast.error(error.message);
},
});
const columns: ColumnDef<Column>[] = [
{
accessorKey: "id",
cell: () => null,
enableHiding: false,
header: () => null,
},
{
accessorKey: "username",
cell: ({ row }) => {
return (
<Link
className="flex items-center gap-2"
href={`/area-riservata/admin/user-view/ricerca/${row.original.user_id}`}
>
<UserAvatar
className="size-8"
userId={row.original.user_id}
username={row.original.username}
/>
<span className="font-semibold text-sm">
{row.original.username}
</span>
</Link>
);
},
enableHiding: false,
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.username}
/>
),
},
{
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,
},
{
accessorKey: "decorrenza",
cell: ({ row }) => {
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>
);
},
enableHiding: false,
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.decorrenza}
/>
),
sortingFn: "datetime",
},
{
accessorKey: "tipologia",
cell: ({ row }) => {
const { tipologia } = row.original;
return (
<Badge
className={cn(
"px-1.5",
tipologia === TipologiaPosizioneEnum.Transitorio
? "border-transitorio bg-transitorio/15"
: "border-stabile bg-stabile/15",
)}
variant="outline"
>
{tipologia.toString()}
</Badge>
);
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
},
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.tipologia}
/>
),
},
{
accessorKey: "status",
cell: ({ row }) => {
let badgeClass = "";
let contents: React.ReactNode;
const annunci = row.original.annunci_servizio;
const awaitingConferma = annunci.some(
(a) => a.user_confirmed_at !== null && !a.hasConfermaAdmin,
);
const hasConfermaAdmin = annunci.some((a) => a.hasConfermaAdmin);
if (row.original.isInterrotto) {
badgeClass = "border-destructive bg-destructive/10 text-destructive";
contents = (
<>
<SearchX className="stroke-red-500 dark:stroke-red-400" />
Interrotto
</>
);
} else if (!row.original.onboardOk) {
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
contents = (
<>
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
Attesa attivazione
</>
);
} else if (awaitingConferma) {
badgeClass = "border-sky-500 bg-sky-500/10 text-sky-500";
contents = (
<>
<CircleEllipsis className="stroke-sky-500 dark:stroke-sky-400" />
In Conferma
</>
);
} else if (row.original.isOkSaldo && hasConfermaAdmin) {
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
contents = (
<>
<CircleCheckBig className="stroke-blue-500 dark:stroke-blue-400" />
Saldo pagato
</>
);
} else if (row.original.isOkAcconto) {
badgeClass = "border-green-500 bg-green-500/10 text-green-500";
contents = (
<>
<CircleCheck className="stroke-green-500 dark:stroke-green-400" />
Attivo
</>
);
}
return (
<Badge
className={cn(
"flex items-center px-1.5 text-muted-foreground [&>svg]:size-4",
badgeClass,
)}
variant="outline"
>
{contents}
</Badge>
);
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
},
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.status} />
),
},
{
accessorKey: "annunci_servizio",
cell: ({ row }) => {
const annunci = row.original.annunci_servizio;
return (
<Popover>
<PopoverTrigger asChild>
<Button size="sm" variant="outline">
{annunci.length} Annunci
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
className="max-h-[50vh] w-[80vw] overflow-y-auto sm:max-h-full sm:w-4xl"
>
<PopoverHeader>
<PopoverTitle>Annunci Inseriti</PopoverTitle>
<PopoverDescription className="sr-only">
Desc
</PopoverDescription>
</PopoverHeader>
<div className="flex flex-col gap-4 divide-y">
{annunci.map((a) => (
<AnnuncioCardContent
data={a}
interactions={
<div>
<p>
Visto contatti:{" "}
{a.open_contatti_at
? format(a.open_contatti_at, "dd/MM/yyyy HH:mm")
: "Non ancora visto"}
</p>
{/*TODO CAPIRE COSA AGGIUNGERE */}
</div>
}
isAdmin
key={a.annunci_id}
/>
))}
</div>
</PopoverContent>
</Popover>
);
},
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.annunci} />
),
enableSorting: false,
},
{
cell: ({ row }) => {
return (
<Link
href={`/area-riservata/admin/user-view/servizio/${row.original.user_id}/${row.original.servizio_id}`}
target="_blank"
>
<Button aria-label="Apri servizio" size="sm" variant="outline">
<ExternalLink className="size-5" />
</Button>
</Link>
);
},
enableHiding: false,
id: "actions",
},
];
const searchFiltro: SearchFiltro = {
columnName: "id",
placeholder: "Cerca ordine...",
};
const pinnedFiltri: PinnedFiltro[] = [
{
columnName: "status",
options: statusOptions,
title: "Stato Ordine",
},
{
columnName: "tipologia",
options: type_options,
title: "Tipo Ordine",
},
];
return (
<div className="mx-auto w-full">
<DataTable
columns={columns}
columns_titles={columns_titles}
data={tabledata}
defaultSort={[{ desc: true, id: "decorrenza" }]}
pinnedFiltri={pinnedFiltri}
searchColumn={searchFiltro}
/>
</div>
);
};
const NotePopover = ({
userId,
note,
mutate,
isPending,
}: {
userId: UsersId;
note: string | null;
mutate: (data: { id: UsersId; data: { note: string | null } }) => void;
isPending: boolean;
}) => {
const [newNote, setNewNote] = useState<string | null>(note);
return (
<Popover>
<PopoverTrigger asChild>
<div className="relative inline-flex">
<Button className="inline-flex" size="sm" variant="orange">
<NotebookPen className="size-4" />
</Button>
{note && note.trim().length > 0 && (
<span className="absolute top-0 right-0 -mt-1 -mr-1 flex size-3">
<span className="relative inline-flex size-3 rounded-full bg-green-500"></span>
</span>
)}
</div>
</PopoverTrigger>
<PopoverContent
align="start"
className="max-h-[50vh] w-[80vw] overflow-y-auto sm:max-h-full sm:w-lg"
>
<PopoverHeader>
<PopoverTitle>Annunci Inseriti</PopoverTitle>
<PopoverDescription className="sr-only">Desc</PopoverDescription>
</PopoverHeader>
<div className="flex w-full flex-col gap-4">
<Textarea
className="min-h-64"
id="note"
onChange={(e) => setNewNote(e.target.value)}
placeholder=""
value={newNote || ""}
/>
<LoadingButton
disabled={newNote === note}
loading={isPending}
onClick={() => mutate({ id: userId, data: { note: newNote } })}
variant="success"
>
Salva
</LoadingButton>
</div>
</PopoverContent>
</Popover>
);
};