Refactor and enhance UI components across the application

- Renamed `AnnunciInConferma` to `AnnunciInConfermaDialog` and `AnnunciSelezionati` to `AnnunciSelezionatiDialog` for clarity.
- Simplified button notifications for announcements by always displaying the count.
- Introduced `AnnunciInConfermaAccordion` and `AnnunciSelezionatiAccordion` components to manage accordion behavior for announcements.
- Updated button styles to remove unnecessary flex properties for better alignment.
- Improved form validation logic in `FormNewServizioAcquisto` for better clarity and performance.
- Enhanced the `UploadModal` to accept props directly, simplifying the component structure.
- Added a new `CardAnnuncio` component for displaying announcements with improved layout and functionality.
- Updated global styles to include a new background color variable.
- Refactored API routes and controllers for better clarity and maintainability.
- Cleaned up various components by removing redundant class names and improving accessibility.
This commit is contained in:
Marco Pedone 2025-11-03 10:59:45 +01:00
parent 97503b34e5
commit 6ad74c9002
43 changed files with 829 additions and 416 deletions

View file

@ -109,7 +109,6 @@
"noDangerouslySetInnerHtml": "off" "noDangerouslySetInnerHtml": "off"
}, },
"style": { "style": {
"noNestedTernary": "info",
"noUselessElse": "on", "noUselessElse": "on",
"noYodaExpression": "on", "noYodaExpression": "on",
"useThrowNewError": "on", "useThrowNewError": "on",

View file

@ -199,11 +199,7 @@ const UserViewHeader = () => {
</div> </div>
<Link href={`https://wa.me/${data.telefono}`} target="_blank"> <Link href={`https://wa.me/${data.telefono}`} target="_blank">
<Button <Button size="sm" variant="success">
className="flex items-center gap-2"
size="sm"
variant="success"
>
<WhatsAppIcon2 className="size-4 fill-white" /> <WhatsAppIcon2 className="size-4 fill-white" />
WhatsApp WhatsApp
</Button> </Button>
@ -212,7 +208,7 @@ const UserViewHeader = () => {
<div className="flex flex-row flex-wrap gap-2"> <div className="flex flex-row flex-wrap gap-2">
<Link href={`/area-riservata/admin/user-view/edit-user/${data.id}`}> <Link href={`/area-riservata/admin/user-view/edit-user/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="border"
size="sm" size="sm"
variant={pathname.includes("edit-user") ? "secondary" : "outline"} variant={pathname.includes("edit-user") ? "secondary" : "outline"}
> >
@ -221,7 +217,7 @@ const UserViewHeader = () => {
</Link> </Link>
<Link href={`/area-riservata/admin/user-view/servizio/${data.id}`}> <Link href={`/area-riservata/admin/user-view/servizio/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="border"
size="sm" size="sm"
variant={pathname.includes("servizio") ? "secondary" : "outline"} variant={pathname.includes("servizio") ? "secondary" : "outline"}
> >
@ -231,7 +227,7 @@ const UserViewHeader = () => {
<Link href={`/area-riservata/admin/user-view/ricerca/${data.id}`}> <Link href={`/area-riservata/admin/user-view/ricerca/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="border"
size="sm" size="sm"
variant={pathname.includes("ricerca") ? "secondary" : "outline"} variant={pathname.includes("ricerca") ? "secondary" : "outline"}
> >
@ -240,7 +236,7 @@ const UserViewHeader = () => {
</Link> </Link>
<Link href={`/area-riservata/admin/user-view/comunicazioni/${data.id}`}> <Link href={`/area-riservata/admin/user-view/comunicazioni/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="border"
size="sm" size="sm"
variant={ variant={
pathname.includes("comunicazioni") ? "secondary" : "outline" pathname.includes("comunicazioni") ? "secondary" : "outline"
@ -252,7 +248,7 @@ const UserViewHeader = () => {
<Link href={`/area-riservata/admin/user-view/ordini/${data.id}`}> <Link href={`/area-riservata/admin/user-view/ordini/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="border"
size="sm" size="sm"
variant={pathname.includes("ordini") ? "secondary" : "outline"} variant={pathname.includes("ordini") ? "secondary" : "outline"}
> >
@ -261,7 +257,7 @@ const UserViewHeader = () => {
</Link> </Link>
<Link href={`/area-riservata/admin/user-view/allegati/${data.id}`}> <Link href={`/area-riservata/admin/user-view/allegati/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="border"
size="sm" size="sm"
variant={pathname.includes("allegati") ? "secondary" : "outline"} variant={pathname.includes("allegati") ? "secondary" : "outline"}
> >
@ -281,7 +277,7 @@ const UserViewHeader = () => {
target="_blank" target="_blank"
> >
<Button <Button
className="flex items-center gap-2 border" className="border"
size="sm" size="sm"
variant={!data.chatid ? "ghost" : "outline"} variant={!data.chatid ? "ghost" : "outline"}
> >

View file

@ -146,7 +146,7 @@ const PricePreparation = ({
href={`/servizio/condizioni/${prezziario.testo_condizioni}`} href={`/servizio/condizioni/${prezziario.testo_condizioni}`}
target="_blank" target="_blank"
> >
<Button className="flex w-full items-center gap-2 text-sm"> <Button className="w-full text-sm">
<span>{t.acquisto.leggi_condizioni}</span> <span>{t.acquisto.leggi_condizioni}</span>
<ExternalLink /> <ExternalLink />
</Button> </Button>
@ -154,7 +154,7 @@ const PricePreparation = ({
)} )}
<Button <Button
className="flex w-full items-center gap-2 text-lg" className="w-full text-lg"
disabled={isLoading || !prezziario} disabled={isLoading || !prezziario}
onClick={onProcedi} onClick={onProcedi}
size="xl" size="xl"

View file

@ -1,4 +1,4 @@
import { CardAnnuncio } from "~/components/annuncio_card"; import { CardAnnuncio } from "~/components/annuncio_card2";
import { getTitoloTranslation } from "~/lib/annuncio_details"; import { getTitoloTranslation } from "~/lib/annuncio_details";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller"; import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";

View file

@ -54,7 +54,7 @@ export const CTA_TipologiaModal = () => {
open={open} open={open}
> >
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="flex items-center gap-2" variant="info"> <Button variant="info">
<CircleHelp />{" "} <CircleHelp />{" "}
<span className="hidden sm:block"> <span className="hidden sm:block">
{t.tipologia_tutorial.btn_title} {t.tipologia_tutorial.btn_title}
@ -78,7 +78,7 @@ export const CTA_TipologiaModal = () => {
<p>{t.tipologia_tutorial.transitorio_desc}</p> <p>{t.tipologia_tutorial.transitorio_desc}</p>
<Button <Button
aria-label={t.tipologia_tutorial.transitorio_cta} aria-label={t.tipologia_tutorial.transitorio_cta}
className="flex w-full items-center gap-2 bg-transitorio" className="w-full bg-transitorio"
onClick={async () => await update("Transitorio")} onClick={async () => await update("Transitorio")}
> >
{t.tipologia_tutorial.transitorio_cta} {t.tipologia_tutorial.transitorio_cta}
@ -93,7 +93,7 @@ export const CTA_TipologiaModal = () => {
<p>{t.tipologia_tutorial.stabile_desc}</p> <p>{t.tipologia_tutorial.stabile_desc}</p>
<Button <Button
aria-label={t.tipologia_tutorial.stabile_cta} aria-label={t.tipologia_tutorial.stabile_cta}
className="flex w-full items-center gap-2 bg-stabile" className="w-full bg-stabile"
onClick={async () => await update("Stabile")} onClick={async () => await update("Stabile")}
> >
{t.tipologia_tutorial.stabile_cta} {t.tipologia_tutorial.stabile_cta}
@ -104,7 +104,7 @@ export const CTA_TipologiaModal = () => {
<Button <Button
aria-label={t.tipologia_tutorial.alone} aria-label={t.tipologia_tutorial.alone}
className="flex w-full items-center gap-2" className="w-full"
onClick={async () => await update(null)} onClick={async () => await update(null)}
variant="outline" variant="outline"
> >

View file

@ -100,7 +100,7 @@ const ServizioInteraction = ({
aria-label="Vai alla tua area riservata" aria-label="Vai alla tua area riservata"
href={`/area-riservata/dashboard`} href={`/area-riservata/dashboard`}
> >
<Button className="flex w-full items-center gap-2" variant="success"> <Button className="w-full" variant="success">
Vai alla tua area riservata <ArrowRight /> Vai alla tua area riservata <ArrowRight />
</Button> </Button>
</Link> </Link>
@ -128,7 +128,7 @@ const InteressatoButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
if (isLoading || hasInterest === undefined) { if (isLoading || hasInterest === undefined) {
return ( return (
<Button className="flex w-full items-center gap-2" disabled> <Button className="w-full" disabled>
<BadgePlus className="size-6" /> <BadgePlus className="size-6" />
Caricamento... Caricamento...
</Button> </Button>
@ -146,7 +146,7 @@ const InteressatoButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
return ( return (
<Button <Button
aria-label="Sono interessato" aria-label="Sono interessato"
className="flex w-full items-center gap-2" className="w-full"
//size="xl" //size="xl"
onClick={() => { onClick={() => {
add({ add({
@ -188,7 +188,7 @@ export const InteressatoButtonBatchV = ({
return ( return (
<Button <Button
aria-label="Sono interessato" aria-label="Sono interessato"
className="flex w-full items-center gap-2" className="w-full"
//size="xl" //size="xl"
onClick={() => { onClick={() => {
add({ add({

View file

@ -18,10 +18,7 @@ export const ContattoAnnuncio = () => {
return ( return (
<Credenza> <Credenza>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button className="w-full" variant="destructive">
className="flex w-full items-center gap-2"
variant="destructive"
>
<MessageCircleQuestion className="size-6" /> Contattaci <MessageCircleQuestion className="size-6" /> Contattaci
</Button> </Button>
</CredenzaTrigger> </CredenzaTrigger>

View file

@ -89,7 +89,7 @@ export const CardAnnuncio = ({
<Carousel opts={{ loop: true }}> <Carousel opts={{ loop: true }}>
<CarouselContent> <CarouselContent>
{images?.map((img, idx) => ( {images?.map((img, idx) => (
<CarouselItem className="" key={`${img.img}key`}> <CarouselItem key={`${img.img}key`}>
<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`}

View file

@ -0,0 +1,404 @@
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>
</>
);
};

View file

@ -30,7 +30,7 @@ export const AdminDashboard = () => {
<div className="text-red-500">Errore caricamento statistiche</div> <div className="text-red-500">Errore caricamento statistiche</div>
) : ( ) : (
<div className="grid grid-cols-1 gap-4 px-2 pt-4 sm:grid-cols-2 sm:px-4"> <div className="grid grid-cols-1 gap-4 px-2 pt-4 sm:grid-cols-2 sm:px-4">
<Card className=""> <Card>
<CardHeader> <CardHeader>
<CardTitle>Utenti</CardTitle> <CardTitle>Utenti</CardTitle>
<CardDescription> <CardDescription>

View file

@ -145,7 +145,7 @@ export const OrdiniModal = ({
href={`/area-riservata/payment-recap/${data.ordine_id}`} href={`/area-riservata/payment-recap/${data.ordine_id}`}
target="_blank" target="_blank"
> >
<Button className="flex items-center gap-2" variant="outline"> <Button variant="outline">
Ricevuta di cortesia <ReceiptText /> Ricevuta di cortesia <ReceiptText />
</Button> </Button>
</Link> </Link>

View file

@ -102,7 +102,7 @@ export const ChatAttachments = ({
target="_blank" target="_blank"
> >
<Button <Button
className="flex h-8 items-center gap-2 truncate p-0 text-base has-[>svg]:px-0" className="h-8 truncate p-0 text-base has-[>svg]:px-0"
variant="link" variant="link"
> >
<ExtIcon mimeType={file.mimeType} /> <ExtIcon mimeType={file.mimeType} />

View file

@ -157,7 +157,7 @@ const FormMessage = React.forwardRef<
return ( return (
<p <p
className={cn( className={cn(
"font-medium text-red-500 text-sm dark:text-red-600", "font-medium text-red-500 text-sm leading-none dark:text-red-600",
className, className,
)} )}
id={formMessageId} id={formMessageId}

View file

@ -107,12 +107,7 @@ export const OnboardTutorial = () => {
</div> </div>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<Button <Button onClick={handleClose} size="lg" variant="success">
className="flex items-center gap-2"
onClick={handleClose}
size="lg"
variant="success"
>
<span>{t.onboarding_tutorial.button_text}</span> <span>{t.onboarding_tutorial.button_text}</span>
<ArrowRight className="size-5" /> <ArrowRight className="size-5" />
</Button> </Button>

View file

@ -200,7 +200,7 @@ export const PricingComponent = ({
href={"/prezzi#contratti"} href={"/prezzi#contratti"}
id="contratto-link" id="contratto-link"
> >
<Button className="flex items-center gap-2"> <Button>
Più info <ExternalLink className="size-4" /> Più info <ExternalLink className="size-4" />
</Button> </Button>
</Link> </Link>

View file

@ -216,7 +216,7 @@ function SchedaAnnuncio({ data }: { data: Annunci }) {
className="flex items-center justify-start gap-2" className="flex items-center justify-start gap-2"
key={item.text} key={item.text}
> >
<span className=""> <span>
<IconMatrix type={item.icon} /> <IconMatrix type={item.icon} />
</span> </span>
<span> <span>

View file

@ -7,6 +7,7 @@ import {
FolderKanban, FolderKanban,
Pen, Pen,
Plus, Plus,
ShieldUser,
Trash2, Trash2,
Wrench, Wrench,
} from "lucide-react"; } from "lucide-react";
@ -99,7 +100,7 @@ export const AnnuncioActions = () => {
href={`https://win-jk822fggr6b:480/id/${data.gestionale_id}`} href={`https://win-jk822fggr6b:480/id/${data.gestionale_id}`}
target="_blank" target="_blank"
> >
<Button className="flex items-center gap-2"> <Button>
<FolderKanban className="size-5" /> Gestionale <FolderKanban className="size-5" /> Gestionale
</Button> </Button>
</Link> </Link>
@ -107,18 +108,29 @@ export const AnnuncioActions = () => {
</> </>
)} )}
{isAdmin && ( {isAdmin && (
<> <Popover>
<PopoverTrigger asChild>
<Button>
<ShieldUser />
<span>Admin</span>
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
className="flex w-fit flex-col items-center gap-2"
>
<Link <Link
href={`/area-riservata/admin/edit-annuncio/${annuncioId}`} href={`/area-riservata/admin/edit-annuncio/${annuncioId}`}
target="_blank" target="_blank"
> >
<Button <Button
aria-label="Edit Annuncio" aria-label="Edit Annuncio"
className="flex w-full items-center gap-2" className="w-full"
size="sm" size="sm"
variant="secondary" variant="secondary"
> >
<Wrench className="size-6" /> <Wrench />
<span>Modifica Annuncio</span>
</Button> </Button>
</Link> </Link>
@ -134,14 +146,16 @@ export const AnnuncioActions = () => {
> >
<Button <Button
aria-label="Delete Annuncio" aria-label="Delete Annuncio"
className="flex items-center gap-2" className="w-full"
size="sm" size="sm"
variant="destructive" variant="destructive"
> >
<Trash2 className="size-6" /> <Trash2 />
<span>Elimina Annuncio</span>
</Button> </Button>
</Confirm> </Confirm>
</> </PopoverContent>
</Popover>
)} )}
</div> </div>
); );
@ -232,7 +246,6 @@ const ContrattoImport = ({
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Button <Button
aria-label="Import Contratto" aria-label="Import Contratto"
className="flex items-center gap-2"
onClick={async () => { onClick={async () => {
const clipboardText = await navigator.clipboard.readText(); const clipboardText = await navigator.clipboard.readText();
@ -245,7 +258,6 @@ const ContrattoImport = ({
</Button> </Button>
<Button <Button
aria-label="Reset Contratto" aria-label="Reset Contratto"
className="flex items-center gap-2"
onClick={async () => { onClick={async () => {
setContratto({ setContratto({
contratto_decorrenza: null, contratto_decorrenza: null,
@ -445,7 +457,7 @@ const AdminContratto = () => {
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
aria-label="Open Contratto Section" aria-label="Open Contratto Section"
className="flex w-fit items-center gap-2" className="w-fit"
variant={data.doc_contratto_added ? "success" : "secondary"} variant={data.doc_contratto_added ? "success" : "secondary"}
> >
{data.doc_contratto_added ? ( {data.doc_contratto_added ? (
@ -709,7 +721,7 @@ const AdminLavoraConferma = () => {
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
aria-label="Open Lavora Conferma Section" aria-label="Open Lavora Conferma Section"
className="flex w-fit items-center gap-2" className="w-fit"
variant={data.hasConfermaAdmin ? "success" : "secondary"} variant={data.hasConfermaAdmin ? "success" : "secondary"}
> >
{data.hasConfermaAdmin ? ( {data.hasConfermaAdmin ? (
@ -983,7 +995,6 @@ const Caparra = ({
<div className="flex flex-wrap items-center gap-3"> <div className="flex flex-wrap items-center gap-3">
<Button <Button
aria-label="Import Caparra" aria-label="Import Caparra"
className="flex items-center gap-2"
onClick={async () => { onClick={async () => {
const clipboardText = await navigator.clipboard.readText(); const clipboardText = await navigator.clipboard.readText();
@ -996,7 +1007,6 @@ const Caparra = ({
</Button> </Button>
<Button <Button
aria-label="Reset Caparra" aria-label="Reset Caparra"
className="flex items-center gap-2"
onClick={async () => { onClick={async () => {
setCaparra({ setCaparra({
caparra_causale: null, caparra_causale: null,

View file

@ -120,10 +120,7 @@ export const BasicAnnuncioCard = ({
aria-label="Pagina Annuncio" aria-label="Pagina Annuncio"
href={`/annuncio/${data.codice}`} href={`/annuncio/${data.codice}`}
> >
<Button <Button variant="outline">
className="flex items-center gap-2"
variant="outline"
>
<ExternalLink className="size-6" /> <ExternalLink className="size-6" />
</Button> </Button>
</Link> </Link>
@ -159,11 +156,7 @@ const AnnuncioDettaglio = ({ data }: { data: AnnuncioData }) => {
return ( return (
<Credenza> <Credenza>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button aria-label="Dettagli Annuncio" variant="outline">
aria-label="Dettagli Annuncio"
className="flex items-center gap-2"
variant="outline"
>
<UnfoldVertical className="size-6" /> Dettagli annuncio <UnfoldVertical className="size-6" /> Dettagli annuncio
</Button> </Button>
</CredenzaTrigger> </CredenzaTrigger>

View file

@ -58,10 +58,7 @@ export const Interactions = () => {
aria-label="Conferma immobile" aria-label="Conferma immobile"
href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`} href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`}
> >
<Button <Button className="w-fit" variant="success">
className="flex w-fit items-center gap-2"
variant="success"
>
<Pointer className="size-6" /> <Pointer className="size-6" />
Confermato - Procedi ora Confermato - Procedi ora
</Button> </Button>
@ -71,7 +68,7 @@ export const Interactions = () => {
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
className="flex w-fit cursor-not-allowed items-center gap-2 opacity-50" className="w-fit cursor-not-allowed opacity-50"
variant="success" variant="success"
> >
<Pointer className="size-6" /> <Pointer className="size-6" />
@ -92,7 +89,7 @@ export const Interactions = () => {
aria-label="Conferma immobile" aria-label="Conferma immobile"
href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`} href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`}
> >
<Button className="flex w-fit items-center gap-2" variant="success"> <Button className="w-fit" variant="success">
<Pointer className="size-6" /> <Pointer className="size-6" />
Rivedi la conferma Rivedi la conferma
</Button> </Button>
@ -136,7 +133,7 @@ export const Interactions = () => {
href={`/servizio/contratto/${servizioId}/${annuncioId}`} href={`/servizio/contratto/${servizioId}/${annuncioId}`}
target="_blank" target="_blank"
> >
<Button className="flex w-fit items-center gap-2"> <Button className="w-fit">
<FileText className="size-6" /> <FileText className="size-6" />
Visualizza il contratto Visualizza il contratto
</Button> </Button>
@ -145,7 +142,7 @@ export const Interactions = () => {
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button className="flex w-fit cursor-not-allowed items-center gap-2 opacity-50"> <Button className="w-fit cursor-not-allowed opacity-50">
<FileText className="size-6" /> <FileText className="size-6" />
Visualizza il contratto Visualizza il contratto
</Button> </Button>
@ -172,7 +169,7 @@ export const Interactions = () => {
href={`/servizio/contratto-registrazione/${servizioId}/${annuncioId}`} href={`/servizio/contratto-registrazione/${servizioId}/${annuncioId}`}
target="_blank" target="_blank"
> >
<Button className="flex w-fit items-center gap-2"> <Button className="w-fit">
<FileBadge className="size-6" /> <FileBadge className="size-6" />
Visualizza la registrazione Visualizza la registrazione
</Button> </Button>
@ -181,7 +178,7 @@ export const Interactions = () => {
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button className="flex w-fit cursor-not-allowed items-center gap-2 opacity-50"> <Button className="w-fit cursor-not-allowed opacity-50">
<FileBadge className="size-6" /> <FileBadge className="size-6" />
Visualizza la registrazione Visualizza la registrazione
</Button> </Button>
@ -230,7 +227,7 @@ export const SaldoButton = ({
return ( return (
<Button <Button
aria-label="Procedi al saldo" aria-label="Procedi al saldo"
className={cn("flex w-fit items-center gap-2", className)} className={cn("w-fit", className)}
onClick={() => { onClick={() => {
mutate({ mutate({
annuncioId, annuncioId,
@ -271,7 +268,7 @@ const SaldoConsulenzaButton = ({
return ( return (
<Button <Button
aria-label="Procedi al saldo consulenza" aria-label="Procedi al saldo consulenza"
className={cn("flex w-fit items-center gap-2", className)} className={cn("w-fit", className)}
onClick={() => { onClick={() => {
mutate({ mutate({
annuncioId, annuncioId,
@ -311,10 +308,7 @@ const AnnuncioConfirm = () => {
return ( return (
<Credenza onOpenChange={setOpen} open={open}> <Credenza onOpenChange={setOpen} open={open}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button aria-label="Conferma immobile" className="w-fit">
aria-label="Conferma immobile"
className="flex w-fit items-center gap-2"
>
<Handshake className="size-6" /> Conferma immobile <Handshake className="size-6" /> Conferma immobile
</Button> </Button>
</CredenzaTrigger> </CredenzaTrigger>
@ -356,10 +350,7 @@ const ConfermaLavorazioneModal = () => {
return ( return (
<Credenza> <Credenza>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button aria-label="Conferma in lavorazione" className="w-fit">
aria-label="Conferma in lavorazione"
className="flex w-fit items-center gap-2"
>
<CircleCheck className="size-7" /> <CircleCheck className="size-7" />
Conferma in lavorazione Conferma in lavorazione
</Button> </Button>

View file

@ -1,3 +1,4 @@
import { add } from "date-fns";
import { Package, PackageCheck, UserCircle } from "lucide-react"; import { Package, PackageCheck, UserCircle } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -5,8 +6,7 @@ import { useEffect } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { AnnuncioCard } from "~/components/servizio/annuncio_card"; import { AnnuncioCard } from "~/components/servizio/annuncio_card";
import { SaldoButton } from "~/components/servizio/interactions"; import { SaldoButton } from "~/components/servizio/interactions";
import { ServizioDuration } from "~/components/servizio/service-duration-display"; import { ServizioActions } from "~/components/servizio/servizio_actions";
import { ServizioActions2 } from "~/components/servizio/servizio_actions";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { import {
Card, Card,
@ -24,7 +24,8 @@ import {
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { import {
AnnunciCompatibili, AnnunciCompatibili,
AnnunciSelezionati, AnnunciSelezionatiAccordion,
AnnunciSelezionatiDialog,
} from "./servizio_annunci_accordions"; } from "./servizio_annunci_accordions";
const ServizioCard = ({ const ServizioCard = ({
@ -42,7 +43,10 @@ const ServizioCard = ({
}) => { }) => {
return ( return (
<Card <Card
className={cn("gap-2 border-neutral-200 bg-neutral-200", cardClassName)} className={cn(
"gap-2 rounded-md border-neutral-200 bg-neutral-200",
cardClassName,
)}
> >
<CardHeader <CardHeader
className={cn( className={cn(
@ -149,7 +153,7 @@ export const ServizioContent = () => {
</Button> </Button>
)} )}
</div> </div>
{isAdmin && <AnnunciSelezionati annunci={servizio.annunci} />} {isAdmin && <AnnunciSelezionatiDialog annunci={servizio.annunci} />}
</div> </div>
} }
header={ header={
@ -157,7 +161,7 @@ export const ServizioContent = () => {
<span className="text-foreground/70 text-lg"> <span className="text-foreground/70 text-lg">
Servizio non attivo Servizio non attivo
</span> </span>
<ServizioActions2 /> <ServizioActions />
</div> </div>
} }
/> />
@ -183,7 +187,7 @@ export const ServizioContent = () => {
} }
header={ header={
<> <>
<ServizioActions2 /> <ServizioActions />
</> </>
} }
/> />
@ -192,7 +196,6 @@ export const ServizioContent = () => {
const openContattiAnnunci = servizio.annunci.filter( const openContattiAnnunci = servizio.annunci.filter(
(a) => a.open_contatti_at !== null && a.user_confirmed_at === null, (a) => a.open_contatti_at !== null && a.user_confirmed_at === null,
); );
const annunciInConferma = servizio.annunci.filter( const annunciInConferma = servizio.annunci.filter(
@ -206,19 +209,27 @@ export const ServizioContent = () => {
<ServizioCard <ServizioCard
content={ content={
<> <>
<div className="flex flex-col gap-2"> <div className="flex w-full flex-col gap-4 sm:flex-row sm:gap-8">
<div className="flex flex-2 flex-col gap-2 sm:max-w-1/2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<UserCircle className="size-6 text-primary" /> <UserCircle className="size-6 text-primary" />
<span className="font-medium">Contatti sbloccati</span> <span className="font-medium">Contatti sbloccati</span>
</div> </div>
<span className="text-sm">{openContattiAnnunci.length} / 10</span> <span className="text-sm">
{openContattiAnnunci.length} / 10
</span>
</div> </div>
<Progress className="h-2" value={openContattiAnnunci.length * 10} /> <Progress
className="h-2"
value={openContattiAnnunci.length * 10}
/>
</div>
<span>
{`Durata servizio: ${new Date(servizio.decorrenza).toLocaleDateString("it-IT")} - ${new Date(add(servizio.decorrenza, { days: 60 })).toLocaleDateString("it-IT")}`}
</span>
{/* <ServizioDuration decorrenza={servizio.decorrenza} /> */}
</div> </div>
<ServizioDuration decorrenza={servizio.decorrenza} />
{annuncioConfermato.length > 0 && ( {annuncioConfermato.length > 0 && (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -256,15 +267,6 @@ export const ServizioContent = () => {
</div> </div>
)} )}
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-col gap-2 overflow-y-auto">
{openContattiAnnunci.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard className="border-2 border-green-500" />
</ServizioAnnuncioProvider>
);
})}
</div>
<div className="flex flex-col gap-2 overflow-y-auto"> <div className="flex flex-col gap-2 overflow-y-auto">
{annunciInConferma.map((data) => { {annunciInConferma.map((data) => {
return ( return (
@ -275,8 +277,8 @@ export const ServizioContent = () => {
})} })}
</div> </div>
<div className="flex flex-col items-center gap-4 sm:flex-row"> <AnnunciSelezionatiAccordion annunci={annunciNotInConferma} />
<AnnunciSelezionati annunci={annunciNotInConferma} /> <div className="flex items-center justify-center">
<AnnunciCompatibili /> <AnnunciCompatibili />
</div> </div>
</div> </div>
@ -284,7 +286,7 @@ export const ServizioContent = () => {
} }
header={ header={
<> <>
<ServizioActions2 /> <ServizioActions />
</> </>
} }
/> />

View file

@ -32,13 +32,19 @@ export const ContattiProprietarioModal = () => {
const { servizio } = useServizio(); const { servizio } = useServizio();
const { data } = useServizioAnnuncio(); const { data } = useServizioAnnuncio();
const { t } = useTranslation(); const { t } = useTranslation();
const isUnlocked = data.open_contatti_at != null;
const needsDocuments =
!servizio.skipControlloDoc &&
(!servizio.doc_personale_fronte || !servizio.doc_personale_retro);
return ( return (
<Credenza> <Credenza>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
{data.open_contatti_at != null ? ( {data.open_contatti_at != null ? (
<Button <Button
aria-label="Vedi contatti proprietario" aria-label="Vedi contatti proprietario"
className="flex w-fit items-center gap-2" className="w-fit"
variant="success" variant="success"
> >
<CircleUser className="size-7" /> <CircleUser className="size-7" />
@ -47,7 +53,7 @@ export const ContattiProprietarioModal = () => {
) : ( ) : (
<Button <Button
aria-label="Contatta proprietario" aria-label="Contatta proprietario"
className="flex w-fit items-center gap-2" className="w-fit"
variant="warning" variant="warning"
> >
<CircleUser className="size-7" /> <CircleUser className="size-7" />
@ -55,21 +61,19 @@ export const ContattiProprietarioModal = () => {
</Button> </Button>
)} )}
</CredenzaTrigger> </CredenzaTrigger>
<CredenzaContent className="max-h-[90vh]"> <CredenzaContent className="max-h-[90vh] sm:max-w-2xl">
<CredenzaHeader> <CredenzaHeader>
<CredenzaTitle>{t.contatto.title}</CredenzaTitle> <CredenzaTitle className="text-xl">{t.contatto.title}</CredenzaTitle>
<CredenzaDescription className="sr-only"> <CredenzaDescription className="sr-only">
{t.contatto.descrizione} {t.contatto.descrizione}
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
{servizio.doc_personale_fronte == null || {needsDocuments ? (
servizio.doc_personale_retro == null ? (
<DocCheckpoint /> <DocCheckpoint />
// biome-ignore lint/style/noNestedTernary: <this is fine> ) : isUnlocked ? (
) : data.open_contatti_at == null ? (
<PreUnlock />
) : (
<PostUnlock /> <PostUnlock />
) : (
<PreUnlock />
)} )}
</CredenzaContent> </CredenzaContent>
</Credenza> </Credenza>
@ -88,22 +92,30 @@ const DocComponent = ({
}) => { }) => {
const { isAdmin, userId } = useServizio(); const { isAdmin, userId } = useServizio();
if (!docInfo?.storageId) { if (docInfo?.storageId) {
return <UploadModal cb_onUpload={cb} isAdmin={isAdmin} userId={userId} />;
}
return ( return (
<div className="flex items-center gap-2 text-green-500"> <div className="flex items-center gap-2 text-green-500">
<CircleCheck className="size-5" /> <CircleCheck className="size-5" />
<span>Documento caricato</span> <span>Documento caricato</span>
</div> </div>
); );
}
return (
<UploadModal
cb_onUpload={cb}
isAdmin={isAdmin}
maxFiles={1}
userId={userId}
/>
);
}; };
const DocCheckpoint = () => { const DocCheckpoint = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { userId, servizio } = useServizio(); const { userId, servizio } = useServizio();
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate } = api.servizio.updateServizio.useMutation({ const { mutate, isPending } = api.servizio.updateServizio.useMutation({
onSuccess: async () => { onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ await utils.servizio.getAllServizioAnnunci.invalidate({
userId, userId,
@ -117,37 +129,38 @@ const DocCheckpoint = () => {
toast.error(`Errore aggiornamento servizio: ${err.message}`); toast.error(`Errore aggiornamento servizio: ${err.message}`);
}, },
}); });
const handleUpload = (
field: "doc_personale_fronte_ref" | "doc_personale_retro_ref",
id: string,
) => {
mutate({
servizioId: servizio.servizio_id,
data: { [field]: id },
});
};
return ( return (
<> <>
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5"> <CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
<div className="space-y-5"> <div className="space-y-5">
<p>Devi ancora caricare un tuo documento</p> <p>Devi ancora caricare un tuo documento</p>
<div className="flex items-center gap-2"> <div className="flex flex-col gap-2">
Fronte documento:{" "} <span>Fronte documento:</span>
<DocComponent <DocComponent
cb={({ userStorageIds }) => cb={({ userStorageIds }) =>
userStorageIds.length > 0 && userStorageIds.length > 0 &&
mutate({ userStorageIds[0] &&
servizioId: servizio.servizio_id, handleUpload("doc_personale_fronte_ref", userStorageIds[0])
data: {
doc_personale_fronte_ref: userStorageIds[0],
},
})
} }
docInfo={servizio.doc_personale_fronte} docInfo={servizio.doc_personale_fronte}
/> />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex flex-col gap-2">
Retro documento:{" "} <span>Retro documento:</span>
<DocComponent <DocComponent
cb={({ userStorageIds }) => cb={({ userStorageIds }) =>
userStorageIds.length > 0 && userStorageIds.length > 0 &&
mutate({ userStorageIds[0] &&
servizioId: servizio.servizio_id, handleUpload("doc_personale_retro_ref", userStorageIds[0])
data: {
doc_personale_retro_ref: userStorageIds[0],
},
})
} }
docInfo={servizio.doc_personale_retro} docInfo={servizio.doc_personale_retro}
/> />
@ -166,7 +179,7 @@ const DocCheckpoint = () => {
</CredenzaBody> </CredenzaBody>
<CredenzaFooter> <CredenzaFooter>
<CredenzaClose asChild> <CredenzaClose asChild>
<Button aria-label="Chiudi modal credenza" variant="outline"> <Button aria-label="Chiudi" disabled={isPending} variant="outline">
{t.chiudi} {t.chiudi}
</Button> </Button>
</CredenzaClose> </CredenzaClose>
@ -182,7 +195,8 @@ const PreUnlock = () => {
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate } = api.servizio.unlockServizioContatti.useMutation({ const { mutate, isPending } = api.servizio.unlockServizioContatti.useMutation(
{
onError: (err) => { onError: (err) => {
toast.error(err.message, { toast.error(err.message, {
id: "unlock-contacts", id: "unlock-contacts",
@ -207,11 +221,11 @@ const PreUnlock = () => {
userId, userId,
}); });
}, },
}); },
);
const d = new Date(); const currentHour = new Date().getHours();
const tooEarly = d.getHours() < 9; const isOutsideBusinessHours = currentHour < 9 || currentHour >= 20;
const tooLate = d.getHours() >= 20;
return ( return (
<> <>
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5"> <CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
@ -221,13 +235,12 @@ const PreUnlock = () => {
<p className="text-muted-foreground text-xs"> <p className="text-muted-foreground text-xs">
{t.contatto.disclaimer3} {t.contatto.disclaimer3}
</p> </p>
{(tooEarly || tooLate) && ( {isOutsideBusinessHours && (
<div className="flex items-center gap-2 rounded-md bg-yellow-200 p-2"> <div className="flex items-center gap-2 rounded-md bg-yellow-200 p-2">
<TriangleAlert className="size-8" /> <TriangleAlert className="size-8" />
{/*TODO testare */}
<p> <p>
Il proprietario riceverà la notifica alle ore 9.00{" "} Il proprietario riceverà la notifica alle ore 9.00{" "}
{tooLate && "di domani"} {currentHour >= 20 && "di domani"}
</p> </p>
</div> </div>
)} )}
@ -236,6 +249,7 @@ const PreUnlock = () => {
<CredenzaFooter> <CredenzaFooter>
<Button <Button
aria-label="Contatta proprietario" aria-label="Contatta proprietario"
disabled={isPending}
onClick={() => onClick={() =>
mutate({ mutate({
annuncioId, annuncioId,
@ -248,7 +262,11 @@ const PreUnlock = () => {
</Button> </Button>
<CredenzaClose asChild> <CredenzaClose asChild>
<Button aria-label="Chiudi modal credenza" variant="outline"> <Button
aria-label="Chiudi modal credenza"
disabled={isPending}
variant="outline"
>
{t.chiudi} {t.chiudi}
</Button> </Button>
</CredenzaClose> </CredenzaClose>
@ -258,18 +276,14 @@ const PreUnlock = () => {
}; };
function getNavigationUrl(lat: string, lon: string): string { function getNavigationUrl(lat: string, lon: string): string {
if (typeof window !== "undefined") { if (typeof window === "undefined") {
const isIOS =
navigator.userAgent.toUpperCase().includes("MAC") ||
navigator.userAgent.toUpperCase().includes("IPAD") ||
navigator.userAgent.toUpperCase().includes("IPHONE") ||
navigator.userAgent.toUpperCase().includes("IOS") ||
navigator.userAgent.toUpperCase().includes("IPOD");
return `${isIOS ? "maps" : "https"}://www.google.com/maps/dir/?api=1&travelmode=driving&layer=traffic&destination=${lat},${lon}`;
}
return `https://www.google.com/maps/dir/?api=1&travelmode=driving&layer=traffic&destination=${lat},${lon}`; return `https://www.google.com/maps/dir/?api=1&travelmode=driving&layer=traffic&destination=${lat},${lon}`;
} }
const isIOS = /iPad|iPhone|iPod|Mac/.test(navigator.userAgent);
const protocol = isIOS ? "maps" : "https";
return `${protocol}://www.google.com/maps/dir/?api=1&travelmode=driving&layer=traffic&destination=${lat},${lon}`;
}
const PostUnlock = () => { const PostUnlock = () => {
const { servizioId } = useServizio(); const { servizioId } = useServizio();
@ -290,80 +304,86 @@ const PostUnlock = () => {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex justify-center"> <CredenzaBody className="flex justify-center p-8">
<LoadingPage /> <LoadingPage />
</div> </CredenzaBody>
); );
} }
if (!data || !data.propData) { if (!data || !data.propData) {
return <p>{t.contatto.error}</p>; return (
<CredenzaBody>
<p className="text-destructive">{t.contatto.error}</p>
</CredenzaBody>
);
} }
const { opened_at, propData } = data;
return ( return (
<> <>
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5"> <CredenzaBody className="max-h-[80vh] overflow-auto pb-5 sm:max-w-xl">
<div className="flex flex-col gap-2"> <div className="space-y-10">
<h3 className="font-semibold">{t.contatto.propietario}:</h3> {/* Proprietario Section */}
<p> <section>
{t.contatto.nome}: {data.propData.locatore} <h3 className="mb-2 font-semibold">{t.contatto.propietario}:</h3>
<p className="mb-2">
{t.contatto.nome}: {propData.locatore}
</p> </p>
<div className="flex items-center gap-2"> <div className="flex flex-wrap items-center gap-5">
<a <a
className="flex items-center gap-2" className="flex items-center gap-2 text-blue-600 hover:underline"
href={`tel:${data.propData.numero}`} href={`tel:${propData.numero}`}
> >
<Phone />: <Phone className="size-4" />
<span className="text-blue-600 underline"> <span>{propData.numero}</span>
{data.propData.numero}
</span>
</a> </a>
<Link <Link href={`https://wa.me/${propData.numero}`} target="_blank">
href={`https://wa.me/${data.propData.numero}`} <Button size="sm" variant="success">
target="_blank"
>
<Button
className="flex items-center gap-2"
size="sm"
variant="success"
>
<WhatsAppIcon2 className="size-4 fill-white" /> <WhatsAppIcon2 className="size-4 fill-white" />
WhatsApp WhatsApp
</Button> </Button>
</Link> </Link>
</div> </div>
</section>
<br /> {/* Immobile Section */}
<h3 className="font-semibold">{t.contatto.immobile}:</h3> <section>
<p> <h3 className="mb-2 font-semibold">{t.contatto.immobile}:</h3>
{t.contatto.indirizzo}: {data.propData.indirizzo},{" "} <p className="mb-2">
{data.propData.civico} {data.propData.comune} ( {t.contatto.indirizzo}: {propData.indirizzo}, {propData.civico}{" "}
{data.propData.provincia}) {propData.comune} ({propData.provincia})
</p> </p>
{navigationUrl && (
<a <a
className="flex w-fit items-center gap-2 rounded-md border border-border px-3 py-2" className="inline-flex items-center gap-2 rounded-md border border-border px-3 py-2 hover:bg-accent"
href={navigationUrl} href={navigationUrl}
rel="noreferrer" rel="noreferrer"
target="_blank" target="_blank"
> >
<Navigation /> <span>{t.contatto.apri_nav}</span> <Navigation className="size-4" />
<span>{t.contatto.apri_nav}</span>
</a> </a>
<br /> )}
<p> </section>
{/* Warning */}
<div className="flex w-fit items-start gap-3 rounded-md bg-red-500 p-2 text-white">
<TriangleAlert className="size-6 shrink-0" />
<div className="space-y-1">
<p className="font-medium">{t.contatto.contatta}</p>
<p className="text-sm">{t.contatto.orari}</p>
</div>
</div>
{/* Metadata */}
<p className="text-muted-foreground text-sm">
{t.contatto.sbloccato_il}:{" "} {t.contatto.sbloccato_il}:{" "}
{data.opened_at.toLocaleString("it", { {opened_at.toLocaleString("it", {
day: "2-digit", day: "2-digit",
hour: "2-digit",
minute: "2-digit",
month: "2-digit", month: "2-digit",
year: "numeric", year: "numeric",
hour: "2-digit",
minute: "2-digit",
})} })}
</p> </p>
<div className="flex items-center gap-2 rounded-md bg-red-500 p-2 text-white">
<TriangleAlert className="size-8" />
<div>
<p>{t.contatto.contatta}</p>
<p className="font-semibold">{t.contatto.orari}</p>
</div>
</div>
</div> </div>
</CredenzaBody> </CredenzaBody>
<CredenzaFooter> <CredenzaFooter>

View file

@ -1,5 +1,5 @@
import { add } from "date-fns"; import { add } from "date-fns";
import { Bug, Calculator, ClockAlert, Plus, Wrench } from "lucide-react"; import { Bug, Calculator, ClockAlert, Plus, ShieldUser } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { import {
@ -50,7 +50,7 @@ import type { ServizioServizioId } from "~/schemas/public/Servizio";
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
export const ServizioActions2 = () => { export const ServizioActions = () => {
const { servizio, isAdmin, userId } = useServizio(); const { servizio, isAdmin, userId } = useServizio();
const canUserEdit = const canUserEdit =
@ -65,7 +65,7 @@ export const ServizioActions2 = () => {
<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 items-center gap-3">
<span className="hidden sm:block">Azioni: </span> {/* <span className="hidden sm:block">Azioni: </span> */}
<EditPreferenze /> <EditPreferenze />
<InterruzioneServizio /> <InterruzioneServizio />
</div> </div>
@ -123,7 +123,7 @@ export const AddAnnuncio = ({
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
aria-label="Add Annuncio" aria-label="Add Annuncio"
className="items-center gap-2" className=""
onClick={() => { onClick={() => {
setSelectedAnnunci([]); setSelectedAnnunci([]);
}} }}
@ -222,11 +222,10 @@ const InterruzioneServizio = () => {
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button <Button
aria-label="Request Interruzione" aria-label="Request Interruzione"
className="items-center gap-2" className=""
disabled={!beforeInterruzioneLimit} disabled={!beforeInterruzioneLimit}
variant="outline"
> >
<ClockAlert /> <span>Interruzione</span> <ClockAlert /> <span> Richiedi Interruzione Servizio</span>
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
@ -288,12 +287,9 @@ const EditPreferenze = () => {
return ( return (
<Credenza onOpenChange={setOpenEditPreferenze} open={openEditPreferenze}> <Credenza onOpenChange={setOpenEditPreferenze} open={openEditPreferenze}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button aria-label="Preferenze di ricerca" className="">
aria-label="Preferenze di ricerca"
className="items-center gap-2"
>
<Calculator /> <Calculator />
Preferenze Modifica 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">
@ -352,11 +348,8 @@ const EditServizioAdmin = () => {
return ( return (
<Credenza onOpenChange={setOpen} open={open}> <Credenza onOpenChange={setOpen} open={open}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button aria-label="Modifica Servizio Admin">
aria-label="Modifica Servizio Admin" <ShieldUser />
className="items-center gap-2"
>
<Wrench />
Admin Admin
</Button> </Button>
</CredenzaTrigger> </CredenzaTrigger>
@ -375,7 +368,7 @@ const EditServizioAdmin = () => {
)} )}
<Collapsible> <Collapsible>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<Button className="items-center gap-2" variant="warning"> <Button variant="warning">
<Bug /> Debug Info Servizio <Bug /> Debug Info Servizio
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>

View file

@ -56,7 +56,7 @@ const DialogServizio = ({
</Dialog> </Dialog>
); );
}; };
export const AnnunciInConferma = ({ export const AnnunciInConfermaDialog = ({
annunci, annunci,
}: { }: {
annunci: ServizioData["annunci"]; annunci: ServizioData["annunci"];
@ -74,11 +74,10 @@ export const AnnunciInConferma = ({
> >
Annunci in conferma Annunci in conferma
</Button> </Button>
{annunci.length > 0 && (
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white"> <span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{annunci.length} {annunci.length || 0}
</span> </span>
)}
</div> </div>
} }
> >
@ -95,7 +94,7 @@ export const AnnunciInConferma = ({
); );
}; };
export const AnnunciSelezionati = ({ export const AnnunciSelezionatiDialog = ({
annunci, annunci,
}: { }: {
annunci: ServizioData["annunci"]; annunci: ServizioData["annunci"];
@ -112,11 +111,10 @@ export const AnnunciSelezionati = ({
> >
Annunci selezionati Annunci selezionati
</Button> </Button>
{annunci.length > 0 && (
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white"> <span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{annunci.length} {annunci.length || 0}
</span> </span>
)}
</div> </div>
} }
> >
@ -149,6 +147,79 @@ export const AnnunciSelezionati = ({
); );
}; };
export const AnnunciInConfermaAccordion = ({
annunci,
}: {
annunci: ServizioData["annunci"];
}) => {
if (annunci.length === 0) {
return null; // If there are no announcements in confirmation, do not render this section
}
return (
<Accordion collapsible defaultValue="in-conferma" type="single">
<AccordionItem className="space-y-4" value="in-conferma">
<AccordionTrigger className="rounded-md border-2 border-green-500 bg-white p-2">
Annunci in conferma:
</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col flex-wrap gap-5">
{annunci.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard className="border-2 border-green-500" />
</ServizioAnnuncioProvider>
);
})}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
);
};
export const AnnunciSelezionatiAccordion = ({
annunci,
}: {
annunci: ServizioData["annunci"];
}) => {
return (
<Accordion collapsible defaultValue="open" type="single">
<AccordionItem className="space-y-4" value="open">
<AccordionTrigger className="cursor-pointer rounded-md border-2 border-sky-500 bg-white p-2">
Annunci Selezionati:
</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col flex-wrap gap-5">
{annunci.length > 0 ? (
annunci.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard className="border-2 border-sky-500" />
</ServizioAnnuncioProvider>
);
})
) : (
<div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex">
<span>
Nessun annuncio selezionato, fai una ricerca nella pagina{" "}
<Link
aria-label="Annunci Compatibili"
className="inline-flex items-center gap-1 text-blue-500 underline"
href="/annunci"
>
Annunci
<ExternalLink className="size-4" />
</Link>
</span>
</div>
)}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
);
};
export const AnnunciCompatibili = () => { export const AnnunciCompatibili = () => {
const { servizioId, userId, isAdmin } = useServizio(); const { servizioId, userId, isAdmin } = useServizio();
const { data, isLoading } = api.servizio.getCompatibileAnnunci.useQuery({ const { data, isLoading } = api.servizio.getCompatibileAnnunci.useQuery({
@ -163,16 +234,9 @@ export const AnnunciCompatibili = () => {
<DialogServizio <DialogServizio
title="Annunci compatibili" title="Annunci compatibili"
trigger={ trigger={
<div className="relative flex w-full sm:w-fit"> <Button className="w-full bg-purple-500 sm:w-fit">
<Button className="w-full bg-purple-500 text-lg sm:w-fit" size="xl"> Esplora Annunci Compatibili
Annunci compatibili
</Button> </Button>
{data && data.length > 0 && (
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
{data?.length}
</span>
)}
</div>
} }
> >
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto"> <div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
@ -254,7 +318,6 @@ export const AnnunciRichiesti = ({ userId }: { userId: UsersId }) => {
data={a} data={a}
interactions={ interactions={
<Button <Button
className="flex items-center gap-2"
onClick={() => { onClick={() => {
deleteAnnuncio({ annuncioId: a.id, userId }); deleteAnnuncio({ annuncioId: a.id, userId });
}} }}

View file

@ -51,11 +51,7 @@ export const AnnunciTable = ({
href={`/area-riservata/admin/edit-annuncio/${row.original.id}`} href={`/area-riservata/admin/edit-annuncio/${row.original.id}`}
target="_blank" target="_blank"
> >
<Button <Button className="px-2" size="sm" variant="default">
className="flex items-center gap-2 px-2"
size="sm"
variant="default"
>
<Wrench /> <Wrench />
</Button> </Button>
</Link> </Link>

View file

@ -166,7 +166,6 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
return ( return (
<Button <Button
aria-label="Apri Dettagli Ordine" aria-label="Apri Dettagli Ordine"
className="flex items-center gap-2"
onClick={() => { onClick={() => {
setSelectedOrdine(row.original.id); setSelectedOrdine(row.original.id);
setModalOpen(true); setModalOpen(true);

View file

@ -33,17 +33,7 @@ export type UploadCallback = (args: {
userStorageIds: UsersStorageUserStorageId[]; userStorageIds: UsersStorageUserStorageId[];
}) => void; }) => void;
type UploadModalProps = { export const UploadModal = (props: UploadComponentProps) => {
cb_onUpload?: UploadCallback;
isAdmin?: boolean;
userId?: UsersId;
};
export const UploadModal = ({
cb_onUpload,
isAdmin,
userId,
}: UploadModalProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -63,11 +53,7 @@ export const UploadModal = ({
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5"> <CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
<UploadComponent <UploadComponent {...props} />
cb_onUpload={cb_onUpload}
isAdmin={isAdmin}
userId={userId}
/>
</CredenzaBody> </CredenzaBody>
</CredenzaContent> </CredenzaContent>
</Credenza> </Credenza>

View file

@ -246,12 +246,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithImages }) => {
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Link href={`/annuncio/${data.codice}`} target="_blank"> <Link href={`/annuncio/${data.codice}`} target="_blank">
<Button <Button size="sm" type="button" variant="outline">
className="flex items-center gap-2"
size="sm"
type="button"
variant="outline"
>
<Eye /> pubblica <Eye /> pubblica
</Button> </Button>
</Link> </Link>
@ -259,28 +254,19 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithImages }) => {
href={`/area-riservata/admin/scheda-annuncio-stampa/${data.id}`} href={`/area-riservata/admin/scheda-annuncio-stampa/${data.id}`}
target="_blank" target="_blank"
> >
<Button <Button size="sm" type="button" variant="outline">
className="flex items-center gap-2"
size="sm"
type="button"
variant="outline"
>
<Printer /> Scheda <Printer /> Scheda
</Button> </Button>
</Link> </Link>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button size="sm" type="button">
className="flex items-center gap-2"
size="sm"
type="button"
>
<CodeSquare /> Strumenti <CodeSquare /> Strumenti
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="flex w-fit items-center gap-2"> <PopoverContent className="flex w-fit items-center gap-2">
<Button <Button
className="flex items-center gap-2 bg-purple-500" className="bg-purple-500"
onClick={async () => { onClick={async () => {
await revalidate({ await revalidate({
cod: data.codice, cod: data.codice,
@ -298,12 +284,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithImages }) => {
}} }}
title="Aggiornamento Annuncio" title="Aggiornamento Annuncio"
> >
<Button <Button size="sm" type="button" variant="warning">
className="flex items-center gap-2"
size="sm"
type="button"
variant="warning"
>
<TriangleAlert /> Aggiorna <TriangleAlert /> Aggiorna
</Button> </Button>
</Confirm> </Confirm>

View file

@ -182,7 +182,6 @@ export const FormEditServizio = ({
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<Label htmlFor="reset_decorrenza">Reset Decorrenza</Label> <Label htmlFor="reset_decorrenza">Reset Decorrenza</Label>
<Button <Button
className="flex items-center gap-2"
id="reset_decorrenza" id="reset_decorrenza"
onClick={() => form.setValue("decorrenza", null)} onClick={() => form.setValue("decorrenza", null)}
type="button" type="button"

View file

@ -233,10 +233,7 @@ export const FormNewServizioAcquisto = ({
path: ["n_adulti"], path: ["n_adulti"],
}); });
} }
if ( if (!permanenza && tipologia === TipologiaPosizioneEnum.Transitorio) {
permanenza === null &&
tipologia === TipologiaPosizioneEnum.Transitorio
) {
refinementContext.issues.push({ refinementContext.issues.push({
code: "custom", code: "custom",
input: "", input: "",
@ -264,6 +261,14 @@ export const FormNewServizioAcquisto = ({
path: ["scadenza_motivazione_transitoria"], path: ["scadenza_motivazione_transitoria"],
}); });
} }
if (!permanenza) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci permanenza",
path: ["permanenza"],
});
}
} else { } else {
if (reddito === "" || reddito === null) { if (reddito === "" || reddito === null) {
refinementContext.issues.push({ refinementContext.issues.push({
@ -315,7 +320,7 @@ export const FormNewServizioAcquisto = ({
defaultValues: defaultValues, defaultValues: defaultValues,
}); });
const DatePickerLocale = locale === "it" ? it : enUS; const DatePickerLocale = locale === "it" ? it : enUS;
const { mutate } = api.servizio.postAcquistoData.useMutation({ const { mutate } = api.servizio.submitOnboardForPurchase.useMutation({
onError: (error) => { onError: (error) => {
toast.error(`Errore invio dati: ${error.message}`, { toast.error(`Errore invio dati: ${error.message}`, {
id: "salvataggio", id: "salvataggio",
@ -424,7 +429,7 @@ export const FormNewServizioAcquisto = ({
fattura: "Vuoi che la fatturazione sia intestata alla tua azienda?", fattura: "Vuoi che la fatturazione sia intestata alla tua azienda?",
personali: "Dati personali", personali: "Dati personali",
personali_desc: "Inserisci i tuoi dati personali", personali_desc: "Inserisci i tuoi dati personali",
salva: "Salva e procedi al pagamento", salva: "Salva e procedi",
sdi: "Codice destinatario SDI", sdi: "Codice destinatario SDI",
} }
: { : {
@ -433,7 +438,7 @@ export const FormNewServizioAcquisto = ({
fattura: "Do you want the invoice to be issued to your company?", fattura: "Do you want the invoice to be issued to your company?",
personali: "Personal Data", personali: "Personal Data",
personali_desc: "Enter your personal data", personali_desc: "Enter your personal data",
salva: "Save and proceed to payment", salva: "Save and proceed",
sdi: "SDI Recipient Code", sdi: "SDI Recipient Code",
}; };
@ -694,7 +699,7 @@ export const FormNewServizioAcquisto = ({
</div> </div>
{tipologia === TipologiaPosizioneEnum.Transitorio ? ( {tipologia === TipologiaPosizioneEnum.Transitorio ? (
<> <div className="items-top flex w-full flex-col justify-between gap-4 sm:flex-row">
<FormField <FormField
control={form.control} control={form.control}
key={tipologia} key={tipologia}
@ -704,10 +709,9 @@ export const FormNewServizioAcquisto = ({
t.preferenze.motivazioni_mappings.find( t.preferenze.motivazioni_mappings.find(
(m) => m.id === field.value, (m) => m.id === field.value,
); );
return ( return (
<FormItem> <FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2"> <div className="flex w-full flex-wrap items-center gap-x-2">
<FormLabel htmlFor="motivazione_transitorio"> <FormLabel htmlFor="motivazione_transitorio">
{t.preferenze.motivazione_label} {t.preferenze.motivazione_label}
</FormLabel> </FormLabel>
@ -736,7 +740,6 @@ export const FormNewServizioAcquisto = ({
)} )}
placeholder={t.seleziona_placeholder} placeholder={t.seleziona_placeholder}
/> />
<FormDescription> <FormDescription>
{t.preferenze.motivazione_desc} {t.preferenze.motivazione_desc}
</FormDescription> </FormDescription>
@ -794,7 +797,7 @@ export const FormNewServizioAcquisto = ({
</FormItem> </FormItem>
)} )}
/> />
</> </div>
) : ( ) : (
<FormField <FormField
control={form.control} control={form.control}

View file

@ -60,7 +60,7 @@ 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 px-2 py-5 md:px-8"> <main className="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-primary/10 px-3 py-1 text-primary text-sm">
{t.annunci.titolo} {t.annunci.titolo}
</div> </div>

View file

@ -150,7 +150,7 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
href={`/area-riservata/admin/edit-annuncio/${data.id}`} href={`/area-riservata/admin/edit-annuncio/${data.id}`}
> >
<Button <Button
className="flex w-full items-center gap-2" className="w-full"
type="button" type="button"
> >
<Edit /> Modifica annuncio <Edit /> Modifica annuncio
@ -162,7 +162,7 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
target="_blank" target="_blank"
> >
<Button <Button
className="flex w-full items-center gap-2" className="w-full"
type="button" type="button"
variant="outline" variant="outline"
> >
@ -669,7 +669,7 @@ const AnnuncioFooter = ({
}} }}
target="_blank" target="_blank"
> >
<Button className="my-10 flex w-full items-center gap-2 bg-neutral-500 text-xl"> <Button className="my-10 w-full bg-neutral-500 text-xl">
Cerca annunci simili <ExternalLink className="size-6" /> Cerca annunci simili <ExternalLink className="size-6" />
</Button> </Button>
</Link> </Link>

View file

@ -53,17 +53,13 @@ const Admin_Annunci: NextPageWithLayout = () => {
</button> </button>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button size="sm" type="button">
className="flex items-center gap-2"
size="sm"
type="button"
>
<CodeSquare /> Strumenti avanzati <CodeSquare /> Strumenti avanzati
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="flex flex-col items-center gap-2"> <PopoverContent className="flex flex-col items-center gap-2">
<Button <Button
className="flex items-center gap-2 bg-purple-500" className="bg-purple-500"
onClick={async () => { onClick={async () => {
await revalidate(); await revalidate();
}} }}
@ -79,12 +75,7 @@ const Admin_Annunci: NextPageWithLayout = () => {
}} }}
title="Aggiornamento Annunci" title="Aggiornamento Annunci"
> >
<Button <Button size="sm" type="button" variant="warning">
className="flex items-center gap-2"
size="sm"
type="button"
variant="warning"
>
<TriangleAlert /> Aggiorna tutti gli annunci <TriangleAlert /> Aggiorna tutti gli annunci
</Button> </Button>
</Confirm> </Confirm>

View file

@ -61,7 +61,7 @@ const AdminBanners: NextPageWithLayout = () => {
</div> </div>
<div className="mt-3 md:mt-0"> <div className="mt-3 md:mt-0">
<Button <Button
className="flex items-center gap-2 font-semibold" className="font-semibold"
onClick={() => { onClick={() => {
handleEdit(null); handleEdit(null);
handleOpen(true); handleOpen(true);

View file

@ -62,7 +62,7 @@ const AdminBlacklist: NextPageWithLayout = () => {
</div> </div>
<div className="mt-3 md:mt-0"> <div className="mt-3 md:mt-0">
<Button <Button
className="flex items-center gap-2 font-semibold" className="font-semibold"
onClick={() => { onClick={() => {
handleEdit(null); handleEdit(null);
handleOpen(true); handleOpen(true);

View file

@ -62,7 +62,7 @@ const AdminEtichette: NextPageWithLayout = () => {
</div> </div>
<div className="mt-3 md:mt-0"> <div className="mt-3 md:mt-0">
<Button <Button
className="flex items-center gap-2 font-semibold" className="font-semibold"
onClick={() => { onClick={() => {
handleEdit(null); handleEdit(null);
handleOpen(true); handleOpen(true);

View file

@ -60,7 +60,7 @@ const AdminFlags: NextPageWithLayout = () => {
</div> </div>
<div className="mt-3 md:mt-0"> <div className="mt-3 md:mt-0">
<Button <Button
className="flex items-center gap-2 font-semibold" className="font-semibold"
onClick={() => { onClick={() => {
handleEdit(null); handleEdit(null);
handleOpen(true); handleOpen(true);

View file

@ -62,7 +62,7 @@ const AdminTestiStringhe: NextPageWithLayout = () => {
</div> </div>
<div className="mt-3 md:mt-0"> <div className="mt-3 md:mt-0">
<Button <Button
className="flex items-center gap-2 font-semibold" className="font-semibold"
onClick={() => { onClick={() => {
handleEdit(null); handleEdit(null);
handleOpen(true); handleOpen(true);

View file

@ -125,10 +125,7 @@ const EditUser: NextPageWithLayout<EditUserProps> = ({
<div className="flex flex-wrap items-end gap-2"> <div className="flex flex-wrap items-end gap-2">
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button <Button variant={userData.isBlocked ? "success" : "orange"}>
className="flex items-center gap-2"
variant={userData.isBlocked ? "success" : "orange"}
>
{userData.isBlocked ? ( {userData.isBlocked ? (
<> <>
<Unlock className="size-4" /> <Unlock className="size-4" />
@ -161,10 +158,7 @@ const EditUser: NextPageWithLayout<EditUserProps> = ({
</AlertDialog> </AlertDialog>
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button <Button variant="destructive">
className="flex items-center gap-2"
variant="destructive"
>
<Trash2 className="size-4" /> Elimina utente permanentemente <Trash2 className="size-4" /> Elimina utente permanentemente
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>

View file

@ -65,7 +65,7 @@ const PreOnboardServizio: NextPageWithLayout<PreOnboardServizioProps> = ({
</div> </div>
<div className="flex justify-center"> <div className="flex justify-center">
<Button <Button
className="flex items-center gap-2 text-xl" className="text-xl"
onClick={async () => { onClick={async () => {
if (data.hasPrevious) { if (data.hasPrevious) {
await router.push({ await router.push({

View file

@ -65,7 +65,7 @@ const RiapriConferma: NextPageWithLayout<RiapriConfermaProps> = ({
</div> </div>
<Button <Button
className="mx-auto mb-4 flex items-center gap-2" className="mx-auto mb-4"
onClick={() => router.back()} onClick={() => router.back()}
variant="secondary" variant="secondary"
> >

View file

@ -250,7 +250,7 @@ export const servizioRouter = createTRPCRouter({
return await isOrdineAwaitingPayment(input.ordineId); return await isOrdineAwaitingPayment(input.ordineId);
}), }),
postAcquistoData: protectedProcedure submitOnboardForPurchase: protectedProcedure
.input( .input(
z.object({ z.object({
anagrafica: z.custom<UsersAnagraficaUpdate>(), anagrafica: z.custom<UsersAnagraficaUpdate>(),

View file

@ -247,6 +247,7 @@ export const postAcquistoDataHandler = async ({
}) => { }) => {
try { try {
const servizioData = await db.transaction().execute(async (tx) => { const servizioData = await db.transaction().execute(async (tx) => {
// Update user and anagrafica data
await tx await tx
.updateTable("users") .updateTable("users")
.set(user) .set(user)
@ -274,13 +275,7 @@ export const postAcquistoDataHandler = async ({
.returningAll() .returningAll()
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
} }
// Update servizio data
await tx
.updateTable("users_anagrafica")
.set(anagrafica)
.where("userid", "=", userId)
.executeTakeFirstOrThrow();
return await tx return await tx
.updateTable("servizio") .updateTable("servizio")
.set(servizio) .set(servizio)
@ -290,6 +285,7 @@ export const postAcquistoDataHandler = async ({
}); });
if (skipOrdine) { if (skipOrdine) {
// If skipping order, set servizio as active with decorrenza today
await updateServizio({ await updateServizio({
data: { data: {
decorrenza: new Date(), decorrenza: new Date(),
@ -627,6 +623,9 @@ export const getAllServizioAnnunci = async (userId: UsersId) => {
"servizio.isOkAcconto", "servizio.isOkAcconto",
"servizio.isOkSaldo", "servizio.isOkSaldo",
"servizio.isOkConsulenza", "servizio.isOkConsulenza",
"servizio.skipPayment",
"servizio.skipControlloDoc",
"servizio.skipDocMotivazione",
jsonObjectFrom( jsonObjectFrom(
eb eb
.selectFrom("users_storage") .selectFrom("users_storage")

View file

@ -42,6 +42,7 @@
--radius-lg: var(--radius); --radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px); --radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background); --color-background: var(--background);
--color-background2: var(--background2);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-card: var(--card); --color-card: var(--card);
--color-card-foreground: var(--card-foreground); --color-card-foreground: var(--card-foreground);
@ -171,6 +172,7 @@
:root { :root {
--radius: 0.625rem; --radius: 0.625rem;
--background: oklch(1 0 0); --background: oklch(1 0 0);
--background2: oklch(95.041% 0.02791 62.217);
--foreground: oklch(0.145 0 0); --foreground: oklch(0.145 0 0);
--card: oklch(1 0 0); --card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0); --card-foreground: oklch(0.145 0 0);