This commit is contained in:
Marco Pedone 2025-10-21 16:57:59 +02:00
parent 1e3a28d81a
commit 40076ce48a
4 changed files with 167 additions and 303 deletions

View file

@ -1,5 +1,6 @@
{
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
},
"editor.formatOnSave": true,

View file

@ -1,19 +1,19 @@
TODOS:
- Updated_at timestamp per appartamento se in backend ho modificato le foto, e uso quello per cache key
- Indicare di dover modificare le info nel onboarding
- Modale annunci compatibili invece di collapsible
- Ordine annunci selezionati non deve variare
- Sostituire collapre ricerca con modale con anche counter annunci come in gestionale
- Modale contatti mettere Proprietario/Referente
- Modale contatti, aggiunti tasto whatsapp
- Modale contatti, warning contatti in rosso
- Mod contatti, caricamento doc identità, con ref salvato nell' servizio-annunci row
- Mod conferma deve essere tipo "sei interessato a confermare imm.." con caricamento del file di motivazione
- Parametri del servizio transitorio specificare termine contrato/scadenza altra motivazione transitoria
- Preparazione sevizio, aggiungere flags "può saltare pagamento", "può saltare controllo documento", "può confermare senza doc motivazione"
- Se può saltare passaggi i bottoni o sezioni allegare doc sono nascosti
- Preparazione sevizio, aggiungere flags "può saltare pagamento", "può saltare controllo documento", "può confermare senza doc motivazione" Se può saltare passaggi i bottoni o sezioni allegare doc sono nascosti
- Mod conferma deve essere tipo "sei interessato a confermare imm.." con caricamento del file di motivazione
- Modale conferma mostrare scorciatoia a doc lavoro o simile per facilitare
- Post modale conferma, mostrare se transitorio , modale con informativa tipo:" il saldo del servizio e il periodo di permanenza veranno adeguati alle condizioni stabilite insieme al locatore nel momento della trattativa e alla documentazione presentata come giustificazione di transitorietà" (modificato)
- Mostrare last update in annunci e in singolo annuncio
NEW IDEA TODOS:

View file

@ -1,25 +1,16 @@
import { ExternalLink, Package, PackageCheck, UserCircle } from "lucide-react";
import { Package, PackageCheck, UserCircle } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactNode, useEffect } from "react";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { InteressatoButtonBatchV } from "~/components/annuncio-interactions/annuncio_interactions";
import { AddButton } from "~/components/servizio/annuncio_actions";
import {
AnnuncioCard,
BasicAnnuncioCard,
} from "~/components/servizio/annuncio_card";
import { AnnuncioCard } from "~/components/servizio/annuncio_card";
import { SaldoButton } from "~/components/servizio/interactions";
import { ServizioDuration } from "~/components/servizio/service-duration-display";
import {
ServizioActions,
ServizioActions2,
} from "~/components/servizio/servizio_actions";
import {
AnnunciCompatibili,
AnnunciInConferma,
AnnunciSelezionati,
} from "~/components/servizio/servizio_annunci_accordions";
import { Button } from "~/components/ui/button";
import {
Card,
@ -28,14 +19,6 @@ import {
CardHeader,
CardTitle,
} from "~/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { Progress } from "~/components/ui/progress";
import { cn } from "~/lib/utils";
import {
@ -46,6 +29,11 @@ import {
import type { UsersId } from "~/schemas/public/Users";
import type { ServizioData } from "~/server/controllers/servizio.controller";
import { api } from "~/utils/api";
import {
AnnunciCompatibili,
AnnunciInConferma,
AnnunciSelezionati,
} from "./servizio_annunci_accordions";
export const ServizioContainer = ({
servizio,
@ -190,12 +178,7 @@ export const ServizioContent = () => {
</Button>
)}
</div>
{isAdmin && (
<AnnunciSelezionati
annunci={servizio.annunci}
isConfirming={false}
/>
)}
{isAdmin && <AnnunciSelezionati annunci={servizio.annunci} />}
</div>
}
header={
@ -301,145 +284,10 @@ export const ServizioContent = () => {
</div>
)}
{/* <div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati
annunci={annunciNotInConferma}
isConfirming={annunciInConferma.length > 0}
/>
<AnnunciCompatibili />
</div> */}
<div className="flex items-center justify-between">
<DialogServizio
title="Annunci in conferma"
trigger={
<div className="relative">
<Button
disabled={annunciInConferma.length === 0}
size="xl"
variant="success"
>
Annunci in conferma
</Button>
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{annunciInConferma.length}
</span>
</div>
}
>
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
{annunciInConferma.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard className="border-2 border-green-500" />
</ServizioAnnuncioProvider>
);
})}
</div>
</DialogServizio>
<DialogServizio
title="Annunci selezionati"
trigger={
<div className="relative">
<Button size="xl" variant="orange">
Annunci selezionati
</Button>
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{annunciNotInConferma.length}
</span>
</div>
}
>
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
{annunciNotInConferma.length > 0 ? (
annunciNotInConferma.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard className="border-2 border-sky-500" />
</ServizioAnnuncioProvider>
);
})
) : (
<div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex">
<span>
Nessun annuncio selezionato, fai una ricerca nella pagina{" "}
<Link
aria-label="Annunci Compatibili"
className="inline-flex items-center gap-1 text-blue-500 underline"
href="/annunci"
>
Annunci
<ExternalLink className="size-4" />
</Link>
</span>
</div>
)}
</div>
</DialogServizio>
{(() => {
const { servizioId, userId, isAdmin } = useServizio();
const { data, isLoading } =
api.servizio.getCompatibileAnnunci.useQuery({
servizioId,
});
const { data: userIntrests } =
api.intrests.getUserInterests.useQuery({
userId,
});
if (isLoading) return <div>Loading...</div>;
return (
<DialogServizio
title="Annunci compatibili"
trigger={
<div className="relative">
<Button className="bg-purple-500" size="xl">
Annunci compatibili
</Button>
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{data?.length}
</span>
</div>
}
>
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
{!data || data.length === 0 ? (
<div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex">
<span>
Nessun annuncio compatibile trovato. Amplia la ricerca
per trovare annunci compatibili.
</span>
</div>
) : (
data.map((a) => {
return (
<BasicAnnuncioCard
className="border-2 border-neutral-500"
data={a}
interactions={
<>
{isAdmin ? (
<AddButton annuncioId={a.id} />
) : (
<InteressatoButtonBatchV
annuncioId={a.id}
hasInterest={
userIntrests?.includes(a.id) || false
}
/>
)}
</>
}
key={a.id}
/>
);
})
)}
</div>
</DialogServizio>
);
})()}
<AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati annunci={annunciNotInConferma} />
<AnnunciCompatibili />
</div>
</>
}
@ -452,31 +300,6 @@ export const ServizioContent = () => {
);
};
const DialogServizio = ({
trigger,
title,
children,
}: {
trigger: ReactNode;
title: string;
children: ReactNode;
}) => {
return (
<Dialog>
<DialogTrigger asChild>{trigger}</DialogTrigger>
<DialogContent className="md:max-w-5xl">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription className="sr-only">
Servizio Dialog
</DialogDescription>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
};
const ServizioMain = () => {
const { servizio, isAdmin, userId } = useServizio();
const utils = api.useUtils();
@ -567,12 +390,7 @@ const ServizioMain = () => {
</Button>
)}
</div>
{isAdmin && (
<AnnunciSelezionati
annunci={servizio.annunci}
isConfirming={false}
/>
)}
{isAdmin && <AnnunciSelezionati annunci={servizio.annunci} />}
</div>
}
header={
@ -704,10 +522,7 @@ const ServizioMain = () => {
<div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati
annunci={annunciNotInConferma}
isConfirming={annunciInConferma.length > 0}
/>
<AnnunciSelezionati annunci={annunciNotInConferma} />
<AnnunciCompatibili />
</div>
</>

View file

@ -1,5 +1,6 @@
import { ExternalLink, Trash2 } from "lucide-react";
import Link from "next/link";
import type { ReactNode } from "react";
import toast from "react-hot-toast";
import { InteressatoButtonBatchV } from "~/components/annuncio-interactions/annuncio_interactions";
import { AddButton } from "~/components/servizio/annuncio_actions";
@ -21,24 +22,67 @@ import {
import type { UsersId } from "~/schemas/public/Users";
import type { ServizioData } from "~/server/controllers/servizio.controller";
import { api } from "~/utils/api";
import { LoadingPage } from "../loading";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
const DialogServizio = ({
trigger,
title,
children,
}: {
trigger: ReactNode;
title: string;
children: ReactNode;
}) => {
return (
<Dialog>
<DialogTrigger asChild>{trigger}</DialogTrigger>
<DialogContent className="md:max-w-5xl">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription className="sr-only">
Servizio Dialog
</DialogDescription>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
};
export const AnnunciInConferma = ({
annunci,
}: {
annunci: ServizioData["annunci"];
}) => {
if (annunci.length === 0) {
return null; // If there are no announcements in confirmation, do not render this section
}
return (
<Accordion collapsible defaultValue="in-conferma" type="single">
<AccordionItem className="space-y-4" value="in-conferma">
<AccordionTrigger className="rounded-md border-2 border-green-500 bg-white p-2">
Annunci in conferma:
</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col flex-wrap gap-5">
<DialogServizio
title="Annunci in conferma"
trigger={
<div className="relative">
<Button
className="text-lg"
disabled={annunci.length === 0}
size="xl"
variant="success"
>
Annunci in conferma
</Button>
{annunci.length > 0 && (
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{annunci.length}
</span>
)}
</div>
}
>
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
{annunci.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
@ -47,31 +91,32 @@ export const AnnunciInConferma = ({
);
})}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</DialogServizio>
);
};
export const AnnunciSelezionati = ({
isConfirming,
annunci,
}: {
isConfirming: boolean;
annunci: ServizioData["annunci"];
}) => {
return (
<Accordion
collapsible
defaultValue={!isConfirming ? "not-in-conferma" : undefined}
type="single"
<DialogServizio
title="Annunci selezionati"
trigger={
<div className="relative">
<Button className="text-lg" size="xl" variant="orange">
Annunci selezionati
</Button>
{annunci.length > 0 && (
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{annunci.length}
</span>
)}
</div>
}
>
<AccordionItem className="space-y-4" value="not-in-conferma">
<AccordionTrigger className="cursor-pointer rounded-md border-2 border-sky-500 bg-white p-2">
Annunci Selezionati:
</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col flex-wrap gap-5">
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
{annunci.length > 0 ? (
annunci.map((data) => {
return (
@ -96,9 +141,7 @@ export const AnnunciSelezionati = ({
</div>
)}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</DialogServizio>
);
};
@ -112,20 +155,27 @@ export const AnnunciCompatibili = () => {
userId,
});
if (isLoading) return <div>Loading...</div>;
return (
<Accordion
collapsible
defaultValue={!data || data.length === 0 ? "compatibili" : undefined}
type="single"
<DialogServizio
title="Annunci compatibili"
trigger={
<div className="relative">
<Button className="bg-purple-500 text-lg" size="xl">
Annunci compatibili
</Button>
{data && data.length > 0 && (
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{data?.length}
</span>
)}
</div>
}
>
<AccordionItem className="space-y-4" value="compatibili">
<AccordionTrigger className="cursor-pointer rounded-md border-2 border-neutral-500 bg-white p-2">
Annunci Compatibili:
</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col flex-wrap gap-5">
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
{isLoading ? (
<LoadingPage />
) : (
<>
{!data || data.length === 0 ? (
<div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex">
<span>
@ -134,8 +184,7 @@ export const AnnunciCompatibili = () => {
</span>
</div>
) : (
data.map((a) => {
return (
data.map((a) => (
<BasicAnnuncioCard
className="border-2 border-neutral-500"
data={a}
@ -153,13 +202,12 @@ export const AnnunciCompatibili = () => {
}
key={a.id}
/>
);
})
))
)}
</>
)}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</DialogServizio>
);
};