refactor: streamline ServizioContent layout and enhance ServizioInfos with pack details retrieval

This commit is contained in:
Marco Pedone 2025-12-17 14:14:30 +01:00
parent cd2c96ad58
commit b376de48bb
7 changed files with 173 additions and 61 deletions

View file

@ -203,16 +203,9 @@ export const ServizioContent = () => {
); );
} }
const annunciSelezionati = servizio.annunci.filter(
(a) => a.user_confirmed_at === null,
);
const annunciInConferma = servizio.annunci.filter(
(a) => a.user_confirmed_at !== null,
);
return ( return (
<ServizioCard <ServizioCard
cardClassName="gap-y-2"
content={ content={
<> <>
{annuncioConfermato.length > 0 && ( {annuncioConfermato.length > 0 && (
@ -252,8 +245,8 @@ export const ServizioContent = () => {
</div> </div>
)} )}
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-col gap-2 overflow-y-auto"> <div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
{annunciInConferma.map((data) => { {servizio.annunci.map((data) => {
return ( return (
<ServizioAnnuncioProvider <ServizioAnnuncioProvider
data={data} data={data}
@ -261,35 +254,12 @@ export const ServizioContent = () => {
servizioId={servizio.servizio_id} servizioId={servizio.servizio_id}
userId={userId} userId={userId}
> >
<AnnuncioCard className="border-2 border-green-500" /> <AnnuncioCard className="rounded-md border-primary" />
</ServizioAnnuncioProvider> </ServizioAnnuncioProvider>
); );
})} })}
</div> </div>
{annunciSelezionati.length > 0 && (
<div className="flex flex-col gap-3">
<h2 className="px-1 font-semibold text-xl">
Annunci selezionati:
</h2>
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
{annunciSelezionati.map((data) => {
return (
<ServizioAnnuncioProvider
data={data}
key={data.id}
servizioId={servizio.servizio_id}
userId={userId}
>
<AnnuncioCard className="rounded-md border-primary" />
</ServizioAnnuncioProvider>
);
})}
</div>
</div>
)}
{/* {annunciSelezionati.length > 0 && (
<AnnunciSelezionatiAccordion annunci={annunciSelezionati} />
)} */}
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
<AnnunciCompatibili /> <AnnunciCompatibili />
</div> </div>

View file

@ -55,7 +55,7 @@ import {
FormEditServizio, FormEditServizio,
} from "~/forms/FormEditServizioAdmin"; } from "~/forms/FormEditServizioAdmin";
import { FormNewServizio, type FormValues } from "~/forms/FormNewServizio"; import { FormNewServizio, type FormValues } from "~/forms/FormNewServizio";
import { cn } from "~/lib/utils"; import { cn, formatCurrency } from "~/lib/utils";
import { useServizio } from "~/providers/ServizioProvider"; import { useServizio } from "~/providers/ServizioProvider";
import type { AnnunciId } from "~/schemas/public/Annunci"; import type { AnnunciId } from "~/schemas/public/Annunci";
import type { ServizioServizioId } from "~/schemas/public/Servizio"; import type { ServizioServizioId } from "~/schemas/public/Servizio";
@ -94,10 +94,13 @@ const ServizioInfos = () => {
add(servizio.decorrenza, { days: 60 }), add(servizio.decorrenza, { days: 60 }),
).toLocaleDateString("it-IT"); ).toLocaleDateString("it-IT");
return ( return (
<Collapsible className="space-y-3" onOpenChange={setOpen} open={open}> <Collapsible className="space-y-2" onOpenChange={setOpen} open={open}>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
{open ? ( {open ? (
<Button className="w-full justify-between" variant="outline"> <Button
className="w-full justify-between data-[state=open]:bg-accent dark:data-[state=open]:bg-input/50"
variant="outline"
>
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<Info /> <Info />
<span>Informazioni servizio</span> <span>Informazioni servizio</span>
@ -126,7 +129,7 @@ const ServizioInfos = () => {
</Button> </Button>
)} )}
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="flex w-full flex-col gap-8 rounded-md border bg-card p-2 sm:items-start sm:gap-4"> <CollapsibleContent className="mb-4 flex w-full flex-col gap-8 rounded-md border bg-card p-2 sm:items-start sm:gap-4">
<ServizioBasicActions /> <ServizioBasicActions />
<div className="flex w-full flex-col gap-8 sm:flex-row sm:items-start"> <div className="flex w-full flex-col gap-8 sm:flex-row sm:items-start">
<div className="flex flex-2 flex-col gap-2 sm:w-1/2"> <div className="flex flex-2 flex-col gap-2 sm:w-1/2">
@ -149,6 +152,7 @@ const ServizioInfos = () => {
</span> </span>
</div> </div>
</div> </div>
<ServizioPacksInfos />
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </Collapsible>
); );
@ -172,6 +176,49 @@ const ServizioBasicActions = ({ className }: { className?: string }) => {
} }
return null; return null;
}; };
const ServizioPacksInfos = () => {
const { servizio } = useServizio();
const { data: packs } = api.servizio.getPacksSolution.useQuery({
servizioId: servizio.servizio_id,
});
if (!packs) return null;
return (
<div className="space-y-2">
<h3 className="font-semibold text-lg">Prezzi:</h3>
<div className="flex w-full flex-col gap-4 md:flex-row">
{packs.acconto && (
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
<span className="font-medium">{packs.acconto.nome_it}</span>
<span className="text-muted-foreground text-sm">
{packs.acconto.desc_it}
</span>
<span className="font-semibold text-lg">
{formatCurrency(packs.acconto.prezzo_cent / 100)}
</span>
<span className="text-muted-foreground text-sm">
COD: {packs.acconto.idprezziario}
</span>
</div>
)}
{packs.saldo && (
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
<span className="font-medium">{packs.saldo.nome_it}</span>
<span className="text-muted-foreground text-sm">
{packs.saldo.desc_it}
</span>
<span className="font-semibold text-lg">
{formatCurrency(packs.saldo.prezzo_cent / 100)}
</span>
<span className="text-muted-foreground text-sm">
COD: {packs.saldo.idprezziario}
</span>
</div>
)}
</div>
</div>
);
};
export const AddAnnuncio = ({ export const AddAnnuncio = ({
servizioId, servizioId,
userId, userId,

View file

@ -14,10 +14,6 @@ export const handleConsegna = ({
aggiornamento: string; aggiornamento: string;
}) => { }) => {
const currentMonth = new Date().getMonth() + 1; const currentMonth = new Date().getMonth() + 1;
console.log("handleConsegna", {
consegna: consegna,
currentMonth: currentMonth,
});
if (consegna !== null) { if (consegna !== null) {
if (consegna !== currentMonth) { if (consegna !== currentMonth) {
return `${mesi[consegna]}`; return `${mesi[consegna]}`;

View file

@ -85,7 +85,7 @@ export const prezziarioRouter = createTRPCRouter({
getPrezzoPerServizio: publicProcedure getPrezzoPerServizio: publicProcedure
.input(z.object({ idprezziario: zPrezziarioId })) .input(z.object({ idprezziario: zPrezziarioId }))
.query(async ({ input }) => { .query(async ({ input }) => {
return await getPrezziarioByIdHandler({ db, input }); return await getPrezziarioByIdHandler({ id: input.idprezziario });
}), }),
updatePrezziario: adminProcedure updatePrezziario: adminProcedure
.input( .input(

View file

@ -29,6 +29,7 @@ import {
getCompatibileAnnunci, getCompatibileAnnunci,
getDataPerAcquisto, getDataPerAcquisto,
getOrdineRicevutaData, getOrdineRicevutaData,
getPackPerServizio,
getPreOnboardData, getPreOnboardData,
getServiziByUserId, getServiziByUserId,
getServizioAnnuncio, getServizioAnnuncio,
@ -50,6 +51,7 @@ import {
userAccettaConferma, userAccettaConferma,
userSendConfermaIntent, userSendConfermaIntent,
} from "~/server/controllers/servizio.controller"; } from "~/server/controllers/servizio.controller";
import { getPrezziarioByIdHandler } from "~/server/services/prezziario.service";
import { import {
zAnnuncioId, zAnnuncioId,
zOrdineId, zOrdineId,
@ -340,7 +342,33 @@ export const servizioRouter = createTRPCRouter({
servizioId: input.servizioId, servizioId: input.servizioId,
}); });
}), }),
getPacksSolution: protectedProcedure
.input(
z.object({
servizioId: zServizioId,
}),
)
.query(async ({ input }) => {
const accontoId = await getPackPerServizio({
stage: "acconto",
servizioId: input.servizioId,
});
const saldoId = await getPackPerServizio({
stage: "saldo",
servizioId: input.servizioId,
});
const acconto = accontoId.success
? await getPrezziarioByIdHandler({ id: accontoId.packId })
: null;
const saldo = saldoId.success
? await getPrezziarioByIdHandler({ id: saldoId.packId })
: null;
return {
acconto,
saldo,
};
}),
unlockServizioContatti: protectedProcedure unlockServizioContatti: protectedProcedure
.input( .input(
z.object({ z.object({

View file

@ -506,6 +506,74 @@ const ConsulenzaPriceMapping = {
Transitorio: "CONS_TRAN" as PrezziarioIdprezziario, Transitorio: "CONS_TRAN" as PrezziarioIdprezziario,
}; };
type GetPackError = { success: false; message: string };
type GetPackResult = { success: true; packId: PrezziarioIdprezziario };
export const getPackPerServizio = async ({
stage,
servizioId,
}: {
stage: "acconto" | "saldo";
servizioId: ServizioServizioId;
}): Promise<GetPackResult | GetPackError> => {
const servizio = await getServizioById(servizioId);
if (!servizio) {
return { success: false, message: "Servizio not found" };
}
switch (stage) {
case "acconto": {
switch (servizio.tipologia) {
case TipologiaPosizioneEnum.Transitorio: {
return {
success: true,
packId: "ACCONTO_BD" as PrezziarioIdprezziario,
};
}
case TipologiaPosizioneEnum.Stabile: {
return {
success: true,
packId: "ACCONTO_CA" as PrezziarioIdprezziario,
};
}
default:
return { success: false, message: "Invalid tipologia" };
}
}
case "saldo": {
switch (servizio.tipologia) {
case TipologiaPosizioneEnum.Transitorio: {
const mpp =
servizio.permanenza && SaldoTransitorioMapping[servizio.permanenza];
if (!servizio.permanenza || !mpp) {
return {
success: false,
message: "Permanenza not set for transitorio servizio",
};
}
return { success: true, packId: mpp };
}
case TipologiaPosizioneEnum.Stabile: {
if (servizio.budget >= 500) {
return {
success: true,
packId: "SALDOCA500+" as PrezziarioIdprezziario,
};
}
return {
success: true,
packId: "SALDOCA500" as PrezziarioIdprezziario,
};
}
default:
return { success: false, message: "Invalid tipologia" };
}
}
default:
return { success: false, message: "Invalid stage" };
}
};
type TipoEnum = keyof typeof ConsulenzaPriceMapping; type TipoEnum = keyof typeof ConsulenzaPriceMapping;
export const setupSaldoConsulenzaServizio = async ({ export const setupSaldoConsulenzaServizio = async ({
servizioId, servizioId,
@ -711,12 +779,12 @@ export const getAllServizioAnnunci = async (userId: UsersId) => {
//ignora annunci che non sono disponibili //ignora annunci che non sono disponibili
.where("annunci.web", "=", true) .where("annunci.web", "=", true)
.where("annunci.stato", "!=", "Sospeso") .where("annunci.stato", "!=", "Sospeso")
// .orderBy("servizio_annunci.user_confirmed_at", (ob) => .orderBy("servizio_annunci.user_confirmed_at", (ob) =>
// ob.asc().nullsLast(), ob.asc().nullsLast(),
// ) )
// .orderBy("servizio_annunci.open_contatti_at", (ob) => .orderBy("servizio_annunci.open_contatti_at", (ob) =>
// ob.asc().nullsFirst(), ob.asc().nullsFirst(),
// ) )
.orderBy("servizio_annunci.created_at", "asc") .orderBy("servizio_annunci.created_at", "asc")
.whereRef( .whereRef(
"servizio_annunci.servizio_id", "servizio_annunci.servizio_id",

View file

@ -26,19 +26,22 @@ export const getPrezziario = async () => {
}; };
export const getPrezziarioByIdHandler = async ({ export const getPrezziarioByIdHandler = async ({
db, id,
input,
}: { }: {
db: Querier; id: PrezziarioIdprezziario;
input: {
idprezziario: PrezziarioIdprezziario;
};
}) => { }) => {
return await db try {
.selectFrom("prezziario") return await db
.selectAll() .selectFrom("prezziario")
.where("prezziario.idprezziario", "=", input.idprezziario) .selectAll()
.executeTakeFirst(); .where("prezziario.idprezziario", "=", id)
.executeTakeFirst();
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error getting prezziario by id: ${(error as Error).message}`,
});
}
}; };
export const addPrezziario = async ({ export const addPrezziario = async ({