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:
parent
97503b34e5
commit
6ad74c9002
43 changed files with 829 additions and 416 deletions
|
|
@ -109,7 +109,6 @@
|
|||
"noDangerouslySetInnerHtml": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNestedTernary": "info",
|
||||
"noUselessElse": "on",
|
||||
"noYodaExpression": "on",
|
||||
"useThrowNewError": "on",
|
||||
|
|
|
|||
|
|
@ -199,11 +199,7 @@ const UserViewHeader = () => {
|
|||
</div>
|
||||
|
||||
<Link href={`https://wa.me/${data.telefono}`} target="_blank">
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
variant="success"
|
||||
>
|
||||
<Button size="sm" variant="success">
|
||||
<WhatsAppIcon2 className="size-4 fill-white" />
|
||||
WhatsApp
|
||||
</Button>
|
||||
|
|
@ -212,7 +208,7 @@ const UserViewHeader = () => {
|
|||
<div className="flex flex-row flex-wrap gap-2">
|
||||
<Link href={`/area-riservata/admin/user-view/edit-user/${data.id}`}>
|
||||
<Button
|
||||
className="flex items-center gap-2 border"
|
||||
className="border"
|
||||
size="sm"
|
||||
variant={pathname.includes("edit-user") ? "secondary" : "outline"}
|
||||
>
|
||||
|
|
@ -221,7 +217,7 @@ const UserViewHeader = () => {
|
|||
</Link>
|
||||
<Link href={`/area-riservata/admin/user-view/servizio/${data.id}`}>
|
||||
<Button
|
||||
className="flex items-center gap-2 border"
|
||||
className="border"
|
||||
size="sm"
|
||||
variant={pathname.includes("servizio") ? "secondary" : "outline"}
|
||||
>
|
||||
|
|
@ -231,7 +227,7 @@ const UserViewHeader = () => {
|
|||
|
||||
<Link href={`/area-riservata/admin/user-view/ricerca/${data.id}`}>
|
||||
<Button
|
||||
className="flex items-center gap-2 border"
|
||||
className="border"
|
||||
size="sm"
|
||||
variant={pathname.includes("ricerca") ? "secondary" : "outline"}
|
||||
>
|
||||
|
|
@ -240,7 +236,7 @@ const UserViewHeader = () => {
|
|||
</Link>
|
||||
<Link href={`/area-riservata/admin/user-view/comunicazioni/${data.id}`}>
|
||||
<Button
|
||||
className="flex items-center gap-2 border"
|
||||
className="border"
|
||||
size="sm"
|
||||
variant={
|
||||
pathname.includes("comunicazioni") ? "secondary" : "outline"
|
||||
|
|
@ -252,7 +248,7 @@ const UserViewHeader = () => {
|
|||
|
||||
<Link href={`/area-riservata/admin/user-view/ordini/${data.id}`}>
|
||||
<Button
|
||||
className="flex items-center gap-2 border"
|
||||
className="border"
|
||||
size="sm"
|
||||
variant={pathname.includes("ordini") ? "secondary" : "outline"}
|
||||
>
|
||||
|
|
@ -261,7 +257,7 @@ const UserViewHeader = () => {
|
|||
</Link>
|
||||
<Link href={`/area-riservata/admin/user-view/allegati/${data.id}`}>
|
||||
<Button
|
||||
className="flex items-center gap-2 border"
|
||||
className="border"
|
||||
size="sm"
|
||||
variant={pathname.includes("allegati") ? "secondary" : "outline"}
|
||||
>
|
||||
|
|
@ -281,7 +277,7 @@ const UserViewHeader = () => {
|
|||
target="_blank"
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-2 border"
|
||||
className="border"
|
||||
size="sm"
|
||||
variant={!data.chatid ? "ghost" : "outline"}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ const PricePreparation = ({
|
|||
href={`/servizio/condizioni/${prezziario.testo_condizioni}`}
|
||||
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>
|
||||
<ExternalLink />
|
||||
</Button>
|
||||
|
|
@ -154,7 +154,7 @@ const PricePreparation = ({
|
|||
)}
|
||||
|
||||
<Button
|
||||
className="flex w-full items-center gap-2 text-lg"
|
||||
className="w-full text-lg"
|
||||
disabled={isLoading || !prezziario}
|
||||
onClick={onProcedi}
|
||||
size="xl"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CardAnnuncio } from "~/components/annuncio_card";
|
||||
import { CardAnnuncio } from "~/components/annuncio_card2";
|
||||
import { getTitoloTranslation } from "~/lib/annuncio_details";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export const CTA_TipologiaModal = () => {
|
|||
open={open}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="flex items-center gap-2" variant="info">
|
||||
<Button variant="info">
|
||||
<CircleHelp />{" "}
|
||||
<span className="hidden sm:block">
|
||||
{t.tipologia_tutorial.btn_title}
|
||||
|
|
@ -78,7 +78,7 @@ export const CTA_TipologiaModal = () => {
|
|||
<p>{t.tipologia_tutorial.transitorio_desc}</p>
|
||||
<Button
|
||||
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")}
|
||||
>
|
||||
{t.tipologia_tutorial.transitorio_cta}
|
||||
|
|
@ -93,7 +93,7 @@ export const CTA_TipologiaModal = () => {
|
|||
<p>{t.tipologia_tutorial.stabile_desc}</p>
|
||||
<Button
|
||||
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")}
|
||||
>
|
||||
{t.tipologia_tutorial.stabile_cta}
|
||||
|
|
@ -104,7 +104,7 @@ export const CTA_TipologiaModal = () => {
|
|||
|
||||
<Button
|
||||
aria-label={t.tipologia_tutorial.alone}
|
||||
className="flex w-full items-center gap-2"
|
||||
className="w-full"
|
||||
onClick={async () => await update(null)}
|
||||
variant="outline"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ const ServizioInteraction = ({
|
|||
aria-label="Vai alla tua area riservata"
|
||||
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 />
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -128,7 +128,7 @@ const InteressatoButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
|
|||
|
||||
if (isLoading || hasInterest === undefined) {
|
||||
return (
|
||||
<Button className="flex w-full items-center gap-2" disabled>
|
||||
<Button className="w-full" disabled>
|
||||
<BadgePlus className="size-6" />
|
||||
Caricamento...
|
||||
</Button>
|
||||
|
|
@ -146,7 +146,7 @@ const InteressatoButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
|
|||
return (
|
||||
<Button
|
||||
aria-label="Sono interessato"
|
||||
className="flex w-full items-center gap-2"
|
||||
className="w-full"
|
||||
//size="xl"
|
||||
onClick={() => {
|
||||
add({
|
||||
|
|
@ -188,7 +188,7 @@ export const InteressatoButtonBatchV = ({
|
|||
return (
|
||||
<Button
|
||||
aria-label="Sono interessato"
|
||||
className="flex w-full items-center gap-2"
|
||||
className="w-full"
|
||||
//size="xl"
|
||||
onClick={() => {
|
||||
add({
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@ export const ContattoAnnuncio = () => {
|
|||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
className="flex w-full items-center gap-2"
|
||||
variant="destructive"
|
||||
>
|
||||
<Button className="w-full" variant="destructive">
|
||||
<MessageCircleQuestion className="size-6" /> Contattaci
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export const CardAnnuncio = ({
|
|||
<Carousel opts={{ loop: true }}>
|
||||
<CarouselContent>
|
||||
{images?.map((img, idx) => (
|
||||
<CarouselItem className="" key={`${img.img}key`}>
|
||||
<CarouselItem key={`${img.img}key`}>
|
||||
<ImageFlbk
|
||||
alt={t.card.alt_immagine}
|
||||
blurDataURL={`/storage-api/get/${img.thumb}?image=true`}
|
||||
|
|
|
|||
404
apps/infoalloggi/src/components/annuncio_card2.tsx
Normal file
404
apps/infoalloggi/src/components/annuncio_card2.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -30,7 +30,7 @@ export const AdminDashboard = () => {
|
|||
<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">
|
||||
<Card className="">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Utenti</CardTitle>
|
||||
<CardDescription>
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ export const OrdiniModal = ({
|
|||
href={`/area-riservata/payment-recap/${data.ordine_id}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button className="flex items-center gap-2" variant="outline">
|
||||
<Button variant="outline">
|
||||
Ricevuta di cortesia <ReceiptText />
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export const ChatAttachments = ({
|
|||
target="_blank"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<ExtIcon mimeType={file.mimeType} />
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ const FormMessage = React.forwardRef<
|
|||
return (
|
||||
<p
|
||||
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,
|
||||
)}
|
||||
id={formMessageId}
|
||||
|
|
|
|||
|
|
@ -107,12 +107,7 @@ export const OnboardTutorial = () => {
|
|||
</div>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
onClick={handleClose}
|
||||
size="lg"
|
||||
variant="success"
|
||||
>
|
||||
<Button onClick={handleClose} size="lg" variant="success">
|
||||
<span>{t.onboarding_tutorial.button_text}</span>
|
||||
<ArrowRight className="size-5" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ export const PricingComponent = ({
|
|||
href={"/prezzi#contratti"}
|
||||
id="contratto-link"
|
||||
>
|
||||
<Button className="flex items-center gap-2">
|
||||
<Button>
|
||||
Più info <ExternalLink className="size-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ function SchedaAnnuncio({ data }: { data: Annunci }) {
|
|||
className="flex items-center justify-start gap-2"
|
||||
key={item.text}
|
||||
>
|
||||
<span className="">
|
||||
<span>
|
||||
<IconMatrix type={item.icon} />
|
||||
</span>
|
||||
<span>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
FolderKanban,
|
||||
Pen,
|
||||
Plus,
|
||||
ShieldUser,
|
||||
Trash2,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
|
|
@ -99,7 +100,7 @@ export const AnnuncioActions = () => {
|
|||
href={`https://win-jk822fggr6b:480/id/${data.gestionale_id}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button className="flex items-center gap-2">
|
||||
<Button>
|
||||
<FolderKanban className="size-5" /> Gestionale
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -107,18 +108,29 @@ export const AnnuncioActions = () => {
|
|||
</>
|
||||
)}
|
||||
{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
|
||||
href={`/area-riservata/admin/edit-annuncio/${annuncioId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button
|
||||
aria-label="Edit Annuncio"
|
||||
className="flex w-full items-center gap-2"
|
||||
className="w-full"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
<Wrench className="size-6" />
|
||||
<Wrench />
|
||||
<span>Modifica Annuncio</span>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
|
|
@ -134,14 +146,16 @@ export const AnnuncioActions = () => {
|
|||
>
|
||||
<Button
|
||||
aria-label="Delete Annuncio"
|
||||
className="flex items-center gap-2"
|
||||
className="w-full"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 className="size-6" />
|
||||
<Trash2 />
|
||||
<span>Elimina Annuncio</span>
|
||||
</Button>
|
||||
</Confirm>
|
||||
</>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -232,7 +246,6 @@ const ContrattoImport = ({
|
|||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
aria-label="Import Contratto"
|
||||
className="flex items-center gap-2"
|
||||
onClick={async () => {
|
||||
const clipboardText = await navigator.clipboard.readText();
|
||||
|
||||
|
|
@ -245,7 +258,6 @@ const ContrattoImport = ({
|
|||
</Button>
|
||||
<Button
|
||||
aria-label="Reset Contratto"
|
||||
className="flex items-center gap-2"
|
||||
onClick={async () => {
|
||||
setContratto({
|
||||
contratto_decorrenza: null,
|
||||
|
|
@ -445,7 +457,7 @@ const AdminContratto = () => {
|
|||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
aria-label="Open Contratto Section"
|
||||
className="flex w-fit items-center gap-2"
|
||||
className="w-fit"
|
||||
variant={data.doc_contratto_added ? "success" : "secondary"}
|
||||
>
|
||||
{data.doc_contratto_added ? (
|
||||
|
|
@ -709,7 +721,7 @@ const AdminLavoraConferma = () => {
|
|||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
aria-label="Open Lavora Conferma Section"
|
||||
className="flex w-fit items-center gap-2"
|
||||
className="w-fit"
|
||||
variant={data.hasConfermaAdmin ? "success" : "secondary"}
|
||||
>
|
||||
{data.hasConfermaAdmin ? (
|
||||
|
|
@ -983,7 +995,6 @@ const Caparra = ({
|
|||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Button
|
||||
aria-label="Import Caparra"
|
||||
className="flex items-center gap-2"
|
||||
onClick={async () => {
|
||||
const clipboardText = await navigator.clipboard.readText();
|
||||
|
||||
|
|
@ -996,7 +1007,6 @@ const Caparra = ({
|
|||
</Button>
|
||||
<Button
|
||||
aria-label="Reset Caparra"
|
||||
className="flex items-center gap-2"
|
||||
onClick={async () => {
|
||||
setCaparra({
|
||||
caparra_causale: null,
|
||||
|
|
|
|||
|
|
@ -120,10 +120,7 @@ export const BasicAnnuncioCard = ({
|
|||
aria-label="Pagina Annuncio"
|
||||
href={`/annuncio/${data.codice}`}
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
variant="outline"
|
||||
>
|
||||
<Button variant="outline">
|
||||
<ExternalLink className="size-6" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -159,11 +156,7 @@ const AnnuncioDettaglio = ({ data }: { data: AnnuncioData }) => {
|
|||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
aria-label="Dettagli Annuncio"
|
||||
className="flex items-center gap-2"
|
||||
variant="outline"
|
||||
>
|
||||
<Button aria-label="Dettagli Annuncio" variant="outline">
|
||||
<UnfoldVertical className="size-6" /> Dettagli annuncio
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
|
|
|
|||
|
|
@ -58,10 +58,7 @@ export const Interactions = () => {
|
|||
aria-label="Conferma immobile"
|
||||
href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`}
|
||||
>
|
||||
<Button
|
||||
className="flex w-fit items-center gap-2"
|
||||
variant="success"
|
||||
>
|
||||
<Button className="w-fit" variant="success">
|
||||
<Pointer className="size-6" />
|
||||
Confermato - Procedi ora
|
||||
</Button>
|
||||
|
|
@ -71,7 +68,7 @@ export const Interactions = () => {
|
|||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
className="flex w-fit cursor-not-allowed items-center gap-2 opacity-50"
|
||||
className="w-fit cursor-not-allowed opacity-50"
|
||||
variant="success"
|
||||
>
|
||||
<Pointer className="size-6" />
|
||||
|
|
@ -92,7 +89,7 @@ export const Interactions = () => {
|
|||
aria-label="Conferma immobile"
|
||||
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" />
|
||||
Rivedi la conferma
|
||||
</Button>
|
||||
|
|
@ -136,7 +133,7 @@ export const Interactions = () => {
|
|||
href={`/servizio/contratto/${servizioId}/${annuncioId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button className="flex w-fit items-center gap-2">
|
||||
<Button className="w-fit">
|
||||
<FileText className="size-6" />
|
||||
Visualizza il contratto
|
||||
</Button>
|
||||
|
|
@ -145,7 +142,7 @@ export const Interactions = () => {
|
|||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<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" />
|
||||
Visualizza il contratto
|
||||
</Button>
|
||||
|
|
@ -172,7 +169,7 @@ export const Interactions = () => {
|
|||
href={`/servizio/contratto-registrazione/${servizioId}/${annuncioId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button className="flex w-fit items-center gap-2">
|
||||
<Button className="w-fit">
|
||||
<FileBadge className="size-6" />
|
||||
Visualizza la registrazione
|
||||
</Button>
|
||||
|
|
@ -181,7 +178,7 @@ export const Interactions = () => {
|
|||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<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" />
|
||||
Visualizza la registrazione
|
||||
</Button>
|
||||
|
|
@ -230,7 +227,7 @@ export const SaldoButton = ({
|
|||
return (
|
||||
<Button
|
||||
aria-label="Procedi al saldo"
|
||||
className={cn("flex w-fit items-center gap-2", className)}
|
||||
className={cn("w-fit", className)}
|
||||
onClick={() => {
|
||||
mutate({
|
||||
annuncioId,
|
||||
|
|
@ -271,7 +268,7 @@ const SaldoConsulenzaButton = ({
|
|||
return (
|
||||
<Button
|
||||
aria-label="Procedi al saldo consulenza"
|
||||
className={cn("flex w-fit items-center gap-2", className)}
|
||||
className={cn("w-fit", className)}
|
||||
onClick={() => {
|
||||
mutate({
|
||||
annuncioId,
|
||||
|
|
@ -311,10 +308,7 @@ const AnnuncioConfirm = () => {
|
|||
return (
|
||||
<Credenza onOpenChange={setOpen} open={open}>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
aria-label="Conferma immobile"
|
||||
className="flex w-fit items-center gap-2"
|
||||
>
|
||||
<Button aria-label="Conferma immobile" className="w-fit">
|
||||
<Handshake className="size-6" /> Conferma immobile
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
|
|
@ -356,10 +350,7 @@ const ConfermaLavorazioneModal = () => {
|
|||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
aria-label="Conferma in lavorazione"
|
||||
className="flex w-fit items-center gap-2"
|
||||
>
|
||||
<Button aria-label="Conferma in lavorazione" className="w-fit">
|
||||
<CircleCheck className="size-7" />
|
||||
Conferma in lavorazione
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { add } from "date-fns";
|
||||
import { Package, PackageCheck, UserCircle } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
|
@ -5,8 +6,7 @@ import { useEffect } from "react";
|
|||
import toast from "react-hot-toast";
|
||||
import { AnnuncioCard } from "~/components/servizio/annuncio_card";
|
||||
import { SaldoButton } from "~/components/servizio/interactions";
|
||||
import { ServizioDuration } from "~/components/servizio/service-duration-display";
|
||||
import { ServizioActions2 } from "~/components/servizio/servizio_actions";
|
||||
import { ServizioActions } from "~/components/servizio/servizio_actions";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -24,7 +24,8 @@ import {
|
|||
import { api } from "~/utils/api";
|
||||
import {
|
||||
AnnunciCompatibili,
|
||||
AnnunciSelezionati,
|
||||
AnnunciSelezionatiAccordion,
|
||||
AnnunciSelezionatiDialog,
|
||||
} from "./servizio_annunci_accordions";
|
||||
|
||||
const ServizioCard = ({
|
||||
|
|
@ -42,7 +43,10 @@ const ServizioCard = ({
|
|||
}) => {
|
||||
return (
|
||||
<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
|
||||
className={cn(
|
||||
|
|
@ -149,7 +153,7 @@ export const ServizioContent = () => {
|
|||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isAdmin && <AnnunciSelezionati annunci={servizio.annunci} />}
|
||||
{isAdmin && <AnnunciSelezionatiDialog annunci={servizio.annunci} />}
|
||||
</div>
|
||||
}
|
||||
header={
|
||||
|
|
@ -157,7 +161,7 @@ export const ServizioContent = () => {
|
|||
<span className="text-foreground/70 text-lg">
|
||||
Servizio non attivo
|
||||
</span>
|
||||
<ServizioActions2 />
|
||||
<ServizioActions />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
@ -183,7 +187,7 @@ export const ServizioContent = () => {
|
|||
}
|
||||
header={
|
||||
<>
|
||||
<ServizioActions2 />
|
||||
<ServizioActions />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
@ -192,7 +196,6 @@ export const ServizioContent = () => {
|
|||
|
||||
const openContattiAnnunci = servizio.annunci.filter(
|
||||
(a) => a.open_contatti_at !== null && a.user_confirmed_at === null,
|
||||
|
||||
);
|
||||
|
||||
const annunciInConferma = servizio.annunci.filter(
|
||||
|
|
@ -206,19 +209,27 @@ export const ServizioContent = () => {
|
|||
<ServizioCard
|
||||
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 gap-2">
|
||||
<UserCircle className="size-6 text-primary" />
|
||||
<span className="font-medium">Contatti sbloccati</span>
|
||||
</div>
|
||||
<span className="text-sm">{openContattiAnnunci.length} / 10</span>
|
||||
<span className="text-sm">
|
||||
{openContattiAnnunci.length} / 10
|
||||
</span>
|
||||
</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>
|
||||
|
||||
<ServizioDuration decorrenza={servizio.decorrenza} />
|
||||
|
||||
{annuncioConfermato.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -256,15 +267,6 @@ export const ServizioContent = () => {
|
|||
</div>
|
||||
)}
|
||||
<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">
|
||||
{annunciInConferma.map((data) => {
|
||||
return (
|
||||
|
|
@ -275,8 +277,8 @@ export const ServizioContent = () => {
|
|||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row">
|
||||
<AnnunciSelezionati annunci={annunciNotInConferma} />
|
||||
<AnnunciSelezionatiAccordion annunci={annunciNotInConferma} />
|
||||
<div className="flex items-center justify-center">
|
||||
<AnnunciCompatibili />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -284,7 +286,7 @@ export const ServizioContent = () => {
|
|||
}
|
||||
header={
|
||||
<>
|
||||
<ServizioActions2 />
|
||||
<ServizioActions />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -32,13 +32,19 @@ export const ContattiProprietarioModal = () => {
|
|||
const { servizio } = useServizio();
|
||||
const { data } = useServizioAnnuncio();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isUnlocked = data.open_contatti_at != null;
|
||||
const needsDocuments =
|
||||
!servizio.skipControlloDoc &&
|
||||
(!servizio.doc_personale_fronte || !servizio.doc_personale_retro);
|
||||
|
||||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
{data.open_contatti_at != null ? (
|
||||
<Button
|
||||
aria-label="Vedi contatti proprietario"
|
||||
className="flex w-fit items-center gap-2"
|
||||
className="w-fit"
|
||||
variant="success"
|
||||
>
|
||||
<CircleUser className="size-7" />
|
||||
|
|
@ -47,7 +53,7 @@ export const ContattiProprietarioModal = () => {
|
|||
) : (
|
||||
<Button
|
||||
aria-label="Contatta proprietario"
|
||||
className="flex w-fit items-center gap-2"
|
||||
className="w-fit"
|
||||
variant="warning"
|
||||
>
|
||||
<CircleUser className="size-7" />
|
||||
|
|
@ -55,21 +61,19 @@ export const ContattiProprietarioModal = () => {
|
|||
</Button>
|
||||
)}
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh]">
|
||||
<CredenzaContent className="max-h-[90vh] sm:max-w-2xl">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>{t.contatto.title}</CredenzaTitle>
|
||||
<CredenzaTitle className="text-xl">{t.contatto.title}</CredenzaTitle>
|
||||
<CredenzaDescription className="sr-only">
|
||||
{t.contatto.descrizione}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
{servizio.doc_personale_fronte == null ||
|
||||
servizio.doc_personale_retro == null ? (
|
||||
{needsDocuments ? (
|
||||
<DocCheckpoint />
|
||||
// biome-ignore lint/style/noNestedTernary: <this is fine>
|
||||
) : data.open_contatti_at == null ? (
|
||||
<PreUnlock />
|
||||
) : (
|
||||
) : isUnlocked ? (
|
||||
<PostUnlock />
|
||||
) : (
|
||||
<PreUnlock />
|
||||
)}
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
|
|
@ -88,22 +92,30 @@ const DocComponent = ({
|
|||
}) => {
|
||||
const { isAdmin, userId } = useServizio();
|
||||
|
||||
if (!docInfo?.storageId) {
|
||||
return <UploadModal cb_onUpload={cb} isAdmin={isAdmin} userId={userId} />;
|
||||
}
|
||||
if (docInfo?.storageId) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-green-500">
|
||||
<CircleCheck className="size-5" />
|
||||
<span>Documento caricato</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<UploadModal
|
||||
cb_onUpload={cb}
|
||||
isAdmin={isAdmin}
|
||||
maxFiles={1}
|
||||
userId={userId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DocCheckpoint = () => {
|
||||
const { t } = useTranslation();
|
||||
const { userId, servizio } = useServizio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate } = api.servizio.updateServizio.useMutation({
|
||||
const { mutate, isPending } = api.servizio.updateServizio.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
||||
userId,
|
||||
|
|
@ -117,37 +129,38 @@ const DocCheckpoint = () => {
|
|||
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 (
|
||||
<>
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||
<div className="space-y-5">
|
||||
<p>Devi ancora caricare un tuo documento</p>
|
||||
<div className="flex items-center gap-2">
|
||||
Fronte documento:{" "}
|
||||
<div className="flex flex-col gap-2">
|
||||
<span>Fronte documento:</span>
|
||||
<DocComponent
|
||||
cb={({ userStorageIds }) =>
|
||||
userStorageIds.length > 0 &&
|
||||
mutate({
|
||||
servizioId: servizio.servizio_id,
|
||||
data: {
|
||||
doc_personale_fronte_ref: userStorageIds[0],
|
||||
},
|
||||
})
|
||||
userStorageIds[0] &&
|
||||
handleUpload("doc_personale_fronte_ref", userStorageIds[0])
|
||||
}
|
||||
docInfo={servizio.doc_personale_fronte}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
Retro documento:{" "}
|
||||
<div className="flex flex-col gap-2">
|
||||
<span>Retro documento:</span>
|
||||
<DocComponent
|
||||
cb={({ userStorageIds }) =>
|
||||
userStorageIds.length > 0 &&
|
||||
mutate({
|
||||
servizioId: servizio.servizio_id,
|
||||
data: {
|
||||
doc_personale_retro_ref: userStorageIds[0],
|
||||
},
|
||||
})
|
||||
userStorageIds[0] &&
|
||||
handleUpload("doc_personale_retro_ref", userStorageIds[0])
|
||||
}
|
||||
docInfo={servizio.doc_personale_retro}
|
||||
/>
|
||||
|
|
@ -166,7 +179,7 @@ const DocCheckpoint = () => {
|
|||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi modal credenza" variant="outline">
|
||||
<Button aria-label="Chiudi" disabled={isPending} variant="outline">
|
||||
{t.chiudi}
|
||||
</Button>
|
||||
</CredenzaClose>
|
||||
|
|
@ -182,7 +195,8 @@ const PreUnlock = () => {
|
|||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { mutate } = api.servizio.unlockServizioContatti.useMutation({
|
||||
const { mutate, isPending } = api.servizio.unlockServizioContatti.useMutation(
|
||||
{
|
||||
onError: (err) => {
|
||||
toast.error(err.message, {
|
||||
id: "unlock-contacts",
|
||||
|
|
@ -207,11 +221,11 @@ const PreUnlock = () => {
|
|||
userId,
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const d = new Date();
|
||||
const tooEarly = d.getHours() < 9;
|
||||
const tooLate = d.getHours() >= 20;
|
||||
const currentHour = new Date().getHours();
|
||||
const isOutsideBusinessHours = currentHour < 9 || currentHour >= 20;
|
||||
return (
|
||||
<>
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||
|
|
@ -221,13 +235,12 @@ const PreUnlock = () => {
|
|||
<p className="text-muted-foreground text-xs">
|
||||
{t.contatto.disclaimer3}
|
||||
</p>
|
||||
{(tooEarly || tooLate) && (
|
||||
{isOutsideBusinessHours && (
|
||||
<div className="flex items-center gap-2 rounded-md bg-yellow-200 p-2">
|
||||
<TriangleAlert className="size-8" />
|
||||
{/*TODO testare */}
|
||||
<p>
|
||||
Il proprietario riceverà la notifica alle ore 9.00{" "}
|
||||
{tooLate && "di domani"}
|
||||
{currentHour >= 20 && "di domani"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -236,6 +249,7 @@ const PreUnlock = () => {
|
|||
<CredenzaFooter>
|
||||
<Button
|
||||
aria-label="Contatta proprietario"
|
||||
disabled={isPending}
|
||||
onClick={() =>
|
||||
mutate({
|
||||
annuncioId,
|
||||
|
|
@ -248,7 +262,11 @@ const PreUnlock = () => {
|
|||
</Button>
|
||||
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi modal credenza" variant="outline">
|
||||
<Button
|
||||
aria-label="Chiudi modal credenza"
|
||||
disabled={isPending}
|
||||
variant="outline"
|
||||
>
|
||||
{t.chiudi}
|
||||
</Button>
|
||||
</CredenzaClose>
|
||||
|
|
@ -258,17 +276,13 @@ const PreUnlock = () => {
|
|||
};
|
||||
|
||||
function getNavigationUrl(lat: string, lon: string): string {
|
||||
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}`;
|
||||
}
|
||||
if (typeof window === "undefined") {
|
||||
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 = () => {
|
||||
|
|
@ -290,80 +304,86 @@ const PostUnlock = () => {
|
|||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<CredenzaBody className="flex justify-center p-8">
|
||||
<LoadingPage />
|
||||
</div>
|
||||
</CredenzaBody>
|
||||
);
|
||||
}
|
||||
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 (
|
||||
<>
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-semibold">{t.contatto.propietario}:</h3>
|
||||
<p>
|
||||
{t.contatto.nome}: {data.propData.locatore}
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5 sm:max-w-xl">
|
||||
<div className="space-y-10">
|
||||
{/* Proprietario Section */}
|
||||
<section>
|
||||
<h3 className="mb-2 font-semibold">{t.contatto.propietario}:</h3>
|
||||
<p className="mb-2">
|
||||
{t.contatto.nome}: {propData.locatore}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-5">
|
||||
<a
|
||||
className="flex items-center gap-2"
|
||||
href={`tel:${data.propData.numero}`}
|
||||
className="flex items-center gap-2 text-blue-600 hover:underline"
|
||||
href={`tel:${propData.numero}`}
|
||||
>
|
||||
<Phone />:
|
||||
<span className="text-blue-600 underline">
|
||||
{data.propData.numero}
|
||||
</span>
|
||||
<Phone className="size-4" />
|
||||
<span>{propData.numero}</span>
|
||||
</a>
|
||||
<Link
|
||||
href={`https://wa.me/${data.propData.numero}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
variant="success"
|
||||
>
|
||||
<Link href={`https://wa.me/${propData.numero}`} target="_blank">
|
||||
<Button size="sm" variant="success">
|
||||
<WhatsAppIcon2 className="size-4 fill-white" />
|
||||
WhatsApp
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<br />
|
||||
<h3 className="font-semibold">{t.contatto.immobile}:</h3>
|
||||
<p>
|
||||
{t.contatto.indirizzo}: {data.propData.indirizzo},{" "}
|
||||
{data.propData.civico} {data.propData.comune} (
|
||||
{data.propData.provincia})
|
||||
{/* Immobile Section */}
|
||||
<section>
|
||||
<h3 className="mb-2 font-semibold">{t.contatto.immobile}:</h3>
|
||||
<p className="mb-2">
|
||||
{t.contatto.indirizzo}: {propData.indirizzo}, {propData.civico}{" "}
|
||||
{propData.comune} ({propData.provincia})
|
||||
</p>
|
||||
{navigationUrl && (
|
||||
<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}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Navigation /> <span>{t.contatto.apri_nav}</span>
|
||||
<Navigation className="size-4" />
|
||||
<span>{t.contatto.apri_nav}</span>
|
||||
</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}:{" "}
|
||||
{data.opened_at.toLocaleString("it", {
|
||||
{opened_at.toLocaleString("it", {
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</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>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 toast from "react-hot-toast";
|
||||
import {
|
||||
|
|
@ -50,7 +50,7 @@ import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
|||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export const ServizioActions2 = () => {
|
||||
export const ServizioActions = () => {
|
||||
const { servizio, isAdmin, userId } = useServizio();
|
||||
|
||||
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">
|
||||
{(isAdmin || canUserEdit) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="hidden sm:block">Azioni: </span>
|
||||
{/* <span className="hidden sm:block">Azioni: </span> */}
|
||||
<EditPreferenze />
|
||||
<InterruzioneServizio />
|
||||
</div>
|
||||
|
|
@ -123,7 +123,7 @@ export const AddAnnuncio = ({
|
|||
<DialogTrigger asChild>
|
||||
<Button
|
||||
aria-label="Add Annuncio"
|
||||
className="items-center gap-2"
|
||||
className=""
|
||||
onClick={() => {
|
||||
setSelectedAnnunci([]);
|
||||
}}
|
||||
|
|
@ -222,11 +222,10 @@ const InterruzioneServizio = () => {
|
|||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
aria-label="Request Interruzione"
|
||||
className="items-center gap-2"
|
||||
className=""
|
||||
disabled={!beforeInterruzioneLimit}
|
||||
variant="outline"
|
||||
>
|
||||
<ClockAlert /> <span>Interruzione</span>
|
||||
<ClockAlert /> <span> Richiedi Interruzione Servizio</span>
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
|
|
@ -288,12 +287,9 @@ const EditPreferenze = () => {
|
|||
return (
|
||||
<Credenza onOpenChange={setOpenEditPreferenze} open={openEditPreferenze}>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
aria-label="Preferenze di ricerca"
|
||||
className="items-center gap-2"
|
||||
>
|
||||
<Button aria-label="Preferenze di ricerca" className="">
|
||||
<Calculator />
|
||||
Preferenze
|
||||
Modifica Preferenze
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh] w-full sm:max-w-5xl">
|
||||
|
|
@ -352,11 +348,8 @@ const EditServizioAdmin = () => {
|
|||
return (
|
||||
<Credenza onOpenChange={setOpen} open={open}>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button
|
||||
aria-label="Modifica Servizio Admin"
|
||||
className="items-center gap-2"
|
||||
>
|
||||
<Wrench />
|
||||
<Button aria-label="Modifica Servizio Admin">
|
||||
<ShieldUser />
|
||||
Admin
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
|
|
@ -375,7 +368,7 @@ const EditServizioAdmin = () => {
|
|||
)}
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button className="items-center gap-2" variant="warning">
|
||||
<Button variant="warning">
|
||||
<Bug /> Debug Info Servizio
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ const DialogServizio = ({
|
|||
</Dialog>
|
||||
);
|
||||
};
|
||||
export const AnnunciInConferma = ({
|
||||
export const AnnunciInConfermaDialog = ({
|
||||
annunci,
|
||||
}: {
|
||||
annunci: ServizioData["annunci"];
|
||||
|
|
@ -74,11 +74,10 @@ export const AnnunciInConferma = ({
|
|||
>
|
||||
Annunci in conferma
|
||||
</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">
|
||||
{annunci.length}
|
||||
{annunci.length || 0}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
|
@ -95,7 +94,7 @@ export const AnnunciInConferma = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const AnnunciSelezionati = ({
|
||||
export const AnnunciSelezionatiDialog = ({
|
||||
annunci,
|
||||
}: {
|
||||
annunci: ServizioData["annunci"];
|
||||
|
|
@ -112,11 +111,10 @@ export const AnnunciSelezionati = ({
|
|||
>
|
||||
Annunci selezionati
|
||||
</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">
|
||||
{annunci.length}
|
||||
{annunci.length || 0}
|
||||
</span>
|
||||
)}
|
||||
</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 = () => {
|
||||
const { servizioId, userId, isAdmin } = useServizio();
|
||||
const { data, isLoading } = api.servizio.getCompatibileAnnunci.useQuery({
|
||||
|
|
@ -163,16 +234,9 @@ export const AnnunciCompatibili = () => {
|
|||
<DialogServizio
|
||||
title="Annunci compatibili"
|
||||
trigger={
|
||||
<div className="relative flex w-full sm:w-fit">
|
||||
<Button className="w-full bg-purple-500 text-lg sm:w-fit" size="xl">
|
||||
Annunci compatibili
|
||||
<Button className="w-full bg-purple-500 sm:w-fit">
|
||||
Esplora Annunci Compatibili
|
||||
</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">
|
||||
|
|
@ -254,7 +318,6 @@ export const AnnunciRichiesti = ({ userId }: { userId: UsersId }) => {
|
|||
data={a}
|
||||
interactions={
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => {
|
||||
deleteAnnuncio({ annuncioId: a.id, userId });
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -51,11 +51,7 @@ export const AnnunciTable = ({
|
|||
href={`/area-riservata/admin/edit-annuncio/${row.original.id}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-2 px-2"
|
||||
size="sm"
|
||||
variant="default"
|
||||
>
|
||||
<Button className="px-2" size="sm" variant="default">
|
||||
<Wrench />
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -166,7 +166,6 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
|||
return (
|
||||
<Button
|
||||
aria-label="Apri Dettagli Ordine"
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => {
|
||||
setSelectedOrdine(row.original.id);
|
||||
setModalOpen(true);
|
||||
|
|
|
|||
|
|
@ -33,17 +33,7 @@ export type UploadCallback = (args: {
|
|||
userStorageIds: UsersStorageUserStorageId[];
|
||||
}) => void;
|
||||
|
||||
type UploadModalProps = {
|
||||
cb_onUpload?: UploadCallback;
|
||||
isAdmin?: boolean;
|
||||
userId?: UsersId;
|
||||
};
|
||||
|
||||
export const UploadModal = ({
|
||||
cb_onUpload,
|
||||
isAdmin,
|
||||
userId,
|
||||
}: UploadModalProps) => {
|
||||
export const UploadModal = (props: UploadComponentProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
|
|
@ -63,11 +53,7 @@ export const UploadModal = ({
|
|||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||
<UploadComponent
|
||||
cb_onUpload={cb_onUpload}
|
||||
isAdmin={isAdmin}
|
||||
userId={userId}
|
||||
/>
|
||||
<UploadComponent {...props} />
|
||||
</CredenzaBody>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
|
|
|
|||
|
|
@ -246,12 +246,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithImages }) => {
|
|||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Link href={`/annuncio/${data.codice}`} target="_blank">
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<Button size="sm" type="button" variant="outline">
|
||||
<Eye /> pubblica
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -259,28 +254,19 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithImages }) => {
|
|||
href={`/area-riservata/admin/scheda-annuncio-stampa/${data.id}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<Button size="sm" type="button" variant="outline">
|
||||
<Printer /> Scheda
|
||||
</Button>
|
||||
</Link>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
type="button"
|
||||
>
|
||||
<Button size="sm" type="button">
|
||||
<CodeSquare /> Strumenti
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex w-fit items-center gap-2">
|
||||
<Button
|
||||
className="flex items-center gap-2 bg-purple-500"
|
||||
className="bg-purple-500"
|
||||
onClick={async () => {
|
||||
await revalidate({
|
||||
cod: data.codice,
|
||||
|
|
@ -298,12 +284,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithImages }) => {
|
|||
}}
|
||||
title="Aggiornamento Annuncio"
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="warning"
|
||||
>
|
||||
<Button size="sm" type="button" variant="warning">
|
||||
<TriangleAlert /> Aggiorna
|
||||
</Button>
|
||||
</Confirm>
|
||||
|
|
|
|||
|
|
@ -182,7 +182,6 @@ export const FormEditServizio = ({
|
|||
<div className="flex flex-col items-start gap-2">
|
||||
<Label htmlFor="reset_decorrenza">Reset Decorrenza</Label>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
id="reset_decorrenza"
|
||||
onClick={() => form.setValue("decorrenza", null)}
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -233,10 +233,7 @@ export const FormNewServizioAcquisto = ({
|
|||
path: ["n_adulti"],
|
||||
});
|
||||
}
|
||||
if (
|
||||
permanenza === null &&
|
||||
tipologia === TipologiaPosizioneEnum.Transitorio
|
||||
) {
|
||||
if (!permanenza && tipologia === TipologiaPosizioneEnum.Transitorio) {
|
||||
refinementContext.issues.push({
|
||||
code: "custom",
|
||||
input: "",
|
||||
|
|
@ -264,6 +261,14 @@ export const FormNewServizioAcquisto = ({
|
|||
path: ["scadenza_motivazione_transitoria"],
|
||||
});
|
||||
}
|
||||
if (!permanenza) {
|
||||
refinementContext.issues.push({
|
||||
code: "custom",
|
||||
input: "",
|
||||
message: "Inserisci permanenza",
|
||||
path: ["permanenza"],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (reddito === "" || reddito === null) {
|
||||
refinementContext.issues.push({
|
||||
|
|
@ -315,7 +320,7 @@ export const FormNewServizioAcquisto = ({
|
|||
defaultValues: defaultValues,
|
||||
});
|
||||
const DatePickerLocale = locale === "it" ? it : enUS;
|
||||
const { mutate } = api.servizio.postAcquistoData.useMutation({
|
||||
const { mutate } = api.servizio.submitOnboardForPurchase.useMutation({
|
||||
onError: (error) => {
|
||||
toast.error(`Errore invio dati: ${error.message}`, {
|
||||
id: "salvataggio",
|
||||
|
|
@ -424,7 +429,7 @@ export const FormNewServizioAcquisto = ({
|
|||
fattura: "Vuoi che la fatturazione sia intestata alla tua azienda?",
|
||||
personali: "Dati personali",
|
||||
personali_desc: "Inserisci i tuoi dati personali",
|
||||
salva: "Salva e procedi al pagamento",
|
||||
salva: "Salva e procedi",
|
||||
sdi: "Codice destinatario SDI",
|
||||
}
|
||||
: {
|
||||
|
|
@ -433,7 +438,7 @@ export const FormNewServizioAcquisto = ({
|
|||
fattura: "Do you want the invoice to be issued to your company?",
|
||||
personali: "Personal Data",
|
||||
personali_desc: "Enter your personal data",
|
||||
salva: "Save and proceed to payment",
|
||||
salva: "Save and proceed",
|
||||
sdi: "SDI Recipient Code",
|
||||
};
|
||||
|
||||
|
|
@ -694,7 +699,7 @@ export const FormNewServizioAcquisto = ({
|
|||
</div>
|
||||
|
||||
{tipologia === TipologiaPosizioneEnum.Transitorio ? (
|
||||
<>
|
||||
<div className="items-top flex w-full flex-col justify-between gap-4 sm:flex-row">
|
||||
<FormField
|
||||
control={form.control}
|
||||
key={tipologia}
|
||||
|
|
@ -704,10 +709,9 @@ export const FormNewServizioAcquisto = ({
|
|||
t.preferenze.motivazioni_mappings.find(
|
||||
(m) => m.id === field.value,
|
||||
);
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<div className="flex flex-wrap items-center gap-x-2">
|
||||
<FormItem className="w-full">
|
||||
<div className="flex w-full flex-wrap items-center gap-x-2">
|
||||
<FormLabel htmlFor="motivazione_transitorio">
|
||||
{t.preferenze.motivazione_label}
|
||||
</FormLabel>
|
||||
|
|
@ -736,7 +740,6 @@ export const FormNewServizioAcquisto = ({
|
|||
)}
|
||||
placeholder={t.seleziona_placeholder}
|
||||
/>
|
||||
|
||||
<FormDescription>
|
||||
{t.preferenze.motivazione_desc}
|
||||
</FormDescription>
|
||||
|
|
@ -794,7 +797,7 @@ export const FormNewServizioAcquisto = ({
|
|||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
) : (
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const Annunci = ({ options }: AnnunciPageProps) => {
|
|||
<meta content={t.heads.annunci_description} name="description" />
|
||||
</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">
|
||||
{t.annunci.titolo}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
|||
href={`/area-riservata/admin/edit-annuncio/${data.id}`}
|
||||
>
|
||||
<Button
|
||||
className="flex w-full items-center gap-2"
|
||||
className="w-full"
|
||||
type="button"
|
||||
>
|
||||
<Edit /> Modifica annuncio
|
||||
|
|
@ -162,7 +162,7 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
|||
target="_blank"
|
||||
>
|
||||
<Button
|
||||
className="flex w-full items-center gap-2"
|
||||
className="w-full"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
|
|
@ -669,7 +669,7 @@ const AnnuncioFooter = ({
|
|||
}}
|
||||
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" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -53,17 +53,13 @@ const Admin_Annunci: NextPageWithLayout = () => {
|
|||
</button>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
type="button"
|
||||
>
|
||||
<Button size="sm" type="button">
|
||||
<CodeSquare /> Strumenti avanzati
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex flex-col items-center gap-2">
|
||||
<Button
|
||||
className="flex items-center gap-2 bg-purple-500"
|
||||
className="bg-purple-500"
|
||||
onClick={async () => {
|
||||
await revalidate();
|
||||
}}
|
||||
|
|
@ -79,12 +75,7 @@ const Admin_Annunci: NextPageWithLayout = () => {
|
|||
}}
|
||||
title="Aggiornamento Annunci"
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="warning"
|
||||
>
|
||||
<Button size="sm" type="button" variant="warning">
|
||||
<TriangleAlert /> Aggiorna tutti gli annunci
|
||||
</Button>
|
||||
</Confirm>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ const AdminBanners: NextPageWithLayout = () => {
|
|||
</div>
|
||||
<div className="mt-3 md:mt-0">
|
||||
<Button
|
||||
className="flex items-center gap-2 font-semibold"
|
||||
className="font-semibold"
|
||||
onClick={() => {
|
||||
handleEdit(null);
|
||||
handleOpen(true);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const AdminBlacklist: NextPageWithLayout = () => {
|
|||
</div>
|
||||
<div className="mt-3 md:mt-0">
|
||||
<Button
|
||||
className="flex items-center gap-2 font-semibold"
|
||||
className="font-semibold"
|
||||
onClick={() => {
|
||||
handleEdit(null);
|
||||
handleOpen(true);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const AdminEtichette: NextPageWithLayout = () => {
|
|||
</div>
|
||||
<div className="mt-3 md:mt-0">
|
||||
<Button
|
||||
className="flex items-center gap-2 font-semibold"
|
||||
className="font-semibold"
|
||||
onClick={() => {
|
||||
handleEdit(null);
|
||||
handleOpen(true);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const AdminFlags: NextPageWithLayout = () => {
|
|||
</div>
|
||||
<div className="mt-3 md:mt-0">
|
||||
<Button
|
||||
className="flex items-center gap-2 font-semibold"
|
||||
className="font-semibold"
|
||||
onClick={() => {
|
||||
handleEdit(null);
|
||||
handleOpen(true);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const AdminTestiStringhe: NextPageWithLayout = () => {
|
|||
</div>
|
||||
<div className="mt-3 md:mt-0">
|
||||
<Button
|
||||
className="flex items-center gap-2 font-semibold"
|
||||
className="font-semibold"
|
||||
onClick={() => {
|
||||
handleEdit(null);
|
||||
handleOpen(true);
|
||||
|
|
|
|||
|
|
@ -125,10 +125,7 @@ const EditUser: NextPageWithLayout<EditUserProps> = ({
|
|||
<div className="flex flex-wrap items-end gap-2">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
variant={userData.isBlocked ? "success" : "orange"}
|
||||
>
|
||||
<Button variant={userData.isBlocked ? "success" : "orange"}>
|
||||
{userData.isBlocked ? (
|
||||
<>
|
||||
<Unlock className="size-4" />
|
||||
|
|
@ -161,10 +158,7 @@ const EditUser: NextPageWithLayout<EditUserProps> = ({
|
|||
</AlertDialog>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
variant="destructive"
|
||||
>
|
||||
<Button variant="destructive">
|
||||
<Trash2 className="size-4" /> Elimina utente permanentemente
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const PreOnboardServizio: NextPageWithLayout<PreOnboardServizioProps> = ({
|
|||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
className="flex items-center gap-2 text-xl"
|
||||
className="text-xl"
|
||||
onClick={async () => {
|
||||
if (data.hasPrevious) {
|
||||
await router.push({
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const RiapriConferma: NextPageWithLayout<RiapriConfermaProps> = ({
|
|||
</div>
|
||||
|
||||
<Button
|
||||
className="mx-auto mb-4 flex items-center gap-2"
|
||||
className="mx-auto mb-4"
|
||||
onClick={() => router.back()}
|
||||
variant="secondary"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ export const servizioRouter = createTRPCRouter({
|
|||
return await isOrdineAwaitingPayment(input.ordineId);
|
||||
}),
|
||||
|
||||
postAcquistoData: protectedProcedure
|
||||
submitOnboardForPurchase: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
anagrafica: z.custom<UsersAnagraficaUpdate>(),
|
||||
|
|
|
|||
|
|
@ -247,6 +247,7 @@ export const postAcquistoDataHandler = async ({
|
|||
}) => {
|
||||
try {
|
||||
const servizioData = await db.transaction().execute(async (tx) => {
|
||||
// Update user and anagrafica data
|
||||
await tx
|
||||
.updateTable("users")
|
||||
.set(user)
|
||||
|
|
@ -274,13 +275,7 @@ export const postAcquistoDataHandler = async ({
|
|||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
await tx
|
||||
.updateTable("users_anagrafica")
|
||||
.set(anagrafica)
|
||||
.where("userid", "=", userId)
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
// Update servizio data
|
||||
return await tx
|
||||
.updateTable("servizio")
|
||||
.set(servizio)
|
||||
|
|
@ -290,6 +285,7 @@ export const postAcquistoDataHandler = async ({
|
|||
});
|
||||
|
||||
if (skipOrdine) {
|
||||
// If skipping order, set servizio as active with decorrenza today
|
||||
await updateServizio({
|
||||
data: {
|
||||
decorrenza: new Date(),
|
||||
|
|
@ -627,6 +623,9 @@ export const getAllServizioAnnunci = async (userId: UsersId) => {
|
|||
"servizio.isOkAcconto",
|
||||
"servizio.isOkSaldo",
|
||||
"servizio.isOkConsulenza",
|
||||
"servizio.skipPayment",
|
||||
"servizio.skipControlloDoc",
|
||||
"servizio.skipDocMotivazione",
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-background2: var(--background2);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
|
|
@ -171,6 +172,7 @@
|
|||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--background2: oklch(95.041% 0.02791 62.217);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue