feat: enhance RichiesteTable with improved status and decorrenza handling
- Updated RichiesteTable to display decorrenza with tooltip based on status. - Enhanced status display with badges indicating various states (e.g., Attesa attivazione, Interrotto). - Added Popover for displaying Annunci details in RichiesteTable. - Refactored API to fetch user interests and related announcements. - Introduced Queue component to display email events for users. - Updated translations to include new "Requests" section in admin navigation. - Improved database queries for fetching user interests and announcements.
This commit is contained in:
parent
8d07c82e7e
commit
7adb33f485
12 changed files with 659 additions and 380 deletions
|
|
@ -15,7 +15,10 @@ import { getStorageUrl } from "~/lib/storage_utils";
|
|||
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 { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||
import type { ServizioAnnuncioData } from "~/server/controllers/servizio.controller";
|
||||
import { api } from "~/utils/api";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
|
|
@ -30,15 +33,9 @@ export const AnnuncioCard = ({ className }: { className?: string }) => {
|
|||
const { data } = useServizioAnnuncio();
|
||||
|
||||
return (
|
||||
<BasicAnnuncioCard
|
||||
<AnnuncioCardContent
|
||||
className={className}
|
||||
data={{
|
||||
...data,
|
||||
modificato_il: data.modificato_il ? new Date(data.modificato_il) : null,
|
||||
media_updated_at: data.media_updated_at
|
||||
? new Date(data.media_updated_at)
|
||||
: null,
|
||||
}}
|
||||
data={data}
|
||||
interactions={
|
||||
isAdmin ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
|
|
@ -60,20 +57,22 @@ export const AnnuncioCard = ({ className }: { className?: string }) => {
|
|||
<UserActions />
|
||||
)
|
||||
}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const BasicAnnuncioCard = ({
|
||||
export const AnnuncioCardContent = ({
|
||||
interactions,
|
||||
className,
|
||||
data,
|
||||
isAdmin,
|
||||
}: {
|
||||
interactions?: React.ReactNode;
|
||||
className?: string;
|
||||
data: AnnuncioRicerca;
|
||||
data: ServizioAnnuncioData;
|
||||
isAdmin: boolean;
|
||||
}) => {
|
||||
const { isAdmin } = useServizio();
|
||||
return (
|
||||
<Card
|
||||
className={cn(
|
||||
|
|
@ -99,18 +98,29 @@ export const BasicAnnuncioCard = ({
|
|||
|
||||
<div className="flex w-full flex-col gap-4">{interactions}</div>
|
||||
</div>
|
||||
{isAdmin && <AnnuncioNote />}
|
||||
{isAdmin && (
|
||||
<AnnuncioNote
|
||||
annuncioId={data.id}
|
||||
note={data.note}
|
||||
servizioId={data.servizio_id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const AnnuncioNote = () => {
|
||||
const { servizioId } = useServizio();
|
||||
const { data } = useServizioAnnuncio();
|
||||
|
||||
const [newNote, setNewNote] = useState(data.note || "");
|
||||
const AnnuncioNote = ({
|
||||
servizioId,
|
||||
annuncioId,
|
||||
note,
|
||||
}: {
|
||||
servizioId: ServizioServizioId;
|
||||
annuncioId: AnnunciId;
|
||||
note: string | null;
|
||||
}) => {
|
||||
const [newNote, setNewNote] = useState<string | null>(note);
|
||||
const utils = api.useUtils();
|
||||
const { mutate, isPending } = api.servizio.updateServizioAnnunci.useMutation({
|
||||
onSuccess: async () => {
|
||||
|
|
@ -142,21 +152,22 @@ const AnnuncioNote = () => {
|
|||
<div className="flex w-full flex-col gap-2">
|
||||
<Textarea
|
||||
className="min-h-64"
|
||||
id={`note-${data.id}`}
|
||||
id={`note-${annuncioId}`}
|
||||
onChange={(e) => setNewNote(e.target.value)}
|
||||
placeholder=""
|
||||
value={newNote}
|
||||
value={newNote || ""}
|
||||
/>
|
||||
<LoadingButton
|
||||
disabled={newNote === data.note}
|
||||
disabled={newNote === note}
|
||||
loading={isPending}
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
mutate({
|
||||
annuncioId: data.id,
|
||||
annuncioId,
|
||||
servizioId,
|
||||
data: { note: newNote },
|
||||
})
|
||||
}
|
||||
});
|
||||
}}
|
||||
type="button"
|
||||
variant="success"
|
||||
>
|
||||
Salva
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
type LucideIcon,
|
||||
MousePointerClick,
|
||||
PackageCheck,
|
||||
SearchX,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
|
@ -160,7 +161,7 @@ const ServizioLinkCard = ({
|
|||
|
||||
if (servizio.isInterrotto) {
|
||||
status = "interrotto";
|
||||
StatusIcon = Trash2;
|
||||
StatusIcon = SearchX;
|
||||
} else if (!servizio.isOkAcconto) {
|
||||
status = "in_attesa";
|
||||
StatusIcon = ClockFading;
|
||||
|
|
@ -332,7 +333,7 @@ const Content = () => {
|
|||
};
|
||||
|
||||
const Main = () => {
|
||||
const { servizio, isAdmin } = useServizio();
|
||||
const { servizio, isAdmin, userId } = useServizio();
|
||||
const { t } = useTranslation();
|
||||
const utils = api.useUtils();
|
||||
const { mutate: attivazioneSenzaPagamento } =
|
||||
|
|
@ -349,10 +350,28 @@ const Main = () => {
|
|||
// se interrotto, mostra avvisos
|
||||
if (servizio.isInterrotto) {
|
||||
return (
|
||||
<>
|
||||
<p className="font-medium text-lg">{t.servizio.servizio_interrotto}</p>
|
||||
<p>{t.servizio.servizio_interrotto_desc}</p>
|
||||
</>
|
||||
<div className="flex w-full flex-col items-center justify-center gap-4 py-10 text-center">
|
||||
<div className="flex size-14 items-center justify-center rounded-full bg-red-100 dark:bg-red-500/10">
|
||||
<SearchX className="size-7 text-red-500 dark:text-red-400" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="font-semibold text-xl">
|
||||
{t.servizio.servizio_interrotto}
|
||||
</p>
|
||||
<p className="max-w-sm text-muted-foreground text-sm">
|
||||
{t.servizio.servizio_interrotto_desc}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={
|
||||
isAdmin
|
||||
? `/area-riservata/admin/user-view/ricerca/${userId}`
|
||||
: "/area-riservata/dashboard"
|
||||
}
|
||||
>
|
||||
<Button variant="secondary">Torna alla lista dei servizi</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// mostra onboarding
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Plus, Trash2 } from "lucide-react";
|
||||
import toast from "react-hot-toast";
|
||||
import { BasicAnnuncioCard } from "~/components/servizio/annuncio_card";
|
||||
import { AnnuncioDisplay } from "~/components/servizio/annuncio_card";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
|
|
@ -8,6 +8,8 @@ import {
|
|||
AccordionTrigger,
|
||||
} from "~/components/ui/accordion";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { cn } from "~/lib/utils";
|
||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import { api } from "~/utils/api";
|
||||
|
|
@ -64,35 +66,49 @@ export const AnnunciRichiesti = ({
|
|||
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
|
||||
{data.map((a) => {
|
||||
return (
|
||||
<BasicAnnuncioCard
|
||||
className="w-full border-2 border-pink-500"
|
||||
data={a}
|
||||
interactions={
|
||||
<>
|
||||
{servizioId && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
addToServizio({ annunci: [a.id], servizioId });
|
||||
}}
|
||||
variant="success"
|
||||
>
|
||||
Aggiungi al servizio
|
||||
<Plus className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeIterest({ annuncioId: a.id, userId });
|
||||
}}
|
||||
variant="destructive"
|
||||
>
|
||||
Rimuovi
|
||||
<Trash2 className="size-4" />
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
<Card
|
||||
className="w-full max-w-7xl gap-2 rounded-md border-2 border-pink-500 bg-muted py-3"
|
||||
key={a.id}
|
||||
/>
|
||||
>
|
||||
<CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 px-3 pb-2">
|
||||
<CardTitle className="text-2xl">
|
||||
Annuncio {a.codice}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-3">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div
|
||||
className={cn("@container flex w-full flex-col gap-4")}
|
||||
id="container"
|
||||
>
|
||||
<AnnuncioDisplay data={a} />
|
||||
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
{servizioId && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
addToServizio({ annunci: [a.id], servizioId });
|
||||
}}
|
||||
variant="success"
|
||||
>
|
||||
Aggiungi al servizio
|
||||
<Plus className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
removeIterest({ annuncioId: a.id, userId });
|
||||
}}
|
||||
variant="destructive"
|
||||
>
|
||||
Rimuovi
|
||||
<Trash2 className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
{data.length % 2 === 1 && (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import {
|
||||
CircleCheck,
|
||||
CircleCheckBig,
|
||||
CircleEllipsis,
|
||||
ClockFading,
|
||||
ExternalLink,
|
||||
SearchX,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { AnnuncioCardContent } from "~/components/servizio/annuncio_card";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { DataTable } from "~/components/ui/data-table";
|
||||
import { DataTableColumnHeader } from "~/components/ui/dataTable-header";
|
||||
|
|
@ -8,6 +18,21 @@ import type {
|
|||
PinnedFiltro,
|
||||
SearchFiltro,
|
||||
} from "~/components/ui/dataTable-toolbar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverHeader,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "~/components/ui/popover";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "~/components/ui/tooltip";
|
||||
import { UserAvatar } from "~/components/user_avatar";
|
||||
import { cn } from "~/lib/utils";
|
||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||
import type { RichiesteData } from "~/server/controllers/servizio.controller";
|
||||
|
||||
|
|
@ -17,13 +42,12 @@ type RichiesteTableProps = {
|
|||
|
||||
export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||
const tabledata = data;
|
||||
|
||||
const columns_titles = {
|
||||
id: "ID Ordine",
|
||||
username: "Utente",
|
||||
created_at: "Creato il",
|
||||
decorrenza: "Decorrenza",
|
||||
status: "Status",
|
||||
tipo: "Tipo",
|
||||
tipologia: "Tipo",
|
||||
annunci: "Annunci",
|
||||
actions: "Azioni",
|
||||
};
|
||||
|
|
@ -39,10 +63,6 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
{ label: "Stabile", value: TipologiaPosizioneEnum.Stabile },
|
||||
];
|
||||
|
||||
// const [selectedOrdine, setSelectedOrdine] = useState<OrdiniOrdineId | null>(
|
||||
// null,
|
||||
// );
|
||||
|
||||
type Column = (typeof tabledata)[number];
|
||||
|
||||
const columns: ColumnDef<Column>[] = [
|
||||
|
|
@ -58,10 +78,17 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
cell: ({ row }) => {
|
||||
return (
|
||||
<Link
|
||||
aria-label="Visualizza Utente"
|
||||
className="flex items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/ricerca/${row.original.user_id}`}
|
||||
>
|
||||
<Button size="sm">{row.original.username}</Button>
|
||||
<UserAvatar
|
||||
className="size-8"
|
||||
userId={row.original.user_id}
|
||||
username={row.original.username}
|
||||
/>
|
||||
<span className="font-semibold text-sm">
|
||||
{row.original.username}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
|
|
@ -74,16 +101,39 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "created_at",
|
||||
accessorKey: "decorrenza",
|
||||
cell: ({ row }) => {
|
||||
const date = row.getValue("created_at");
|
||||
return new Date(date as Date).toLocaleDateString();
|
||||
let decorrenzaColor = "";
|
||||
let decorrenzaTooltip = "";
|
||||
if (!row.original.isOkAcconto || !row.original.decorrenza) {
|
||||
decorrenzaColor = "text-muted-foreground";
|
||||
decorrenzaTooltip = "Attesa di attivazione";
|
||||
} else if (row.original.isInterrotto) {
|
||||
decorrenzaColor = "text-destructive";
|
||||
decorrenzaTooltip = "Servizio interrotto";
|
||||
} else if (row.original.isOkAcconto && !row.original.isOkSaldo) {
|
||||
decorrenzaColor = "text-warning";
|
||||
decorrenzaTooltip = "Attesa di saldo";
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger className={decorrenzaColor}>
|
||||
{row.original.decorrenza
|
||||
? format(row.original.decorrenza, "dd/MM")
|
||||
: "---"}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{decorrenzaTooltip}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
enableHiding: false,
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader
|
||||
column={column}
|
||||
title={columns_titles.created_at}
|
||||
title={columns_titles.decorrenza}
|
||||
/>
|
||||
),
|
||||
sortingFn: "datetime",
|
||||
|
|
@ -91,37 +141,94 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
{
|
||||
accessorKey: "tipologia",
|
||||
cell: ({ row }) => {
|
||||
return row.original.tipologia.toString();
|
||||
const { tipologia } = row.original;
|
||||
return (
|
||||
<Badge
|
||||
className={cn(
|
||||
"px-1.5",
|
||||
tipologia === TipologiaPosizioneEnum.Transitorio
|
||||
? "border-transitorio bg-transitorio/15"
|
||||
: "border-stabile bg-stabile/15",
|
||||
)}
|
||||
variant="outline"
|
||||
>
|
||||
{tipologia.toString()}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
return value.includes(row.getValue(id));
|
||||
},
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title={columns_titles.tipo} />
|
||||
<DataTableColumnHeader
|
||||
column={column}
|
||||
title={columns_titles.tipologia}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "status",
|
||||
cell: ({ row }) => {
|
||||
let badgeClass = "";
|
||||
let contents: React.ReactNode;
|
||||
const annunci = row.original.annunci_servizio;
|
||||
|
||||
const awaitingConferma = annunci.some(
|
||||
(a) => a.user_confirmed_at !== null && !a.hasConfermaAdmin,
|
||||
);
|
||||
if (awaitingConferma) {
|
||||
return "Conferma in corso";
|
||||
}
|
||||
|
||||
if (row.original.isOkSaldo) {
|
||||
return "Saldo pagato";
|
||||
}
|
||||
if (row.original.isOkAcconto) {
|
||||
return "Acconto pagato";
|
||||
}
|
||||
if (!row.original.onboardOk) {
|
||||
return "Attesa di attivazione";
|
||||
const hasConfermaAdmin = annunci.some((a) => a.hasConfermaAdmin);
|
||||
if (row.original.isInterrotto) {
|
||||
badgeClass = "border-destructive bg-destructive/10 text-destructive";
|
||||
contents = (
|
||||
<>
|
||||
<SearchX className="stroke-red-500 dark:stroke-red-400" />
|
||||
Interrotto
|
||||
</>
|
||||
);
|
||||
} else if (!row.original.onboardOk) {
|
||||
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
||||
contents = (
|
||||
<>
|
||||
<ClockFading className="stroke-yellow-500 dark:stroke-yellow-400" />
|
||||
Attesa attivazione
|
||||
</>
|
||||
);
|
||||
} else if (awaitingConferma) {
|
||||
badgeClass = "border-sky-500 bg-sky-500/10 text-sky-500";
|
||||
contents = (
|
||||
<>
|
||||
<CircleEllipsis className="stroke-sky-500 dark:stroke-sky-400" />
|
||||
In Conferma
|
||||
</>
|
||||
);
|
||||
} else if (row.original.isOkSaldo && hasConfermaAdmin) {
|
||||
badgeClass = "border-blue-500 bg-blue-500/10 text-blue-500";
|
||||
contents = (
|
||||
<>
|
||||
<CircleCheckBig className="stroke-blue-500 dark:stroke-blue-400" />
|
||||
Saldo pagato
|
||||
</>
|
||||
);
|
||||
} else if (row.original.isOkAcconto) {
|
||||
badgeClass = "border-green-500 bg-green-500/10 text-green-500";
|
||||
contents = (
|
||||
<>
|
||||
<CircleCheck className="stroke-green-500 dark:stroke-green-400" />
|
||||
Attivo
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Badge
|
||||
className={cn(
|
||||
"flex items-center px-1.5 text-muted-foreground [&>svg]:size-4",
|
||||
badgeClass,
|
||||
)}
|
||||
variant="outline"
|
||||
>
|
||||
{contents}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
|
||||
filterFn: (row, id, value) => {
|
||||
|
|
@ -135,7 +242,43 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
accessorKey: "annunci_servizio",
|
||||
cell: ({ row }) => {
|
||||
const annunci = row.original.annunci_servizio;
|
||||
return <span>{annunci.length}</span>;
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button size="sm" variant="outline">
|
||||
{annunci.length} Annunci
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-4xl">
|
||||
<PopoverHeader>
|
||||
<PopoverTitle>Annunci Inseriti</PopoverTitle>
|
||||
<PopoverDescription className="sr-only">
|
||||
Desc
|
||||
</PopoverDescription>
|
||||
</PopoverHeader>
|
||||
<div className="flex flex-col gap-4 divide-y">
|
||||
{annunci.map((a) => (
|
||||
<AnnuncioCardContent
|
||||
data={a}
|
||||
interactions={
|
||||
<div>
|
||||
<p>
|
||||
Visto contatti:{" "}
|
||||
{a.open_contatti_at
|
||||
? format(a.open_contatti_at, "dd/MM/yyyy HH:mm")
|
||||
: "Non ancora visto"}
|
||||
</p>
|
||||
{/*TODO CAPIRE COSA AGGIUNGERE */}
|
||||
</div>
|
||||
}
|
||||
isAdmin
|
||||
key={a.annunci_id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title={columns_titles.annunci} />
|
||||
|
|
@ -143,16 +286,16 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
enableSorting: false,
|
||||
},
|
||||
{
|
||||
cell: () => {
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Button
|
||||
aria-label="Apri Dettagli Ordine"
|
||||
//onClick={() => {}}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
<Link
|
||||
href={`/area-riservata/admin/user-view/servizio/${row.original.user_id}/${row.original.servizio_id}`}
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="size-5" />
|
||||
</Button>
|
||||
<Button aria-label="Apri servizio" size="sm" variant="outline">
|
||||
<ExternalLink className="size-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
enableHiding: false,
|
||||
|
|
@ -173,7 +316,7 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
title: "Stato Ordine",
|
||||
},
|
||||
{
|
||||
columnName: "tipo",
|
||||
columnName: "tipologia",
|
||||
options: type_options,
|
||||
title: "Tipo Ordine",
|
||||
},
|
||||
|
|
@ -185,16 +328,10 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
|||
columns={columns}
|
||||
columns_titles={columns_titles}
|
||||
data={tabledata}
|
||||
defaultSort={[{ desc: true, id: "created_at" }]}
|
||||
defaultSort={[{ desc: true, id: "decorrenza" }]}
|
||||
pinnedFiltri={pinnedFiltri}
|
||||
searchColumn={searchFiltro}
|
||||
/>
|
||||
|
||||
{/* <AdminWrapperOrdiniModal
|
||||
ordineId={selectedOrdine}
|
||||
setSelected={setSelectedOrdine}
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import { Popover as PopoverPrimitive } from "radix-ui";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
function Popover({
|
||||
|
|
@ -28,7 +27,7 @@ function PopoverContent({
|
|||
<PopoverPrimitive.Content
|
||||
align={align}
|
||||
className={cn(
|
||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-popover-foreground text-sm shadow-md outline-hidden ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
|
||||
className,
|
||||
)}
|
||||
data-slot="popover-content"
|
||||
|
|
@ -45,4 +44,45 @@ function PopoverAnchor({
|
|||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
||||
function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
className={cn("flex flex-col gap-0.5 text-sm", className)}
|
||||
data-slot="popover-header"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
|
||||
return (
|
||||
<div
|
||||
className={cn("cn-font-heading font-medium", className)}
|
||||
data-slot="popover-title"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PopoverDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"p">) {
|
||||
return (
|
||||
<p
|
||||
className={cn("text-muted-foreground", className)}
|
||||
data-slot="popover-description"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Popover,
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverHeader,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -657,6 +657,7 @@ export const en: LangDict = {
|
|||
items: [
|
||||
{ href: "/area-riservata/admin/potenziali", title: "Potenziali" },
|
||||
{ href: "/area-riservata/admin/utenti", title: "Users" },
|
||||
{ href: "/area-riservata/admin/richieste", title: "Requests" },
|
||||
{ href: "/area-riservata/admin/chats", title: "Chat List" },
|
||||
{ href: "/area-riservata/admin/ordini", title: "Orders" },
|
||||
{ href: "/area-riservata/admin/incroci", title: "Incroci" },
|
||||
|
|
|
|||
|
|
@ -662,6 +662,7 @@ export const it: LangDict = {
|
|||
items: [
|
||||
{ href: "/area-riservata/admin/potenziali", title: "Potenziali" },
|
||||
{ href: "/area-riservata/admin/utenti", title: "Utenti" },
|
||||
{ href: "/area-riservata/admin/richieste", title: "Richieste" },
|
||||
{ href: "/area-riservata/admin/chats", title: "Lista Chat" },
|
||||
{ href: "/area-riservata/admin/ordini", title: "Ordini" },
|
||||
{ href: "/area-riservata/admin/incroci", title: "Incroci" },
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ const ComunicazioniUser: NextPageWithLayout<ComunicazioniUserProps> = ({
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<Queue userId={userId} />
|
||||
<EmailAccordion isAdmin={user.isAdmin} userId={userId} />
|
||||
</main>
|
||||
);
|
||||
|
|
@ -119,3 +119,13 @@ export const getServerSideProps = (async (context) => {
|
|||
ComunicazioniUser.getLayout = function getLayout(page) {
|
||||
return <AreaRiservataLayoutUserView>{page}</AreaRiservataLayoutUserView>;
|
||||
};
|
||||
|
||||
const Queue = ({ userId }: { userId: UsersId }) => {
|
||||
const { data } = api.comunicazioni.getUserQueuedEmails.useQuery({ userId });
|
||||
console.log("Queue data", data);
|
||||
return (
|
||||
<div>
|
||||
<h2>Eventi email in coda per user {userId}</h2>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import { db } from "~/server/db";
|
|||
import {
|
||||
AddIntrest,
|
||||
GetUserInterests,
|
||||
GetUserInterestsAnnunci,
|
||||
HasUserInterest,
|
||||
RemoveInterest,
|
||||
} from "~/server/services/interests.service";
|
||||
import { NewMail } from "~/server/services/mailer";
|
||||
import { findUser_byId_MINI } from "~/server/services/user.service";
|
||||
import { zAnnuncioId, zUserId } from "~/server/utils/zod_types";
|
||||
import { withImages, withVideos } from "~/utils/kysely-helper";
|
||||
|
||||
export const intrestsRouter = createTRPCRouter({
|
||||
addIntrest: protectedProcedure
|
||||
|
|
@ -85,49 +85,7 @@ export const intrestsRouter = createTRPCRouter({
|
|||
}),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
const interests = await GetUserInterests(
|
||||
input.userId || ctx.session.id,
|
||||
);
|
||||
if (!interests || interests.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const annunci = await db
|
||||
.selectFrom("annunci")
|
||||
.select((_eb) => [
|
||||
"annunci.id",
|
||||
"annunci.codice",
|
||||
"annunci.comune",
|
||||
"annunci.provincia",
|
||||
"annunci.prezzo",
|
||||
"annunci.consegna",
|
||||
"annunci.numero_camere",
|
||||
"annunci.mq",
|
||||
"annunci.tipo",
|
||||
"annunci.titolo_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.desc_en",
|
||||
"annunci.desc_it",
|
||||
"annunci.modificato_il",
|
||||
"annunci.stato",
|
||||
"annunci.external_videos",
|
||||
"annunci.media_updated_at",
|
||||
"annunci.homepage",
|
||||
"annunci.web",
|
||||
withImages(),
|
||||
withVideos(),
|
||||
])
|
||||
.where("web", "=", true)
|
||||
.where("stato", "!=", "Sospeso")
|
||||
.where("annunci.id", "in", interests)
|
||||
.execute();
|
||||
return annunci;
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Errore durante il recupero degli annunci: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
return await GetUserInterestsAnnunci(input.userId || ctx.session.id);
|
||||
}),
|
||||
hasUserInterest: protectedProcedure
|
||||
.input(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import type { Override } from "TypeHelpers.type";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { add, set } from "date-fns";
|
||||
import { sql } from "kysely";
|
||||
import type { Attachment } from "nodemailer/lib/mailer";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import type { NewEventQueue } from "~/schemas/public/EventQueue";
|
||||
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import {
|
||||
type MinimalSMSData,
|
||||
SendMessage,
|
||||
|
|
@ -155,3 +157,20 @@ export const processEvents = async () => {
|
|||
}
|
||||
return { message: `${events.length} events processed` };
|
||||
};
|
||||
|
||||
export const userEmailQueue = async (userId: UsersId) => {
|
||||
try {
|
||||
const events = await db
|
||||
.selectFrom("event_queue")
|
||||
.selectAll()
|
||||
.where(sql`data->>'tipo'`, "=", "email")
|
||||
.where(sql`data->'data'->>'userId'`, "=", userId)
|
||||
.execute();
|
||||
return events;
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Error while getting user events from queue, ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { Override } from "TypeHelpers.type";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { add, isBefore } from "date-fns";
|
||||
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
|
||||
|
|
@ -7,6 +6,7 @@ import type { PurchaseData } from "~/components/acquisto_receipt";
|
|||
import { env } from "~/env";
|
||||
import type { PaymentType } from "~/i18n/stripe";
|
||||
import { parseDBComune, parseDBNazione } from "~/lib/catasto";
|
||||
import { withParsedDates } from "~/lib/dateParserJsonKysely";
|
||||
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
|
||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||
import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||
|
|
@ -344,45 +344,47 @@ type DocRef = {
|
|||
storageId: string;
|
||||
ref: UsersStorageUserStorageId | null;
|
||||
};
|
||||
|
||||
export type ServizioAnnuncioData = ServizioAnnunci &
|
||||
Pick<
|
||||
Annunci,
|
||||
| "id"
|
||||
| "codice"
|
||||
| "comune"
|
||||
| "provincia"
|
||||
| "prezzo"
|
||||
| "consegna"
|
||||
| "numero_camere"
|
||||
| "mq"
|
||||
| "tipo"
|
||||
| "titolo_it"
|
||||
| "titolo_en"
|
||||
| "desc_en"
|
||||
| "desc_it"
|
||||
| "modificato_il"
|
||||
| "stato"
|
||||
| "external_videos"
|
||||
| "media_updated_at"
|
||||
| "homepage"
|
||||
| "web"
|
||||
| "disponibile_da"
|
||||
| "persone"
|
||||
| "permanenza"
|
||||
> & {
|
||||
images: {
|
||||
img: string;
|
||||
thumb: string;
|
||||
}[];
|
||||
videos: {
|
||||
thumb: string;
|
||||
video: string;
|
||||
}[];
|
||||
};
|
||||
export type ServizioData = Servizio & {
|
||||
doc_personale_fronte: DocRef | null;
|
||||
doc_personale_retro: DocRef | null;
|
||||
doc_motivazione: DocRef | null;
|
||||
annunci: (ServizioAnnunci &
|
||||
Pick<
|
||||
Annunci,
|
||||
| "id"
|
||||
| "codice"
|
||||
| "comune"
|
||||
| "provincia"
|
||||
| "prezzo"
|
||||
| "consegna"
|
||||
| "numero_camere"
|
||||
| "mq"
|
||||
| "tipo"
|
||||
| "titolo_it"
|
||||
| "titolo_en"
|
||||
| "desc_en"
|
||||
| "desc_it"
|
||||
| "modificato_il"
|
||||
| "stato"
|
||||
| "external_videos"
|
||||
| "media_updated_at"
|
||||
| "homepage"
|
||||
| "web"
|
||||
| "disponibile_da"
|
||||
| "persone"
|
||||
| "permanenza"
|
||||
> & {
|
||||
images: {
|
||||
img: string;
|
||||
thumb: string;
|
||||
}[];
|
||||
videos: {
|
||||
thumb: string;
|
||||
video: string;
|
||||
}[];
|
||||
})[];
|
||||
annunci: ServizioAnnuncioData[];
|
||||
ordini: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
||||
};
|
||||
|
||||
|
|
@ -390,147 +392,134 @@ export const getAllServizioAnnunci = async (
|
|||
userId: UsersId,
|
||||
): Promise<ServizioData[]> => {
|
||||
try {
|
||||
const data = await db
|
||||
.selectFrom("servizio")
|
||||
.where("user_id", "=", userId)
|
||||
.leftJoin(
|
||||
"users_anagrafica",
|
||||
"servizio.user_id",
|
||||
"users_anagrafica.userid",
|
||||
)
|
||||
.selectAll("servizio")
|
||||
.select((eb) => [
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"users_anagrafica.doc_personale_fronte_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"users_anagrafica.doc_personale_fronte_ref",
|
||||
),
|
||||
).as("doc_personale_fronte"),
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"users_anagrafica.doc_personale_retro_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"users_anagrafica.doc_personale_retro_ref",
|
||||
),
|
||||
).as("doc_personale_retro"),
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"servizio.doc_motivazione_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"servizio.doc_motivazione_ref",
|
||||
),
|
||||
).as("doc_motivazione"),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("servizio_annunci")
|
||||
.selectAll("servizio_annunci")
|
||||
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
||||
.select((_eb) => [
|
||||
"annunci.id",
|
||||
"annunci.codice",
|
||||
"annunci.comune",
|
||||
"annunci.provincia",
|
||||
"annunci.prezzo",
|
||||
"annunci.consegna",
|
||||
"annunci.numero_camere",
|
||||
"annunci.mq",
|
||||
"annunci.tipo",
|
||||
"annunci.titolo_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.desc_en",
|
||||
"annunci.desc_it",
|
||||
"annunci.modificato_il",
|
||||
"annunci.stato",
|
||||
"annunci.external_videos",
|
||||
"annunci.media_updated_at",
|
||||
"annunci.homepage",
|
||||
"annunci.web",
|
||||
"annunci.disponibile_da",
|
||||
"annunci.persone",
|
||||
"annunci.permanenza",
|
||||
withImages(),
|
||||
withVideos(),
|
||||
])
|
||||
//ignora annunci che non sono disponibili
|
||||
//.where("annunci.web", "=", true)
|
||||
//.where("annunci.stato", "!=", "Sospeso")
|
||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.created_at", "asc")
|
||||
.whereRef(
|
||||
"servizio_annunci.servizio_id",
|
||||
"=",
|
||||
"servizio.servizio_id",
|
||||
),
|
||||
).as("annunci"),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("ordini")
|
||||
.selectAll("ordini")
|
||||
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
||||
.select("prezziario.testo_condizioni")
|
||||
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
||||
.orderBy("ordini.created_at", "desc"),
|
||||
).as("ordini"),
|
||||
])
|
||||
.orderBy("servizio.created_at", "desc")
|
||||
.execute();
|
||||
const data = await withParsedDates(
|
||||
db
|
||||
.selectFrom("servizio")
|
||||
.where("user_id", "=", userId)
|
||||
.leftJoin(
|
||||
"users_anagrafica",
|
||||
"servizio.user_id",
|
||||
"users_anagrafica.userid",
|
||||
)
|
||||
.selectAll("servizio")
|
||||
.select((eb) => [
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"users_anagrafica.doc_personale_fronte_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"users_anagrafica.doc_personale_fronte_ref",
|
||||
),
|
||||
).as("doc_personale_fronte"),
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"users_anagrafica.doc_personale_retro_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"users_anagrafica.doc_personale_retro_ref",
|
||||
),
|
||||
).as("doc_personale_retro"),
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"servizio.doc_motivazione_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"servizio.doc_motivazione_ref",
|
||||
),
|
||||
).as("doc_motivazione"),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("servizio_annunci")
|
||||
.selectAll("servizio_annunci")
|
||||
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
||||
.select((_eb) => [
|
||||
"annunci.id",
|
||||
"annunci.codice",
|
||||
"annunci.comune",
|
||||
"annunci.provincia",
|
||||
"annunci.prezzo",
|
||||
"annunci.consegna",
|
||||
"annunci.numero_camere",
|
||||
"annunci.mq",
|
||||
"annunci.tipo",
|
||||
"annunci.titolo_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.desc_en",
|
||||
"annunci.desc_it",
|
||||
"annunci.modificato_il",
|
||||
"annunci.stato",
|
||||
"annunci.external_videos",
|
||||
"annunci.media_updated_at",
|
||||
"annunci.homepage",
|
||||
"annunci.web",
|
||||
"annunci.disponibile_da",
|
||||
"annunci.persone",
|
||||
"annunci.permanenza",
|
||||
withImages(),
|
||||
withVideos(),
|
||||
])
|
||||
//ignora annunci che non sono disponibili
|
||||
//.where("annunci.web", "=", true)
|
||||
//.where("annunci.stato", "!=", "Sospeso")
|
||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.created_at", "asc")
|
||||
.whereRef(
|
||||
"servizio_annunci.servizio_id",
|
||||
"=",
|
||||
"servizio.servizio_id",
|
||||
),
|
||||
).as("annunci"),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("ordini")
|
||||
.selectAll("ordini")
|
||||
.innerJoin(
|
||||
"prezziario",
|
||||
"prezziario.idprezziario",
|
||||
"ordini.packid",
|
||||
)
|
||||
.select("prezziario.testo_condizioni")
|
||||
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
||||
.orderBy("ordini.created_at", "desc"),
|
||||
).as("ordini"),
|
||||
])
|
||||
.orderBy("servizio.created_at", "desc"),
|
||||
[
|
||||
"annunci.modificato_il",
|
||||
"annunci.created_at",
|
||||
"annunci.open_contatti_at",
|
||||
"annunci.user_confirmed_at",
|
||||
"annunci.accettato_conferma_at",
|
||||
"annunci.contratto_decorrenza",
|
||||
"annunci.contratto_scadenza",
|
||||
"annunci.disponibile_da",
|
||||
"annunci.media_updated_at",
|
||||
"ordini.created_at",
|
||||
"ordini.paid_at",
|
||||
],
|
||||
).execute();
|
||||
|
||||
const formattedData = data.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
annunci: item.annunci.map((annuncio) => ({
|
||||
...annuncio,
|
||||
created_at: new Date(annuncio.created_at),
|
||||
open_contatti_at: parseNullableDateString(annuncio.open_contatti_at),
|
||||
user_confirmed_at: parseNullableDateString(
|
||||
annuncio.user_confirmed_at,
|
||||
),
|
||||
accettato_conferma_at: parseNullableDateString(
|
||||
annuncio.accettato_conferma_at,
|
||||
),
|
||||
contratto_decorrenza: parseNullableDateString(
|
||||
annuncio.contratto_decorrenza,
|
||||
),
|
||||
contratto_scadenza: parseNullableDateString(
|
||||
annuncio.contratto_scadenza,
|
||||
),
|
||||
modificato_il: parseNullableDateString(annuncio.modificato_il),
|
||||
disponibile_da: parseNullableDateString(annuncio.disponibile_da),
|
||||
media_updated_at: parseNullableDateString(annuncio.media_updated_at),
|
||||
})),
|
||||
ordini: item.ordini.map((ordine) => ({
|
||||
...ordine,
|
||||
created_at: new Date(ordine.created_at),
|
||||
paid_at: ordine.paid_at ? new Date(ordine.paid_at) : null,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
return formattedData;
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
|
|
@ -1460,61 +1449,95 @@ export const getOrdineRicevutaData = async ({
|
|||
};
|
||||
export type RichiesteData = Servizio &
|
||||
Pick<Users, "username"> & {
|
||||
annunci_servizio: (Override<
|
||||
ServizioAnnunci,
|
||||
{
|
||||
created_at: string;
|
||||
open_contatti_at: string | null;
|
||||
user_confirmed_at: string | null;
|
||||
accettato_conferma_at: string | null;
|
||||
contratto_decorrenza: string | null;
|
||||
contratto_scadenza: string | null;
|
||||
}
|
||||
> &
|
||||
Pick<Annunci, "codice" | "titolo_it">)[];
|
||||
ordini_servizio: (Override<
|
||||
Ordini,
|
||||
{
|
||||
created_at: string;
|
||||
paid_at: string | null;
|
||||
}
|
||||
> &
|
||||
Pick<Prezziario, "testo_condizioni">)[];
|
||||
annunci_servizio: ServizioAnnuncioData[];
|
||||
ordini_servizio: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
||||
};
|
||||
|
||||
export const getRichieste = async (): Promise<RichiesteData[]> => {
|
||||
try {
|
||||
return await db
|
||||
.selectFrom("servizio")
|
||||
.innerJoin("users", "users.id", "servizio.user_id")
|
||||
.selectAll("servizio")
|
||||
.select(["users.username"])
|
||||
.select((eb) => [
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("servizio_annunci")
|
||||
.selectAll("servizio_annunci")
|
||||
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
||||
.select(["annunci.codice", "annunci.titolo_it"])
|
||||
.whereRef(
|
||||
"servizio.servizio_id",
|
||||
"=",
|
||||
"servizio_annunci.servizio_id",
|
||||
)
|
||||
.orderBy("servizio_annunci.created_at", "desc"),
|
||||
).as("annunci_servizio"),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("ordini")
|
||||
.selectAll("ordini")
|
||||
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
||||
.select("prezziario.testo_condizioni")
|
||||
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
||||
.orderBy("ordini.created_at", "desc"),
|
||||
).as("ordini_servizio"),
|
||||
])
|
||||
.orderBy("servizio.created_at", "desc")
|
||||
.execute();
|
||||
const data = await withParsedDates(
|
||||
db
|
||||
.selectFrom("servizio")
|
||||
.innerJoin("users", "users.id", "servizio.user_id")
|
||||
.selectAll("servizio")
|
||||
.select(["users.username"])
|
||||
.select((eb) => [
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("servizio_annunci")
|
||||
.selectAll("servizio_annunci")
|
||||
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
||||
.select((_eb) => [
|
||||
"annunci.id",
|
||||
"annunci.codice",
|
||||
"annunci.comune",
|
||||
"annunci.provincia",
|
||||
"annunci.prezzo",
|
||||
"annunci.consegna",
|
||||
"annunci.numero_camere",
|
||||
"annunci.mq",
|
||||
"annunci.tipo",
|
||||
"annunci.titolo_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.desc_en",
|
||||
"annunci.desc_it",
|
||||
"annunci.modificato_il",
|
||||
"annunci.stato",
|
||||
"annunci.external_videos",
|
||||
"annunci.media_updated_at",
|
||||
"annunci.homepage",
|
||||
"annunci.web",
|
||||
"annunci.disponibile_da",
|
||||
"annunci.persone",
|
||||
"annunci.permanenza",
|
||||
withImages(),
|
||||
withVideos(),
|
||||
])
|
||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||
ob.asc().nullsLast(),
|
||||
)
|
||||
.orderBy("servizio_annunci.created_at", "asc")
|
||||
|
||||
.whereRef(
|
||||
"servizio.servizio_id",
|
||||
"=",
|
||||
"servizio_annunci.servizio_id",
|
||||
)
|
||||
.orderBy("servizio_annunci.created_at", "desc"),
|
||||
).as("annunci_servizio"),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("ordini")
|
||||
.selectAll("ordini")
|
||||
.innerJoin(
|
||||
"prezziario",
|
||||
"prezziario.idprezziario",
|
||||
"ordini.packid",
|
||||
)
|
||||
.select("prezziario.testo_condizioni")
|
||||
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
||||
.orderBy("ordini.created_at", "desc"),
|
||||
).as("ordini_servizio"),
|
||||
])
|
||||
.orderBy("servizio.created_at", "desc"),
|
||||
[
|
||||
"annunci_servizio.created_at",
|
||||
"annunci_servizio.open_contatti_at",
|
||||
"annunci_servizio.disponibile_da",
|
||||
"annunci_servizio.user_confirmed_at",
|
||||
"annunci_servizio.accettato_conferma_at",
|
||||
"annunci_servizio.contratto_decorrenza",
|
||||
"annunci_servizio.contratto_scadenza",
|
||||
"annunci_servizio.modificato_il",
|
||||
"annunci_servizio.media_updated_at",
|
||||
"ordini_servizio.created_at",
|
||||
"ordini_servizio.paid_at",
|
||||
],
|
||||
).execute();
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { TRPCError } from "@trpc/server";
|
|||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import { db } from "~/server/db";
|
||||
import { withImages, withVideos } from "~/utils/kysely-helper";
|
||||
|
||||
export const AddIntrest = async ({
|
||||
annuncioId,
|
||||
|
|
@ -91,3 +92,46 @@ export const GetUserInterests = async (userId: UsersId) => {
|
|||
});
|
||||
}
|
||||
};
|
||||
export const GetUserInterestsAnnunci = async (userId: UsersId) => {
|
||||
try {
|
||||
const interests = await GetUserInterests(userId);
|
||||
if (!interests || interests.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const annunci = await db
|
||||
.selectFrom("annunci")
|
||||
.select((_eb) => [
|
||||
"annunci.id",
|
||||
"annunci.codice",
|
||||
"annunci.comune",
|
||||
"annunci.provincia",
|
||||
"annunci.prezzo",
|
||||
"annunci.consegna",
|
||||
"annunci.numero_camere",
|
||||
"annunci.mq",
|
||||
"annunci.tipo",
|
||||
"annunci.titolo_it",
|
||||
"annunci.titolo_en",
|
||||
"annunci.desc_en",
|
||||
"annunci.desc_it",
|
||||
"annunci.modificato_il",
|
||||
"annunci.stato",
|
||||
"annunci.external_videos",
|
||||
"annunci.media_updated_at",
|
||||
"annunci.homepage",
|
||||
"annunci.web",
|
||||
withImages(),
|
||||
withVideos(),
|
||||
])
|
||||
.where("web", "=", true)
|
||||
.where("stato", "!=", "Sospeso")
|
||||
.where("annunci.id", "in", interests)
|
||||
.execute();
|
||||
return annunci;
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Errore durante il recupero degli annunci: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue