refactor: Remove AnnunciGrid component and update Annunci page to use CardAnnuncio directly
This commit is contained in:
parent
608e6a8e10
commit
7b0469467f
12 changed files with 290 additions and 263 deletions
|
|
@ -1,14 +0,0 @@
|
|||
import { CardAnnuncio } from "~/components/annuncio_card";
|
||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||
|
||||
//TODO usare anche in accordion e dialog pag ricerca servizio
|
||||
|
||||
export const AnnunciGrid = ({ pagedata }: { pagedata: AnnuncioRicerca[] }) => {
|
||||
return (
|
||||
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
|
||||
{pagedata.map((annuncio) => (
|
||||
<CardAnnuncio key={annuncio.codice} {...annuncio} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -7,6 +7,7 @@ import LoadingButton from "~/components/custom_ui/loading-button";
|
|||
import { Button, buttonVariants } from "~/components/ui/button";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useAnnuncio } from "~/providers/AnnuncioProvider";
|
||||
import { useServizio } from "~/providers/ServizioProvider";
|
||||
import type { SessionContextType } from "~/providers/SessionProvider";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import type { UsersId } from "~/schemas/public/Users";
|
||||
|
|
@ -108,10 +109,14 @@ const ServizioInteraction = ({
|
|||
);
|
||||
}
|
||||
|
||||
return <InteressatoButton annuncioId={annuncioId} />;
|
||||
return <InteressatoButtonUserOnly annuncioId={annuncioId} />;
|
||||
};
|
||||
|
||||
const InteressatoButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
|
||||
const InteressatoButtonUserOnly = ({
|
||||
annuncioId,
|
||||
}: {
|
||||
annuncioId: AnnunciId;
|
||||
}) => {
|
||||
const { data: hasInterest, isLoading } =
|
||||
api.intrests.hasUserInterest.useQuery({
|
||||
annuncioId,
|
||||
|
|
@ -161,36 +166,57 @@ const InteressatoButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const InteressatoButtonBatchV = ({
|
||||
export const InteressatoButtonServizio = ({
|
||||
annuncioId,
|
||||
hasInterest,
|
||||
}: {
|
||||
annuncioId: AnnunciId;
|
||||
hasInterest: boolean;
|
||||
}) => {
|
||||
const { isAdmin, servizioId, userId } = useServizio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate: add } = api.intrests.addIntrest.useMutation({
|
||||
|
||||
const { mutate: adminAdd, isPending: isAdminPending } =
|
||||
api.servizio.addServizioAnnunci.useMutation({
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
toast.error("Errore durante l'aggiunta dell'annuncio");
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
||||
await utils.servizio.getAnnunciAvailableToAdd.invalidate({
|
||||
servizioId,
|
||||
});
|
||||
await utils.servizio.AnnuncioServizioTipologiaMatch.invalidate({
|
||||
userId,
|
||||
});
|
||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
||||
servizioId,
|
||||
});
|
||||
|
||||
toast.success("Annuncio aggiunto alla ricerca");
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: add, isPending } = api.intrests.addIntrest.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.intrests.getUserInterests.invalidate();
|
||||
toast.success("Richiesta di interesse inviata con successo!");
|
||||
},
|
||||
});
|
||||
|
||||
if (hasInterest) {
|
||||
return (
|
||||
<span className="flex w-full items-center justify-center gap-2 rounded-md bg-green-500 p-2 font-semibold text-sm text-white">
|
||||
<BadgePlus className="inline size-5" />
|
||||
Hai già mandato una richiesta
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
<LoadingButton
|
||||
aria-label="Sono interessato"
|
||||
className="w-full"
|
||||
//size="xl"
|
||||
loading={isPending || isAdminPending}
|
||||
onClick={() => {
|
||||
if (isAdmin && servizioId) {
|
||||
adminAdd({
|
||||
servizioId,
|
||||
annunci: [annuncioId],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
add({
|
||||
annuncioId,
|
||||
});
|
||||
|
|
@ -198,7 +224,7 @@ export const InteressatoButtonBatchV = ({
|
|||
variant="info"
|
||||
>
|
||||
<BadgePlus className="size-6" />
|
||||
Sono interessato
|
||||
</Button>
|
||||
{isAdmin ? "Aggiungi annuncio" : "Sono interessato"}
|
||||
</LoadingButton>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ import { VideoPlayer } from "./videoPlayer";
|
|||
|
||||
type CardAnnuncioProps = AnnuncioRicerca & {
|
||||
className?: string;
|
||||
noLink?: boolean;
|
||||
onlyFirstImage?: boolean;
|
||||
};
|
||||
|
||||
export const CardAnnuncio = ({
|
||||
|
|
@ -55,23 +57,36 @@ export const CardAnnuncio = ({
|
|||
updated_at,
|
||||
images,
|
||||
homepage,
|
||||
noLink = false,
|
||||
onlyFirstImage = false,
|
||||
}: CardAnnuncioProps) => {
|
||||
const { t, locale } = useTranslation();
|
||||
const currentMonth = new Date().getMonth() + 1;
|
||||
const isAvailableNow = consegna === 0 || consegna === currentMonth;
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
if (noLink) {
|
||||
return <div className="flex h-full flex-col gap-2 p-2">{children}</div>;
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
className="flex h-full flex-col gap-2 p-2"
|
||||
href={`/annuncio/${codice}`}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"h-[31.1rem] rounded-md bg-white shadow-neutral-100 shadow-sm hover:shadow-md hover:outline-neutral-600",
|
||||
"relative h-[31.1rem] rounded-md bg-white shadow-neutral-100 shadow-sm outline outline-neutral-300 hover:shadow-md hover:outline-neutral-600",
|
||||
className,
|
||||
)}
|
||||
id={`card-annuncio-${id}`}
|
||||
>
|
||||
<Link
|
||||
className="flex h-full flex-col gap-2 p-2"
|
||||
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
||||
>
|
||||
<Wrapper>
|
||||
<div
|
||||
className={cn("group relative overflow-clip text-clip rounded-md")}
|
||||
>
|
||||
|
|
@ -120,69 +135,99 @@ export const CardAnnuncio = ({
|
|||
</Badge>
|
||||
</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}
|
||||
{!onlyFirstImage ? (
|
||||
<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>
|
||||
);
|
||||
})}
|
||||
</CarouselContent>
|
||||
))}
|
||||
{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
|
||||
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<CarouselPrevious
|
||||
className="left-2 cursor-pointer"
|
||||
disabled={!images || images.length === 1}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Add this to the buttons too
|
||||
}}
|
||||
/>
|
||||
<CarouselNext
|
||||
className="right-2 cursor-pointer"
|
||||
disabled={!images || images.length === 1}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Add this to the buttons too
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Carousel>
|
||||
) : (
|
||||
<>
|
||||
{images && images.length > 0 && images[0] ? (
|
||||
<ImageFlbk
|
||||
alt={t.card.alt_immagine}
|
||||
blurDataURL={`/storage-api/get/${images[0].thumb}?image=true`}
|
||||
className={"h-64 w-full object-cover"}
|
||||
height={1080}
|
||||
priority={true}
|
||||
src={`/storage-api/get/${images[0].img}?image=true`}
|
||||
width={1920}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-64 w-full items-center justify-center bg-neutral-200 text-neutral-500"></div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex h-full grow flex-col gap-2">
|
||||
<div
|
||||
|
|
@ -270,7 +315,7 @@ export const CardAnnuncio = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Wrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,17 +15,14 @@ const LoadingButton = ({
|
|||
}: ButtonProps & { loading?: boolean }) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(buttonVariants({ className, size, variant }))}
|
||||
className={cn(
|
||||
buttonVariants({ className, size, variant }),
|
||||
loading && "text-transparent",
|
||||
)}
|
||||
disabled={loading || props.disabled}
|
||||
{...props}
|
||||
>
|
||||
{loading && (
|
||||
<LoaderCircle
|
||||
aria-hidden="true"
|
||||
className="-ms-1 me-2 animate-spin"
|
||||
size={16}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
)}
|
||||
{loading && <LoaderCircle className="absolute animate-spin text-muted" />}
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,21 +6,16 @@ import {
|
|||
ExternalLink,
|
||||
FolderKanban,
|
||||
Pen,
|
||||
Plus,
|
||||
ShieldUser,
|
||||
Trash2,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { z } from "zod/v4";
|
||||
import { ExtIcon } from "~/components/area-riservata/allegati";
|
||||
import { Confirm } from "~/components/confirm";
|
||||
import {
|
||||
AnimatedButton,
|
||||
type AnimatedButtonRef,
|
||||
} from "~/components/custom_ui/animated-button";
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
|
|
@ -62,7 +57,6 @@ import { UploadModal } from "~/components/upload_modal";
|
|||
import { cn, formatCurrency } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
|
||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||
import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
|
||||
|
|
@ -117,7 +111,7 @@ export const AnnuncioActions = () => {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="flex w-fit flex-col items-center gap-2"
|
||||
className="flex w-fit flex-col items-center gap-4"
|
||||
>
|
||||
<Link
|
||||
href={`/area-riservata/admin/edit-annuncio/${annuncioId}`}
|
||||
|
|
@ -1213,51 +1207,3 @@ const SendConferma = ({
|
|||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
|
||||
const { servizioId, userId } = useServizio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate } = api.servizio.addServizioAnnunci.useMutation({
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
toast.error("Errore durante l'aggiunta dell'annuncio");
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
||||
await utils.servizio.getAnnunciAvailableToAdd.invalidate({
|
||||
servizioId,
|
||||
});
|
||||
await utils.servizio.AnnuncioServizioTipologiaMatch.invalidate({
|
||||
userId,
|
||||
});
|
||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
||||
servizioId,
|
||||
});
|
||||
|
||||
toast.success("Annuncio aggiunto alla ricerca");
|
||||
},
|
||||
});
|
||||
|
||||
const buttonRef = useRef<AnimatedButtonRef>(null);
|
||||
return (
|
||||
<AnimatedButton
|
||||
className="group flex w-full gap-2"
|
||||
onClick={async () => {
|
||||
buttonRef.current?.sparkle();
|
||||
//wait for the animation to finish
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
mutate({ annunci: [annuncioId], servizioId });
|
||||
}}
|
||||
ref={buttonRef}
|
||||
sparklesClassName="fill-yellow-500 stroke-transparent"
|
||||
sparklesIcon="thumbs-up"
|
||||
sparklesNumber={15}
|
||||
sparklesRadius={60}
|
||||
sparklesScale={[1, 1.5]}
|
||||
variant="info"
|
||||
>
|
||||
<Plus className="size-5" />
|
||||
<span>Aggiungi agli annunci da contattare</span>
|
||||
</AnimatedButton>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { add } from "date-fns";
|
||||
import { Package, PackageCheck, UserCircle } from "lucide-react";
|
||||
import { CalendarClock, Package, PackageCheck, UserCircle } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
|
@ -209,7 +209,7 @@ export const ServizioContent = () => {
|
|||
<ServizioCard
|
||||
content={
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-4 sm:flex-row sm:gap-8">
|
||||
<div className="flex w-full flex-col gap-8 sm:flex-row sm:items-start">
|
||||
<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">
|
||||
|
|
@ -225,10 +225,15 @@ export const ServizioContent = () => {
|
|||
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 className="flex flex-col flex-wrap gap-2 sm:flex-row sm:items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<CalendarClock className="size-6 text-primary" />
|
||||
<span className="font-medium">Durata servizio:</span>
|
||||
</div>
|
||||
<span>
|
||||
{`${new Date(servizio.decorrenza).toLocaleDateString("it-IT")} - ${new Date(add(servizio.decorrenza, { days: 60 })).toLocaleDateString("it-IT")}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{annuncioConfermato.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
|
|
@ -245,17 +250,17 @@ export const ServizioContent = () => {
|
|||
>
|
||||
<h3>
|
||||
{a.codice} - confermato il:{" "}
|
||||
{/** biome-ignore lint/style/noNonNullAssertion: <already checked above> */}
|
||||
{new Date(a.accettato_conferma_at!).toLocaleDateString(
|
||||
"it",
|
||||
{
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
},
|
||||
)}
|
||||
{a.accettato_conferma_at &&
|
||||
new Date(a.accettato_conferma_at).toLocaleDateString(
|
||||
"it",
|
||||
{
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
},
|
||||
)}
|
||||
</h3>
|
||||
<SaldoButton
|
||||
annuncioId={a.annunci_id}
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ const EditPreferenze = () => {
|
|||
Preferenze
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody className="max-h-[80vh] w-full max-w-3xl overflow-y-auto pb-5">
|
||||
<CredenzaBody className="max-h-[80vh] w-full max-w-3xl overflow-y-auto pb-5 sm:max-w-5xl">
|
||||
{isLoading ? (
|
||||
<LoadingPage />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import { ExternalLink, Trash2 } from "lucide-react";
|
|||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { InteressatoButtonBatchV } from "~/components/annuncio-interactions/annuncio_interactions";
|
||||
import { AddButton } from "~/components/servizio/annuncio_actions";
|
||||
import { InteressatoButtonServizio } from "~/components/annuncio-interactions/annuncio_interactions";
|
||||
import {
|
||||
AnnuncioCard,
|
||||
BasicAnnuncioCard,
|
||||
|
|
@ -22,6 +21,7 @@ import {
|
|||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
||||
import { api } from "~/utils/api";
|
||||
import { CardAnnuncio } from "../annuncio_card";
|
||||
import { LoadingPage } from "../loading";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -44,7 +44,7 @@ const DialogServizio = ({
|
|||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
||||
<DialogContent className="md:max-w-5xl">
|
||||
<DialogContent className="p-2 sm:max-w-5xl sm:p-6 md:max-w-7xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
|
|
@ -221,25 +221,21 @@ export const AnnunciSelezionatiAccordion = ({
|
|||
);
|
||||
};
|
||||
export const AnnunciCompatibili = () => {
|
||||
const { servizioId, userId, isAdmin } = useServizio();
|
||||
const { servizioId } = useServizio();
|
||||
const { data, isLoading } = api.servizio.getCompatibileAnnunci.useQuery({
|
||||
servizioId,
|
||||
});
|
||||
|
||||
const { data: userIntrests } = api.intrests.getUserInterests.useQuery({
|
||||
userId,
|
||||
});
|
||||
|
||||
return (
|
||||
<DialogServizio
|
||||
title="Annunci compatibili"
|
||||
title="Annunci compatibili con le preferenze del servizio"
|
||||
trigger={
|
||||
<Button className="w-full bg-purple-500 sm:w-fit">
|
||||
Esplora Annunci Compatibili
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
|
||||
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto p-0.5 sm:p-2">
|
||||
{isLoading ? (
|
||||
<LoadingPage />
|
||||
) : (
|
||||
|
|
@ -252,25 +248,23 @@ export const AnnunciCompatibili = () => {
|
|||
</span>
|
||||
</div>
|
||||
) : (
|
||||
data.map((a) => (
|
||||
<BasicAnnuncioCard
|
||||
className="border-2 border-neutral-500"
|
||||
data={a}
|
||||
interactions={
|
||||
<>
|
||||
{isAdmin ? (
|
||||
<AddButton annuncioId={a.id} />
|
||||
) : (
|
||||
<InteressatoButtonBatchV
|
||||
annuncioId={a.id}
|
||||
hasInterest={userIntrests?.includes(a.id) || false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
key={a.id}
|
||||
/>
|
||||
))
|
||||
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 sm:gap-y-8 lg:grid-cols-3">
|
||||
{data.map((annuncio) => (
|
||||
<div
|
||||
className="flex flex-col justify-center gap-1"
|
||||
key={annuncio.codice}
|
||||
>
|
||||
<CardAnnuncio
|
||||
{...annuncio}
|
||||
className="outline outline-neutral-500"
|
||||
noLink
|
||||
onlyFirstImage
|
||||
/>
|
||||
|
||||
<InteressatoButtonServizio annuncioId={annuncio.id} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -471,42 +471,56 @@ export const FormNewServizio = ({
|
|||
</FormLabel>
|
||||
<FormMessage />
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
className={cn(
|
||||
"h-[42px] w-full pl-3 text-left font-medium",
|
||||
!field.value && "text-muted-foreground",
|
||||
`rounded-lg border border-neutral-300 bg-white text-primary shadow-xs outline-hidden file:border-0 file:bg-transparent file:font-medium file:text-sm hover:border-neutral-400 focus:border-neutral-400 disabled:cursor-not-allowed disabled:opacity-50 aria-expanded:border-neutral-400 dark:border-neutral-500 dark:bg-primary dark:text-white dark:aria-expanded:border-neutral-400 dark:focus:border-transparent dark:hover:border-neutral-400 dark:placeholder:text-neutral-400`,
|
||||
)}
|
||||
id="scadenza_motivazione_transitoria"
|
||||
variant={"outline"}
|
||||
>
|
||||
{field.value ? (
|
||||
format(field.value, "PPP", {
|
||||
locale: it,
|
||||
})
|
||||
) : (
|
||||
<span>{t.anagrafica.scegli_data}</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto size-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="w-auto p-0">
|
||||
<Calendar
|
||||
autoFocus
|
||||
captionLayout="dropdown"
|
||||
defaultMonth={field.value || new Date()}
|
||||
endMonth={new Date(2050, 12)}
|
||||
locale={it}
|
||||
mode="single"
|
||||
onSelect={field.onChange}
|
||||
selected={field.value || undefined}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{isLimited ? (
|
||||
<div className="space-y-0">
|
||||
<p className="font-semibold text-lg">
|
||||
{field.value
|
||||
? format(field.value, "dd/MM/yyyy")
|
||||
: "Errore data invalida"}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
La scadenza non può essere modificata. Contatta il
|
||||
supporto se necessario.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
className={cn(
|
||||
"h-[42px] w-full pl-3 text-left font-medium",
|
||||
!field.value && "text-muted-foreground",
|
||||
`rounded-lg border border-neutral-300 bg-white text-primary shadow-xs outline-hidden file:border-0 file:bg-transparent file:font-medium file:text-sm hover:border-neutral-400 focus:border-neutral-400 disabled:cursor-not-allowed disabled:opacity-50 aria-expanded:border-neutral-400 dark:border-neutral-500 dark:bg-primary dark:text-white dark:aria-expanded:border-neutral-400 dark:focus:border-transparent dark:hover:border-neutral-400 dark:placeholder:text-neutral-400`,
|
||||
)}
|
||||
id="scadenza_motivazione_transitoria"
|
||||
variant={"outline"}
|
||||
>
|
||||
{field.value ? (
|
||||
format(field.value, "PPP", {
|
||||
locale: it,
|
||||
})
|
||||
) : (
|
||||
<span>{t.anagrafica.scegli_data}</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto size-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="w-auto p-0">
|
||||
<Calendar
|
||||
autoFocus
|
||||
captionLayout="dropdown"
|
||||
defaultMonth={field.value || new Date()}
|
||||
endMonth={new Date(2050, 12)}
|
||||
locale={it}
|
||||
mode="single"
|
||||
onSelect={field.onChange}
|
||||
selected={field.value || undefined}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ import type { GetServerSideProps } from "next";
|
|||
import Head from "next/head";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { AccordionComp } from "~/components/accordionComp";
|
||||
import { AnnunciGrid } from "~/components/annunci_grid";
|
||||
import { MapSection } from "~/components/annunci_map";
|
||||
import { CTA_TipologiaModal } from "~/components/annunci_tutorial";
|
||||
import { CardAnnuncio } from "~/components/annuncio_card";
|
||||
import { CodiceBox } from "~/components/codiceRicerca";
|
||||
import { Layout } from "~/components/Layout";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
|
|
@ -367,7 +367,11 @@ const AnnunciList = () => {
|
|||
{status === "pending" ? (
|
||||
<LoadingPage />
|
||||
) : (
|
||||
<AnnunciGrid pagedata={data.annunci} />
|
||||
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
|
||||
{data.annunci.map((annuncio) => (
|
||||
<CardAnnuncio key={annuncio.codice} {...annuncio} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ const NewServizioModal = ({
|
|||
Nuovo Servizio
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-[80%] overflow-auto p-4 sm:max-w-5xl">
|
||||
<DialogContent className="max-h-[80%] w-full overflow-auto p-4 sm:max-w-5xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Creazione Servizio</DialogTitle>
|
||||
<DialogDescription className="sr-only">desc</DialogDescription>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import {
|
|||
import { db } from "~/server/db";
|
||||
import { NewMail, type NewMailProps } from "~/server/services/mailer";
|
||||
import { getUser } from "~/server/services/user.service";
|
||||
import type { AnnuncioRicerca } from "./annunci.controller";
|
||||
|
||||
export const addServizio = async (data: NewServizio) => {
|
||||
try {
|
||||
|
|
@ -1514,7 +1515,9 @@ export const SendContactEmail = async ({
|
|||
}
|
||||
};
|
||||
|
||||
export const getCompatibileAnnunci = async (servizioId: ServizioServizioId) => {
|
||||
export const getCompatibileAnnunci = async (
|
||||
servizioId: ServizioServizioId,
|
||||
): Promise<AnnuncioRicerca[]> => {
|
||||
try {
|
||||
const servizio = await getServizioById(servizioId);
|
||||
if (!servizio) {
|
||||
|
|
@ -1527,18 +1530,25 @@ export const getCompatibileAnnunci = async (servizioId: ServizioServizioId) => {
|
|||
let qry = db
|
||||
.selectFrom("annunci")
|
||||
.select((eb) => [
|
||||
"annunci.id",
|
||||
"annunci.codice",
|
||||
"annunci.prezzo",
|
||||
"annunci.desc_en",
|
||||
"annunci.desc_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.titolo_it",
|
||||
"annunci.tipo",
|
||||
"annunci.consegna",
|
||||
"annunci.stato",
|
||||
"annunci.web",
|
||||
"annunci.updated_at",
|
||||
"id",
|
||||
"codice",
|
||||
"comune",
|
||||
"provincia",
|
||||
"mq",
|
||||
"prezzo",
|
||||
"desc_en",
|
||||
"desc_it",
|
||||
"titolo_en",
|
||||
"titolo_it",
|
||||
"tipo",
|
||||
"consegna",
|
||||
"stato",
|
||||
"web",
|
||||
"updated_at",
|
||||
"homepage",
|
||||
"numero_camere",
|
||||
"url_video",
|
||||
"modificato_il",
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("images_refs")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue