Refactor Annuncio Card Component and Remove Legacy Code
- Updated `CardAnnuncio` component to enhance functionality and styling. - Removed `CardAnnuncio2` component as it was redundant. - Added new icons and badges for better visual representation of availability and status. - Improved layout and responsiveness of the card. - Updated translations in English and Italian for consistency. - Enhanced pagination controls in the `Annunci` page with new button styles. - Adjusted footer components to accept additional class names for customization. - Cleaned up unused CSS animations and styles in `globals.css`. - Modified server-side controller to include homepage field in the Annuncio data structure.
This commit is contained in:
parent
4ca5c59ffe
commit
1fc855393f
17 changed files with 316 additions and 604 deletions
|
|
@ -27,6 +27,7 @@ import { WhatsAppIcon2 } from "~/components/svgs";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Separator } from "~/components/ui/separator";
|
import { Separator } from "~/components/ui/separator";
|
||||||
import { UserViewContext, UserViewProvider } from "~/lib/userViewContext";
|
import { UserViewContext, UserViewProvider } from "~/lib/userViewContext";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import {
|
import {
|
||||||
EnforcedSessionContext,
|
EnforcedSessionContext,
|
||||||
|
|
@ -37,41 +38,55 @@ import type { ValidSession } from "~/server/api/trpc";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
bodyClassName?: string;
|
||||||
|
containerClassName?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
noFooter?: boolean;
|
noFooter?: boolean;
|
||||||
|
footerClassName?: string;
|
||||||
}
|
}
|
||||||
export const Layout = ({ children, noFooter }: Props) => {
|
export const Layout = ({
|
||||||
|
children,
|
||||||
|
noFooter,
|
||||||
|
bodyClassName,
|
||||||
|
containerClassName,
|
||||||
|
footerClassName,
|
||||||
|
}: Props) => {
|
||||||
const { data: bannerData } = api.settings.getBannerData.useQuery({
|
const { data: bannerData } = api.settings.getBannerData.useQuery({
|
||||||
area_riservata: false,
|
area_riservata: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen w-full flex-col">
|
<div className={cn("flex min-h-screen w-full flex-col", bodyClassName)}>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
{bannerData?.map((banner) => (
|
{bannerData?.map((banner) => (
|
||||||
<div key={`banner-elem-${banner.idbanner}`}>
|
<div key={`banner-elem-${banner.idbanner}`}>
|
||||||
{BannerFactory(banner)}
|
{BannerFactory(banner)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<main className="h-full flex-1 grow">{children}</main>
|
<main className={cn("h-full flex-1 grow", containerClassName)}>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
|
||||||
{!noFooter && <Footer />}
|
{!noFooter && <Footer className={footerClassName} />}
|
||||||
{/* <CookieConsent /> */}
|
{/* <CookieConsent /> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface AreaRiservataLayoutProps extends Props {
|
||||||
|
ignoreSessionCheck?: boolean;
|
||||||
|
noSidebar?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const AreaRiservataLayout = ({
|
export const AreaRiservataLayout = ({
|
||||||
children,
|
children,
|
||||||
noSidebar,
|
noSidebar,
|
||||||
noFooter,
|
noFooter,
|
||||||
ignoreSessionCheck,
|
ignoreSessionCheck,
|
||||||
}: {
|
bodyClassName,
|
||||||
children: ReactNode;
|
containerClassName,
|
||||||
noSidebar?: boolean;
|
footerClassName,
|
||||||
noFooter?: boolean;
|
}: AreaRiservataLayoutProps) => {
|
||||||
ignoreSessionCheck?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { data: bannerData } = api.settings.getBannerData.useQuery({
|
const { data: bannerData } = api.settings.getBannerData.useQuery({
|
||||||
area_riservata: true,
|
area_riservata: true,
|
||||||
});
|
});
|
||||||
|
|
@ -79,43 +94,70 @@ export const AreaRiservataLayout = ({
|
||||||
if (!ignoreSessionCheck) {
|
if (!ignoreSessionCheck) {
|
||||||
if (session.status === "pending")
|
if (session.status === "pending")
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-screen flex-col text-clip">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-full min-h-screen flex-col text-clip",
|
||||||
|
bodyClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
|
|
||||||
<main className="flex h-full flex-1 overflow-auto">
|
<main
|
||||||
|
className={cn(
|
||||||
|
"flex h-full flex-1 overflow-auto",
|
||||||
|
containerClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex h-auto w-full flex-col md:flex-row">
|
<div className="flex h-auto w-full flex-col md:flex-row">
|
||||||
<LoadingPage />
|
<LoadingPage />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{!noFooter && <MiniFooter />}
|
{!noFooter && <MiniFooter className={footerClassName} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
if (session.status !== "success" || !session.user) {
|
if (session.status !== "success" || !session.user) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-screen flex-col text-clip">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-full min-h-screen flex-col text-clip",
|
||||||
|
bodyClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
|
|
||||||
<main className="flex h-full flex-1 overflow-auto">
|
<main
|
||||||
|
className={cn(
|
||||||
|
"flex h-full flex-1 overflow-auto",
|
||||||
|
containerClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex h-auto w-full flex-col md:flex-row">
|
<div className="flex h-auto w-full flex-col md:flex-row">
|
||||||
<LoadingPage />
|
<LoadingPage />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{!noFooter && <MiniFooter />}
|
{!noFooter && <MiniFooter className={footerClassName} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-screen flex-col text-clip">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-full min-h-screen flex-col text-clip",
|
||||||
|
bodyClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
{bannerData?.map((banner) => (
|
{bannerData?.map((banner) => (
|
||||||
<div key={`banner-elem-${banner.idbanner}`}>
|
<div key={`banner-elem-${banner.idbanner}`}>
|
||||||
{BannerFactory(banner)}
|
{BannerFactory(banner)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<main className="flex h-full flex-1 overflow-auto">
|
<main
|
||||||
|
className={cn("flex h-full flex-1 overflow-auto", containerClassName)}
|
||||||
|
>
|
||||||
<div className="flex h-auto w-full flex-col md:flex-row">
|
<div className="flex h-auto w-full flex-col md:flex-row">
|
||||||
{noSidebar ? null : (
|
{noSidebar ? null : (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
|
|
@ -128,7 +170,7 @@ export const AreaRiservataLayout = ({
|
||||||
</EnforcedSessionContext.Provider>
|
</EnforcedSessionContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{!noFooter && <MiniFooter />}
|
{!noFooter && <MiniFooter className={footerClassName} />}
|
||||||
|
|
||||||
{/* <CookieConsent /> */}
|
{/* <CookieConsent /> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
interface AccordionCompProps extends InputHTMLAttributes<HTMLDivElement> {
|
interface AccordionCompProps extends InputHTMLAttributes<HTMLDivElement> {
|
||||||
texts: Faq[];
|
texts: Faq[];
|
||||||
|
defaultOpen?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -29,7 +30,7 @@ interface AccordionCompProps extends InputHTMLAttributes<HTMLDivElement> {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const AccordionComp = forwardRef<HTMLDivElement, AccordionCompProps>(
|
export const AccordionComp = forwardRef<HTMLDivElement, AccordionCompProps>(
|
||||||
({ texts, className }, ref) => {
|
({ texts, defaultOpen, className }, ref) => {
|
||||||
if (!texts || texts.length === 0) {
|
if (!texts || texts.length === 0) {
|
||||||
return null; // Return null if no texts are provided
|
return null; // Return null if no texts are provided
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +39,14 @@ export const AccordionComp = forwardRef<HTMLDivElement, AccordionCompProps>(
|
||||||
className={cn("mx-auto flex max-w-xl justify-center", className)}
|
className={cn("mx-auto flex max-w-xl justify-center", className)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<Accordion className="w-full" collapsible type="single">
|
<Accordion
|
||||||
|
className="w-full"
|
||||||
|
collapsible
|
||||||
|
defaultValue={
|
||||||
|
defaultOpen !== undefined ? `item-${defaultOpen}` : undefined
|
||||||
|
}
|
||||||
|
type="single"
|
||||||
|
>
|
||||||
{texts.map((text, index) => (
|
{texts.map((text, index) => (
|
||||||
// biome-ignore lint/suspicious/noArrayIndexKey: <index is safe here>
|
// biome-ignore lint/suspicious/noArrayIndexKey: <index is safe here>
|
||||||
<AccordionItem key={`ac-item-${index}`} value={`item-${index}`}>
|
<AccordionItem key={`ac-item-${index}`} value={`item-${index}`}>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { CardAnnuncio } from "~/components/annuncio_card2";
|
import { CardAnnuncio } from "~/components/annuncio_card";
|
||||||
import { getTitoloTranslation } from "~/lib/annuncio_details";
|
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
|
||||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||||
|
|
||||||
export const AnnunciGrid = ({ pagedata }: { pagedata: AnnuncioRicerca[] }) => {
|
export const AnnunciGrid = ({ pagedata }: { pagedata: AnnuncioRicerca[] }) => {
|
||||||
return (
|
return (
|
||||||
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
|
||||||
{pagedata.map((annuncio) => (
|
{pagedata.map((annuncio) => (
|
||||||
<CardAnnuncio key={annuncio.codice} {...annuncio} />
|
<CardAnnuncio key={annuncio.codice} {...annuncio} />
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
import { BedDouble, CalendarClock, Maximize2, Ruler } from "lucide-react";
|
import {
|
||||||
|
BedDouble,
|
||||||
|
CalendarClock,
|
||||||
|
MapPin,
|
||||||
|
Maximize2,
|
||||||
|
Ruler,
|
||||||
|
Siren,
|
||||||
|
Star,
|
||||||
|
TrafficCone,
|
||||||
|
} from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
@ -20,69 +29,95 @@ import {
|
||||||
import { camereTesti, handleConsegna } from "~/lib/annuncio_details";
|
import { camereTesti, handleConsegna } from "~/lib/annuncio_details";
|
||||||
import { cn, formatCurrency } from "~/lib/utils";
|
import { cn, formatCurrency } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||||
|
import { Badge } from "./ui/badge";
|
||||||
import { VideoPlayer } from "./videoPlayer";
|
import { VideoPlayer } from "./videoPlayer";
|
||||||
|
|
||||||
type CardAnnuncioProps = {
|
type CardAnnuncioProps = AnnuncioRicerca & {
|
||||||
id: number;
|
|
||||||
codice: string;
|
|
||||||
prezzo: number;
|
|
||||||
titolo: string | null;
|
|
||||||
mq: string | null;
|
|
||||||
comune: string | null;
|
|
||||||
provincia: string | null;
|
|
||||||
consegna: number | null;
|
|
||||||
camere: number | null;
|
|
||||||
//immagini: string[] | undefined;
|
|
||||||
tipo: string | null;
|
|
||||||
stato: string | null;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
videos?: string[] | undefined;
|
|
||||||
updated_at: Date | null;
|
|
||||||
images: { img: string; thumb: string }[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CardAnnuncio = ({
|
export const CardAnnuncio = ({
|
||||||
id,
|
id,
|
||||||
codice,
|
codice,
|
||||||
prezzo,
|
prezzo,
|
||||||
titolo,
|
titolo_it,
|
||||||
|
titolo_en,
|
||||||
mq,
|
mq,
|
||||||
comune,
|
comune,
|
||||||
provincia,
|
provincia,
|
||||||
consegna,
|
consegna,
|
||||||
camere,
|
numero_camere: camere,
|
||||||
//immagini,
|
|
||||||
tipo,
|
tipo,
|
||||||
stato,
|
stato,
|
||||||
className,
|
className,
|
||||||
videos,
|
url_video: videos,
|
||||||
updated_at,
|
updated_at,
|
||||||
images,
|
images,
|
||||||
|
homepage,
|
||||||
}: CardAnnuncioProps) => {
|
}: CardAnnuncioProps) => {
|
||||||
const { t } = useTranslation();
|
const { t, locale } = useTranslation();
|
||||||
|
const currentMonth = new Date().getMonth() + 1;
|
||||||
|
const isAvailableNow = consegna === 0 || consegna === currentMonth;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-[464px] rounded-md bg-white shadow-neutral-100 shadow-sm outline outline-neutral-400 hover:shadow-md hover:outline-neutral-600",
|
"h-[31.1rem] rounded-md bg-white shadow-neutral-100 shadow-sm hover:shadow-md hover:outline-neutral-600",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
id={`card-annuncio-${id}`}
|
id={`card-annuncio-${id}`}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
className="block p-2"
|
className="flex h-full flex-col gap-2 p-2"
|
||||||
//target="_blank"
|
|
||||||
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
||||||
>
|
>
|
||||||
<div className="group relative overflow-clip text-clip rounded-md outline outline-neutral-300">
|
<div
|
||||||
|
className={cn("group relative overflow-clip text-clip rounded-md")}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute inset-0 z-20 size-full rounded-md",
|
||||||
|
homepage &&
|
||||||
|
(isAvailableNow
|
||||||
|
? "border-[.35rem] border-green-500"
|
||||||
|
: "border-[.35rem] border-blue-500"),
|
||||||
|
|
||||||
|
stato === "Trattativa" && "border-[.35rem] border-violet-500",
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
{stato !== "Trattativa" && homepage && (
|
||||||
|
<div className="absolute top-1 z-20 flex w-full justify-center px-2">
|
||||||
|
<Badge
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-1.5 rounded-t-none px-1 py-0.5 font-semibold text-base text-white shadow-lg [&>svg]:size-4.5",
|
||||||
|
isAvailableNow ? "bg-green-500" : "bg-blue-500",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isAvailableNow ? (
|
||||||
|
<>
|
||||||
|
<span>Disponibile Subito</span>
|
||||||
|
<Siren />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span>In Evidenza</span>
|
||||||
|
<Star />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{stato === "Trattativa" && (
|
{stato === "Trattativa" && (
|
||||||
<div>
|
<div className="absolute top-1 z-20 flex w-full justify-center px-2">
|
||||||
<div className="absolute z-20 h-56 w-full">
|
<Badge
|
||||||
<div className="absolute bottom-0 w-full touch-none select-none rounded-b-md bg-violet-500 text-center text-lg text-white">
|
className={cn(
|
||||||
Annuncio in Trattativa
|
"flex items-center gap-1.5 rounded-t-none bg-violet-500 px-1 py-0.5 font-semibold text-base text-white shadow-lg [&>svg]:size-4.5",
|
||||||
</div>
|
)}
|
||||||
</div>
|
>
|
||||||
|
<span>In Trattativa</span>
|
||||||
|
<TrafficCone />
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -93,7 +128,7 @@ export const CardAnnuncio = ({
|
||||||
<ImageFlbk
|
<ImageFlbk
|
||||||
alt={t.card.alt_immagine}
|
alt={t.card.alt_immagine}
|
||||||
blurDataURL={`/storage-api/get/${img.thumb}?image=true`}
|
blurDataURL={`/storage-api/get/${img.thumb}?image=true`}
|
||||||
className={"h-56 w-full rounded-md object-cover"}
|
className={"h-64 w-full object-cover"}
|
||||||
height={1080}
|
height={1080}
|
||||||
priority={idx === 0}
|
priority={idx === 0}
|
||||||
src={`/storage-api/get/${img.img}?image=true`}
|
src={`/storage-api/get/${img.img}?image=true`}
|
||||||
|
|
@ -149,27 +184,27 @@ export const CardAnnuncio = ({
|
||||||
</div>
|
</div>
|
||||||
</Carousel>
|
</Carousel>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex h-full grow flex-col gap-2">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-2 flex w-full items-center justify-center rounded-md",
|
"flex w-full items-center justify-center rounded-md",
|
||||||
tipo === "Transitorio" && "bg-transitorio",
|
tipo === "Transitorio" && "bg-transitorio",
|
||||||
tipo === "Stabile" && "bg-stabile",
|
tipo === "Stabile" && "bg-stabile",
|
||||||
tipo === "Cessione" && "bg-blue-400",
|
tipo === "Cessione" && "bg-blue-400",
|
||||||
tipo === "Vendita" && "bg-green-400",
|
tipo === "Vendita" && "bg-green-400",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="font-semibold text-white">{tipo}</div>
|
<div className="font-semibold text-white">Affitto {tipo}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 space-y-2">
|
<div className="flex flex-row justify-between gap-3 px-4">
|
||||||
<div className="flex flex-row justify-around">
|
<div className="w-full text-center">
|
||||||
<div className="text-center">
|
|
||||||
<span className="sr-only">{t.card.codice}</span>
|
<span className="sr-only">{t.card.codice}</span>
|
||||||
|
|
||||||
<span className="font-semibold text-red-600 text-xl">
|
<span className="font-semibold text-red-500 text-xl">
|
||||||
Cod: {codice}
|
Cod: {codice}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="w-full text-center">
|
||||||
<span className="sr-only">{t.card.prezzo}</span>
|
<span className="sr-only">{t.card.prezzo}</span>
|
||||||
|
|
||||||
<span className="font-semibold text-xl">
|
<span className="font-semibold text-xl">
|
||||||
|
|
@ -177,26 +212,26 @@ export const CardAnnuncio = ({
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex h-full grow flex-col justify-between">
|
||||||
<div className="mb-3 text-center text-muted-foreground">
|
<div className="px-4 text-center text-muted-foreground">
|
||||||
<span className="sr-only">{t.card.titolo}</span>
|
<span className="sr-only">{t.card.titolo}</span>
|
||||||
|
|
||||||
<span className="line-clamp-2 min-h-[3.5rem] font-medium text-base md:text-lg">
|
<span className="line-clamp-3 overflow-ellipsis font-medium text-base">
|
||||||
{titolo}
|
{locale === "it" ? titolo_it : titolo_en}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
<div className="text-center text-muted-foreground">
|
<div className="text-center text-muted-foreground">
|
||||||
<span className="sr-only">{t.card.indirizzo}</span>
|
<span className="sr-only">{t.card.indirizzo}</span>
|
||||||
|
<MapPin className="mr-1 mb-1 inline-block size-4" />
|
||||||
<span className="font-medium text-sm">
|
<span className="font-medium text-sm">
|
||||||
{comune && provincia && `${comune} (${provincia})`}
|
{comune && provincia && `${comune} (${provincia})`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4 flex items-center justify-around gap-2 text-muted-foreground text-xs">
|
<div className="flex h-9 items-center justify-around gap-1.5 text-neutral-700 text-xs">
|
||||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-neutral-100">
|
||||||
<Ruler className="size-4 text-indigo-700" />
|
<Ruler className="size-4 text-neutral-900" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
|
|
@ -207,8 +242,8 @@ export const CardAnnuncio = ({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-neutral-100">
|
||||||
<BedDouble className="size-4 text-indigo-700" />
|
<BedDouble className="size-4 text-neutral-900" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
|
|
@ -216,8 +251,8 @@ export const CardAnnuncio = ({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-neutral-100">
|
||||||
<CalendarClock className="size-4 text-indigo-700" />
|
<CalendarClock className="size-4 text-neutral-900" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="truncate font-medium">
|
<p className="truncate font-medium">
|
||||||
|
|
@ -232,6 +267,9 @@ export const CardAnnuncio = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -273,7 +311,7 @@ export const CarouselAnnuncio = ({
|
||||||
<ImageFlbk
|
<ImageFlbk
|
||||||
alt={img}
|
alt={img}
|
||||||
className={cn(
|
className={cn(
|
||||||
"aspect-square max-h-92 w-full rounded-md object-cover sm:max-h-80",
|
"aspect-square max-h-92 w-full rounded-md object-cover sm:max-h-80 xl:max-h-[25rem]",
|
||||||
immagini.length === 1 && "object-contain",
|
immagini.length === 1 && "object-contain",
|
||||||
)}
|
)}
|
||||||
height={400}
|
height={400}
|
||||||
|
|
|
||||||
|
|
@ -1,404 +0,0 @@
|
||||||
import { BedDouble, CalendarClock, Maximize2, Ruler } from "lucide-react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { ImageFlbk } from "~/components/ImageWithFallback";
|
|
||||||
import {
|
|
||||||
Carousel,
|
|
||||||
CarouselContent,
|
|
||||||
CarouselItem,
|
|
||||||
CarouselNext,
|
|
||||||
CarouselPrevious,
|
|
||||||
} from "~/components/ui/carousel";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "~/components/ui/dialog";
|
|
||||||
import { camereTesti, handleConsegna } from "~/lib/annuncio_details";
|
|
||||||
import { cn, formatCurrency } from "~/lib/utils";
|
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
|
||||||
|
|
||||||
import { VideoPlayer } from "./videoPlayer";
|
|
||||||
|
|
||||||
type CardAnnuncioProps = {
|
|
||||||
id: number;
|
|
||||||
codice: string;
|
|
||||||
prezzo: number;
|
|
||||||
titolo: string | null;
|
|
||||||
mq: string | null;
|
|
||||||
comune: string | null;
|
|
||||||
provincia: string | null;
|
|
||||||
consegna: number | null;
|
|
||||||
camere: number | null;
|
|
||||||
//immagini: string[] | undefined;
|
|
||||||
tipo: string | null;
|
|
||||||
stato: string | null;
|
|
||||||
className?: string;
|
|
||||||
videos?: string[] | undefined;
|
|
||||||
updated_at: Date | null;
|
|
||||||
images: { img: string; thumb: string }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CardAnnuncio = ({
|
|
||||||
id,
|
|
||||||
codice,
|
|
||||||
prezzo,
|
|
||||||
titolo,
|
|
||||||
mq,
|
|
||||||
comune,
|
|
||||||
provincia,
|
|
||||||
consegna,
|
|
||||||
camere,
|
|
||||||
//immagini,
|
|
||||||
tipo,
|
|
||||||
stato,
|
|
||||||
className,
|
|
||||||
videos,
|
|
||||||
updated_at,
|
|
||||||
images,
|
|
||||||
}: CardAnnuncioProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"h-[464px] rounded-md bg-white shadow-neutral-100 shadow-sm hover:shadow-md hover:outline-neutral-600",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
id={`card-annuncio-${id}`}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
className="block p-2"
|
|
||||||
//target="_blank"
|
|
||||||
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
|
||||||
>
|
|
||||||
<div className="group relative overflow-clip text-clip rounded-md">
|
|
||||||
<div className="absolute top-2 left-2 z-20 rounded-md bg-white px-2 py-1 font-semibold text-neutral-800 text-sm shadow-md shadow-neutral-300 backdrop-blur-sm">
|
|
||||||
Cod: {codice}
|
|
||||||
</div>
|
|
||||||
{stato === "Trattativa" && (
|
|
||||||
<div>
|
|
||||||
<div className="absolute z-20 h-56 w-full">
|
|
||||||
<div className="absolute bottom-0 w-full touch-none select-none rounded-b-md bg-violet-500 text-center text-lg text-white">
|
|
||||||
Annuncio in Trattativa
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Carousel opts={{ loop: true }}>
|
|
||||||
<CarouselContent>
|
|
||||||
{images?.map((img, idx) => (
|
|
||||||
<CarouselItem key={`${img.img}key`}>
|
|
||||||
<ImageFlbk
|
|
||||||
alt={t.card.alt_immagine}
|
|
||||||
blurDataURL={`/storage-api/get/${img.thumb}?image=true`}
|
|
||||||
className={"h-64 w-full object-cover"}
|
|
||||||
height={1080}
|
|
||||||
priority={idx === 0}
|
|
||||||
src={`/storage-api/get/${img.img}?image=true`}
|
|
||||||
width={1920}
|
|
||||||
/>
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
{videos?.map((video) => {
|
|
||||||
if (!video) return null;
|
|
||||||
if (video.includes("youtu")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<CarouselItem
|
|
||||||
className={cn("group relative")}
|
|
||||||
key={`videoplayer-${video}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VideoPlayer
|
|
||||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
|
||||||
className="h-56"
|
|
||||||
coverImage={
|
|
||||||
images?.[0]
|
|
||||||
? `/storage-api/get/${images[0].img}?image=true`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
key={`videoplayer-${video}`}
|
|
||||||
videoSrc={video}
|
|
||||||
/>
|
|
||||||
</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={!images || images.length === 1}
|
|
||||||
/>
|
|
||||||
<CarouselNext
|
|
||||||
className="right-2 cursor-pointer"
|
|
||||||
disabled={!images || images.length === 1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Carousel>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"mt-2 flex w-full items-center justify-center rounded-md",
|
|
||||||
tipo === "Transitorio" && "bg-transitorio",
|
|
||||||
tipo === "Stabile" && "bg-stabile",
|
|
||||||
tipo === "Cessione" && "bg-blue-400",
|
|
||||||
tipo === "Vendita" && "bg-green-400",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="font-semibold text-white">{tipo}</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 space-y-2">
|
|
||||||
<div className="flex flex-row justify-around">
|
|
||||||
<div className="text-center">
|
|
||||||
<span className="sr-only">{t.card.codice}</span>
|
|
||||||
|
|
||||||
<span className="font-semibold text-red-600 text-xl">
|
|
||||||
Cod: {codice}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<span className="sr-only">{t.card.prezzo}</span>
|
|
||||||
|
|
||||||
<span className="font-semibold text-xl">
|
|
||||||
{formatCurrency(prezzo / 1e2)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-3 text-center text-muted-foreground">
|
|
||||||
<span className="sr-only">{t.card.titolo}</span>
|
|
||||||
|
|
||||||
<span className="line-clamp-2 min-h-[3.5rem] font-medium text-base md:text-lg">
|
|
||||||
{titolo}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-center text-muted-foreground">
|
|
||||||
<span className="sr-only">{t.card.indirizzo}</span>
|
|
||||||
|
|
||||||
<span className="font-medium text-sm">
|
|
||||||
{comune && provincia && `${comune} (${provincia})`}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4 flex items-center justify-around gap-2 text-muted-foreground text-xs">
|
|
||||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
|
||||||
<Ruler className="size-4 text-indigo-700" />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">
|
|
||||||
{mq ? Number.parseFloat(mq) : 0}{" "}
|
|
||||||
<span>
|
|
||||||
m<sup>2</sup>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
|
||||||
<BedDouble className="size-4 text-indigo-700" />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">
|
|
||||||
{camereTesti({ camere: camere, testi: t.card })}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
|
||||||
<CalendarClock className="size-4 text-indigo-700" />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="truncate font-medium">
|
|
||||||
{handleConsegna({
|
|
||||||
aggiornamento: t.card.in_aggiornamento,
|
|
||||||
consegna: consegna,
|
|
||||||
consegna_da: t.card.consegna_da,
|
|
||||||
mesi: t.preferenze.mesi,
|
|
||||||
subito: t.card.consegna_subito,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CarouselAnnuncio = ({
|
|
||||||
single,
|
|
||||||
immagini,
|
|
||||||
videos,
|
|
||||||
updated_at,
|
|
||||||
}: {
|
|
||||||
single?: boolean;
|
|
||||||
immagini: string[] | null;
|
|
||||||
videos: string[] | null;
|
|
||||||
updated_at: Date | null;
|
|
||||||
}) => {
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
|
||||||
const [idxModal, setIdxModal] = useState(0);
|
|
||||||
function handleOpenModal(position: number) {
|
|
||||||
setIdxModal(position);
|
|
||||||
setOpenModal(true);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mx-auto my-4 w-full text-clip rounded-md">
|
|
||||||
<div className="relative">
|
|
||||||
<Carousel opts={{ align: "start", loop: true }}>
|
|
||||||
<CarouselContent>
|
|
||||||
{immagini?.map((img, idx) => (
|
|
||||||
<CarouselItem
|
|
||||||
className={cn(
|
|
||||||
"group relative flex items-center justify-center",
|
|
||||||
immagini && immagini.length === 1
|
|
||||||
? ""
|
|
||||||
: !single && "md:basis-1/2 lg:basis-1/3",
|
|
||||||
)}
|
|
||||||
key={`${img}-cF`}
|
|
||||||
>
|
|
||||||
<ImageFlbk
|
|
||||||
alt={img}
|
|
||||||
className={cn(
|
|
||||||
"aspect-square max-h-92 w-full rounded-md object-cover sm:max-h-80",
|
|
||||||
immagini.length === 1 && "object-contain",
|
|
||||||
)}
|
|
||||||
height={400}
|
|
||||||
onClick={() => handleOpenModal(idx)}
|
|
||||||
priority={idx === 0}
|
|
||||||
src={`/storage-api/get/${img}?image=true&${updated_at?.toString() || new Date().toString()}`}
|
|
||||||
width={800}
|
|
||||||
/>
|
|
||||||
<Maximize2
|
|
||||||
className="absolute right-2 bottom-2 size-7 origin-bottom-right cursor-pointer rounded-md bg-white/70 stroke-2 p-1 transition-all duration-300 ease-in-out hover:scale-150 group-hover:opacity-100 sm:opacity-0"
|
|
||||||
onClick={() => handleOpenModal(idx)}
|
|
||||||
/>
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
{videos?.map((video, idx) => {
|
|
||||||
if (!video) return null;
|
|
||||||
if (video.includes("youtu")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<CarouselItem
|
|
||||||
className={cn(
|
|
||||||
"group relative",
|
|
||||||
immagini && immagini.length === 1
|
|
||||||
? ""
|
|
||||||
: !single && "md:basis-1/2 lg:basis-1/3",
|
|
||||||
)}
|
|
||||||
key={`videoplayer-${video}`}
|
|
||||||
>
|
|
||||||
<VideoPlayer
|
|
||||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
|
||||||
className="h-72"
|
|
||||||
coverImage={
|
|
||||||
immagini?.[0]
|
|
||||||
? `/storage-api/get/${immagini[0]}?image=true`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
key={`videoplayer-${idx}-${openModal}`}
|
|
||||||
videoSrc={video}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Maximize2
|
|
||||||
className="absolute right-2 bottom-2 size-7 origin-bottom-right cursor-pointer rounded-md bg-white/70 stroke-2 p-1 transition-all duration-300 ease-in-out hover:scale-150 group-hover:opacity-100 sm:opacity-0"
|
|
||||||
onClick={() =>
|
|
||||||
handleOpenModal(immagini?.length || 0 + idx)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</CarouselItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CarouselContent>
|
|
||||||
|
|
||||||
<CarouselPrevious
|
|
||||||
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
|
|
||||||
disabled={!immagini || immagini.length === 1}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
<CarouselNext
|
|
||||||
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
|
|
||||||
disabled={!immagini || immagini.length === 1}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
</Carousel>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Dialog onOpenChange={setOpenModal} open={openModal}>
|
|
||||||
<DialogContent className="flex h-full w-full max-w-full items-center justify-center border-transparent border-none bg-white p-0 md:max-w-[80vw] md:p-6 [&_#dialog-close>svg]:size-8">
|
|
||||||
<DialogHeader className="absolute top-0 right-0 left-0 z-10">
|
|
||||||
<DialogTitle className="sr-only">Image Dialog</DialogTitle>
|
|
||||||
<DialogDescription className="sr-only">Immagine</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<Carousel opts={{ loop: true, startIndex: idxModal }}>
|
|
||||||
<CarouselContent>
|
|
||||||
{immagini?.map((img, idx) => (
|
|
||||||
<CarouselItem key={`${img}-cD`}>
|
|
||||||
<Image
|
|
||||||
alt={`carousel-img-${idx}`}
|
|
||||||
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]"
|
|
||||||
height={1080}
|
|
||||||
src={`/storage-api/get/${img}?image=true&${updated_at?.toString() || new Date().toString()}`}
|
|
||||||
width={1920}
|
|
||||||
/>
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
{videos?.map((video) => {
|
|
||||||
if (!video) return null;
|
|
||||||
if (video.includes("youtu")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<CarouselItem
|
|
||||||
className="relative flex items-center justify-center"
|
|
||||||
key={`carousel-videoplayer-${video}`}
|
|
||||||
>
|
|
||||||
<VideoPlayer
|
|
||||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
|
||||||
className="absolute mx-auto h-[90vh] w-full max-w-fit rounded-md object-contain"
|
|
||||||
coverImage={
|
|
||||||
immagini?.[0]
|
|
||||||
? `/storage-api/get/${immagini[0]}?image=true`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
videoSrc={video}
|
|
||||||
/>
|
|
||||||
</CarouselItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CarouselContent>
|
|
||||||
|
|
||||||
<CarouselPrevious
|
|
||||||
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
|
|
||||||
disabled={!immagini || immagini.length === 1}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
<CarouselNext
|
|
||||||
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
|
|
||||||
disabled={!immagini || immagini.length === 1}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
</Carousel>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,12 +1,22 @@
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import BlurryDivider from "~/components/blurry_divider";
|
import BlurryDivider from "~/components/blurry_divider";
|
||||||
import { LogoSvg } from "~/components/svgs";
|
import { LogoSvg } from "~/components/svgs";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
export const Footer = () => {
|
|
||||||
|
type FooterProps = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
export const Footer = ({ className }: FooterProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="mt-auto">
|
<div className="mt-auto">
|
||||||
<footer className="bottom-0 z-40 w-full bg-muted text-muted-foreground">
|
<footer
|
||||||
|
className={cn(
|
||||||
|
"bottom-0 z-40 w-full bg-muted text-muted-foreground",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="mx-auto max-w-6xl px-4 py-6 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-6xl px-4 py-6 sm:px-6 lg:px-8">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -73,11 +83,16 @@ export const Footer = () => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export const MiniFooter = () => {
|
export const MiniFooter = ({ className }: FooterProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="mt-auto flex w-full">
|
<div className="mt-auto flex w-full">
|
||||||
<footer className="z-40 w-full bg-neutral-100 text-neutral-700 dark:bg-primary dark:text-white">
|
<footer
|
||||||
|
className={cn(
|
||||||
|
"z-40 w-full bg-neutral-100 text-neutral-700 dark:bg-primary dark:text-white",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="mx-auto max-w-5xl px-2 py-2 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-5xl px-2 py-2 sm:px-6 lg:px-8">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link
|
<Link
|
||||||
|
|
|
||||||
|
|
@ -278,9 +278,9 @@ export const en: LangDict = {
|
||||||
annulla: "Cancel",
|
annulla: "Cancel",
|
||||||
annunci: {
|
annunci: {
|
||||||
accordion_minifaq: [
|
accordion_minifaq: [
|
||||||
|
Faqs.cosa_significa_transitorio,
|
||||||
Faqs.posso_visitarli,
|
Faqs.posso_visitarli,
|
||||||
Faqs.posso_contattarvi,
|
Faqs.posso_contattarvi,
|
||||||
Faqs.cosa_significa_transitorio,
|
|
||||||
Faqs.come_confermare,
|
Faqs.come_confermare,
|
||||||
],
|
],
|
||||||
comune: "City",
|
comune: "City",
|
||||||
|
|
|
||||||
|
|
@ -283,9 +283,9 @@ export const it: LangDict = {
|
||||||
annulla: "Annulla",
|
annulla: "Annulla",
|
||||||
annunci: {
|
annunci: {
|
||||||
accordion_minifaq: [
|
accordion_minifaq: [
|
||||||
|
Faqs.cosa_significa_transitorio,
|
||||||
Faqs.posso_visitarli,
|
Faqs.posso_visitarli,
|
||||||
Faqs.posso_contattarvi,
|
Faqs.posso_contattarvi,
|
||||||
Faqs.cosa_significa_transitorio,
|
|
||||||
Faqs.come_confermare,
|
Faqs.come_confermare,
|
||||||
],
|
],
|
||||||
comune: "Comune",
|
comune: "Comune",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { keepPreviousData } from "@tanstack/react-query";
|
import { keepPreviousData } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
ChevronsRight,
|
ChevronsLeft,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
Filter,
|
Filter,
|
||||||
FilterX,
|
FilterX,
|
||||||
|
|
@ -18,6 +19,7 @@ import { AnnunciGrid } from "~/components/annunci_grid";
|
||||||
import { MapSection } from "~/components/annunci_map";
|
import { MapSection } from "~/components/annunci_map";
|
||||||
import { CTA_TipologiaModal } from "~/components/annunci_tutorial";
|
import { CTA_TipologiaModal } from "~/components/annunci_tutorial";
|
||||||
import { CodiceBox } from "~/components/codiceRicerca";
|
import { CodiceBox } from "~/components/codiceRicerca";
|
||||||
|
import { Layout } from "~/components/Layout";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { Status500 } from "~/components/status-page";
|
import { Status500 } from "~/components/status-page";
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
|
@ -60,23 +62,52 @@ const Annunci = ({ options }: AnnunciPageProps) => {
|
||||||
<meta content={t.heads.annunci_description} name="description" />
|
<meta content={t.heads.annunci_description} name="description" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<main className="mx-auto w-full max-w-7xl space-y-5 bg-background2 px-2 py-5 md:px-8">
|
<main className="relative mx-auto w-full max-w-7xl space-y-5 bg-background2 px-2 py-5 md:px-8">
|
||||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||||
{t.annunci.titolo}
|
{t.annunci.titolo}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<GoBackButton />
|
||||||
|
|
||||||
<Ricerca />
|
<Ricerca />
|
||||||
<AccordionComp
|
<AccordionComp
|
||||||
className="max-w-6xl px-4 py-4 md:py-8"
|
className="max-w-6xl px-4 py-4 md:py-8"
|
||||||
|
defaultOpen={0}
|
||||||
texts={t.annunci.accordion_minifaq}
|
texts={t.annunci.accordion_minifaq}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
</RicercaProvider>
|
</RicercaProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const GoBackButton = () => {
|
||||||
|
const { page, setPage } = useRicerca();
|
||||||
|
if (page >= 2) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className="absolute right-0 h-auto py-0 text-base text-neutral-600 underline underline-offset-1 sm:hidden"
|
||||||
|
onClick={async () => {
|
||||||
|
await setPage(0);
|
||||||
|
}}
|
||||||
|
variant="link"
|
||||||
|
>
|
||||||
|
Torna all'inizio
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
export default Annunci;
|
export default Annunci;
|
||||||
|
|
||||||
|
Annunci.getLayout = (page: React.ReactNode) => {
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
containerClassName="bg-background2"
|
||||||
|
footerClassName="bg-background2"
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getServerSideProps = (async () => {
|
export const getServerSideProps = (async () => {
|
||||||
const helper = generateSSGHelper();
|
const helper = generateSSGHelper();
|
||||||
const options = await helper.annunci.getAnnunciOptions.fetch();
|
const options = await helper.annunci.getAnnunciOptions.fetch();
|
||||||
|
|
@ -94,11 +125,11 @@ const Ricerca = () => {
|
||||||
}, [page]);
|
}, [page]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="space-y-5">
|
||||||
<RicercaFilters />
|
<RicercaFilters />
|
||||||
<br />
|
|
||||||
{map ? <MapSection /> : <AnnunciList />}
|
{map ? <MapSection /> : <AnnunciList />}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -338,63 +369,53 @@ const AnnunciList = () => {
|
||||||
) : (
|
) : (
|
||||||
<AnnunciGrid pagedata={data.annunci} />
|
<AnnunciGrid pagedata={data.annunci} />
|
||||||
)}
|
)}
|
||||||
<br />
|
|
||||||
|
|
||||||
<div className="mx-auto grid max-w-sm grid-cols-5 items-center justify-between gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex w-24 items-center justify-end gap-2">
|
||||||
<button
|
{page >= 2 && (
|
||||||
aria-label="Reset Page"
|
<Button
|
||||||
className={cn(
|
aria-label="Go to first page"
|
||||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
|
||||||
page < 2 ? "invisible" : "visible",
|
|
||||||
)}
|
|
||||||
disabled={page < 2}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await setPage(0);
|
await setPage(0);
|
||||||
}}
|
}}
|
||||||
type="button"
|
title="Go to first page"
|
||||||
|
variant="outline"
|
||||||
>
|
>
|
||||||
<ChevronsRight className="rotate-180" height={30} />
|
<ChevronsLeft className="h-4 w-4" />
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<button
|
|
||||||
aria-label="Previous Page"
|
|
||||||
className={cn(
|
|
||||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
|
||||||
page < 1 ? "invisible" : "visible",
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
aria-label="Previous page"
|
||||||
disabled={page === 0}
|
disabled={page === 0}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await setPage((old) => (old ? Math.max(old - 1, 0) : 0));
|
await setPage((old) => (old ? Math.max(old - 1, 0) : 0));
|
||||||
}}
|
}}
|
||||||
type="button"
|
title="Previous page"
|
||||||
|
variant="outline"
|
||||||
>
|
>
|
||||||
<ChevronRight className="rotate-180" height={30} />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<span className="font-semibold text-lg text-neutral-500">
|
<div className="mx-4 w-12 rounded-md bg-white py-2 text-center font-medium text-lg">
|
||||||
{page + 1}
|
{page + 1}
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<button
|
<div className="flex w-24 items-center justify-start gap-2">
|
||||||
aria-label="Next Page"
|
<Button
|
||||||
className={cn(
|
aria-label="Next page"
|
||||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
|
||||||
data?.hasMore ? "visible" : "invisible",
|
|
||||||
)}
|
|
||||||
disabled={isPlaceholderData || !data?.hasMore}
|
disabled={isPlaceholderData || !data?.hasMore}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await setPage((old) =>
|
await setPage((old) =>
|
||||||
old ? (data?.hasMore ? old + 1 : old) : 1,
|
old ? (data?.hasMore ? old + 1 : old) : 1,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
type="button"
|
title="Next page"
|
||||||
|
variant="outline"
|
||||||
>
|
>
|
||||||
<ChevronRight height={30} />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
||||||
return (
|
return (
|
||||||
<AnnuncioContext.Provider value={data}>
|
<AnnuncioContext.Provider value={data}>
|
||||||
<TouchProvider>
|
<TouchProvider>
|
||||||
<div className="mx-auto w-full px-2 sm:px-8">
|
<div className="mx-auto w-full max-w-[100rem] px-2 sm:px-8">
|
||||||
<CarouselAnnuncio
|
<CarouselAnnuncio
|
||||||
immagini={data.images.map((img) => img.img)}
|
immagini={data.images.map((img) => img.img)}
|
||||||
updated_at={data.updated_at}
|
updated_at={data.updated_at}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const ChiSiamo: NextPage = () => {
|
||||||
<meta content={t.heads.main_description} name="description" />
|
<meta content={t.heads.main_description} name="description" />
|
||||||
</Head>
|
</Head>
|
||||||
<main className="mx-auto w-full max-w-7xl space-y-5 px-2 py-5 md:px-8">
|
<main className="mx-auto w-full max-w-7xl space-y-5 px-2 py-5 md:px-8">
|
||||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||||
{t.chi_siamo.title}
|
{t.chi_siamo.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto w-full max-w-6xl space-y-5 sm:space-y-14">
|
<div className="mx-auto w-full max-w-6xl space-y-5 sm:space-y-14">
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ const Contatto: NextPage = () => {
|
||||||
<meta content={t.heads.main_description} name="description" />
|
<meta content={t.heads.main_description} name="description" />
|
||||||
</Head>
|
</Head>
|
||||||
<main className="mx-auto w-full max-w-7xl px-2 py-5 md:px-8">
|
<main className="mx-auto w-full max-w-7xl px-2 py-5 md:px-8">
|
||||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||||
{t.contatti}
|
{t.contatti}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const Guida: NextPage = () => {
|
||||||
<meta content={t.heads.main_description} name="description" />
|
<meta content={t.heads.main_description} name="description" />
|
||||||
</Head>
|
</Head>
|
||||||
<main className="mx-auto w-full max-w-7xl px-2 py-5 md:px-8">
|
<main className="mx-auto w-full max-w-7xl px-2 py-5 md:px-8">
|
||||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||||
{t.guida}
|
{t.guida}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ const Prezzi: NextPage = () => {
|
||||||
<meta content={t.heads.prezzi_description} name="description" />
|
<meta content={t.heads.prezzi_description} name="description" />
|
||||||
</Head>
|
</Head>
|
||||||
<section className="mx-auto max-w-7xl px-2 py-5 md:px-8">
|
<section className="mx-auto max-w-7xl px-2 py-5 md:px-8">
|
||||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||||
{t.prezzi_titolo}
|
{t.prezzi_titolo}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 space-y-14">
|
<div className="mt-5 space-y-14">
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const Proprietari: NextPage = () => {
|
||||||
<meta content={t.proprietari.description} name="description" />
|
<meta content={t.proprietari.description} name="description" />
|
||||||
</Head>
|
</Head>
|
||||||
<main className="mx-auto w-full max-w-7xl space-y-5 px-2 py-5 md:px-8">
|
<main className="mx-auto w-full max-w-7xl space-y-5 px-2 py-5 md:px-8">
|
||||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||||
{t.proprietari.titolo}
|
{t.proprietari.titolo}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto w-full space-y-5 sm:space-y-14">
|
<div className="mx-auto w-full space-y-5 sm:space-y-14">
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,7 @@ export const get_AnnunciPositionsHandler = async ({
|
||||||
"lon_secondario",
|
"lon_secondario",
|
||||||
"lat_secondario",
|
"lat_secondario",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
"homepage",
|
||||||
jsonArrayFrom(
|
jsonArrayFrom(
|
||||||
eb
|
eb
|
||||||
.selectFrom("images_refs")
|
.selectFrom("images_refs")
|
||||||
|
|
@ -268,6 +269,7 @@ export type AnnuncioRicerca = Pick<
|
||||||
| "stato"
|
| "stato"
|
||||||
| "url_video"
|
| "url_video"
|
||||||
| "updated_at"
|
| "updated_at"
|
||||||
|
| "homepage"
|
||||||
> & {
|
> & {
|
||||||
images: Pick<ImagesRefs, "img" | "thumb">[];
|
images: Pick<ImagesRefs, "img" | "thumb">[];
|
||||||
};
|
};
|
||||||
|
|
@ -286,7 +288,10 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
comune?: string;
|
comune?: string;
|
||||||
consegna?: number;
|
consegna?: number;
|
||||||
sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
|
sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
|
||||||
}) => {
|
}): Promise<{
|
||||||
|
annunci: AnnuncioRicerca[];
|
||||||
|
hasMore: boolean;
|
||||||
|
}> => {
|
||||||
try {
|
try {
|
||||||
let query = db
|
let query = db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
|
|
@ -306,6 +311,7 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
"stato",
|
"stato",
|
||||||
"url_video",
|
"url_video",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
"homepage",
|
||||||
jsonArrayFrom(
|
jsonArrayFrom(
|
||||||
eb
|
eb
|
||||||
.selectFrom("images_refs")
|
.selectFrom("images_refs")
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
--accordion-down: accordion-down 0.2s ease-out;
|
--accordion-down: accordion-down 0.2s ease-out;
|
||||||
--accordion-up: accordion-up 0.2s ease-out;
|
--accordion-up: accordion-up 0.2s ease-out;
|
||||||
--caret-blink: caret-blink 1.25s ease-out infinite;
|
--caret-blink: caret-blink 1.25s ease-out infinite;
|
||||||
--border-pulse: border-pulse 2s infinite;
|
|
||||||
|
|
||||||
@keyframes accordion-down {
|
@keyframes accordion-down {
|
||||||
0% {
|
0% {
|
||||||
|
|
@ -141,19 +141,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes border-pulse {
|
|
||||||
0%: {
|
|
||||||
border-color: hsl(var(--border)) / 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
50%: {
|
|
||||||
border-color: hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
100%: {
|
|
||||||
border-color: hsl(var(--border)) / 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
--animate-expand: expand 0.3s ease-out forwards;
|
--animate-expand: expand 0.3s ease-out forwards;
|
||||||
@keyframes expand {
|
@keyframes expand {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue