diff --git a/apps/infoalloggi/src/components/area-riservata/ordine_manuale.tsx b/apps/infoalloggi/src/components/area-riservata/ordine_manuale.tsx new file mode 100644 index 0000000..9672c29 --- /dev/null +++ b/apps/infoalloggi/src/components/area-riservata/ordine_manuale.tsx @@ -0,0 +1,88 @@ +import { useState } from "react"; +import { FormNewOrder } from "~/forms/FormNewOrdine"; +import type { ServizioServizioId } from "~/schemas/public/Servizio"; +import type { UsersId } from "~/schemas/public/Users"; +import { api } from "~/utils/api"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle, + CredenzaTrigger, +} from "../custom_ui/credenza"; +import { LoadingPage } from "../loading"; +import { Button } from "../ui/button"; + +export const OrdineManualeDialog = ({ + userId, + servizioId, +}: { + userId: UsersId; + servizioId: ServizioServizioId; +}) => { + const [open, setOpen] = useState(false); + return ( + + + + + + + Nuovo Ordine Manuale + + nuovo ordine modal + + + + + + ); +}; + +const Content = ({ + userId, + servizioId, + setOpen, +}: { + userId: UsersId; + servizioId: ServizioServizioId; + setOpen: React.Dispatch>; +}) => { + const { data, isLoading } = api.prezziario.getPrezziario.useQuery(); + + if (isLoading) return ; + + if (!data) + return ( + <> + +

+ Errore nel caricamento del prezziario. +

+
+ + + + + + + + ); + + return ( + + ); +}; diff --git a/apps/infoalloggi/src/components/servizio/servizio_actions.tsx b/apps/infoalloggi/src/components/servizio/servizio_actions.tsx index 6fb8ff0..4e8a521 100644 --- a/apps/infoalloggi/src/components/servizio/servizio_actions.tsx +++ b/apps/infoalloggi/src/components/servizio/servizio_actions.tsx @@ -9,6 +9,7 @@ import { ExternalLink, Info, Plus, + Search, SlidersHorizontal, Trash2, UserCircle, @@ -78,6 +79,14 @@ export const ServizioActions = () => { Azioni Admin: + + + )} diff --git a/apps/infoalloggi/src/components/tables/orders-table.tsx b/apps/infoalloggi/src/components/tables/orders-table.tsx index 478d28c..22313f3 100644 --- a/apps/infoalloggi/src/components/tables/orders-table.tsx +++ b/apps/infoalloggi/src/components/tables/orders-table.tsx @@ -191,7 +191,7 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => { } : { columnName: "packid", - placeholder: "Cerca pagamento...", + placeholder: "Cerca pacchetto...", }; const pinnedFiltri: PinnedFiltro[] = [ diff --git a/apps/infoalloggi/src/components/tables/payments-table.tsx b/apps/infoalloggi/src/components/tables/payments-table.tsx index d8793ea..86f89a2 100644 --- a/apps/infoalloggi/src/components/tables/payments-table.tsx +++ b/apps/infoalloggi/src/components/tables/payments-table.tsx @@ -70,6 +70,7 @@ export const PaymentsTable = ({ paid_at: row.paid_at, raw_status: row.paymentstatus, status: StatusMapping(row.paymentstatus), + intent_id: row.intent_id, }; }); @@ -164,6 +165,7 @@ export const PaymentsTable = ({ Azioni + @@ -206,7 +208,7 @@ export const PaymentsTable = ({ remove({ pagamentoId: data.id }) } > - + Elimina Pagamento diff --git a/apps/infoalloggi/src/forms/FormNewOrdine.tsx b/apps/infoalloggi/src/forms/FormNewOrdine.tsx new file mode 100644 index 0000000..32a0084 --- /dev/null +++ b/apps/infoalloggi/src/forms/FormNewOrdine.tsx @@ -0,0 +1,258 @@ +import { format } from "date-fns"; +import { it } from "date-fns/locale"; +import { CalendarIcon } from "lucide-react"; +import toast from "react-hot-toast"; +import { z } from "zod/v4"; +import { + CredenzaBody, + CredenzaClose, + CredenzaFooter, +} from "~/components/custom_ui/credenza"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "~/components/custom_ui/form"; +import { MultiSelect } from "~/components/custom_ui/multiselect"; +import { Button } from "~/components/ui/button"; +import { Calendar } from "~/components/ui/calendar"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "~/components/ui/popover"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "~/components/ui/select"; +import { Switch } from "~/components/ui/switch"; +import { cn, formatCurrency } from "~/lib/utils"; +import { useZodForm } from "~/lib/zodForm"; +import OrderTypeEnum from "~/schemas/public/OrderTypeEnum"; +import type { Prezziario } from "~/schemas/public/Prezziario"; +import type { ServizioServizioId } from "~/schemas/public/Servizio"; +import type { UsersId } from "~/schemas/public/Users"; +import { zPrezziarioId } from "~/server/utils/zod_types"; +import { api } from "~/utils/api"; + +type FormProps = { + prezziario: Prezziario[]; + servizioId: ServizioServizioId; + userId: UsersId; + setOpen: React.Dispatch>; +}; + +export const FormNewOrder = ({ + prezziario, + servizioId, + userId, + setOpen, +}: FormProps) => { + const schema = z.object({ + packid: zPrezziarioId, + isActive: z.boolean(), + type: z.custom(), + created_at: z.date(), + }); + type FormValues = z.infer; + + z.config(z.locales.it()); + + const form = useZodForm(schema, { + defaultValues: {}, + }); + const utils = api.useUtils(); + const { mutate: createOrder } = api.servizio.createOrdine.useMutation({ + onError: (error) => { + toast.error(`Errore nella creazione dell'ordine: ${error.message}`); + }, + onSuccess: async () => { + toast.success("Ordine creato con successo"); + await utils.servizio.getOrdini.invalidate({ + userId, + }); + setOpen(false); + form.reset(); + }, + }); + function onSubmit(fields: FormValues) { + createOrder({ + data: { + ...fields, + servizio_id: servizioId, + userid: userId, + }, + }); + } + + return ( + <> +
+ + +
+ ( + +
+ Servizio + +
+ + { + const selected = zPrezziarioId.safeParse(value.value); + if (selected.success) { + field.onChange(selected.data); + } else { + console.error( + "Invalid packid selected", + value.value, + ); + } + }} + options={prezziario.map((p) => ({ + label: `${p.idprezziario} - ${p.nome_it} (${formatCurrency(p.prezzo_cent / 100)})`, + value: p.idprezziario, + }))} + placeholder="Seleziona..." + /> + +
+ )} + /> + + ( + +
+ Tipologia + +
+ + + +
+ )} + /> + ( + +
+ Data Ordine + +
+ + + + + + + + + + +
+ )} + /> + ( + +
+ Attivo + + + + +
+ + + Imposta se il servizio รจ attivo + +
+ )} + /> +
+
+ + + + + + + +
+ + + ); +}; diff --git a/apps/infoalloggi/src/pages/area-riservata/admin/user-view/ordini/[userId].tsx b/apps/infoalloggi/src/pages/area-riservata/admin/user-view/ordini/[userId].tsx index cdaabb0..283dc55 100644 --- a/apps/infoalloggi/src/pages/area-riservata/admin/user-view/ordini/[userId].tsx +++ b/apps/infoalloggi/src/pages/area-riservata/admin/user-view/ordini/[userId].tsx @@ -1,20 +1,26 @@ import type { GetServerSideProps } from "next"; +import Link from "next/link"; +import { OrdineManualeDialog } from "~/components/area-riservata/ordine_manuale"; import { AreaRiservataLayoutUserView } from "~/components/Layout"; import { LoadingPage } from "~/components/loading"; import { Status500 } from "~/components/status-page"; import { OrdiniTable } from "~/components/tables/orders-table"; +import { Button } from "~/components/ui/button"; import type { NextPageWithLayout } from "~/pages/_app"; +import type { ServizioServizioId } from "~/schemas/public/Servizio"; import type { UsersId } from "~/schemas/public/Users"; import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper"; -import { zUserId } from "~/server/utils/zod_types"; +import { zServizioId, zUserId } from "~/server/utils/zod_types"; import { api } from "~/utils/api"; type OrdiniUserProps = { userId: UsersId; + servizioId: ServizioServizioId | null; }; const OrdiniUser: NextPageWithLayout = ({ userId, + servizioId, }: OrdiniUserProps) => { const { data, isLoading } = api.servizio.getOrdini.useQuery({ userId, @@ -24,10 +30,31 @@ const OrdiniUser: NextPageWithLayout = ({ if (!data) return ; return (
-
-

