import type { LoaderFunctionArgs } from "react-router"; import { Form, useLoaderData, useSearchParams } from "react-router"; import prisma from "../db.server"; import { localParts } from "../engine/time"; import { adminContext } from "../lib/admin.server"; import { bookingWhere } from "../lib/bookings-query.server"; import { METHOD_LABEL, STATUS_TONE, niceDate, slotLabel } from "../lib/format"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { cfg } = await adminContext(request); const sp = new URL(request.url).searchParams; const today = localParts(Date.now(), cfg.shop.timezone).date; const page = Math.max(1, Number(sp.get("page") ?? 1)); const where = bookingWhere(cfg.shop.id, sp, today); const [rows, total] = await Promise.all([ prisma.booking.findMany({ where, orderBy: [{ status: "desc" }, { date: "asc" }, { slotStart: "asc" }], take: 50, skip: (page - 1) * 50, }), prisma.booking.count({ where }), ]); return { rows: rows.map((b) => ({ ...b, startUtc: b.startUtc?.toISOString() ?? null, endUtc: b.endUtc?.toISOString() ?? null, createdAt: b.createdAt.toISOString(), updatedAt: b.updatedAt.toISOString() })), total, page, locations: cfg.locations.map((l) => ({ id: l.id, name: l.name })), shop: cfg.shop.domain, }; }; export default function Bookings() { const { rows, total, page, locations } = useLoaderData(); const [sp] = useSearchParams(); const locName = (id: string | null) => locations.find((l) => l.id === id)?.name ?? "—"; const exportHref = `/app/bookings/export?${sp.toString()}`; return ( Export CSV
Upcoming Needs review Confirmed Fulfilled Cancelled All All methods Delivery Pickup Shipping All locations {locations.map((l) => ( {l.name} ))} Apply
1}> Order Date Time Method Location Customer Status {rows.map((b) => ( {b.orderName ?? "No order"} {niceDate(b.date)} {b.method === "shipping" ? "Dispatch" : slotLabel(b.slotStart, b.slotEnd)} {METHOD_LABEL[b.method]} {b.peakDayId && Peak} {locName(b.locationId)} {b.customerName ?? b.customerEmail ?? "—"} {b.status.replace("_", " ")} {b.reviewReason && {b.reviewReason}} ))} {rows.length === 0 && ( No bookings match these filters. )} {page > 1 && Previous} {total} bookings · page {page} {page * 50 < total && Next}
); } function withPage(sp: URLSearchParams, p: number) { const n = new URLSearchParams(sp); n.set("page", String(p)); return n.toString(); }