feat: update package dependencies and enhance UI components with badges; refactor service dialogs and remove unused components
This commit is contained in:
parent
23525d8f14
commit
b7850f6994
12 changed files with 237 additions and 431 deletions
2
apps/infoalloggi/package-lock.json
generated
2
apps/infoalloggi/package-lock.json
generated
|
|
@ -64,7 +64,7 @@
|
|||
"nuqs": "^2.4.3",
|
||||
"pg": "^8.16.3",
|
||||
"postcss": "^8.5.6",
|
||||
"radix-ui": "^1.4.2",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-day-picker": "^9.11.1",
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
"nuqs": "^2.4.3",
|
||||
"pg": "^8.16.3",
|
||||
"postcss": "^8.5.6",
|
||||
"radix-ui": "^1.4.2",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-day-picker": "^9.11.1",
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ export const InteressatoButtonServizio = ({
|
|||
variant="info"
|
||||
>
|
||||
<BadgePlus className="size-6" />
|
||||
{isAdmin ? "Aggiungi annuncio" : "Sono interessato"}
|
||||
{isAdmin ? "Aggiungi" : "Sono interessato"}
|
||||
</LoadingButton>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -230,17 +230,17 @@ export const CardAnnuncio = ({
|
|||
)}
|
||||
</div>
|
||||
<div className="flex h-full grow flex-col gap-2">
|
||||
<div
|
||||
<Badge
|
||||
className={cn(
|
||||
"flex w-full items-center justify-center rounded-md",
|
||||
"w-full font-semibold text-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">Affitto {tipo}</div>
|
||||
</div>
|
||||
Affitto {tipo}
|
||||
</Badge>
|
||||
<div className="flex flex-row justify-between gap-3 px-4">
|
||||
<div className="w-full text-center">
|
||||
<span className="sr-only">{t.card.codice}</span>
|
||||
|
|
|
|||
|
|
@ -1,344 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
import {
|
||||
type ComponentPropsWithoutRef,
|
||||
createContext,
|
||||
type Dispatch,
|
||||
type ElementRef,
|
||||
forwardRef,
|
||||
type HTMLAttributes,
|
||||
type PointerEvent,
|
||||
type ReactNode,
|
||||
type SetStateAction,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
interface DialogContextValue {
|
||||
innerOpen: boolean;
|
||||
setInnerOpen: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const DialogContext = createContext<DialogContextValue | undefined>(undefined);
|
||||
|
||||
function Dialog({ children }: { children: ReactNode }) {
|
||||
const [outerOpen, setOuterOpen] = useState(false);
|
||||
const [innerOpen, setInnerOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<DialogContext.Provider value={{ innerOpen, setInnerOpen }}>
|
||||
<DialogPrimitive.Root onOpenChange={setOuterOpen} open={outerOpen}>
|
||||
{children}
|
||||
</DialogPrimitive.Root>
|
||||
</DialogContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
className={cn(
|
||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-background/40 backdrop-blur-sm data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Content>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const context = useContext(DialogContext);
|
||||
if (!context) throw new Error("DialogContent must be used within a Dialog");
|
||||
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
className={cn(
|
||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:rounded-lg",
|
||||
context.innerOpen && "translate-y-[-55%] scale-[0.97]",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogClose className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
});
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
function InnerDialog({ children }: { children: ReactNode }) {
|
||||
const context = useContext(DialogContext);
|
||||
if (!context) throw new Error("InnerDialog must be used within a Dialog");
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscapeKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && context.innerOpen) {
|
||||
context.setInnerOpen(false);
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleEscapeKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscapeKeyDown);
|
||||
};
|
||||
}, [context.innerOpen, context.setInnerOpen]);
|
||||
|
||||
return (
|
||||
<DialogPrimitive.Root
|
||||
onOpenChange={context.setInnerOpen}
|
||||
open={context.innerOpen}
|
||||
>
|
||||
{children}
|
||||
</DialogPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
||||
const InnerDialogTrigger = DialogPrimitive.Trigger;
|
||||
const InnerDialogClose = DialogPrimitive.Close;
|
||||
|
||||
interface InnerDialogContentProps
|
||||
extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
||||
position?: "default" | "bottom" | "top" | "left" | "right";
|
||||
draggable?: boolean;
|
||||
}
|
||||
|
||||
const InnerDialogContent = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Content>,
|
||||
InnerDialogContentProps
|
||||
>(
|
||||
(
|
||||
{ className, children, position = "default", draggable = false, ...props },
|
||||
ref,
|
||||
) => {
|
||||
const context = useContext(DialogContext);
|
||||
if (!context)
|
||||
throw new Error("InnerDialogContent must be used within a Dialog");
|
||||
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [startY, setStartY] = useState(0);
|
||||
const [currentY, setCurrentY] = useState(0);
|
||||
const [isClosingByDrag, setIsClosingByDrag] = useState(false);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (context.innerOpen) {
|
||||
setCurrentY(0);
|
||||
setIsClosingByDrag(false);
|
||||
}
|
||||
}, [context.innerOpen]);
|
||||
|
||||
const handlePointerDown = (e: PointerEvent<HTMLDivElement>) => {
|
||||
if (!draggable) return;
|
||||
setIsDragging(true);
|
||||
setStartY(e.clientY - currentY);
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
};
|
||||
|
||||
const handlePointerMove = (e: PointerEvent<HTMLDivElement>) => {
|
||||
if (!isDragging || !draggable) return;
|
||||
const newY = e.clientY - startY;
|
||||
setCurrentY(newY > 0 ? newY : 0);
|
||||
};
|
||||
|
||||
const handlePointerUp = () => {
|
||||
if (!draggable) return;
|
||||
setIsDragging(false);
|
||||
if (currentY > (contentRef.current?.offsetHeight || 0) / 2) {
|
||||
setIsClosingByDrag(true);
|
||||
context.setInnerOpen(false);
|
||||
} else {
|
||||
setCurrentY(0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogPrimitive.Content
|
||||
className={cn(
|
||||
"fixed top-[50%] left-[50%] z-[60] grid w-full max-w-lg translate-x-[-50%] translate-y-[-45%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200",
|
||||
isClosingByDrag
|
||||
? "data-[state=closed]:fade-out-0 data-[state=closed]:animate-none"
|
||||
: "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
position === "default" &&
|
||||
"data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
|
||||
position === "bottom" &&
|
||||
"data-[state=closed]:slide-out-to-bottom-full data-[state=open]:slide-in-from-bottom-full",
|
||||
position === "top" &&
|
||||
"data-[state=closed]:slide-out-to-top-full data-[state=open]:slide-in-from-top-full",
|
||||
position === "left" &&
|
||||
"data-[state=closed]:slide-out-to-left-full data-[state=open]:slide-in-from-left-full",
|
||||
position === "right" &&
|
||||
"data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-right-full",
|
||||
draggable && "",
|
||||
className,
|
||||
)}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={handlePointerUp}
|
||||
ref={ref}
|
||||
style={{
|
||||
transform: `translate(-50%, calc(-50% + ${currentY}px))`,
|
||||
transition: isDragging ? "none" : "transform 0.3s ease-out",
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<div ref={contentRef}>{children}</div>
|
||||
<InnerDialogClose className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</InnerDialogClose>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
},
|
||||
);
|
||||
InnerDialogContent.displayName = "InnerDialogContent";
|
||||
|
||||
const InnerDialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
InnerDialogHeader.displayName = "InnerDialogHeader";
|
||||
|
||||
const InnerDialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("flex flex-col-reverse sm:flex-row sm:space-x-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
InnerDialogFooter.displayName = "InnerDialogFooter";
|
||||
|
||||
const InnerDialogTitle = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Title>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
className={cn(
|
||||
"font-semibold text-lg leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
InnerDialogTitle.displayName = "InnerDialogTitle";
|
||||
|
||||
const InnerDialogDescription = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Description>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
InnerDialogDescription.displayName = "InnerDialogDescription";
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogHeader.displayName = "DialogHeader";
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("flex flex-col-reverse sm:flex-row sm:space-x-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogFooter.displayName = "DialogFooter";
|
||||
|
||||
const DialogTitle = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Title>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
className={cn(
|
||||
"font-semibold text-lg leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const DialogDescription = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Description>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export type { InnerDialogContentProps };
|
||||
export {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogClose,
|
||||
InnerDialog,
|
||||
InnerDialogTrigger,
|
||||
InnerDialogContent,
|
||||
InnerDialogHeader,
|
||||
InnerDialogFooter,
|
||||
InnerDialogTitle,
|
||||
InnerDialogDescription,
|
||||
InnerDialogClose,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
};
|
||||
|
|
@ -29,6 +29,7 @@ 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";
|
||||
|
||||
type AnnuncioData = Pick<
|
||||
Annunci,
|
||||
|
|
@ -138,17 +139,17 @@ export const BasicAnnuncioCard = ({
|
|||
|
||||
const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
|
||||
return (
|
||||
<div
|
||||
<Badge
|
||||
className={cn(
|
||||
"flex w-fit items-center justify-center rounded-md px-3",
|
||||
tipo === "Transitorio" && "bg-orange-400",
|
||||
tipo === "Stabile" && "bg-red-400",
|
||||
"font-semibold text-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>
|
||||
Affitto {tipo}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
195
apps/infoalloggi/src/components/servizio/compatibili_dialog.tsx
Normal file
195
apps/infoalloggi/src/components/servizio/compatibili_dialog.tsx
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
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 { 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 { InteressatoButtonServizio } from "../annuncio-interactions/annuncio_interactions";
|
||||
import { LoadingPage } from "../loading";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../ui/dialog";
|
||||
|
||||
export const AnnunciCompatibili = () => {
|
||||
const { servizioId } = useServizio();
|
||||
const { data, isLoading } = api.servizio.getCompatibileAnnunci.useQuery({
|
||||
servizioId,
|
||||
});
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-full bg-purple-500 sm:w-fit">
|
||||
Esplora Annunci Compatibili
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-xl p-2 sm:max-w-5xl sm:p-6 md:max-w-7xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="pt-6">
|
||||
Annunci compatibili con le preferenze del servizio
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only"></DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto p-0.5 sm:p-2">
|
||||
{isLoading ? (
|
||||
<LoadingPage />
|
||||
) : (
|
||||
<>
|
||||
{!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-7xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 sm:gap-y-8 lg:grid-cols-3">
|
||||
{data.map((annuncio) => (
|
||||
<div
|
||||
className="flex flex-col justify-center gap-1"
|
||||
key={annuncio.codice}
|
||||
>
|
||||
<CardAnnuncio
|
||||
{...annuncio}
|
||||
className="outline outline-neutral-500"
|
||||
noLink
|
||||
onlyFirstImage
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="w-full">
|
||||
<AnnuncioDettaglio data={annuncio} />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<InteressatoButtonServizio annuncioId={annuncio.id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild></DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
|
@ -22,8 +22,8 @@ import {
|
|||
useServizio,
|
||||
} from "~/providers/ServizioProvider";
|
||||
import { api } from "~/utils/api";
|
||||
import { AnnunciCompatibili } from "./compatibili_dialog";
|
||||
import {
|
||||
AnnunciCompatibili,
|
||||
AnnunciSelezionatiAccordion,
|
||||
AnnunciSelezionatiDialog,
|
||||
} from "./servizio_annunci_accordions";
|
||||
|
|
@ -210,9 +210,6 @@ export const ServizioContent = () => {
|
|||
const annunciInConferma = servizio.annunci.filter(
|
||||
(a) => a.user_confirmed_at !== null,
|
||||
);
|
||||
const annunciNotInConferma = servizio.annunci.filter(
|
||||
(a) => a.user_confirmed_at === null,
|
||||
);
|
||||
|
||||
return (
|
||||
<ServizioCard
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export const ServizioActions = () => {
|
|||
return (
|
||||
<div className="flex w-full flex-col flex-wrap justify-between gap-3 sm:flex-row">
|
||||
{(isAdmin || canUserEdit) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{/* <span className="hidden sm:block">Azioni: </span> */}
|
||||
<EditPreferenze />
|
||||
<InterruzioneServizio />
|
||||
|
|
@ -230,7 +230,7 @@ const InterruzioneServizio = () => {
|
|||
servizio.annunci.some((a) => a.user_confirmed_at !== null))
|
||||
}
|
||||
>
|
||||
<ClockAlert /> <span>Interruzione Servizio</span>
|
||||
<ClockAlert /> <span>Richiedi Interruzione</span>
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
|
|
@ -302,7 +302,7 @@ const EditPreferenze = () => {
|
|||
}
|
||||
>
|
||||
<Calculator />
|
||||
Modifica Preferenze
|
||||
Preferenze
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh] w-full sm:max-w-5xl">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { ExternalLink, Trash2 } from "lucide-react";
|
|||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { InteressatoButtonServizio } from "~/components/annuncio-interactions/annuncio_interactions";
|
||||
import {
|
||||
AnnuncioCard,
|
||||
BasicAnnuncioCard,
|
||||
|
|
@ -21,8 +20,6 @@ import {
|
|||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
||||
import { api } from "~/utils/api";
|
||||
import { CardAnnuncio } from "../annuncio_card";
|
||||
import { LoadingPage } from "../loading";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -44,7 +41,7 @@ const DialogServizio = ({
|
|||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
||||
<DialogContent className="p-2 sm:max-w-5xl sm:p-6 md:max-w-7xl">
|
||||
<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">
|
||||
|
|
@ -245,58 +242,6 @@ export const AnnunciSelezionatiAccordion = ({
|
|||
</Accordion>
|
||||
);
|
||||
};
|
||||
export const AnnunciCompatibili = () => {
|
||||
const { servizioId } = useServizio();
|
||||
const { data, isLoading } = api.servizio.getCompatibileAnnunci.useQuery({
|
||||
servizioId,
|
||||
});
|
||||
|
||||
return (
|
||||
<DialogServizio
|
||||
title="Annunci compatibili con le preferenze del servizio"
|
||||
trigger={
|
||||
<Button className="w-full bg-purple-500 sm:w-fit">
|
||||
Esplora Annunci Compatibili
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto p-0.5 sm:p-2">
|
||||
{isLoading ? (
|
||||
<LoadingPage />
|
||||
) : (
|
||||
<>
|
||||
{!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-7xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 sm:gap-y-8 lg:grid-cols-3">
|
||||
{data.map((annuncio) => (
|
||||
<div
|
||||
className="flex flex-col justify-center gap-1"
|
||||
key={annuncio.codice}
|
||||
>
|
||||
<CardAnnuncio
|
||||
{...annuncio}
|
||||
className="outline outline-neutral-500"
|
||||
noLink
|
||||
onlyFirstImage
|
||||
/>
|
||||
|
||||
<InteressatoButtonServizio annuncioId={annuncio.id} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DialogServizio>
|
||||
);
|
||||
};
|
||||
|
||||
export const AnnunciRichiesti = ({ userId }: { userId: UsersId }) => {
|
||||
const { data, isLoading } = api.intrests.getUserInterestsAnnunci.useQuery({
|
||||
|
|
|
|||
|
|
@ -2,7 +2,21 @@ import type { NextPage } from "next";
|
|||
import Example from "~/components/KabanExample";
|
||||
import { NoSSR } from "~/lib/nossr";
|
||||
|
||||
const Test: NextPage = () => {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<NoSSR>
|
||||
<Example />
|
||||
</NoSSR>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Test;
|
||||
|
||||
/*
|
||||
|
||||
ESEMPIO DI RICERCA IN NOMINATIM CON DEBOUNCE
|
||||
|
||||
import { useState } from "react";
|
||||
import { useDebounce } from "react-use";
|
||||
import z from "zod";
|
||||
|
|
@ -147,14 +161,3 @@ const Test: NextPage = () => {
|
|||
};
|
||||
export default Test;
|
||||
*/
|
||||
|
||||
const Test: NextPage = () => {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<NoSSR>
|
||||
<Example />
|
||||
</NoSSR>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Test;
|
||||
|
|
|
|||
|
|
@ -208,6 +208,9 @@ export const get_AnnunciPositionsHandler = async ({
|
|||
"tipo",
|
||||
"titolo_it",
|
||||
"titolo_en",
|
||||
"desc_en",
|
||||
"desc_it",
|
||||
"web",
|
||||
"modificato_il",
|
||||
"stato",
|
||||
"url_video",
|
||||
|
|
@ -265,11 +268,14 @@ export type AnnuncioRicerca = Pick<
|
|||
| "tipo"
|
||||
| "titolo_it"
|
||||
| "titolo_en"
|
||||
| "desc_en"
|
||||
| "desc_it"
|
||||
| "modificato_il"
|
||||
| "stato"
|
||||
| "url_video"
|
||||
| "media_updated_at"
|
||||
| "homepage"
|
||||
| "web"
|
||||
> & {
|
||||
images: Pick<ImagesRefs, "img" | "thumb">[];
|
||||
};
|
||||
|
|
@ -307,6 +313,9 @@ export const getCursor_AnnunciHandler = async ({
|
|||
"tipo",
|
||||
"titolo_it",
|
||||
"titolo_en",
|
||||
"desc_en",
|
||||
"desc_it",
|
||||
"web",
|
||||
"modificato_il",
|
||||
"stato",
|
||||
"url_video",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue