infoalloggi-monorepo/apps/infoalloggi/src/components/servizio/main.tsx

616 lines
16 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import { Package, PackageCheck, UserCircle } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { AnnuncioCard } from "~/components/servizio/annuncio_card";
2025-08-28 18:27:07 +02:00
import { SaldoButton } from "~/components/servizio/interactions";
2025-08-04 17:45:44 +02:00
import { ServizioDuration } from "~/components/servizio/service-duration-display";
2025-08-28 18:27:07 +02:00
import {
ServizioActions,
ServizioActions2,
} from "~/components/servizio/servizio_actions";
import {
AnnunciCompatibili,
AnnunciInConferma,
AnnunciSelezionati,
} from "~/components/servizio/servizio_annunci_accordions";
import { Button } from "~/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Progress } from "~/components/ui/progress";
import { cn } from "~/lib/utils";
import {
ServizioAnnuncioProvider,
ServizioProvider,
useServizio,
} from "~/providers/ServizioProvider";
import type { UsersId } from "~/schemas/public/Users";
import type { ServizioData } from "~/server/controllers/servizio.controller";
import { api } from "~/utils/api";
2025-08-04 17:45:44 +02:00
export const ServizioContainer = ({
2025-08-28 18:27:07 +02:00
servizio,
userId,
isAdmin,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizio: ServizioData;
userId: UsersId;
isAdmin: boolean;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
return (
<ServizioProvider
isAdmin={isAdmin}
userId={userId}
servizioId={servizio.servizio_id}
servizio={servizio}
>
<ServizioMain />
</ServizioProvider>
);
2025-08-04 17:45:44 +02:00
};
const ServizioCard = ({
2025-08-28 18:27:07 +02:00
cardClassName,
header,
headerClassName,
content,
contentClassName,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
cardClassName?: string;
header: React.ReactNode;
headerClassName?: string;
content: React.ReactNode;
contentClassName?: string;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
return (
<Card
className={cn("gap-2 border-neutral-200 bg-neutral-200", cardClassName)}
>
<CardHeader
className={cn(
"flex flex-col items-start justify-between gap-3 space-y-0 px-4 pb-8 sm:flex-row",
headerClassName,
)}
>
{header}
</CardHeader>
<CardContent className={cn("space-y-4 p-4 pt-0", contentClassName)}>
{content}
</CardContent>
</Card>
);
2025-08-04 17:45:44 +02:00
};
export const ServizioContent = () => {
2025-08-28 18:27:07 +02:00
const { servizio, isAdmin, userId } = useServizio();
const utils = api.useUtils();
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
onSuccess: async () => {
toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
},
onError: (error) => {
toast.error(error.message);
},
});
const router = useRouter();
useEffect(() => {
const id = router.asPath.split("#")[1];
if (id) {
document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
});
}
}, []);
if (servizio.isInterrotto) {
return (
<ServizioCard
cardClassName="bg-neutral-100 border-neutral-300"
header={
<>
<div className="flex flex-col gap-4">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 space-y-2 pl-2">
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})}
</p>
</CardDescription>
</div>
</>
}
content={
<>
<p className="text-lg font-medium">Servizio interrotto</p>
<p>
Non è possibile riutilizzare questo servizio, poichè è stato
interrotto.
</p>
</>
}
/>
);
}
if (!servizio.isOkAcconto || servizio.decorrenza === null) {
return (
<ServizioCard
header={
<div className="flex w-full flex-col gap-2">
{/* <div className="flex w-full flex-col gap-4">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 pl-2">
<p>Servizio non attivo</p>
</CardDescription>
</div> */}
2025-08-28 18:27:07 +02:00
<span className="text-foreground/70 text-lg">
Servizio non attivo
</span>
<ServizioActions2 />
</div>
}
content={
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-3">
<Link
href={`/servizio/onboard/${servizio.servizio_id}`}
aria-label="Attiva Servizio"
>
<Button>Procedi all&apos;attivazione</Button>
</Link>
{isAdmin && (
<Button
onClick={() =>
updateServizio({
servizioId: servizio.servizio_id,
data: {
isOkAcconto: true,
decorrenza: new Date(),
},
})
}
variant="success"
>
Attiva Manualmente (Azione Admin)
</Button>
)}
</div>
{isAdmin && (
<AnnunciSelezionati
isConfirming={false}
annunci={servizio.annunci}
/>
)}
</div>
}
/>
);
}
const annuncioConfermato = servizio.annunci.filter(
(a) => a.accettato_conferma_at !== null,
);
if (servizio.isOkSaldo) {
return (
<ServizioCard
header={
<>
{/* <div className="flex w-full flex-wrap items-center gap-2">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 space-y-2 pl-2">
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})}
</p>
</CardDescription>
</div> */}
2025-08-28 18:27:07 +02:00
<ServizioActions2 />
</>
}
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
/>
);
}
const openContattiCount = servizio.annunci.filter(
(a) => a.open_contatti_at !== null,
).length;
const annunciInConferma = servizio.annunci.filter(
(a) => a.user_confirmed_at !== null,
);
const annunciNotInConferma = servizio.annunci.filter(
(a) => a.user_confirmed_at === null,
);
return (
<ServizioCard
header={
<>
{/* <div className="flex w-full flex-col gap-2">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 space-y-2 pl-2">
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})}
</p>
</CardDescription>
</div> */}
2025-08-28 18:27:07 +02:00
<ServizioActions2 />
</>
}
content={
<>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<UserCircle className="text-primary size-6" />
<span className="font-medium">Contatti sbloccati</span>
</div>
<span className="text-sm">{openContattiCount} / 10</span>
</div>
<Progress value={openContattiCount * 10} className="h-2" />
</div>
<ServizioDuration decorrenza={servizio.decorrenza} />
{annuncioConfermato.length > 0 && (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<PackageCheck className="text-primary size-6" />
<span className="font-medium">Saldi in attesa:</span>
</div>
{servizio.annunci
.filter((a) => a.accettato_conferma_at !== null)
.map((a) => (
<div
key={a.annunci_id}
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
>
<h3>
{a.codice} - confermato il:{" "}
{/** biome-ignore lint/style/noNonNullAssertion: <already checked above> */}
{new Date(a.accettato_conferma_at!).toLocaleDateString(
"it",
{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
},
)}
</h3>
<SaldoButton
variant={"outline"}
className="border-none"
annuncioId={a.annunci_id}
/>
</div>
))}
</div>
)}
<div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati
isConfirming={annunciInConferma.length > 0}
annunci={annunciNotInConferma}
/>
<AnnunciCompatibili />
</div>
</>
}
/>
);
};
2025-08-04 17:45:44 +02:00
const ServizioMain = () => {
2025-08-28 18:27:07 +02:00
const { servizio, isAdmin, userId } = useServizio();
const utils = api.useUtils();
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
onSuccess: async () => {
toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
},
onError: (error) => {
toast.error(error.message);
},
});
const router = useRouter();
useEffect(() => {
const id = router.asPath.split("#")[1];
if (id) {
document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
});
}
}, []);
if (servizio.isInterrotto) {
return (
<ServizioCard
cardClassName="bg-neutral-100 border-neutral-300"
header={
<>
<div className="flex flex-col gap-4">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 space-y-2 pl-2">
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})}
</p>
</CardDescription>
</div>
</>
}
content={
<>
<p className="text-lg font-medium">Servizio interrotto</p>
<p>
Non è possibile riutilizzare questo servizio, poichè è stato
interrotto.
</p>
</>
}
/>
);
}
if (!servizio.isOkAcconto || servizio.decorrenza === null) {
return (
<ServizioCard
header={
<>
<div className="flex w-full flex-col gap-4">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 pl-2">
<p>Servizio non attivo</p>
</CardDescription>
</div>
<ServizioActions />
</>
}
content={
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-3">
<Link
href={`/servizio/onboard/${servizio.servizio_id}`}
aria-label="Attiva Servizio"
>
<Button>Procedi all&apos;attivazione</Button>
</Link>
{isAdmin && (
<Button
onClick={() =>
updateServizio({
servizioId: servizio.servizio_id,
data: {
isOkAcconto: true,
decorrenza: new Date(),
},
})
}
variant="success"
>
Attiva Manualmente (Azione Admin)
</Button>
)}
</div>
{isAdmin && (
<AnnunciSelezionati
isConfirming={false}
annunci={servizio.annunci}
/>
)}
</div>
}
/>
);
}
const annuncioConfermato = servizio.annunci.filter(
(a) => a.accettato_conferma_at !== null,
);
if (servizio.isOkSaldo) {
return (
<ServizioCard
header={
<>
<div className="flex w-full flex-wrap items-center gap-2">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 space-y-2 pl-2">
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})}
</p>
</CardDescription>
</div>
<ServizioActions />
</>
}
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
/>
);
}
const openContattiCount = servizio.annunci.filter(
(a) => a.open_contatti_at !== null,
).length;
const annunciInConferma = servizio.annunci.filter(
(a) => a.user_confirmed_at !== null,
);
const annunciNotInConferma = servizio.annunci.filter(
(a) => a.user_confirmed_at === null,
);
return (
<ServizioCard
header={
<>
<div className="flex w-full flex-col gap-2">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 space-y-2 pl-2">
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})}
</p>
</CardDescription>
</div>
<ServizioActions />
</>
}
content={
<>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<UserCircle className="text-primary size-6" />
<span className="font-medium">Contatti sbloccati</span>
</div>
<span className="text-sm">{openContattiCount} / 10</span>
</div>
<Progress value={openContattiCount * 10} className="h-2" />
</div>
<ServizioDuration decorrenza={servizio.decorrenza} />
{annuncioConfermato.length > 0 && (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<PackageCheck className="text-primary size-6" />
<span className="font-medium">Saldi in attesa:</span>
</div>
{servizio.annunci
.filter((a) => a.accettato_conferma_at !== null)
.map((a) => (
<div
key={a.annunci_id}
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
>
<h3>
{a.codice} - confermato il:{" "}
{/** biome-ignore lint/style/noNonNullAssertion: <already checked> */}
{new Date(a.accettato_conferma_at!).toLocaleDateString(
"it",
{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
},
)}
</h3>
<SaldoButton
variant={"outline"}
className="border-none"
annuncioId={a.annunci_id}
/>
</div>
))}
</div>
)}
<div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati
isConfirming={annunciInConferma.length > 0}
annunci={annunciNotInConferma}
/>
<AnnunciCompatibili />
</div>
</>
}
/>
);
2025-08-04 17:45:44 +02:00
};