refactor: enhance styling and layout of AnnuncioCard and AnnuncioDettaglio components

This commit is contained in:
Marco Pedone 2025-12-15 18:58:13 +01:00
parent 831ae0edef
commit 496a947f97
6 changed files with 116 additions and 139 deletions

View file

@ -1,9 +1,6 @@
import { ExternalLink } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
import { AnnuncioActions } from "~/components/servizio/annuncio_actions"; import { AnnuncioActions } from "~/components/servizio/annuncio_actions";
import { Interactions } from "~/components/servizio/interactions"; import { Interactions } from "~/components/servizio/interactions";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { handleConsegna } from "~/lib/annuncio_details"; import { handleConsegna } from "~/lib/annuncio_details";
import { getStorageUrl } from "~/lib/storage_utils"; import { getStorageUrl } from "~/lib/storage_utils";
@ -45,35 +42,40 @@ export const BasicAnnuncioCard = ({
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Card className={cn("w-full border-white py-5", className)}> <Card className={cn("w-full border-white py-3", className)}>
<CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 pb-0"> <CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 px-3 pb-0">
<CardTitle className="text-2xl">Codice: {data.codice}</CardTitle> <CardTitle className="text-2xl">Annuncio {data.codice}</CardTitle>
{actions} {actions}
</CardHeader> </CardHeader>
<CardContent className="px-4"> <CardContent className="px-3">
<div className="flex w-full flex-col gap-4 md:flex-row md:gap-10"> <div className="flex w-full flex-col gap-4 md:gap-10">
<Image <div className="grid grid-cols-[6rem_auto] gap-2 gap-x-4 md:grid-cols-[12rem_auto] md:gap-x-10">
alt={data.codice} <div className="row-span-3">
className="h-auto max-h-72 w-full rounded-md bg-[#e6e9ec] object-contain sm:max-w-xs" <Image
height={1080} alt={data.codice}
priority className="size-24 rounded-md bg-[#e6e9ec] object-cover md:size-48"
src={ height={1080}
(data?.images[0] && priority
getStorageUrl({ src={
storageId: data.images[0].img, (data?.images[0] &&
params: { getStorageUrl({
cacheKey: data.media_updated_at?.toISOString(), storageId: data.images[0].img,
media: "image", params: {
}, cacheKey: data.media_updated_at?.toISOString(),
})) || media: "image",
"/fallback-image.png" },
} })) ||
width={1920} "/fallback-image.png"
/> }
<div className="flex w-full flex-col gap-5"> width={1920}
{interactions} />
</div>
<div className="flex flex-wrap items-center gap-3"> <div className="flex w-full max-w-xl flex-wrap gap-5 md:col-span-1">
<span className="text-wrap">{data.titolo_it}</span>
</div>
<div className="col-span-2 flex w-full max-w-xl flex-wrap items-center justify-between gap-3 md:col-span-1">
<TipoBadge tipo={data.tipo} /> <TipoBadge tipo={data.tipo} />
<span> <span>
{handleConsegna({ {handleConsegna({
@ -84,25 +86,15 @@ export const BasicAnnuncioCard = ({
subito: t.card.consegna_subito, subito: t.card.consegna_subito,
})} })}
</span> </span>
<span>{formatCurrency(data.prezzo / 1e2)}</span> <span className="font-semibold">
{formatCurrency(data.prezzo / 1e2)}
</span>
</div> </div>
<div className="flex w-full flex-wrap items-center gap-5"> <div className="col-span-2 w-full max-w-xl md:col-span-1">
<span className="text-wrap">{data.titolo_it || data.codice}</span> <AnnuncioDettaglio data={data} />
<div className="flex flex-wrap items-center gap-2">
<AnnuncioDettaglio data={data} />
{data.stato !== "Sospeso" && data.web && (
<Link
aria-label="Pagina Annuncio"
href={`/annuncio/${data.codice}`}
>
<Button variant="outline">
<ExternalLink className="size-6" />
</Button>
</Link>
)}
</div>
</div> </div>
</div> </div>
<div className="flex w-full flex-col gap-5">{interactions}</div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View file

@ -1,4 +1,4 @@
import { ExternalLink, UnfoldVertical } from "lucide-react"; import { Expand, ExternalLink } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { replaceWithBr } from "~/lib/newlineToBr"; import { replaceWithBr } from "~/lib/newlineToBr";
import { cn, formatCurrency } from "~/lib/utils"; import { cn, formatCurrency } from "~/lib/utils";
@ -6,16 +6,17 @@ import { useTranslation } from "~/providers/I18nProvider";
import type { Annunci } from "~/schemas/public/Annunci"; import type { Annunci } from "~/schemas/public/Annunci";
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller"; import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
import { CarouselAnnuncio } from "../annuncio_card"; import { CarouselAnnuncio } from "../annuncio_card";
import {
Credenza,
CredenzaBody,
CredenzaContent,
CredenzaDescription,
CredenzaHeader,
CredenzaTitle,
CredenzaTrigger,
} from "../custom_ui/credenza";
import { Badge } from "../ui/badge"; import { Badge } from "../ui/badge";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
export const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => { export const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
return ( return (
@ -37,70 +38,78 @@ export const AnnuncioDettaglio = ({ data }: { data: AnnuncioRicerca }) => {
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
return ( return (
<Dialog> <Credenza>
<DialogTrigger asChild> <CredenzaTrigger asChild>
<Button aria-label="Dettagli Annuncio" variant="outline"> <Button
<UnfoldVertical className="size-6" /> Dettagli annuncio aria-label="Dettagli Annuncio"
className="w-full"
size="sm"
variant="outline"
>
<Expand /> Dettaglio
</Button> </Button>
</DialogTrigger> </CredenzaTrigger>
<DialogContent className="max-h-[70vh] max-w-xl overflow-y-auto sm:max-w-6xl"> <CredenzaContent className="max-w-xl sm:max-w-6xl md:max-h-[90vh] md:overflow-y-auto">
<DialogHeader className="flex-row items-center gap-4"> <CredenzaHeader className="flex-row items-center justify-between gap-4 md:justify-start">
<DialogTitle className="text-xl">Annuncio {data.codice}</DialogTitle> <CredenzaTitle className="text-xl">
Annuncio {data.codice}
</CredenzaTitle>
<Link href={`/annuncio/${data.codice}`} target="_blank"> <Link href={`/annuncio/${data.codice}`} target="_blank">
<Button size="sm" variant="info"> <Button size="sm" variant="info">
<span>Vai alla pagina completa</span>{" "} <span>Pagina completa</span> <ExternalLink />
<ExternalLink className="size-4" />
</Button> </Button>
</Link> </Link>
<DialogDescription className="sr-only"> <CredenzaDescription className="sr-only">
Annuncio {data.codice} Dettaglio
</DialogDescription> </CredenzaDescription>
</DialogHeader> </CredenzaHeader>
<div className="flex flex-col gap-2 px-2"> <CredenzaBody className="h-full overflow-auto pb-5">
<div className="w-full contain-inline-size"> <div className="flex flex-col gap-2 px-2">
<CarouselAnnuncio <div className="w-full contain-inline-size">
immagini={data.images} <CarouselAnnuncio
updated_at={data.media_updated_at} immagini={data.images}
videos={data.videos} updated_at={data.media_updated_at}
/> videos={data.videos}
</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>
<div className="mt-1 space-y-2"> <div className="space-y-3">
<div className="flex flex-row justify-around"> <div className="flex items-center gap-4">
<div className="text-center"> <TipoBadge tipo={data.tipo} />
<dt className="sr-only">{t.card.codice}</dt> {data.stato === "Trattativa" && (
<Badge className="bg-violet-500 font-semibold text-md">
Annuncio in Trattativa
</Badge>
)}
</div>
<dd className="font-semibold text-lg">Cod: {data.codice}</dd> <div className="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>
<div className="text-center">
<dt className="sr-only">{t.card.prezzo}</dt>
<dd className="font-semibold text-lg"> <div className="">
{formatCurrency(data.prezzo / 1e2)} <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> </dd>
</div> </div>
</div>
<div className="mb-3"> <div
<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" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceWithBr( __html: replaceWithBr(
@ -111,8 +120,8 @@ export const AnnuncioDettaglio = ({ data }: { data: AnnuncioRicerca }) => {
</div> </div>
</div> </div>
</div> </div>
</div> </CredenzaBody>
</DialogContent> </CredenzaContent>
</Dialog> </Credenza>
); );
}; };

View file

@ -190,7 +190,7 @@ export const Interactions = () => {
const { data } = useServizioAnnuncio(); const { data } = useServizioAnnuncio();
return ( return (
<div className="flex flex-col gap-3"> <div className="flex w-full flex-col gap-3">
<ContattiProprietarioModal /> <ContattiProprietarioModal />
<SezioneConferma /> <SezioneConferma />

View file

@ -43,7 +43,7 @@ const ServizioCard = ({
return ( return (
<Card <Card
className={cn( className={cn(
"gap-2 rounded-none rounded-b-md border-primary border-t-0", "gap-2 rounded-none rounded-b-md border-primary border-t-0 pb-0",
cardClassName, cardClassName,
)} )}
> >
@ -218,32 +218,6 @@ export const ServizioContent = () => {
<ServizioCard <ServizioCard
content={ content={
<> <>
{/* <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:max-w-1/2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<UserCircle className="size-6 text-primary" />
<span className="font-medium">Contatti sbloccati</span>
</div>
<span className="text-sm">
{openContattiAnnunci.length} / 10
</span>
</div>
<Progress
className="h-2"
value={openContattiAnnunci.length * 10}
/>
</div>
<div className="flex flex-col flex-wrap gap-2 sm:flex-row sm:items-center">
<div className="flex items-center gap-2">
<CalendarClock className="size-6 text-primary" />
<span className="font-medium">Durata servizio:</span>
</div>
<span>
{`${new Date(servizio.decorrenza).toLocaleDateString("it-IT")} - ${new Date(add(servizio.decorrenza, { days: 60 })).toLocaleDateString("it-IT")}`}
</span>
</div>
</div> */}
{annuncioConfermato.length > 0 && ( {annuncioConfermato.length > 0 && (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View file

@ -49,7 +49,7 @@ export const ContattiProprietarioModal = () => {
{isUnlocked ? ( {isUnlocked ? (
<Button <Button
aria-label="Vedi contatti proprietario" aria-label="Vedi contatti proprietario"
className="w-fit" className="w-full md:w-fit"
variant="success" variant="success"
> >
<CircleUser className="size-7" /> <CircleUser className="size-7" />
@ -58,7 +58,7 @@ export const ContattiProprietarioModal = () => {
) : ( ) : (
<Button <Button
aria-label="Contatta proprietario" aria-label="Contatta proprietario"
className="w-fit" className="w-full md:w-fit"
variant="warning" variant="warning"
> >
<CircleUser className="size-7" /> <CircleUser className="size-7" />

View file

@ -765,7 +765,9 @@ export const PasswordSVG = (props: SVGProps<SVGSVGElement>) => (
/> />
</svg> </svg>
); );
/*
from: https://www.svgrepo.com/svg/477007/alarm-clock
*/
export const AlarmClockSVG = (props: SVGProps<SVGSVGElement>) => ( export const AlarmClockSVG = (props: SVGProps<SVGSVGElement>) => (
<svg <svg
className="icon" className="icon"