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",
|
"nuqs": "^2.4.3",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"radix-ui": "^1.4.2",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-day-picker": "^9.11.1",
|
"react-day-picker": "^9.11.1",
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
"nuqs": "^2.4.3",
|
"nuqs": "^2.4.3",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"radix-ui": "^1.4.2",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-day-picker": "^9.11.1",
|
"react-day-picker": "^9.11.1",
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ export const InteressatoButtonServizio = ({
|
||||||
variant="info"
|
variant="info"
|
||||||
>
|
>
|
||||||
<BadgePlus className="size-6" />
|
<BadgePlus className="size-6" />
|
||||||
{isAdmin ? "Aggiungi annuncio" : "Sono interessato"}
|
{isAdmin ? "Aggiungi" : "Sono interessato"}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -230,17 +230,17 @@ export const CardAnnuncio = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-full grow flex-col gap-2">
|
<div className="flex h-full grow flex-col gap-2">
|
||||||
<div
|
<Badge
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full items-center justify-center rounded-md",
|
"w-full font-semibold text-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">Affitto {tipo}</div>
|
Affitto {tipo}
|
||||||
</div>
|
</Badge>
|
||||||
<div className="flex flex-row justify-between gap-3 px-4">
|
<div className="flex flex-row justify-between gap-3 px-4">
|
||||||
<div className="w-full text-center">
|
<div className="w-full text-center">
|
||||||
<span className="sr-only">{t.card.codice}</span>
|
<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 { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useServizioAnnuncio } from "~/providers/ServizioProvider";
|
import { useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
import type { Annunci } from "~/schemas/public/Annunci";
|
import type { Annunci } from "~/schemas/public/Annunci";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
|
||||||
type AnnuncioData = Pick<
|
type AnnuncioData = Pick<
|
||||||
Annunci,
|
Annunci,
|
||||||
|
|
@ -138,17 +139,17 @@ export const BasicAnnuncioCard = ({
|
||||||
|
|
||||||
const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
|
const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<Badge
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit items-center justify-center rounded-md px-3",
|
"font-semibold text-md",
|
||||||
tipo === "Transitorio" && "bg-orange-400",
|
tipo === "Transitorio" && "bg-transitorio",
|
||||||
tipo === "Stabile" && "bg-red-400",
|
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>
|
Affitto {tipo}
|
||||||
</div>
|
</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,
|
useServizio,
|
||||||
} from "~/providers/ServizioProvider";
|
} from "~/providers/ServizioProvider";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
import { AnnunciCompatibili } from "./compatibili_dialog";
|
||||||
import {
|
import {
|
||||||
AnnunciCompatibili,
|
|
||||||
AnnunciSelezionatiAccordion,
|
AnnunciSelezionatiAccordion,
|
||||||
AnnunciSelezionatiDialog,
|
AnnunciSelezionatiDialog,
|
||||||
} from "./servizio_annunci_accordions";
|
} from "./servizio_annunci_accordions";
|
||||||
|
|
@ -210,9 +210,6 @@ export const ServizioContent = () => {
|
||||||
const annunciInConferma = servizio.annunci.filter(
|
const annunciInConferma = servizio.annunci.filter(
|
||||||
(a) => a.user_confirmed_at !== null,
|
(a) => a.user_confirmed_at !== null,
|
||||||
);
|
);
|
||||||
const annunciNotInConferma = servizio.annunci.filter(
|
|
||||||
(a) => a.user_confirmed_at === null,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServizioCard
|
<ServizioCard
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ export const ServizioActions = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col flex-wrap justify-between gap-3 sm:flex-row">
|
<div className="flex w-full flex-col flex-wrap justify-between gap-3 sm:flex-row">
|
||||||
{(isAdmin || canUserEdit) && (
|
{(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> */}
|
{/* <span className="hidden sm:block">Azioni: </span> */}
|
||||||
<EditPreferenze />
|
<EditPreferenze />
|
||||||
<InterruzioneServizio />
|
<InterruzioneServizio />
|
||||||
|
|
@ -230,7 +230,7 @@ const InterruzioneServizio = () => {
|
||||||
servizio.annunci.some((a) => a.user_confirmed_at !== null))
|
servizio.annunci.some((a) => a.user_confirmed_at !== null))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ClockAlert /> <span>Interruzione Servizio</span>
|
<ClockAlert /> <span>Richiedi Interruzione</span>
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
|
|
@ -302,7 +302,7 @@ const EditPreferenze = () => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Calculator />
|
<Calculator />
|
||||||
Modifica Preferenze
|
Preferenze
|
||||||
</Button>
|
</Button>
|
||||||
</CredenzaTrigger>
|
</CredenzaTrigger>
|
||||||
<CredenzaContent className="max-h-[90vh] w-full sm:max-w-5xl">
|
<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 Link from "next/link";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { InteressatoButtonServizio } from "~/components/annuncio-interactions/annuncio_interactions";
|
|
||||||
import {
|
import {
|
||||||
AnnuncioCard,
|
AnnuncioCard,
|
||||||
BasicAnnuncioCard,
|
BasicAnnuncioCard,
|
||||||
|
|
@ -21,8 +20,6 @@ import {
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { CardAnnuncio } from "../annuncio_card";
|
|
||||||
import { LoadingPage } from "../loading";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
|
@ -44,7 +41,7 @@ const DialogServizio = ({
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
<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>
|
<DialogHeader>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogDescription className="sr-only">
|
<DialogDescription className="sr-only">
|
||||||
|
|
@ -245,58 +242,6 @@ export const AnnunciSelezionatiAccordion = ({
|
||||||
</Accordion>
|
</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 }) => {
|
export const AnnunciRichiesti = ({ userId }: { userId: UsersId }) => {
|
||||||
const { data, isLoading } = api.intrests.getUserInterestsAnnunci.useQuery({
|
const { data, isLoading } = api.intrests.getUserInterestsAnnunci.useQuery({
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,21 @@ import type { NextPage } from "next";
|
||||||
import Example from "~/components/KabanExample";
|
import Example from "~/components/KabanExample";
|
||||||
import { NoSSR } from "~/lib/nossr";
|
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 { useState } from "react";
|
||||||
import { useDebounce } from "react-use";
|
import { useDebounce } from "react-use";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
@ -147,14 +161,3 @@ const Test: NextPage = () => {
|
||||||
};
|
};
|
||||||
export default Test;
|
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",
|
"tipo",
|
||||||
"titolo_it",
|
"titolo_it",
|
||||||
"titolo_en",
|
"titolo_en",
|
||||||
|
"desc_en",
|
||||||
|
"desc_it",
|
||||||
|
"web",
|
||||||
"modificato_il",
|
"modificato_il",
|
||||||
"stato",
|
"stato",
|
||||||
"url_video",
|
"url_video",
|
||||||
|
|
@ -265,11 +268,14 @@ export type AnnuncioRicerca = Pick<
|
||||||
| "tipo"
|
| "tipo"
|
||||||
| "titolo_it"
|
| "titolo_it"
|
||||||
| "titolo_en"
|
| "titolo_en"
|
||||||
|
| "desc_en"
|
||||||
|
| "desc_it"
|
||||||
| "modificato_il"
|
| "modificato_il"
|
||||||
| "stato"
|
| "stato"
|
||||||
| "url_video"
|
| "url_video"
|
||||||
| "media_updated_at"
|
| "media_updated_at"
|
||||||
| "homepage"
|
| "homepage"
|
||||||
|
| "web"
|
||||||
> & {
|
> & {
|
||||||
images: Pick<ImagesRefs, "img" | "thumb">[];
|
images: Pick<ImagesRefs, "img" | "thumb">[];
|
||||||
};
|
};
|
||||||
|
|
@ -307,6 +313,9 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
"tipo",
|
"tipo",
|
||||||
"titolo_it",
|
"titolo_it",
|
||||||
"titolo_en",
|
"titolo_en",
|
||||||
|
"desc_en",
|
||||||
|
"desc_it",
|
||||||
|
"web",
|
||||||
"modificato_il",
|
"modificato_il",
|
||||||
"stato",
|
"stato",
|
||||||
"url_video",
|
"url_video",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue