refactor: streamline AnnuncioCard and CompatibiliDialog components, enhance data handling and styling
This commit is contained in:
parent
b741f7ce72
commit
de034c0eb7
5 changed files with 155 additions and 291 deletions
|
|
@ -1,53 +1,17 @@
|
|||
import { ExternalLink, UnfoldVertical } from "lucide-react";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
CredenzaClose,
|
||||
CredenzaContent,
|
||||
CredenzaDescription,
|
||||
CredenzaFooter,
|
||||
CredenzaHeader,
|
||||
CredenzaTitle,
|
||||
CredenzaTrigger,
|
||||
} from "~/components/custom_ui/credenza";
|
||||
import { ImageFlbk } from "~/components/ImageWithFallback";
|
||||
import { AnnuncioActions } from "~/components/servizio/annuncio_actions";
|
||||
import { Interactions } from "~/components/servizio/interactions";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "~/components/ui/carousel";
|
||||
import { handleConsegna } from "~/lib/annuncio_details";
|
||||
import { replaceWithBr } from "~/lib/newlineToBr";
|
||||
import { cn, formatCurrency } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import { useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||
import type { Annunci } from "~/schemas/public/Annunci";
|
||||
import { Badge } from "../ui/badge";
|
||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||
import { AnnuncioDettaglio, TipoBadge } from "./annuncio_dettaglio";
|
||||
|
||||
type AnnuncioData = Pick<
|
||||
Annunci,
|
||||
| "id"
|
||||
| "codice"
|
||||
| "prezzo"
|
||||
| "desc_en"
|
||||
| "desc_it"
|
||||
| "titolo_en"
|
||||
| "titolo_it"
|
||||
| "tipo"
|
||||
| "consegna"
|
||||
| "stato"
|
||||
| "web"
|
||||
| "media_updated_at"
|
||||
> & {
|
||||
images: Pick<{ img: string; thumb: string }, "img">[];
|
||||
};
|
||||
export const AnnuncioCard = ({ className }: { className?: string }) => {
|
||||
const { data } = useServizioAnnuncio();
|
||||
|
||||
|
|
@ -57,6 +21,7 @@ export const AnnuncioCard = ({ className }: { className?: string }) => {
|
|||
className={className}
|
||||
data={{
|
||||
...data,
|
||||
modificato_il: data.modificato_il ? new Date(data.modificato_il) : null,
|
||||
media_updated_at: data.media_updated_at
|
||||
? new Date(data.media_updated_at)
|
||||
: null,
|
||||
|
|
@ -74,7 +39,7 @@ export const BasicAnnuncioCard = ({
|
|||
actions?: React.ReactNode;
|
||||
interactions?: React.ReactNode;
|
||||
className?: string;
|
||||
data: AnnuncioData;
|
||||
data: AnnuncioRicerca;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -136,125 +101,3 @@ export const BasicAnnuncioCard = ({
|
|||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
|
||||
return (
|
||||
<Badge
|
||||
className={cn(
|
||||
"font-semibold text-md",
|
||||
tipo === "Transitorio" && "bg-transitorio",
|
||||
tipo === "Stabile" && "bg-stabile",
|
||||
tipo === "Cessione" && "bg-blue-400",
|
||||
tipo === "Vendita" && "bg-green-400",
|
||||
)}
|
||||
>
|
||||
Affitto {tipo}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const AnnuncioDettaglio = ({ data }: { data: AnnuncioData }) => {
|
||||
const { locale, t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button aria-label="Dettagli Annuncio" variant="outline">
|
||||
<UnfoldVertical className="size-6" /> Dettagli annuncio
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh] md:max-w-6xl">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle className="text-xl">
|
||||
Annuncio {data.codice}
|
||||
</CredenzaTitle>
|
||||
<CredenzaDescription className="sr-only">
|
||||
Annuncio {data.codice}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody className="scrollbar-default max-h-[80vh] overflow-y-auto pb-5">
|
||||
<div className="flex flex-col gap-2 px-2">
|
||||
<Carousel opts={{ loop: true }}>
|
||||
<CarouselContent>
|
||||
{data?.images?.map((img, idx) => (
|
||||
<CarouselItem key={img.img}>
|
||||
<ImageFlbk
|
||||
alt={`${data.codice} - ${idx}`}
|
||||
className={"h-80 w-full object-contain sm:h-[35rem]"}
|
||||
height={200}
|
||||
priority={idx === 0}
|
||||
src={`/storage-api/get/${img.img}?image=true&${data.media_updated_at?.toString() || new Date().toString()}`}
|
||||
width={400}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<div
|
||||
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<CarouselPrevious
|
||||
className="left-2 cursor-pointer"
|
||||
disabled={!data.images || data.images.length === 1}
|
||||
/>
|
||||
<CarouselNext
|
||||
className="right-2 cursor-pointer"
|
||||
disabled={!data.images || data.images.length === 1}
|
||||
/>
|
||||
</div>
|
||||
</Carousel>
|
||||
<div>
|
||||
<TipoBadge tipo={data.tipo} />
|
||||
|
||||
<div className="mt-1 space-y-2">
|
||||
<div className="flex flex-row justify-around">
|
||||
<div className="text-center">
|
||||
<dt className="sr-only">{t.card.codice}</dt>
|
||||
|
||||
<dd className="font-semibold text-lg">
|
||||
Cod: {data.codice}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<dt className="sr-only">{t.card.prezzo}</dt>
|
||||
|
||||
<dd className="font-semibold text-lg">
|
||||
{formatCurrency(data.prezzo / 1e2)}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<dt className="sr-only">{t.card.titolo}</dt>
|
||||
|
||||
<dd className="font-semibold text-base md:text-lg">
|
||||
{locale === "it" ? data.titolo_it : data.titolo_en}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<dd
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceWithBr(
|
||||
(locale === "it" ? data.desc_it : data.desc_en) || "",
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi Credenza">{t.chiudi}</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
118
apps/infoalloggi/src/components/servizio/annuncio_dettaglio.tsx
Normal file
118
apps/infoalloggi/src/components/servizio/annuncio_dettaglio.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { ExternalLink, UnfoldVertical } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { replaceWithBr } from "~/lib/newlineToBr";
|
||||
import { cn, formatCurrency } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import type { Annunci } from "~/schemas/public/Annunci";
|
||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||
import { CarouselAnnuncio } from "../annuncio_card";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../ui/dialog";
|
||||
|
||||
export const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
|
||||
return (
|
||||
<Badge
|
||||
className={cn(
|
||||
"font-semibold text-md",
|
||||
tipo === "Transitorio" && "bg-transitorio",
|
||||
tipo === "Stabile" && "bg-stabile",
|
||||
tipo === "Cessione" && "bg-blue-400",
|
||||
tipo === "Vendita" && "bg-green-400",
|
||||
)}
|
||||
>
|
||||
Affitto {tipo}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
export const AnnuncioDettaglio = ({ data }: { data: AnnuncioRicerca }) => {
|
||||
const { locale, t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button aria-label="Dettagli Annuncio" variant="outline">
|
||||
<UnfoldVertical className="size-6" /> Dettagli annuncio
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-[70vh] max-w-xl overflow-y-auto sm:max-w-6xl">
|
||||
<DialogHeader className="flex-row items-center gap-4">
|
||||
<DialogTitle className="text-xl">Annuncio {data.codice}</DialogTitle>
|
||||
<Link href={`/annuncio/${data.codice}`} target="_blank">
|
||||
<Button size="sm" variant="info">
|
||||
<span>Vai alla pagina completa</span>{" "}
|
||||
<ExternalLink className="size-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
<DialogDescription className="sr-only">
|
||||
Annuncio {data.codice}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-2 px-2">
|
||||
<div className="w-full contain-inline-size">
|
||||
<CarouselAnnuncio
|
||||
immagini={data.images.map((img) => img.img)}
|
||||
updated_at={data.media_updated_at}
|
||||
videos={data.url_video}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-4">
|
||||
<TipoBadge tipo={data.tipo} />
|
||||
{data.stato === "Trattativa" && (
|
||||
<Badge className="bg-violet-500 font-semibold text-md">
|
||||
Annuncio in Trattativa
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-1 space-y-2">
|
||||
<div className="flex flex-row justify-around">
|
||||
<div className="text-center">
|
||||
<dt className="sr-only">{t.card.codice}</dt>
|
||||
|
||||
<dd className="font-semibold text-lg">Cod: {data.codice}</dd>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<dt className="sr-only">{t.card.prezzo}</dt>
|
||||
|
||||
<dd className="font-semibold text-lg">
|
||||
{formatCurrency(data.prezzo / 1e2)}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<dt className="sr-only">{t.card.titolo}</dt>
|
||||
|
||||
<dd className="font-semibold text-base md:text-lg">
|
||||
{locale === "it" ? data.titolo_it : data.titolo_en}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<dd
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceWithBr(
|
||||
(locale === "it" ? data.desc_it : data.desc_en) || "",
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,17 +1,9 @@
|
|||
import { ExternalLink, UnfoldVertical } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { replaceWithBr } from "~/lib/newlineToBr";
|
||||
import { cn, formatCurrency } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import { useServizio } from "~/providers/ServizioProvider";
|
||||
import type { Annunci } from "~/schemas/public/Annunci";
|
||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||
import { api } from "~/utils/api";
|
||||
import { CardAnnuncio, CarouselAnnuncio } from "../annuncio_card";
|
||||
import { CardAnnuncio } from "../annuncio_card";
|
||||
import { InteressatoButtonServizio } from "../annuncio-interactions/annuncio_interactions";
|
||||
import { LoadingPage } from "../loading";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -25,6 +17,7 @@ import {
|
|||
} from "../ui/dialog";
|
||||
import { Label } from "../ui/label";
|
||||
import { Switch } from "../ui/switch";
|
||||
import { AnnuncioDettaglio } from "./annuncio_dettaglio";
|
||||
|
||||
export const AnnunciCompatibili = () => {
|
||||
const { servizioId, isAdmin } = useServizio();
|
||||
|
|
@ -36,7 +29,7 @@ export const AnnunciCompatibili = () => {
|
|||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-full bg-purple-500 sm:w-fit">
|
||||
<Button className="w-full bg-purple-500 hover:bg-purple-600 sm:w-fit dark:text-white">
|
||||
Esplora Annunci Compatibili
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
|
@ -67,12 +60,10 @@ export const AnnunciCompatibili = () => {
|
|||
) : (
|
||||
<>
|
||||
{!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>
|
||||
) : (
|
||||
<div className="relative z-0 mx-auto grid max-w-full grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 sm:gap-y-10 lg:grid-cols-3 2xl:grid-cols-4">
|
||||
{data.map((annuncio) => (
|
||||
|
|
@ -109,106 +100,3 @@ export const AnnunciCompatibili = () => {
|
|||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
|
||||
return (
|
||||
<Badge
|
||||
className={cn(
|
||||
"font-semibold text-md",
|
||||
tipo === "Transitorio" && "bg-transitorio",
|
||||
tipo === "Stabile" && "bg-stabile",
|
||||
tipo === "Cessione" && "bg-blue-400",
|
||||
tipo === "Vendita" && "bg-green-400",
|
||||
)}
|
||||
>
|
||||
Affitto {tipo}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const AnnuncioDettaglio = ({ data }: { data: AnnuncioRicerca }) => {
|
||||
const { locale, t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button aria-label="Dettagli Annuncio" variant="outline">
|
||||
<UnfoldVertical className="size-6" /> Dettagli annuncio
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-[70vh] max-w-xl overflow-y-auto sm:max-w-6xl">
|
||||
<DialogHeader className="flex-row items-center gap-4">
|
||||
<DialogTitle className="text-xl">Annuncio {data.codice}</DialogTitle>
|
||||
<Link href={`/annuncio/${data.codice}`} target="_blank">
|
||||
<Button size="sm" variant="info">
|
||||
<span>Vai alla pagina completa</span>{" "}
|
||||
<ExternalLink className="size-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
<DialogDescription className="sr-only">
|
||||
Annuncio {data.codice}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-2 px-2">
|
||||
<div className="w-full contain-inline-size">
|
||||
<CarouselAnnuncio
|
||||
immagini={data.images.map((img) => img.img)}
|
||||
updated_at={data.media_updated_at}
|
||||
videos={data.url_video}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-4">
|
||||
<TipoBadge tipo={data.tipo} />
|
||||
{data.stato === "Trattativa" && (
|
||||
<Badge className="bg-violet-500 font-semibold text-md">
|
||||
Annuncio in Trattativa
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-1 space-y-2">
|
||||
<div className="flex flex-row justify-around">
|
||||
<div className="text-center">
|
||||
<dt className="sr-only">{t.card.codice}</dt>
|
||||
|
||||
<dd className="font-semibold text-lg">Cod: {data.codice}</dd>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<dt className="sr-only">{t.card.prezzo}</dt>
|
||||
|
||||
<dd className="font-semibold text-lg">
|
||||
{formatCurrency(data.prezzo / 1e2)}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<dt className="sr-only">{t.card.titolo}</dt>
|
||||
|
||||
<dd className="font-semibold text-base md:text-lg">
|
||||
{locale === "it" ? data.titolo_it : data.titolo_en}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<dd
|
||||
className="text-base"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceWithBr(
|
||||
(locale === "it" ? data.desc_it : data.desc_en) || "",
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild></DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -93,16 +93,23 @@ export const intrestsRouter = createTRPCRouter({
|
|||
.select((eb) => [
|
||||
"annunci.id",
|
||||
"annunci.codice",
|
||||
"annunci.comune",
|
||||
"annunci.provincia",
|
||||
"annunci.prezzo",
|
||||
"annunci.consegna",
|
||||
"annunci.numero_camere",
|
||||
"annunci.mq",
|
||||
"annunci.tipo",
|
||||
"annunci.titolo_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.desc_en",
|
||||
"annunci.desc_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.titolo_it",
|
||||
"annunci.tipo",
|
||||
"annunci.consegna",
|
||||
"annunci.modificato_il",
|
||||
"annunci.stato",
|
||||
"annunci.web",
|
||||
"annunci.url_video",
|
||||
"annunci.media_updated_at",
|
||||
"annunci.homepage",
|
||||
"annunci.web",
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("images_refs")
|
||||
|
|
|
|||
|
|
@ -605,6 +605,7 @@ export const setupSaldoConsulenzaServizio = async ({
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
export type ServizioData = Awaited<
|
||||
ReturnType<typeof getAllServizioAnnunci>
|
||||
>[number];
|
||||
|
|
@ -679,16 +680,23 @@ export const getAllServizioAnnunci = async (userId: UsersId) => {
|
|||
.select((eb) => [
|
||||
"annunci.id",
|
||||
"annunci.codice",
|
||||
"annunci.comune",
|
||||
"annunci.provincia",
|
||||
"annunci.prezzo",
|
||||
"annunci.consegna",
|
||||
"annunci.numero_camere",
|
||||
"annunci.mq",
|
||||
"annunci.tipo",
|
||||
"annunci.titolo_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.desc_en",
|
||||
"annunci.desc_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.titolo_it",
|
||||
"annunci.tipo",
|
||||
"annunci.consegna",
|
||||
"annunci.modificato_il",
|
||||
"annunci.stato",
|
||||
"annunci.web",
|
||||
"annunci.url_video",
|
||||
"annunci.media_updated_at",
|
||||
"annunci.homepage",
|
||||
"annunci.web",
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("images_refs")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue