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 { cn, formatCurrency } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
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 { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||||
|
import type { ServizioAnnuncioData } from "~/server/controllers/servizio.controller";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import {
|
import {
|
||||||
|
|
@ -30,15 +33,9 @@ export const AnnuncioCard = ({ className }: { className?: string }) => {
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicAnnuncioCard
|
<AnnuncioCardContent
|
||||||
className={className}
|
className={className}
|
||||||
data={{
|
data={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,
|
|
||||||
}}
|
|
||||||
interactions={
|
interactions={
|
||||||
isAdmin ? (
|
isAdmin ? (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
|
@ -60,20 +57,22 @@ export const AnnuncioCard = ({ className }: { className?: string }) => {
|
||||||
<UserActions />
|
<UserActions />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
isAdmin={isAdmin}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BasicAnnuncioCard = ({
|
export const AnnuncioCardContent = ({
|
||||||
interactions,
|
interactions,
|
||||||
className,
|
className,
|
||||||
data,
|
data,
|
||||||
|
isAdmin,
|
||||||
}: {
|
}: {
|
||||||
interactions?: React.ReactNode;
|
interactions?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
data: AnnuncioRicerca;
|
data: ServizioAnnuncioData;
|
||||||
|
isAdmin: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { isAdmin } = useServizio();
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -99,18 +98,29 @@ export const BasicAnnuncioCard = ({
|
||||||
|
|
||||||
<div className="flex w-full flex-col gap-4">{interactions}</div>
|
<div className="flex w-full flex-col gap-4">{interactions}</div>
|
||||||
</div>
|
</div>
|
||||||
{isAdmin && <AnnuncioNote />}
|
{isAdmin && (
|
||||||
|
<AnnuncioNote
|
||||||
|
annuncioId={data.id}
|
||||||
|
note={data.note}
|
||||||
|
servizioId={data.servizio_id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AnnuncioNote = () => {
|
const AnnuncioNote = ({
|
||||||
const { servizioId } = useServizio();
|
servizioId,
|
||||||
const { data } = useServizioAnnuncio();
|
annuncioId,
|
||||||
|
note,
|
||||||
const [newNote, setNewNote] = useState(data.note || "");
|
}: {
|
||||||
|
servizioId: ServizioServizioId;
|
||||||
|
annuncioId: AnnunciId;
|
||||||
|
note: string | null;
|
||||||
|
}) => {
|
||||||
|
const [newNote, setNewNote] = useState<string | null>(note);
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate, isPending } = api.servizio.updateServizioAnnunci.useMutation({
|
const { mutate, isPending } = api.servizio.updateServizioAnnunci.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
|
|
@ -142,21 +152,22 @@ const AnnuncioNote = () => {
|
||||||
<div className="flex w-full flex-col gap-2">
|
<div className="flex w-full flex-col gap-2">
|
||||||
<Textarea
|
<Textarea
|
||||||
className="min-h-64"
|
className="min-h-64"
|
||||||
id={`note-${data.id}`}
|
id={`note-${annuncioId}`}
|
||||||
onChange={(e) => setNewNote(e.target.value)}
|
onChange={(e) => setNewNote(e.target.value)}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
value={newNote}
|
value={newNote || ""}
|
||||||
/>
|
/>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
disabled={newNote === data.note}
|
disabled={newNote === note}
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
mutate({
|
mutate({
|
||||||
annuncioId: data.id,
|
annuncioId,
|
||||||
servizioId,
|
servizioId,
|
||||||
data: { note: newNote },
|
data: { note: newNote },
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
|
type="button"
|
||||||
variant="success"
|
variant="success"
|
||||||
>
|
>
|
||||||
Salva
|
Salva
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
MousePointerClick,
|
MousePointerClick,
|
||||||
PackageCheck,
|
PackageCheck,
|
||||||
|
SearchX,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
@ -160,7 +161,7 @@ const ServizioLinkCard = ({
|
||||||
|
|
||||||
if (servizio.isInterrotto) {
|
if (servizio.isInterrotto) {
|
||||||
status = "interrotto";
|
status = "interrotto";
|
||||||
StatusIcon = Trash2;
|
StatusIcon = SearchX;
|
||||||
} else if (!servizio.isOkAcconto) {
|
} else if (!servizio.isOkAcconto) {
|
||||||
status = "in_attesa";
|
status = "in_attesa";
|
||||||
StatusIcon = ClockFading;
|
StatusIcon = ClockFading;
|
||||||
|
|
@ -332,7 +333,7 @@ const Content = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Main = () => {
|
const Main = () => {
|
||||||
const { servizio, isAdmin } = useServizio();
|
const { servizio, isAdmin, userId } = useServizio();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate: attivazioneSenzaPagamento } =
|
const { mutate: attivazioneSenzaPagamento } =
|
||||||
|
|
@ -349,10 +350,28 @@ const Main = () => {
|
||||||
// se interrotto, mostra avvisos
|
// se interrotto, mostra avvisos
|
||||||
if (servizio.isInterrotto) {
|
if (servizio.isInterrotto) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex w-full flex-col items-center justify-center gap-4 py-10 text-center">
|
||||||
<p className="font-medium text-lg">{t.servizio.servizio_interrotto}</p>
|
<div className="flex size-14 items-center justify-center rounded-full bg-red-100 dark:bg-red-500/10">
|
||||||
<p>{t.servizio.servizio_interrotto_desc}</p>
|
<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
|
// mostra onboarding
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Plus, Trash2 } from "lucide-react";
|
import { Plus, Trash2 } from "lucide-react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { BasicAnnuncioCard } from "~/components/servizio/annuncio_card";
|
import { AnnuncioDisplay } from "~/components/servizio/annuncio_card";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
|
|
@ -8,6 +8,8 @@ import {
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "~/components/ui/accordion";
|
} from "~/components/ui/accordion";
|
||||||
import { Button } from "~/components/ui/button";
|
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 { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
@ -64,35 +66,49 @@ export const AnnunciRichiesti = ({
|
||||||
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
|
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
|
||||||
{data.map((a) => {
|
{data.map((a) => {
|
||||||
return (
|
return (
|
||||||
<BasicAnnuncioCard
|
<Card
|
||||||
className="w-full border-2 border-pink-500"
|
className="w-full max-w-7xl gap-2 rounded-md border-2 border-pink-500 bg-muted py-3"
|
||||||
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>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
key={a.id}
|
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 && (
|
{data.length % 2 === 1 && (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,16 @@
|
||||||
import type { ColumnDef } from "@tanstack/react-table";
|
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 Link from "next/link";
|
||||||
|
import { AnnuncioCardContent } from "~/components/servizio/annuncio_card";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { DataTable } from "~/components/ui/data-table";
|
import { DataTable } from "~/components/ui/data-table";
|
||||||
import { DataTableColumnHeader } from "~/components/ui/dataTable-header";
|
import { DataTableColumnHeader } from "~/components/ui/dataTable-header";
|
||||||
|
|
@ -8,6 +18,21 @@ import type {
|
||||||
PinnedFiltro,
|
PinnedFiltro,
|
||||||
SearchFiltro,
|
SearchFiltro,
|
||||||
} from "~/components/ui/dataTable-toolbar";
|
} 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 TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import type { RichiesteData } from "~/server/controllers/servizio.controller";
|
import type { RichiesteData } from "~/server/controllers/servizio.controller";
|
||||||
|
|
||||||
|
|
@ -17,13 +42,12 @@ type RichiesteTableProps = {
|
||||||
|
|
||||||
export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
const tabledata = data;
|
const tabledata = data;
|
||||||
|
|
||||||
const columns_titles = {
|
const columns_titles = {
|
||||||
id: "ID Ordine",
|
id: "ID Ordine",
|
||||||
username: "Utente",
|
username: "Utente",
|
||||||
created_at: "Creato il",
|
decorrenza: "Decorrenza",
|
||||||
status: "Status",
|
status: "Status",
|
||||||
tipo: "Tipo",
|
tipologia: "Tipo",
|
||||||
annunci: "Annunci",
|
annunci: "Annunci",
|
||||||
actions: "Azioni",
|
actions: "Azioni",
|
||||||
};
|
};
|
||||||
|
|
@ -39,10 +63,6 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
{ label: "Stabile", value: TipologiaPosizioneEnum.Stabile },
|
{ label: "Stabile", value: TipologiaPosizioneEnum.Stabile },
|
||||||
];
|
];
|
||||||
|
|
||||||
// const [selectedOrdine, setSelectedOrdine] = useState<OrdiniOrdineId | null>(
|
|
||||||
// null,
|
|
||||||
// );
|
|
||||||
|
|
||||||
type Column = (typeof tabledata)[number];
|
type Column = (typeof tabledata)[number];
|
||||||
|
|
||||||
const columns: ColumnDef<Column>[] = [
|
const columns: ColumnDef<Column>[] = [
|
||||||
|
|
@ -58,10 +78,17 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
aria-label="Visualizza Utente"
|
className="flex items-center gap-2"
|
||||||
href={`/area-riservata/admin/user-view/ricerca/${row.original.user_id}`}
|
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>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -74,16 +101,39 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "created_at",
|
accessorKey: "decorrenza",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const date = row.getValue("created_at");
|
let decorrenzaColor = "";
|
||||||
return new Date(date as Date).toLocaleDateString();
|
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,
|
enableHiding: false,
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader
|
<DataTableColumnHeader
|
||||||
column={column}
|
column={column}
|
||||||
title={columns_titles.created_at}
|
title={columns_titles.decorrenza}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
sortingFn: "datetime",
|
sortingFn: "datetime",
|
||||||
|
|
@ -91,37 +141,94 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
{
|
{
|
||||||
accessorKey: "tipologia",
|
accessorKey: "tipologia",
|
||||||
cell: ({ row }) => {
|
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) => {
|
filterFn: (row, id, value) => {
|
||||||
return value.includes(row.getValue(id));
|
return value.includes(row.getValue(id));
|
||||||
},
|
},
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader column={column} title={columns_titles.tipo} />
|
<DataTableColumnHeader
|
||||||
|
column={column}
|
||||||
|
title={columns_titles.tipologia}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "status",
|
accessorKey: "status",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
let badgeClass = "";
|
||||||
|
let contents: React.ReactNode;
|
||||||
const annunci = row.original.annunci_servizio;
|
const annunci = row.original.annunci_servizio;
|
||||||
|
|
||||||
const awaitingConferma = annunci.some(
|
const awaitingConferma = annunci.some(
|
||||||
(a) => a.user_confirmed_at !== null && !a.hasConfermaAdmin,
|
(a) => a.user_confirmed_at !== null && !a.hasConfermaAdmin,
|
||||||
);
|
);
|
||||||
if (awaitingConferma) {
|
const hasConfermaAdmin = annunci.some((a) => a.hasConfermaAdmin);
|
||||||
return "Conferma in corso";
|
if (row.original.isInterrotto) {
|
||||||
}
|
badgeClass = "border-destructive bg-destructive/10 text-destructive";
|
||||||
|
contents = (
|
||||||
if (row.original.isOkSaldo) {
|
<>
|
||||||
return "Saldo pagato";
|
<SearchX className="stroke-red-500 dark:stroke-red-400" />
|
||||||
}
|
Interrotto
|
||||||
if (row.original.isOkAcconto) {
|
</>
|
||||||
return "Acconto pagato";
|
);
|
||||||
}
|
} else if (!row.original.onboardOk) {
|
||||||
if (!row.original.onboardOk) {
|
badgeClass = "border-yellow-500 bg-yellow-500/10 text-yellow-600";
|
||||||
return "Attesa di attivazione";
|
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) => {
|
filterFn: (row, id, value) => {
|
||||||
|
|
@ -135,7 +242,43 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
accessorKey: "annunci_servizio",
|
accessorKey: "annunci_servizio",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const annunci = row.original.annunci_servizio;
|
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 }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader column={column} title={columns_titles.annunci} />
|
<DataTableColumnHeader column={column} title={columns_titles.annunci} />
|
||||||
|
|
@ -143,16 +286,16 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cell: () => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Link
|
||||||
aria-label="Apri Dettagli Ordine"
|
href={`/area-riservata/admin/user-view/servizio/${row.original.user_id}/${row.original.servizio_id}`}
|
||||||
//onClick={() => {}}
|
target="_blank"
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
>
|
>
|
||||||
<ExternalLink className="size-5" />
|
<Button aria-label="Apri servizio" size="sm" variant="outline">
|
||||||
</Button>
|
<ExternalLink className="size-5" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
|
|
@ -173,7 +316,7 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
title: "Stato Ordine",
|
title: "Stato Ordine",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
columnName: "tipo",
|
columnName: "tipologia",
|
||||||
options: type_options,
|
options: type_options,
|
||||||
title: "Tipo Ordine",
|
title: "Tipo Ordine",
|
||||||
},
|
},
|
||||||
|
|
@ -185,16 +328,10 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => {
|
||||||
columns={columns}
|
columns={columns}
|
||||||
columns_titles={columns_titles}
|
columns_titles={columns_titles}
|
||||||
data={tabledata}
|
data={tabledata}
|
||||||
defaultSort={[{ desc: true, id: "created_at" }]}
|
defaultSort={[{ desc: true, id: "decorrenza" }]}
|
||||||
pinnedFiltri={pinnedFiltri}
|
pinnedFiltri={pinnedFiltri}
|
||||||
searchColumn={searchFiltro}
|
searchColumn={searchFiltro}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <AdminWrapperOrdiniModal
|
|
||||||
ordineId={selectedOrdine}
|
|
||||||
setSelected={setSelectedOrdine}
|
|
||||||
/>
|
|
||||||
*/}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { Popover as PopoverPrimitive } from "radix-ui";
|
import { Popover as PopoverPrimitive } from "radix-ui";
|
||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
function Popover({
|
function Popover({
|
||||||
|
|
@ -28,7 +27,7 @@ function PopoverContent({
|
||||||
<PopoverPrimitive.Content
|
<PopoverPrimitive.Content
|
||||||
align={align}
|
align={align}
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
data-slot="popover-content"
|
data-slot="popover-content"
|
||||||
|
|
@ -45,4 +44,45 @@ function PopoverAnchor({
|
||||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
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: [
|
items: [
|
||||||
{ href: "/area-riservata/admin/potenziali", title: "Potenziali" },
|
{ href: "/area-riservata/admin/potenziali", title: "Potenziali" },
|
||||||
{ href: "/area-riservata/admin/utenti", title: "Users" },
|
{ 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/chats", title: "Chat List" },
|
||||||
{ href: "/area-riservata/admin/ordini", title: "Orders" },
|
{ href: "/area-riservata/admin/ordini", title: "Orders" },
|
||||||
{ href: "/area-riservata/admin/incroci", title: "Incroci" },
|
{ href: "/area-riservata/admin/incroci", title: "Incroci" },
|
||||||
|
|
|
||||||
|
|
@ -662,6 +662,7 @@ export const it: LangDict = {
|
||||||
items: [
|
items: [
|
||||||
{ href: "/area-riservata/admin/potenziali", title: "Potenziali" },
|
{ href: "/area-riservata/admin/potenziali", title: "Potenziali" },
|
||||||
{ href: "/area-riservata/admin/utenti", title: "Utenti" },
|
{ 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/chats", title: "Lista Chat" },
|
||||||
{ href: "/area-riservata/admin/ordini", title: "Ordini" },
|
{ href: "/area-riservata/admin/ordini", title: "Ordini" },
|
||||||
{ href: "/area-riservata/admin/incroci", title: "Incroci" },
|
{ href: "/area-riservata/admin/incroci", title: "Incroci" },
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ const ComunicazioniUser: NextPageWithLayout<ComunicazioniUserProps> = ({
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
<Queue userId={userId} />
|
||||||
<EmailAccordion isAdmin={user.isAdmin} userId={userId} />
|
<EmailAccordion isAdmin={user.isAdmin} userId={userId} />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
@ -119,3 +119,13 @@ export const getServerSideProps = (async (context) => {
|
||||||
ComunicazioniUser.getLayout = function getLayout(page) {
|
ComunicazioniUser.getLayout = function getLayout(page) {
|
||||||
return <AreaRiservataLayoutUserView>{page}</AreaRiservataLayoutUserView>;
|
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 {
|
import {
|
||||||
AddIntrest,
|
AddIntrest,
|
||||||
GetUserInterests,
|
GetUserInterests,
|
||||||
|
GetUserInterestsAnnunci,
|
||||||
HasUserInterest,
|
HasUserInterest,
|
||||||
RemoveInterest,
|
RemoveInterest,
|
||||||
} from "~/server/services/interests.service";
|
} from "~/server/services/interests.service";
|
||||||
import { NewMail } from "~/server/services/mailer";
|
import { NewMail } from "~/server/services/mailer";
|
||||||
import { findUser_byId_MINI } from "~/server/services/user.service";
|
import { findUser_byId_MINI } from "~/server/services/user.service";
|
||||||
import { zAnnuncioId, zUserId } from "~/server/utils/zod_types";
|
import { zAnnuncioId, zUserId } from "~/server/utils/zod_types";
|
||||||
import { withImages, withVideos } from "~/utils/kysely-helper";
|
|
||||||
|
|
||||||
export const intrestsRouter = createTRPCRouter({
|
export const intrestsRouter = createTRPCRouter({
|
||||||
addIntrest: protectedProcedure
|
addIntrest: protectedProcedure
|
||||||
|
|
@ -85,49 +85,7 @@ export const intrestsRouter = createTRPCRouter({
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
try {
|
return await GetUserInterestsAnnunci(input.userId || ctx.session.id);
|
||||||
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}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
hasUserInterest: protectedProcedure
|
hasUserInterest: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import type { Override } from "TypeHelpers.type";
|
import type { Override } from "TypeHelpers.type";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { add, set } from "date-fns";
|
import { add, set } from "date-fns";
|
||||||
|
import { sql } from "kysely";
|
||||||
import type { Attachment } from "nodemailer/lib/mailer";
|
import type { Attachment } from "nodemailer/lib/mailer";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import type { NewEventQueue } from "~/schemas/public/EventQueue";
|
import type { NewEventQueue } from "~/schemas/public/EventQueue";
|
||||||
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import {
|
import {
|
||||||
type MinimalSMSData,
|
type MinimalSMSData,
|
||||||
SendMessage,
|
SendMessage,
|
||||||
|
|
@ -155,3 +157,20 @@ export const processEvents = async () => {
|
||||||
}
|
}
|
||||||
return { message: `${events.length} events processed` };
|
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 { TRPCError } from "@trpc/server";
|
||||||
import { add, isBefore } from "date-fns";
|
import { add, isBefore } from "date-fns";
|
||||||
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
|
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
|
||||||
|
|
@ -7,6 +6,7 @@ import type { PurchaseData } from "~/components/acquisto_receipt";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import type { PaymentType } from "~/i18n/stripe";
|
import type { PaymentType } from "~/i18n/stripe";
|
||||||
import { parseDBComune, parseDBNazione } from "~/lib/catasto";
|
import { parseDBComune, parseDBNazione } from "~/lib/catasto";
|
||||||
|
import { withParsedDates } from "~/lib/dateParserJsonKysely";
|
||||||
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
|
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini";
|
import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
|
|
@ -344,45 +344,47 @@ type DocRef = {
|
||||||
storageId: string;
|
storageId: string;
|
||||||
ref: UsersStorageUserStorageId | null;
|
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 & {
|
export type ServizioData = Servizio & {
|
||||||
doc_personale_fronte: DocRef | null;
|
doc_personale_fronte: DocRef | null;
|
||||||
doc_personale_retro: DocRef | null;
|
doc_personale_retro: DocRef | null;
|
||||||
doc_motivazione: DocRef | null;
|
doc_motivazione: DocRef | null;
|
||||||
annunci: (ServizioAnnunci &
|
annunci: ServizioAnnuncioData[];
|
||||||
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;
|
|
||||||
}[];
|
|
||||||
})[];
|
|
||||||
ordini: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
ordini: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -390,147 +392,134 @@ export const getAllServizioAnnunci = async (
|
||||||
userId: UsersId,
|
userId: UsersId,
|
||||||
): Promise<ServizioData[]> => {
|
): Promise<ServizioData[]> => {
|
||||||
try {
|
try {
|
||||||
const data = await db
|
const data = await withParsedDates(
|
||||||
.selectFrom("servizio")
|
db
|
||||||
.where("user_id", "=", userId)
|
.selectFrom("servizio")
|
||||||
.leftJoin(
|
.where("user_id", "=", userId)
|
||||||
"users_anagrafica",
|
.leftJoin(
|
||||||
"servizio.user_id",
|
"users_anagrafica",
|
||||||
"users_anagrafica.userid",
|
"servizio.user_id",
|
||||||
)
|
"users_anagrafica.userid",
|
||||||
.selectAll("servizio")
|
)
|
||||||
.select((eb) => [
|
.selectAll("servizio")
|
||||||
jsonObjectFrom(
|
.select((eb) => [
|
||||||
eb
|
jsonObjectFrom(
|
||||||
.selectFrom("users_storage")
|
eb
|
||||||
.select([
|
.selectFrom("users_storage")
|
||||||
"users_storage.storageId as storageId",
|
.select([
|
||||||
"users_anagrafica.doc_personale_fronte_ref as ref",
|
"users_storage.storageId as storageId",
|
||||||
])
|
"users_anagrafica.doc_personale_fronte_ref as ref",
|
||||||
.whereRef(
|
])
|
||||||
"users_storage.user_storage_id",
|
.whereRef(
|
||||||
"=",
|
"users_storage.user_storage_id",
|
||||||
"users_anagrafica.doc_personale_fronte_ref",
|
"=",
|
||||||
),
|
"users_anagrafica.doc_personale_fronte_ref",
|
||||||
).as("doc_personale_fronte"),
|
),
|
||||||
jsonObjectFrom(
|
).as("doc_personale_fronte"),
|
||||||
eb
|
jsonObjectFrom(
|
||||||
.selectFrom("users_storage")
|
eb
|
||||||
.select([
|
.selectFrom("users_storage")
|
||||||
"users_storage.storageId as storageId",
|
.select([
|
||||||
"users_anagrafica.doc_personale_retro_ref as ref",
|
"users_storage.storageId as storageId",
|
||||||
])
|
"users_anagrafica.doc_personale_retro_ref as ref",
|
||||||
.whereRef(
|
])
|
||||||
"users_storage.user_storage_id",
|
.whereRef(
|
||||||
"=",
|
"users_storage.user_storage_id",
|
||||||
"users_anagrafica.doc_personale_retro_ref",
|
"=",
|
||||||
),
|
"users_anagrafica.doc_personale_retro_ref",
|
||||||
).as("doc_personale_retro"),
|
),
|
||||||
jsonObjectFrom(
|
).as("doc_personale_retro"),
|
||||||
eb
|
jsonObjectFrom(
|
||||||
.selectFrom("users_storage")
|
eb
|
||||||
.select([
|
.selectFrom("users_storage")
|
||||||
"users_storage.storageId as storageId",
|
.select([
|
||||||
"servizio.doc_motivazione_ref as ref",
|
"users_storage.storageId as storageId",
|
||||||
])
|
"servizio.doc_motivazione_ref as ref",
|
||||||
.whereRef(
|
])
|
||||||
"users_storage.user_storage_id",
|
.whereRef(
|
||||||
"=",
|
"users_storage.user_storage_id",
|
||||||
"servizio.doc_motivazione_ref",
|
"=",
|
||||||
),
|
"servizio.doc_motivazione_ref",
|
||||||
).as("doc_motivazione"),
|
),
|
||||||
jsonArrayFrom(
|
).as("doc_motivazione"),
|
||||||
eb
|
jsonArrayFrom(
|
||||||
.selectFrom("servizio_annunci")
|
eb
|
||||||
.selectAll("servizio_annunci")
|
.selectFrom("servizio_annunci")
|
||||||
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
.selectAll("servizio_annunci")
|
||||||
.select((_eb) => [
|
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
||||||
"annunci.id",
|
.select((_eb) => [
|
||||||
"annunci.codice",
|
"annunci.id",
|
||||||
"annunci.comune",
|
"annunci.codice",
|
||||||
"annunci.provincia",
|
"annunci.comune",
|
||||||
"annunci.prezzo",
|
"annunci.provincia",
|
||||||
"annunci.consegna",
|
"annunci.prezzo",
|
||||||
"annunci.numero_camere",
|
"annunci.consegna",
|
||||||
"annunci.mq",
|
"annunci.numero_camere",
|
||||||
"annunci.tipo",
|
"annunci.mq",
|
||||||
"annunci.titolo_it",
|
"annunci.tipo",
|
||||||
"annunci.titolo_en",
|
"annunci.titolo_it",
|
||||||
"annunci.desc_en",
|
"annunci.titolo_en",
|
||||||
"annunci.desc_it",
|
"annunci.desc_en",
|
||||||
"annunci.modificato_il",
|
"annunci.desc_it",
|
||||||
"annunci.stato",
|
"annunci.modificato_il",
|
||||||
"annunci.external_videos",
|
"annunci.stato",
|
||||||
"annunci.media_updated_at",
|
"annunci.external_videos",
|
||||||
"annunci.homepage",
|
"annunci.media_updated_at",
|
||||||
"annunci.web",
|
"annunci.homepage",
|
||||||
"annunci.disponibile_da",
|
"annunci.web",
|
||||||
"annunci.persone",
|
"annunci.disponibile_da",
|
||||||
"annunci.permanenza",
|
"annunci.persone",
|
||||||
withImages(),
|
"annunci.permanenza",
|
||||||
withVideos(),
|
withImages(),
|
||||||
])
|
withVideos(),
|
||||||
//ignora annunci che non sono disponibili
|
])
|
||||||
//.where("annunci.web", "=", true)
|
//ignora annunci che non sono disponibili
|
||||||
//.where("annunci.stato", "!=", "Sospeso")
|
//.where("annunci.web", "=", true)
|
||||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
//.where("annunci.stato", "!=", "Sospeso")
|
||||||
ob.asc().nullsLast(),
|
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||||
)
|
ob.asc().nullsLast(),
|
||||||
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
)
|
||||||
ob.asc().nullsLast(),
|
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
||||||
)
|
ob.asc().nullsLast(),
|
||||||
.orderBy("servizio_annunci.created_at", "asc")
|
)
|
||||||
.whereRef(
|
.orderBy("servizio_annunci.created_at", "asc")
|
||||||
"servizio_annunci.servizio_id",
|
.whereRef(
|
||||||
"=",
|
"servizio_annunci.servizio_id",
|
||||||
"servizio.servizio_id",
|
"=",
|
||||||
),
|
"servizio.servizio_id",
|
||||||
).as("annunci"),
|
),
|
||||||
jsonArrayFrom(
|
).as("annunci"),
|
||||||
eb
|
jsonArrayFrom(
|
||||||
.selectFrom("ordini")
|
eb
|
||||||
.selectAll("ordini")
|
.selectFrom("ordini")
|
||||||
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
.selectAll("ordini")
|
||||||
.select("prezziario.testo_condizioni")
|
.innerJoin(
|
||||||
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
"prezziario",
|
||||||
.orderBy("ordini.created_at", "desc"),
|
"prezziario.idprezziario",
|
||||||
).as("ordini"),
|
"ordini.packid",
|
||||||
])
|
)
|
||||||
.orderBy("servizio.created_at", "desc")
|
.select("prezziario.testo_condizioni")
|
||||||
.execute();
|
.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 data;
|
||||||
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;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
@ -1460,61 +1449,95 @@ export const getOrdineRicevutaData = async ({
|
||||||
};
|
};
|
||||||
export type RichiesteData = Servizio &
|
export type RichiesteData = Servizio &
|
||||||
Pick<Users, "username"> & {
|
Pick<Users, "username"> & {
|
||||||
annunci_servizio: (Override<
|
annunci_servizio: ServizioAnnuncioData[];
|
||||||
ServizioAnnunci,
|
ordini_servizio: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
||||||
{
|
|
||||||
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">)[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRichieste = async (): Promise<RichiesteData[]> => {
|
export const getRichieste = async (): Promise<RichiesteData[]> => {
|
||||||
try {
|
try {
|
||||||
return await db
|
const data = await withParsedDates(
|
||||||
.selectFrom("servizio")
|
db
|
||||||
.innerJoin("users", "users.id", "servizio.user_id")
|
.selectFrom("servizio")
|
||||||
.selectAll("servizio")
|
.innerJoin("users", "users.id", "servizio.user_id")
|
||||||
.select(["users.username"])
|
.selectAll("servizio")
|
||||||
.select((eb) => [
|
.select(["users.username"])
|
||||||
jsonArrayFrom(
|
.select((eb) => [
|
||||||
eb
|
jsonArrayFrom(
|
||||||
.selectFrom("servizio_annunci")
|
eb
|
||||||
.selectAll("servizio_annunci")
|
.selectFrom("servizio_annunci")
|
||||||
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
.selectAll("servizio_annunci")
|
||||||
.select(["annunci.codice", "annunci.titolo_it"])
|
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
||||||
.whereRef(
|
.select((_eb) => [
|
||||||
"servizio.servizio_id",
|
"annunci.id",
|
||||||
"=",
|
"annunci.codice",
|
||||||
"servizio_annunci.servizio_id",
|
"annunci.comune",
|
||||||
)
|
"annunci.provincia",
|
||||||
.orderBy("servizio_annunci.created_at", "desc"),
|
"annunci.prezzo",
|
||||||
).as("annunci_servizio"),
|
"annunci.consegna",
|
||||||
jsonArrayFrom(
|
"annunci.numero_camere",
|
||||||
eb
|
"annunci.mq",
|
||||||
.selectFrom("ordini")
|
"annunci.tipo",
|
||||||
.selectAll("ordini")
|
"annunci.titolo_it",
|
||||||
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
"annunci.titolo_en",
|
||||||
.select("prezziario.testo_condizioni")
|
"annunci.desc_en",
|
||||||
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
"annunci.desc_it",
|
||||||
.orderBy("ordini.created_at", "desc"),
|
"annunci.modificato_il",
|
||||||
).as("ordini_servizio"),
|
"annunci.stato",
|
||||||
])
|
"annunci.external_videos",
|
||||||
.orderBy("servizio.created_at", "desc")
|
"annunci.media_updated_at",
|
||||||
.execute();
|
"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) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { TRPCError } from "@trpc/server";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
|
import { withImages, withVideos } from "~/utils/kysely-helper";
|
||||||
|
|
||||||
export const AddIntrest = async ({
|
export const AddIntrest = async ({
|
||||||
annuncioId,
|
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