"use client"; import { useState } from "react"; import { useForm, type FieldValues, type DefaultValues } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { Plus, Pencil, Trash2, Download } from "lucide-react"; import { z } from "zod"; import { Button, Dialog, Input, EmptyState, FormField, ConfirmDialog, Select, } from "@/components/ds"; import { HospitalityTablePage } from "@/modules/hospitality/design-system"; import { HospitalityPageLoader, HospitalityPageError, exportToCsv } from "@/modules/hospitality/pages/shared"; import { useTenantId } from "@/hooks/useTenantId"; import { useHospitalityCapabilities } from "@/modules/hospitality/hooks/useHospitalityCapabilities"; import { HospitalityStatusChip } from "@/modules/hospitality/design-system"; import { HospitalityBundleGate } from "@/modules/hospitality/components/HospitalityBundleGate"; export type HospitalityFieldConfig = { name: string; label: string; type?: "text" | "number" | "select"; options?: { value: string; label: string }[]; required?: boolean; }; export type HospitalityColumnConfig = { key: string; header: string; type?: "status" | "datetime" | "text"; render?: (row: Record) => React.ReactNode; }; type ApiResource = { list: (tenantId: string, params?: { page?: number; page_size?: number }) => Promise[]>; create?: (tenantId: string, body: Record) => Promise; update?: (tenantId: string, id: string, body: Record) => Promise; remove?: (tenantId: string, id: string) => Promise; }; export type HospitalityListConfig = { title: string; description?: string; breadcrumb?: string; resourceKey: string; bundleFeature?: string | null; permission?: string; api: ApiResource; columns: HospitalityColumnConfig[]; createFields?: HospitalityFieldConfig[]; editFields?: HospitalityFieldConfig[]; filterDeleted?: boolean; }; function buildSchema(fields: HospitalityFieldConfig[]) { const shape: Record = {}; for (const f of fields) { shape[f.name] = f.required ? z.string().min(1, `${f.label} الزامی است`) : z.string().optional(); } return z.object(shape); } function renderCell(col: HospitalityColumnConfig, row: Record) { if (col.render) return col.render(row); const val = row[col.key]; if (col.type === "status" && val) return ; if (col.type === "datetime" && val) { try { return new Date(String(val)).toLocaleString("fa-IR"); } catch { return String(val); } } return val != null ? String(val) : "—"; } export function createHospitalityListPage(config: HospitalityListConfig) { return function HospitalityListPage() { const { tenantId } = useTenantId(); const qc = useQueryClient(); const caps = useHospitalityCapabilities(); const [createOpen, setCreateOpen] = useState(false); const [editRow, setEditRow] = useState | null>(null); const [deleteId, setDeleteId] = useState(null); const [page, setPage] = useState(1); const pageSize = 20; const createFields = config.createFields ?? []; const editFields = config.editFields ?? createFields; const createSchema = buildSchema(createFields); const editSchema = buildSchema(editFields); const listQ = useQuery({ queryKey: ["hospitality", tenantId, config.resourceKey, page], queryFn: () => config.api.list(tenantId!, { page, page_size: pageSize }), enabled: !!tenantId, }); const createForm = useForm({ resolver: zodResolver(createSchema), defaultValues: Object.fromEntries(createFields.map((f) => [f.name, ""])) as DefaultValues, }); const editForm = useForm({ resolver: zodResolver(editSchema) }); const invalidate = () => qc.invalidateQueries({ queryKey: ["hospitality", tenantId, config.resourceKey] }); const createM = useMutation({ mutationFn: (d: FieldValues) => config.api.create!(tenantId!, d), onSuccess: async () => { toast.success("رکورد ایجاد شد"); setCreateOpen(false); createForm.reset(); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); const updateM = useMutation({ mutationFn: (d: FieldValues) => config.api.update!(tenantId!, String(editRow!.id), d), onSuccess: async () => { toast.success("به‌روزرسانی شد"); setEditRow(null); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); const deleteM = useMutation({ mutationFn: (id: string) => config.api.remove!(tenantId!, id), onSuccess: async () => { toast.success("حذف شد"); setDeleteId(null); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); if (!tenantId || listQ.isLoading || caps.isLoading) return ; if (listQ.error) return listQ.refetch()} />; let rows = listQ.data ?? []; if (config.filterDeleted) rows = rows.filter((r) => !r.is_deleted); const tableColumns = [ ...config.columns.map((c) => ({ key: c.key, header: c.header, render: (row: Record) => renderCell(c, row), })), { key: "actions", header: "عملیات", searchable: false as const, render: (row: Record) => (
{config.api.update ? ( ) : null} {config.api.remove ? ( ) : null}
), }, ]; const body = ( setCreateOpen(true)}> جدید ) : undefined } toolbar={
صفحه {page}
} empty={ setCreateOpen(true)}>ایجاد اولین رکورد ) : undefined } /> } /> ); const pageContent = config.bundleFeature ? ( {body} ) : ( body ); return ( <> {pageContent} {config.api.create && createFields.length > 0 ? ( setCreateOpen(false)} title={`${config.title} — جدید`}>
createM.mutate(d))}> {createFields.map((f) => ( {f.type === "select" && f.options ? ( ) : ( )} ))}
) : null} {editRow && config.api.update ? ( setEditRow(null)} title={`${config.title} — ویرایش`}>
updateM.mutate(d))}> {editFields.map((f) => ( ))}
) : null} setDeleteId(null)} onConfirm={() => deleteId && deleteM.mutate(deleteId)} title="حذف رکورد" description="آیا از حذف این رکورد مطمئن هستید؟" confirmLabel="حذف" danger /> ); }; }