import { commissionKanbanOrder, createCommission, createCustomer, db, listArtworks, listCommissions, listCustomers, updateCommission, updateCommissionStatus, } from "@cms/db" import { Button } from "@cms/ui/button" import { revalidatePath } from "next/cache" import { redirect } from "next/navigation" import { AdminShell } from "@/components/admin-shell" import { requirePermissionForRoute } from "@/lib/route-guards" export const dynamic = "force-dynamic" type SearchParamsInput = Record function readFirstValue(value: string | string[] | undefined): string | null { if (Array.isArray(value)) { return value[0] ?? null } return value ?? null } function readInputString(formData: FormData, field: string): string { const value = formData.get(field) return typeof value === "string" ? value.trim() : "" } function readNullableString(formData: FormData, field: string): string | null { const value = readInputString(formData, field) return value.length > 0 ? value : null } function readNullableNumber(formData: FormData, field: string): number | null { const value = readInputString(formData, field) if (!value) { return null } const parsed = Number.parseFloat(value) if (!Number.isFinite(parsed)) { return null } return parsed } function readNullableDate(formData: FormData, field: string): Date | null { const value = readInputString(formData, field) if (!value) { return null } const parsed = new Date(value) if (Number.isNaN(parsed.getTime())) { return null } return parsed } function readUuidList(formData: FormData, field: string): string[] { const raw = readInputString(formData, field) if (!raw) { return [] } return raw .split(",") .map((entry) => entry.trim()) .filter((entry) => entry.length > 0) } function redirectWithState(params: { notice?: string; error?: string }) { const query = new URLSearchParams() if (params.notice) { query.set("notice", params.notice) } if (params.error) { query.set("error", params.error) } const value = query.toString() redirect(value ? `/commissions?${value}` : "/commissions") } async function createCustomerAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/commissions", permission: "commissions:write", scope: "own", }) try { await createCustomer({ name: readInputString(formData, "name"), email: readNullableString(formData, "email"), phone: readNullableString(formData, "phone"), instagram: readNullableString(formData, "instagram"), notes: readNullableString(formData, "notes"), isRecurring: readInputString(formData, "isRecurring") === "true", }) } catch { redirectWithState({ error: "Failed to create customer." }) } revalidatePath("/commissions") redirectWithState({ notice: "Customer created." }) } async function createCommissionAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/commissions", permission: "commissions:write", scope: "own", }) try { await createCommission({ title: readInputString(formData, "title"), description: readNullableString(formData, "description"), status: readInputString(formData, "status"), customerId: readNullableString(formData, "customerId"), assignedUserId: readNullableString(formData, "assignedUserId"), linkedArtworkIds: readUuidList(formData, "linkedArtworkIds"), budgetMin: readNullableNumber(formData, "budgetMin"), budgetMax: readNullableNumber(formData, "budgetMax"), dueAt: readNullableDate(formData, "dueAt"), }) } catch { redirectWithState({ error: "Failed to create commission." }) } revalidatePath("/commissions") redirectWithState({ notice: "Commission created." }) } async function updateCommissionAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/commissions", permission: "commissions:write", scope: "own", }) try { await updateCommission({ id: readInputString(formData, "id"), title: readInputString(formData, "title"), description: readNullableString(formData, "description"), customerId: readNullableString(formData, "customerId"), assignedUserId: readNullableString(formData, "assignedUserId"), linkedArtworkIds: readUuidList(formData, "linkedArtworkIds"), budgetMin: readNullableNumber(formData, "budgetMin"), budgetMax: readNullableNumber(formData, "budgetMax"), dueAt: readNullableDate(formData, "dueAt"), }) } catch { redirectWithState({ error: "Failed to update commission details." }) } revalidatePath("/commissions") redirectWithState({ notice: "Commission updated." }) } async function updateCommissionStatusAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/commissions", permission: "commissions:transition", scope: "own", }) try { await updateCommissionStatus({ id: readInputString(formData, "id"), status: readInputString(formData, "status"), }) } catch { redirectWithState({ error: "Failed to transition commission." }) } revalidatePath("/commissions") redirectWithState({ notice: "Commission status updated." }) } function formatDate(value: Date | null) { if (!value) { return "-" } return value.toLocaleDateString("en-US") } function formatDateInput(value: Date | null) { if (!value) { return "" } return value.toISOString().slice(0, 10) } export default async function CommissionsManagementPage({ searchParams, }: { searchParams: Promise }) { const role = await requirePermissionForRoute({ nextPath: "/commissions", permission: "commissions:read", scope: "own", }) const [resolvedSearchParams, customers, commissions, assignees, artworks] = await Promise.all([ searchParams, listCustomers(200), listCommissions(300), db.user.findMany({ where: { isBanned: false, }, orderBy: [{ createdAt: "asc" }], select: { id: true, name: true, username: true, }, }), listArtworks(300), ]) const notice = readFirstValue(resolvedSearchParams.notice) const error = readFirstValue(resolvedSearchParams.error) return ( {notice ? (
{notice}
) : null} {error ? (
{error}
) : null}

Create Customer