Ordini

+
+

+ Ordini{" "} + + {servizioId ? `- Servizio ${servizioId}` : ""} + +

+ {servizioId && ( +
+ + + + +
+ )}
- + + servizioId ? ordine.servizio_id === servizioId : true, + )} + isAdmin + userId={userId} + />
); }; @@ -55,10 +82,19 @@ export const getServerSideProps = (async (context) => { await helpers.trpc.servizio.getOrdini.prefetch({ userId: parsed.data, }); + + let servizioId: ServizioServizioId | null = null; + + const parsedServizioId = zServizioId.safeParse(context.query?.servizioId); + if (parsedServizioId.success) { + servizioId = parsedServizioId.data; + } + return { props: { userId: parsed.data, trpcState: helpers.trpc.dehydrate(), + servizioId, }, }; } diff --git a/apps/infoalloggi/src/server/api/routers/servizio.ts b/apps/infoalloggi/src/server/api/routers/servizio.ts index 746aaef..e1cd21f 100644 --- a/apps/infoalloggi/src/server/api/routers/servizio.ts +++ b/apps/infoalloggi/src/server/api/routers/servizio.ts @@ -1,5 +1,5 @@ import { z } from "zod/v4"; -import type { OrdiniUpdate } from "~/schemas/public/Ordini"; +import type { NewOrdini, OrdiniUpdate } from "~/schemas/public/Ordini"; import type { PaymentsUpdate } from "~/schemas/public/Payments"; import type { NewServizio, ServizioUpdate } from "~/schemas/public/Servizio"; import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci"; @@ -12,6 +12,7 @@ import { publicProcedure, } from "~/server/api/trpc"; import { + createOrdine, getOrdineById, getOrdini, removeOrder, @@ -34,6 +35,7 @@ import { getServiziByUserId, getServizioAnnuncio, getServizioById, + getServizioOrdini, getServizioPagamentoData, getUserPagamenti, InteractionLogicTipologia, @@ -166,6 +168,16 @@ export const servizioRouter = createTRPCRouter({ .query(async ({ input, ctx }) => { return await getOrdineRicevutaData({ ctx, ordineId: input.ordineId }); }), + getServizioOrdini: protectedProcedure + .input( + z.object({ + servizioId: zServizioId, + }), + ) + .query(async ({ input }) => { + return await getServizioOrdini(input.servizioId); + }), + getOrdini: protectedProcedure .input( z.object({ @@ -363,7 +375,15 @@ export const servizioRouter = createTRPCRouter({ servizioId: input.servizioId, }); }), - + createOrdine: adminProcedure + .input( + z.object({ + data: z.custom(), + }), + ) + .mutation(async ({ input }) => { + return await createOrdine(input.data); + }), updateOrder: adminProcedure .input( z.object({ diff --git a/apps/infoalloggi/src/server/controllers/ordini.controller.ts b/apps/infoalloggi/src/server/controllers/ordini.controller.ts index 17770af..cf5508f 100644 --- a/apps/infoalloggi/src/server/controllers/ordini.controller.ts +++ b/apps/infoalloggi/src/server/controllers/ordini.controller.ts @@ -1,6 +1,10 @@ import { TRPCError } from "@trpc/server"; import { jsonArrayFrom } from "kysely/helpers/postgres"; -import type { OrdiniOrdineId, OrdiniUpdate } from "~/schemas/public/Ordini"; +import type { + NewOrdini, + OrdiniOrdineId, + OrdiniUpdate, +} from "~/schemas/public/Ordini"; import type { UsersId } from "~/schemas/public/Users"; import { db, type Querier } from "~/server/db"; @@ -61,6 +65,21 @@ export const getOrdineById = async (ordineId: OrdiniOrdineId) => { } }; +export const createOrdine = async (data: NewOrdini) => { + try { + return await db + .insertInto("ordini") + .values(data) + .returningAll() + .executeTakeFirstOrThrow(); + } catch (e) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: `Error creating ordine: ${(e as Error).message}`, + }); + } +}; + export const removeOrder = async (ordineId: OrdiniOrdineId) => { try { await db diff --git a/apps/infoalloggi/src/server/controllers/servizio.controller.ts b/apps/infoalloggi/src/server/controllers/servizio.controller.ts index 7d19f16..e131149 100644 --- a/apps/infoalloggi/src/server/controllers/servizio.controller.ts +++ b/apps/infoalloggi/src/server/controllers/servizio.controller.ts @@ -1847,3 +1847,37 @@ export const getOrdineRicevutaData = async ({ }); } }; + +export const getServizioOrdini = async (servizioId: ServizioServizioId) => { + try { + const data = await db + .selectFrom("ordini") + .selectAll("ordini") + .select((eb) => [ + jsonArrayFrom( + eb + .selectFrom("payments") + .whereRef("payments.ordine_id", "=", "ordini.ordine_id") + .selectAll("payments") + .orderBy("created_at", "desc"), + ).as("pagamenti"), + ]) + .where("servizio_id", "=", servizioId) + .orderBy("created_at", "desc") + .execute(); + + return data.map((ordine) => ({ + ...ordine, + pagamenti: ordine.pagamenti.map((payment) => ({ + ...payment, + created_at: new Date(payment.created_at), + paid_at: payment.paid_at ? new Date(payment.paid_at) : null, + })), + })); + } catch (e) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: `Error fetching servizio ordini: ${(e as Error).message}`, + }); + } +};