feat: enhance RichiesteTable with context menu for user actions and improve status resolution logic
This commit is contained in:
parent
508f00c02f
commit
bb8c796fa8
3 changed files with 245 additions and 94 deletions
|
|
@ -328,8 +328,8 @@ const Content = () => {
|
||||||
</span>
|
</span>
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button className="" size="sm" variant="outline">
|
<Button className="px-1!" size="sm" variant="outline">
|
||||||
<CircleHelp /> Info
|
<CircleHelp />
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
|
|
@ -552,10 +552,10 @@ const Main = () => {
|
||||||
}
|
}
|
||||||
//servizio scaduto se decorrenza + 60 giorni è passato e non è ok saldo
|
//servizio scaduto se decorrenza + 60 giorni è passato e non è ok saldo
|
||||||
if (
|
if (
|
||||||
new Date() >
|
isBefore(
|
||||||
add(servizio.decorrenza, {
|
add(servizio.decorrenza, { days: DEFAULT_SERVIZIO_DURATION_DAYS }),
|
||||||
days: DEFAULT_SERVIZIO_DURATION_DAYS,
|
new Date(),
|
||||||
}) &&
|
) &&
|
||||||
!servizio.isOkSaldo
|
!servizio.isOkSaldo
|
||||||
) {
|
) {
|
||||||
//todo capire come fare con saldo quando scade
|
//todo capire come fare con saldo quando scade
|
||||||
|
|
@ -586,6 +586,30 @@ const Main = () => {
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{isAdmin && (
|
||||||
|
<div className="w-full space-y-2">
|
||||||
|
<h4 className="text-lg">Altri annunci:</h4>
|
||||||
|
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
|
||||||
|
{servizio.annunci
|
||||||
|
.filter((a) => !a.accettato_conferma_at)
|
||||||
|
.map((data) => {
|
||||||
|
return (
|
||||||
|
<ServizioAnnuncioProvider
|
||||||
|
data={data}
|
||||||
|
key={data.id}
|
||||||
|
servizioId={servizio.servizio_id}
|
||||||
|
>
|
||||||
|
<AnnuncioCard />
|
||||||
|
</ServizioAnnuncioProvider>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{servizio.annunci.length > 1 &&
|
||||||
|
servizio.annunci.length % 2 === 1 && (
|
||||||
|
<div className="@sm:block hidden size-full rounded-md border border-(--pattern-fg) bg-[repeating-linear-gradient(45deg,var(--pattern-fg)_0,var(--pattern-fg)_1px,transparent_0,transparent_50%)] bg-size-[10px_10px] bg-fixed opacity-10 [--pattern-fg:#696969] dark:[--pattern-fg:#e1e1e1]" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,36 @@
|
||||||
import type { ColumnDef, Table } from "@tanstack/react-table";
|
import type { ColumnDef, Table } from "@tanstack/react-table";
|
||||||
import { add, format, formatDuration, intervalToDuration } from "date-fns";
|
import {
|
||||||
|
add,
|
||||||
|
format,
|
||||||
|
formatDuration,
|
||||||
|
intervalToDuration,
|
||||||
|
isBefore,
|
||||||
|
} from "date-fns";
|
||||||
import { it } from "date-fns/locale";
|
import { it } from "date-fns/locale";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CircleCheck,
|
CircleCheck,
|
||||||
CircleCheckBig,
|
CircleCheckBig,
|
||||||
CircleEllipsis,
|
|
||||||
ClockFading,
|
ClockFading,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
SearchX,
|
SearchX,
|
||||||
|
UserCog,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { NotePopover } from "~/components/notePopover";
|
import { NotePopover } from "~/components/notePopover";
|
||||||
import { AnnuncioCardRichieste } from "~/components/servizio/annuncio_card";
|
import { AnnuncioCardRichieste } from "~/components/servizio/annuncio_card";
|
||||||
|
import { WhatsAppIcon2 } from "~/components/svgs";
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuLabel,
|
||||||
|
ContextMenuTrigger,
|
||||||
|
} from "~/components/ui/context-menu";
|
||||||
import { DataTable } from "~/components/ui/data-table";
|
import { DataTable } from "~/components/ui/data-table";
|
||||||
import { DataTableColumnHeader } from "~/components/ui/dataTable-header";
|
import { DataTableColumnHeader } from "~/components/ui/dataTable-header";
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -39,9 +53,11 @@ import {
|
||||||
import { UserAvatar } from "~/components/user_avatar";
|
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 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";
|
||||||
|
import { DEFAULT_SERVIZIO_DURATION_DAYS } from "~/utils/config";
|
||||||
|
|
||||||
type RichiesteTableProps = {
|
type RichiesteTableProps = {
|
||||||
data: RichiesteData[];
|
data: RichiesteData[];
|
||||||
|
|
@ -101,20 +117,55 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
{
|
{
|
||||||
accessorKey: "username",
|
accessorKey: "username",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const data = row.original;
|
||||||
return (
|
return (
|
||||||
|
<ContextMenu>
|
||||||
|
<ContextMenuTrigger asChild>
|
||||||
<Link
|
<Link
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
href={`/area-riservata/admin/user-view/ricerca/${row.original.user_id}`}
|
href={`/area-riservata/admin/user-view/ricerca/${data.user_id}`}
|
||||||
>
|
>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
className="size-8"
|
className="size-8"
|
||||||
userId={row.original.user_id}
|
userId={data.user_id}
|
||||||
username={row.original.username}
|
username={data.username}
|
||||||
/>
|
/>
|
||||||
<span className="font-semibold text-sm">
|
<span className="font-semibold text-sm">{data.username}</span>
|
||||||
{row.original.username}
|
|
||||||
</span>
|
|
||||||
</Link>
|
</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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</ContextMenuContent>
|
||||||
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
|
|
@ -215,20 +266,37 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
let badgeClass = "";
|
let badgeClass = "";
|
||||||
let contents: React.ReactNode;
|
let contents: React.ReactNode;
|
||||||
const annunci = row.original.annunci_servizio;
|
|
||||||
const awaitingConferma = annunci.some(
|
const resolveStatus = () => {
|
||||||
(a) => a.user_confirmed_at !== null && !a.hasConfermaAdmin,
|
|
||||||
);
|
|
||||||
const hasConfermaAdmin = annunci.some((a) => a.hasConfermaAdmin);
|
|
||||||
if (row.original.isInterrotto) {
|
if (row.original.isInterrotto) {
|
||||||
badgeClass = "border-destructive bg-destructive/10 text-destructive";
|
badgeClass =
|
||||||
|
"border-destructive bg-destructive/10 text-destructive";
|
||||||
contents = (
|
contents = (
|
||||||
<>
|
<>
|
||||||
<SearchX className="stroke-red-500 dark:stroke-red-400" />
|
<SearchX className="stroke-red-500 dark:stroke-red-400" />
|
||||||
Interrotto
|
Interrotto
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else if (!row.original.onboardOk) {
|
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(
|
||||||
|
(o) => o.type === OrderTypeEnum.Acconto,
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeAcconto = acconti.find((a) => a.isActive);
|
||||||
|
|
||||||
|
if (activeAcconto) {
|
||||||
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
||||||
contents = (
|
contents = (
|
||||||
<>
|
<>
|
||||||
|
|
@ -236,23 +304,116 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
Attesa attivazione
|
Attesa attivazione
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else if (awaitingConferma) {
|
} else {
|
||||||
badgeClass = "border-sky-500 bg-sky-500/10 text-sky-500";
|
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
||||||
contents = (
|
contents = (
|
||||||
<>
|
<>
|
||||||
<CircleEllipsis className="stroke-sky-500 dark:stroke-sky-400" />
|
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
|
||||||
In Conferma
|
Attesa pagamento acconto
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else if (row.original.isOkSaldo && hasConfermaAdmin) {
|
}
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
if (contrattoInserito.length > 0) {
|
||||||
|
if (row.original.isOkSaldo) {
|
||||||
|
if (row.original.isOkConsulenza) {
|
||||||
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
||||||
contents = (
|
contents = (
|
||||||
<>
|
<>
|
||||||
<CircleCheckBig className="stroke-blue-500 dark:stroke-blue-400" />
|
<CircleCheckBig className="stroke-blue-500 dark:stroke-blue-400" />
|
||||||
Saldo pagato
|
Confermato
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else if (row.original.isOkAcconto) {
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
row.original.ordini_servizio.some(
|
||||||
|
(o) => o.type === OrderTypeEnum.Consulenza && !o.isActive,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const annuncioConfermato = row.original.annunci_servizio.filter(
|
||||||
|
(a) => a.accettato_conferma_at !== null,
|
||||||
|
);
|
||||||
|
if (annuncioConfermato.length > 0) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const annuncioAttesaConferma = row.original.annunci_servizio.filter(
|
||||||
|
(a) => a.accettato_conferma_at == null && a.doc_conferma_added,
|
||||||
|
);
|
||||||
|
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" />
|
||||||
|
Attesa accettazione conferma
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
badgeClass = "border-green-500 bg-green-500/10 text-green-500";
|
badgeClass = "border-green-500 bg-green-500/10 text-green-500";
|
||||||
contents = (
|
contents = (
|
||||||
<>
|
<>
|
||||||
|
|
@ -260,7 +421,8 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
Attivo
|
Attivo
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
resolveStatus();
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -334,41 +496,6 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const annunci = row.original.annunci_servizio;
|
const annunci = row.original.annunci_servizio;
|
||||||
return (
|
return (
|
||||||
// <Popover>
|
|
||||||
// <PopoverTrigger asChild>
|
|
||||||
// <Button
|
|
||||||
// className="data-[state=open]:border-primary data-[state=open]:bg-primary/10"
|
|
||||||
// size="sm"
|
|
||||||
// variant="outline"
|
|
||||||
// >
|
|
||||||
// {annunci.length} Annunci
|
|
||||||
// </Button>
|
|
||||||
// </PopoverTrigger>
|
|
||||||
// <PopoverContent
|
|
||||||
|
|
||||||
// align="end"
|
|
||||||
// className="max-h-[50vh] w-[80vw] overflow-y-auto sm:max-h-220"
|
|
||||||
// collisionPadding={{
|
|
||||||
// top: 20,
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// <PopoverHeader>
|
|
||||||
// <PopoverTitle>Annunci Inseriti</PopoverTitle>
|
|
||||||
// <PopoverDescription className="sr-only">
|
|
||||||
// Desc
|
|
||||||
// </PopoverDescription>
|
|
||||||
// </PopoverHeader>
|
|
||||||
// <div className="flex flex-col gap-4 divide-y">
|
|
||||||
// {annunci.map((a) => (
|
|
||||||
// <AnnuncioCardRichieste
|
|
||||||
// data={a}
|
|
||||||
// key={a.annunci_id}
|
|
||||||
// tableRef={tableRef}
|
|
||||||
// />
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// </PopoverContent>
|
|
||||||
// </Popover>
|
|
||||||
<Sheet>
|
<Sheet>
|
||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -1475,7 +1475,7 @@ export const getOrdineRicevutaData = async ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export type RichiesteData = Servizio &
|
export type RichiesteData = Servizio &
|
||||||
Pick<Users, "username" | "note"> & {
|
Pick<Users, "username" | "note" | "telefono"> & {
|
||||||
annunci_servizio: ServizioAnnuncioData[];
|
annunci_servizio: ServizioAnnuncioData[];
|
||||||
ordini_servizio: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
ordini_servizio: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
||||||
};
|
};
|
||||||
|
|
@ -1487,7 +1487,7 @@ export const getRichieste = async (): Promise<RichiesteData[]> => {
|
||||||
.selectFrom("servizio")
|
.selectFrom("servizio")
|
||||||
.innerJoin("users", "users.id", "servizio.user_id")
|
.innerJoin("users", "users.id", "servizio.user_id")
|
||||||
.selectAll("servizio")
|
.selectAll("servizio")
|
||||||
.select(["users.username", "users.note"])
|
.select(["users.username", "users.note", "users.telefono"])
|
||||||
.select((eb) => [
|
.select((eb) => [
|
||||||
jsonArrayFrom(
|
jsonArrayFrom(
|
||||||
eb
|
eb
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue