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": { "editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit" "source.fixAll.biome": "explicit"
}, },
"editor.formatOnSave": true, "editor.formatOnSave": true,

View file

@ -1,19 +1,19 @@
TODOS: TODOS:
- Updated_at timestamp per appartamento se in backend ho modificato le foto, e uso quello per cache key - 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 - 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 mettere Proprietario/Referente
- Modale contatti, aggiunti tasto whatsapp - Modale contatti, aggiunti tasto whatsapp
- Modale contatti, warning contatti in rosso - Modale contatti, warning contatti in rosso
- Mod contatti, caricamento doc identità, con ref salvato nell' servizio-annunci row - 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 - 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" - 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
- 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 - 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) - 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 - Mostrare last update in annunci e in singolo annuncio
NEW IDEA TODOS: 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 Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { type ReactNode, useEffect } from "react"; import { useEffect } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { InteressatoButtonBatchV } from "~/components/annuncio-interactions/annuncio_interactions";
import { AddButton } from "~/components/servizio/annuncio_actions"; import { AnnuncioCard } from "~/components/servizio/annuncio_card";
import {
AnnuncioCard,
BasicAnnuncioCard,
} from "~/components/servizio/annuncio_card";
import { SaldoButton } from "~/components/servizio/interactions"; import { SaldoButton } from "~/components/servizio/interactions";
import { ServizioDuration } from "~/components/servizio/service-duration-display"; import { ServizioDuration } from "~/components/servizio/service-duration-display";
import { import {
ServizioActions, ServizioActions,
ServizioActions2, ServizioActions2,
} from "~/components/servizio/servizio_actions"; } from "~/components/servizio/servizio_actions";
import {
AnnunciCompatibili,
AnnunciInConferma,
AnnunciSelezionati,
} from "~/components/servizio/servizio_annunci_accordions";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { import {
Card, Card,
@ -28,14 +19,6 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { Progress } from "~/components/ui/progress"; import { Progress } from "~/components/ui/progress";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { import {
@ -46,6 +29,11 @@ import {
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
import type { ServizioData } from "~/server/controllers/servizio.controller"; import type { ServizioData } from "~/server/controllers/servizio.controller";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import {
AnnunciCompatibili,
AnnunciInConferma,
AnnunciSelezionati,
} from "./servizio_annunci_accordions";
export const ServizioContainer = ({ export const ServizioContainer = ({
servizio, servizio,
@ -190,12 +178,7 @@ export const ServizioContent = () => {
</Button> </Button>
)} )}
</div> </div>
{isAdmin && ( {isAdmin && <AnnunciSelezionati annunci={servizio.annunci} />}
<AnnunciSelezionati
annunci={servizio.annunci}
isConfirming={false}
/>
)}
</div> </div>
} }
header={ header={
@ -301,145 +284,10 @@ export const ServizioContent = () => {
</div> </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"> <div className="flex items-center justify-between">
<DialogServizio <AnnunciInConferma annunci={annunciInConferma} />
title="Annunci in conferma" <AnnunciSelezionati annunci={annunciNotInConferma} />
trigger={ <AnnunciCompatibili />
<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>
);
})()}
</div> </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 ServizioMain = () => {
const { servizio, isAdmin, userId } = useServizio(); const { servizio, isAdmin, userId } = useServizio();
const utils = api.useUtils(); const utils = api.useUtils();
@ -567,12 +390,7 @@ const ServizioMain = () => {
</Button> </Button>
)} )}
</div> </div>
{isAdmin && ( {isAdmin && <AnnunciSelezionati annunci={servizio.annunci} />}
<AnnunciSelezionati
annunci={servizio.annunci}
isConfirming={false}
/>
)}
</div> </div>
} }
header={ header={
@ -704,10 +522,7 @@ const ServizioMain = () => {
<div className="space-y-2"> <div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} /> <AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati <AnnunciSelezionati annunci={annunciNotInConferma} />
annunci={annunciNotInConferma}
isConfirming={annunciInConferma.length > 0}
/>
<AnnunciCompatibili /> <AnnunciCompatibili />
</div> </div>
</> </>

View file

@ -1,5 +1,6 @@
import { ExternalLink, Trash2 } from "lucide-react"; import { ExternalLink, Trash2 } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import type { ReactNode } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { InteressatoButtonBatchV } from "~/components/annuncio-interactions/annuncio_interactions"; import { InteressatoButtonBatchV } from "~/components/annuncio-interactions/annuncio_interactions";
import { AddButton } from "~/components/servizio/annuncio_actions"; import { AddButton } from "~/components/servizio/annuncio_actions";
@ -21,84 +22,126 @@ import {
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
import type { ServizioData } from "~/server/controllers/servizio.controller"; import type { ServizioData } from "~/server/controllers/servizio.controller";
import { api } from "~/utils/api"; 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 = ({ export const AnnunciInConferma = ({
annunci, annunci,
}: { }: {
annunci: ServizioData["annunci"]; annunci: ServizioData["annunci"];
}) => { }) => {
if (annunci.length === 0) {
return null; // If there are no announcements in confirmation, do not render this section
}
return ( return (
<Accordion collapsible defaultValue="in-conferma" type="single"> <DialogServizio
<AccordionItem className="space-y-4" value="in-conferma"> title="Annunci in conferma"
<AccordionTrigger className="rounded-md border-2 border-green-500 bg-white p-2"> trigger={
Annunci in conferma: <div className="relative">
</AccordionTrigger> <Button
<AccordionContent> className="text-lg"
<div className="flex flex-col flex-wrap gap-5"> disabled={annunci.length === 0}
{annunci.map((data) => { size="xl"
return ( variant="success"
<ServizioAnnuncioProvider data={data} key={data.id}> >
<AnnuncioCard className="border-2 border-green-500" /> Annunci in conferma
</ServizioAnnuncioProvider> </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">
</div> {annunci.length}
</AccordionContent> </span>
</AccordionItem> )}
</Accordion> </div>
}
>
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
{annunci.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard className="border-2 border-green-500" />
</ServizioAnnuncioProvider>
);
})}
</div>
</DialogServizio>
); );
}; };
export const AnnunciSelezionati = ({ export const AnnunciSelezionati = ({
isConfirming,
annunci, annunci,
}: { }: {
isConfirming: boolean;
annunci: ServizioData["annunci"]; annunci: ServizioData["annunci"];
}) => { }) => {
return ( return (
<Accordion <DialogServizio
collapsible title="Annunci selezionati"
defaultValue={!isConfirming ? "not-in-conferma" : undefined} trigger={
type="single" <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"> <div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
<AccordionTrigger className="cursor-pointer rounded-md border-2 border-sky-500 bg-white p-2"> {annunci.length > 0 ? (
Annunci Selezionati: annunci.map((data) => {
</AccordionTrigger> return (
<AccordionContent> <ServizioAnnuncioProvider data={data} key={data.id}>
<div className="flex flex-col flex-wrap gap-5"> <AnnuncioCard className="border-2 border-sky-500" />
{annunci.length > 0 ? ( </ServizioAnnuncioProvider>
annunci.map((data) => { );
return ( })
<ServizioAnnuncioProvider data={data} key={data.id}> ) : (
<AnnuncioCard className="border-2 border-sky-500" /> <div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex">
</ServizioAnnuncioProvider> <span>
); Nessun annuncio selezionato, fai una ricerca nella pagina{" "}
}) <Link
) : ( aria-label="Annunci Compatibili"
<div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex"> className="inline-flex items-center gap-1 text-blue-500 underline"
<span> href="/annunci"
Nessun annuncio selezionato, fai una ricerca nella pagina{" "} >
<Link Annunci
aria-label="Annunci Compatibili" <ExternalLink className="size-4" />
className="inline-flex items-center gap-1 text-blue-500 underline" </Link>
href="/annunci" </span>
>
Annunci
<ExternalLink className="size-4" />
</Link>
</span>
</div>
)}
</div> </div>
</AccordionContent> )}
</AccordionItem> </div>
</Accordion> </DialogServizio>
); );
}; };
@ -112,20 +155,27 @@ export const AnnunciCompatibili = () => {
userId, userId,
}); });
if (isLoading) return <div>Loading...</div>;
return ( return (
<Accordion <DialogServizio
collapsible title="Annunci compatibili"
defaultValue={!data || data.length === 0 ? "compatibili" : undefined} trigger={
type="single" <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"> <div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
<AccordionTrigger className="cursor-pointer rounded-md border-2 border-neutral-500 bg-white p-2"> {isLoading ? (
Annunci Compatibili: <LoadingPage />
</AccordionTrigger> ) : (
<AccordionContent> <>
<div className="flex flex-col flex-wrap gap-5">
{!data || data.length === 0 ? ( {!data || data.length === 0 ? (
<div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex"> <div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex">
<span> <span>
@ -134,32 +184,30 @@ export const AnnunciCompatibili = () => {
</span> </span>
</div> </div>
) : ( ) : (
data.map((a) => { data.map((a) => (
return ( <BasicAnnuncioCard
<BasicAnnuncioCard className="border-2 border-neutral-500"
className="border-2 border-neutral-500" data={a}
data={a} interactions={
interactions={ <>
<> {isAdmin ? (
{isAdmin ? ( <AddButton annuncioId={a.id} />
<AddButton annuncioId={a.id} /> ) : (
) : ( <InteressatoButtonBatchV
<InteressatoButtonBatchV annuncioId={a.id}
annuncioId={a.id} hasInterest={userIntrests?.includes(a.id) || false}
hasInterest={userIntrests?.includes(a.id) || false} />
/> )}
)} </>
</> }
} key={a.id}
key={a.id} />
/> ))
);
})
)} )}
</div> </>
</AccordionContent> )}
</AccordionItem> </div>
</Accordion> </DialogServizio>
); );
}; };