feat: implement AnnuncioDisplay component and integrate it into ServizioContent for better display of selected announcements

This commit is contained in:
Marco Pedone 2026-01-09 13:13:17 +01:00
parent 6c4c57fd69
commit 76bcf640ca
4 changed files with 106 additions and 187 deletions

View file

@ -35,38 +35,20 @@ export const AnnuncioCard = ({
/>
);
};
export const BasicAnnuncioCard = ({
actions,
interactions,
className,
export const AnnuncioDisplay = ({
data,
confirming,
}: {
actions?: React.ReactNode;
interactions?: React.ReactNode;
className?: string;
data: AnnuncioRicerca;
confirming?: boolean;
}) => {
const { t } = useTranslation();
return (
<Card className={cn("w-full gap-2 border-white py-3", className)}>
<CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 px-3 pb-2">
<CardTitle className="text-2xl">Annuncio {data.codice}</CardTitle>
{actions}
</CardHeader>
<CardContent className="px-3">
<div
className={cn(
"@container flex w-full flex-col gap-4",
confirming && "xl:flex-row xl:gap-6",
)}
id="container"
>
<div
className={cn(
"grid @md:grid-cols-[12rem_1fr] grid-cols-[7rem_1fr] gap-x-4 gap-y-2 rounded-md md:max-w-3xl md:border md:border-white md:bg-[#e6e9ec]/50 lg:gap-x-6 xl:gap-x-8 dark:md:border-primary dark:md:bg-card",
"grid @md:grid-cols-[12rem_1fr] grid-cols-[7rem_1fr] gap-x-4 gap-y-2 rounded-md md:max-w-3xl md:border md:border-white md:bg-[#e6e9ec]/50 lg:gap-x-4 xl:gap-x-6 dark:md:border-primary dark:md:bg-card",
confirming && "xl:w-1/2",
)}
>
@ -114,6 +96,37 @@ export const BasicAnnuncioCard = ({
<AnnuncioDettaglio data={data} />
</div>
</div>
);
};
export const BasicAnnuncioCard = ({
actions,
interactions,
className,
data,
confirming,
}: {
actions?: React.ReactNode;
interactions?: React.ReactNode;
className?: string;
data: AnnuncioRicerca;
confirming?: boolean;
}) => {
return (
<Card className={cn("w-full gap-2 border-white py-3", className)}>
<CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 px-3 pb-2">
<CardTitle className="text-2xl">Annuncio {data.codice}</CardTitle>
{actions}
</CardHeader>
<CardContent className="px-3">
<div
className={cn(
"@container flex w-full flex-col gap-4",
confirming && "xl:flex-row xl:gap-6",
)}
id="container"
>
<AnnuncioDisplay confirming={confirming} data={data} />
<div
className={cn(
"flex w-full flex-col gap-4",
@ -127,19 +140,3 @@ export const BasicAnnuncioCard = ({
</Card>
);
};
export const AnnuncioCardSkeleton = () => {
return (
<Card className="hidden w-full gap-2 rounded-md border-border py-3 opacity-30 lg:flex">
<CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 px-3 pb-2">
<CardTitle className="h-8 w-64 rounded-md bg-muted" />
</CardHeader>
<CardContent className="h-max px-3">
<div className="flex w-full flex-col gap-4">
<div className="h-[194px] w-full rounded-md bg-muted" />
<div className="h-8 w-48 rounded-md bg-muted" />
</div>
</CardContent>
</Card>
);
};

View file

@ -6,7 +6,7 @@ import { useEffect } from "react";
import toast from "react-hot-toast";
import {
AnnuncioCard,
AnnuncioCardSkeleton,
AnnuncioDisplay,
} from "~/components/servizio/annuncio_card";
import { SaldoButton } from "~/components/servizio/interactions";
import { ServizioActions } from "~/components/servizio/servizio_actions";
@ -26,7 +26,6 @@ import {
import { api } from "~/utils/api";
import { AlarmClockSVG } from "../svgs";
import { AnnunciCompatibili } from "./compatibili_dialog";
import { AnnunciSelezionatiDialog } from "./servizio_annunci_accordions";
const ServizioCard = ({
cardClassName,
@ -163,7 +162,34 @@ export const ServizioContent = () => {
)}
</div>
{isAdmin && <AnnunciSelezionatiDialog annunci={servizio.annunci} />}
{servizio.annunci.length > 0 && (
<div className="w-full space-y-2">
<p>
Sono già stati selezionati {servizio.annunci.length} annunci
che potrebbero interessarti.
</p>
<div className="grid grid-cols-2 gap-4">
{servizio.annunci.map((data) => (
<div className="" key={data.id}>
<AnnuncioDisplay
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,
}}
/>
</div>
))}
{servizio.annunci.length % 2 === 1 && (
<div className="size-full rounded-md border border-[var(--pattern-fg)] bg-[repeating-linear-gradient(45deg,_var(--pattern-fg)_0,_var(--pattern-fg)_1px,_transparent_0,_transparent_50%)] bg-[size:10px_10px] bg-fixed opacity-10 [--pattern-fg:#696969] dark:[--pattern-fg:#e1e1e1]" />
)}
</div>
</div>
)}
</div>
}
contentClassName={cn(!isAdmin && "gap-0")}
@ -297,7 +323,9 @@ export const ServizioContent = () => {
</ServizioAnnuncioProvider>
);
})}
{servizio.annunci.length % 2 === 1 && <AnnuncioCardSkeleton />}
{servizio.annunci.length % 2 === 1 && (
<div className="size-full rounded-md border border-[var(--pattern-fg)] bg-[repeating-linear-gradient(45deg,_var(--pattern-fg)_0,_var(--pattern-fg)_1px,_transparent_0,_transparent_50%)] bg-[size:10px_10px] bg-fixed opacity-10 [--pattern-fg:#696969] dark:[--pattern-fg:#e1e1e1]" />
)}
</div>
<div className="flex items-center justify-center">

View file

@ -1,11 +1,6 @@
import { ExternalLink, Trash2 } from "lucide-react";
import Link from "next/link";
import type { ReactNode } from "react";
import { Trash2 } from "lucide-react";
import toast from "react-hot-toast";
import {
AnnuncioCard,
BasicAnnuncioCard,
} from "~/components/servizio/annuncio_card";
import { BasicAnnuncioCard } from "~/components/servizio/annuncio_card";
import {
Accordion,
AccordionContent,
@ -13,105 +8,8 @@ import {
AccordionTrigger,
} from "~/components/ui/accordion";
import { Button } from "~/components/ui/button";
import {
ServizioAnnuncioProvider,
useServizio,
} from "~/providers/ServizioProvider";
import type { UsersId } from "~/schemas/public/Users";
import type { ServizioData } from "~/server/controllers/servizio.controller";
import { api } from "~/utils/api";
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="max-w-xl p-2 sm:max-w-5xl sm:p-6 md:max-w-7xl">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription className="sr-only">
Servizio Dialog
</DialogDescription>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
};
export const AnnunciSelezionatiDialog = ({
annunci,
}: {
annunci: ServizioData["annunci"];
}) => {
const { servizioId, userId } = useServizio();
return (
<DialogServizio
title="Annunci selezionati"
trigger={
<div className="relative flex w-full sm:w-fit">
<Button
className="w-full text-lg sm:w-fit"
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">
{annunci.length || 0}
</span>
</div>
}
>
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
{annunci.length > 0 ? (
annunci.map((data) => {
return (
<ServizioAnnuncioProvider
data={data}
key={data.id}
servizioId={servizioId}
userId={userId}
>
<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>
);
};
export const AnnunciRichiesti = ({ userId }: { userId: UsersId }) => {
const { data, isLoading } = api.intrests.getUserInterestsAnnunci.useQuery({

View file

@ -14,17 +14,13 @@
@custom-variant search-cancel (&::-webkit-search-cancel-button);
@utility stripes-* {
background-image: linear-gradient(45deg,
--value([color]) 12.5%,
transparent 12.5%,
transparent 50%,
--value([color]) 50%,
--value([color]) 62.5%,
transparent 62.5%,
transparent 100%);
background-size: 10.66px 10.66px;
}
/*
STRIPED BACKGROUND EXAMPLE:
<div className="border border-[var(--pattern-fg)]
bg-[repeating-linear-gradient(45deg,_var(--pattern-fg)_0,_var(--pattern-fg)_1px,_transparent_0,_transparent_50%)]
bg-[size:10px_10px] bg-fixed
[--pattern-fg:#696969] dark:[--pattern-fg:#e1e1e1]" />
*/
@theme inline {
--font-sans: var(--font-inter);