refactor: remove unused components and enhance navigation flow in user views

This commit is contained in:
Marco Pedone 2025-12-18 15:12:05 +01:00
parent 51affdc7a8
commit 72a2935797
9 changed files with 51 additions and 414 deletions

View file

@ -6,7 +6,6 @@ import {
Paperclip, Paperclip,
Search, Search,
ShoppingBag, ShoppingBag,
Tickets,
UserCog, UserCog,
UserPen, UserPen,
} from "lucide-react"; } from "lucide-react";
@ -81,16 +80,6 @@ export const UserViewHeader = () => {
<UserCog /> Profilo <UserCog /> Profilo
</Button> </Button>
</Link> </Link>
<Link href={`/area-riservata/admin/user-view/servizio/${data.id}`}>
<Button
className="border"
size="sm"
variant={pathname.includes("servizio") ? "secondary" : "outline"}
>
<Tickets /> Servizi
</Button>
</Link>
<Link href={`/area-riservata/admin/user-view/ricerca/${data.id}`}> <Link href={`/area-riservata/admin/user-view/ricerca/${data.id}`}>
<Button <Button
className="border" className="border"

View file

@ -1,9 +1,10 @@
import type { GetServerSideProps } from "next";
import { useState } from "react"; import { useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { AreaRiservataLayoutUserView } from "~/components/Layout"; import { FormNewServizio, type FormValues } from "~/forms/FormNewServizio";
import { TabServizio } from "~/components/tables/servizio-table"; import type { Servizio } from "~/schemas/public/Servizio";
import { Button } from "~/components/ui/button"; import type { UsersId } from "~/schemas/public/Users";
import { api } from "~/utils/api";
import { Button } from "../ui/button";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -11,27 +12,14 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "~/components/ui/dialog"; } from "../ui/dialog";
import { FormNewServizio, type FormValues } from "~/forms/FormNewServizio";
import type { NextPageWithLayout } from "~/pages/_app";
import type { Servizio } from "~/schemas/public/Servizio";
import type { UsersId } from "~/schemas/public/Users";
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
import { zUserId } from "~/server/utils/zod_types";
import { api } from "~/utils/api";
type ServizioUserProps = { export const ServiziHeader = ({ userId }: { userId: UsersId }) => {
userId: UsersId;
};
const ServizioUser: NextPageWithLayout<ServizioUserProps> = ({
userId,
}: ServizioUserProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [editData, setEditData] = useState<Servizio | undefined>(undefined); const [editData, setEditData] = useState<Servizio | undefined>(undefined);
return ( return (
<div className="mx-auto max-w-8xl grow space-y-4"> <div className="flex items-center justify-end">
<div className="flex justify-between">
<h3 className="font-bold text-2xl">Servizi Utente</h3>
<NewServizioModal <NewServizioModal
editData={editData} editData={editData}
open={open} open={open}
@ -40,53 +28,8 @@ const ServizioUser: NextPageWithLayout<ServizioUserProps> = ({
userId={userId} userId={userId}
/> />
</div> </div>
<TabServizio
setEditData={setEditData}
setOpen={setOpen}
userId={userId}
/>
</div>
); );
}; };
export default ServizioUser;
export const getServerSideProps = (async (context) => {
const userId = context.params?.userId;
if (typeof userId !== "string") {
return {
notFound: true,
};
}
const parsed = zUserId.safeParse(userId);
if (!parsed.success) {
return {
notFound: true,
};
}
const access_token = context.req.cookies.access_token;
const helpers = await TrpcAuthedFetchingIstance({ access_token });
if (helpers) {
await helpers.trpc.servizio.getUserServizi.prefetch({
userId: parsed.data,
});
return {
props: {
userId: parsed.data,
trpcState: helpers.trpc.dehydrate(),
},
};
}
return {
notFound: true,
};
}) satisfies GetServerSideProps<ServizioUserProps>;
ServizioUser.getLayout = function getLayout(page) {
return <AreaRiservataLayoutUserView>{page}</AreaRiservataLayoutUserView>;
};
const NewServizioModal = ({ const NewServizioModal = ({
userId, userId,
@ -111,6 +54,7 @@ const NewServizioModal = ({
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio creato con successo"); toast.success("Servizio creato con successo");
await utils.servizio.getUserServizi.invalidate(); await utils.servizio.getUserServizi.invalidate();
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
setEditData(undefined); setEditData(undefined);
setOpen(false); setOpen(false);
}, },

View file

@ -9,6 +9,7 @@ import {
Info, Info,
Plus, Plus,
SlidersHorizontal, SlidersHorizontal,
Trash2,
UserCircle, UserCircle,
} from "lucide-react"; } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
@ -61,6 +62,7 @@ import type { AnnunciId } from "~/schemas/public/Annunci";
import type { ServizioServizioId } from "~/schemas/public/Servizio"; import type { ServizioServizioId } from "~/schemas/public/Servizio";
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { Confirm } from "../confirm";
import { Progress } from "../ui/progress"; import { Progress } from "../ui/progress";
export const ServizioActions = () => { export const ServizioActions = () => {
@ -229,7 +231,8 @@ const ServizioPacksInfos = () => {
</div> </div>
); );
}; };
export const AddAnnuncio = ({
const AddAnnuncio = ({
servizioId, servizioId,
userId, userId,
}: { }: {
@ -501,6 +504,16 @@ const EditServizioAdmin = () => {
setOpen(false); setOpen(false);
}, },
}); });
const { mutate: remove } = api.servizio.deleteServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Servizio rimosso con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getServizio.invalidate({ servizioId });
},
});
function onSubmit(fields: EditFormValues) { function onSubmit(fields: EditFormValues) {
update({ update({
@ -546,6 +559,18 @@ const EditServizioAdmin = () => {
})()} })()}
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </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>
</CredenzaBody> </CredenzaBody>
</CredenzaContent> </CredenzaContent>
</Credenza> </Credenza>

View file

@ -88,6 +88,9 @@ const Row = ({
<div className="flex items-center gap-2 font-semibold text-primary sm:text-lg"> <div className="flex items-center gap-2 font-semibold text-primary sm:text-lg">
<Building className="size-6 fill-background" /> <Building className="size-6 fill-background" />
Affitto {servizio.tipologia} Affitto {servizio.tipologia}
<span className="text-muted-foreground text-sm">
{`- del ${servizio.created_at.toLocaleDateString("it")}`}
</span>
</div> </div>
<div className="float-end flex items-center justify-start pl-2 text-primary"> <div className="float-end flex items-center justify-start pl-2 text-primary">
{isOpen ? <ChevronUp /> : <ChevronDown />} {isOpen ? <ChevronUp /> : <ChevronDown />}

View file

@ -1,311 +0,0 @@
import {
Copy,
EllipsisVertical,
ExternalLink,
Trash2,
Wrench,
} from "lucide-react";
import Link from "next/link";
import toast from "react-hot-toast";
import { LoadingPage } from "~/components/loading";
import { AddAnnuncio } from "~/components/servizio/servizio_actions";
import { Button } from "~/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "~/components/ui/tooltip";
import { env } from "~/env";
import type { AnnunciId } from "~/schemas/public/Annunci";
import type { Servizio, ServizioServizioId } from "~/schemas/public/Servizio";
import type { UsersId } from "~/schemas/public/Users";
import { api } from "~/utils/api";
export const TabServizio = ({
userId,
setEditData,
setOpen,
}: {
userId: UsersId;
setEditData: (data: Servizio | undefined) => void;
setOpen: (open: boolean) => void;
}) => {
const { data } = api.servizio.getUserServizi.useQuery({ userId });
const utils = api.useUtils();
const { mutate: remove } = api.servizio.deleteServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Servizio rimosso con successo");
await utils.servizio.getUserServizi.invalidate();
},
});
const { mutate: update } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async (data) => {
toast.success("Servizio modificato con successo");
await utils.servizio.getUserServizi.invalidate();
await utils.servizio.getPacksSolution.invalidate({
servizioId: data.servizio_id,
});
},
});
if (!data) return <LoadingPage />;
return (
<Table>
<TableCaption>
{data.length > 0
? "Lista dai servizi dell'utente"
: "Nessun servizio attualmente inserito"}
</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Id</TableHead>
<TableHead>Tipologia</TableHead>
<TableHead>Creato Il</TableHead>
<TableHead>Inviato</TableHead>
<TableHead>Status</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((servizio) => {
const onboardDone =
servizio.isInterrotto ||
servizio.isOkAcconto ||
servizio.decorrenza !== null;
return (
<TableRow key={servizio.servizio_id}>
<TableCell className="max-w-20 truncate font-medium">
{servizio.servizio_id}
</TableCell>
<TableCell>{servizio.tipologia}</TableCell>
<TableCell>
{new Date(servizio.created_at).toLocaleDateString("it-IT", {
day: "2-digit",
month: "2-digit",
year: "numeric",
})}
</TableCell>
<TableCell>
{servizio.isSent ? (
<span className="text-green-500">Inviato</span>
) : (
<span className="text-neutral-500">Non inviato</span>
)}
</TableCell>
<TableCell>
{(() => {
if (servizio.isInterrotto)
return <span className="text-red-500">Interrotto</span>;
if (servizio.isOkAcconto && servizio.decorrenza !== null)
return <span className="text-green-500">Attivo</span>;
return <span className="text-neutral-500">Inattivo</span>;
})()}
</TableCell>
<TableCell>
<AnnunciSection
selectedAnnunci={servizio.annunci}
servizioId={servizio.servizio_id}
userId={userId}
/>
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-label="Servizio Actions" variant="outline">
<EllipsisVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem asChild>
<Link
aria-label="Apri Onboarding Servizio"
className="!p-0"
href={`/area-riservata/admin/user-view/onboard/${servizio.servizio_id}`}
>
<Button
className="flex w-full justify-start gap-3 px-2 font-normal"
disabled={onboardDone}
size="sm"
variant="ghost"
>
<ExternalLink className="size-5 stroke-foreground" />
Apri Onboarding
</Button>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Button
aria-label="Invia Email Servizio"
className="flex w-full justify-start gap-3 px-2 font-normal"
disabled={onboardDone}
onClick={async () => {
await navigator.clipboard.writeText(
`${env.NEXT_PUBLIC_BASE_URL}/servizio/pre-onboard/${servizio.servizio_id}`,
);
toast.success("Copiato negli appunti", {
icon: "📋",
});
}}
size="sm"
variant="ghost"
>
<Copy className="size-5 stroke-foreground" />
Copia Link
</Button>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger className="flex items-center gap-3 hover:cursor-pointer">
<Wrench className="size-5 stroke-foreground" />
<span className="text-foreground">Status Servizio</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
onValueChange={(v) => {
console.log(v);
update({
data: {
decorrenza: v === "true" ? new Date() : null,
isOkAcconto: v === "true",
},
servizioId: servizio.servizio_id,
});
}}
value={String(servizio.isOkAcconto)}
>
<DropdownMenuRadioItem value="true">
Attivo
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="false">
Disattiva
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger className="flex items-center gap-3 hover:cursor-pointer">
<Wrench className="size-5 stroke-foreground" />
<span className="text-foreground">Interruzione</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
onValueChange={(v) => {
update({
data: {
isInterrotto: v === "true",
},
servizioId: servizio.servizio_id,
});
}}
value={servizio.isInterrotto.toString()}
>
<DropdownMenuRadioItem value="true">
Interrotto
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="false">
Non Interrotto
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem
aria-label="Modifica Parametri Servizio"
className="flex items-center gap-3 hover:cursor-pointer"
onClick={() => {
setEditData(servizio);
setOpen(true);
}}
>
<Wrench className="size-5 stroke-foreground" />
Modifica parametri
</DropdownMenuItem>
<DropdownMenuItem
aria-label="Elimina Servizio"
className="flex items-center gap-3 hover:cursor-pointer"
onClick={() =>
confirm("Sei sicuro di voler eliminare il servizio?") &&
remove({ servizioId: servizio.servizio_id })
}
>
<Trash2 className="size-5 stroke-foreground" />
Elimina Servizio
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
};
const AnnunciSection = ({
selectedAnnunci,
servizioId,
userId,
}: {
selectedAnnunci: { codice: string; annunci_id: AnnunciId }[];
servizioId: ServizioServizioId;
userId: UsersId;
}) => {
return (
<div className="flex items-center gap-3">
<Tooltip>
<TooltipTrigger className="w-5">
{selectedAnnunci?.length}
</TooltipTrigger>
<TooltipContent>
{selectedAnnunci?.length > 0 ? (
<ul>
{selectedAnnunci?.map((annuncio) => (
<li key={annuncio.annunci_id}>{annuncio.codice}</li>
))}
</ul>
) : (
<span>Nessun annuncio associato</span>
)}
</TooltipContent>
</Tooltip>
<AddAnnuncio servizioId={servizioId} userId={userId} />
</div>
);
};

View file

@ -7,7 +7,6 @@ import {
Paperclip, Paperclip,
Search, Search,
ShoppingBag, ShoppingBag,
Tickets,
User, User,
UserCog, UserCog,
} from "lucide-react"; } from "lucide-react";
@ -186,15 +185,7 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuLabel>Azioni</DropdownMenuLabel> <DropdownMenuLabel>Azioni</DropdownMenuLabel>
<DropdownMenuItem>
<Link
aria-label="Visualizza Servizio"
className="flex w-full items-center gap-2"
href={`/area-riservata/admin/user-view/servizio/${user.id}`}
>
<Tickets /> Servizio
</Link>
</DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<Link <Link
aria-label="Visualizza Ricerca" aria-label="Visualizza Ricerca"

View file

@ -90,7 +90,7 @@ export const getServerSideProps = (async (context) => {
) { ) {
return { return {
redirect: { redirect: {
destination: `/area-riservata/admin/user-view/servizio/${servizio.user_id}`, destination: `/area-riservata/dashboard`,
permanent: false, permanent: false,
}, },
}; };

View file

@ -1,5 +1,6 @@
import type { GetServerSideProps } from "next"; import type { GetServerSideProps } from "next";
import { AreaRiservataLayoutUserView } from "~/components/Layout"; import { AreaRiservataLayoutUserView } from "~/components/Layout";
import { ServiziHeader } from "~/components/servizio/servizi_header";
import { AnnunciRichiesti } from "~/components/servizio/servizio_annunci_accordions"; import { AnnunciRichiesti } from "~/components/servizio/servizio_annunci_accordions";
import { TabRicerca } from "~/components/tables/ricerca-table"; import { TabRicerca } from "~/components/tables/ricerca-table";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
@ -15,17 +16,11 @@ const RicercaUser: NextPageWithLayout<RicercaUserProps> = ({
userId, userId,
}: RicercaUserProps) => { }: RicercaUserProps) => {
return ( return (
<div className="space-y-5"> <div className="flex flex-col gap-5">
<div className="items-center justify-between md:flex"> <ServiziHeader userId={userId} />
<div className="flex items-center gap-3">
<h3 className="font-bold text-2xl">Le tue ricerche</h3>
</div>
</div>
<div className="flex flex-col space-y-5">
<AnnunciRichiesti userId={userId} /> <AnnunciRichiesti userId={userId} />
<TabRicerca isAdmin userId={userId} /> <TabRicerca isAdmin userId={userId} />
</div> </div>
</div>
); );
}; };

View file

@ -104,8 +104,9 @@ const ConfirmSection = ({
await router.push( await router.push(
`/area-riservata/admin/user-view/ricerca/${data.userId}`, `/area-riservata/admin/user-view/ricerca/${data.userId}`,
); );
} } else {
await router.push("/area-riservata/dashboard"); await router.push("/area-riservata/dashboard");
}
}, },
}); });
return ( return (