"use client"; import { useCallback, 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, RefreshCw, Eye } from "lucide-react"; import Link from "next/link"; import { z } from "zod"; import { Button, Dialog, Drawer, Input, ConfirmDialog, Select, Textarea, FormField, } from "@/components/ds"; import { ExperienceTablePage, ExperienceStatGrid, ExperienceStatusChip, } from "@/modules/experience/design-system"; import { ExperiencePageLoader, ExperiencePageError, exportToCsv, } from "@/modules/experience/pages/shared"; import { useTenantId } from "@/hooks/useTenantId"; import { useExperienceLookups } from "@/modules/experience/hooks/useExperienceCapabilities"; import { useExperiencePermissions } from "@/modules/experience/hooks/useExperiencePermissions"; import { PermissionDeniedState } from "@/src/shared/ui"; import { normalizeList } from "@/modules/experience/services/experience-api"; export type ExperienceFieldConfig = { name: string; label: string; type?: "text" | "number" | "email" | "textarea" | "select" | "workspace" | "site"; options?: { value: string; label: string }[]; required?: boolean; createOnly?: boolean; editOnly?: boolean; placeholder?: string; }; export type ExperienceColumnConfig = { key: string; header: string; type?: "status" | "datetime" | "text" | "link"; linkPrefix?: string; render?: (row: Record) => React.ReactNode; }; type ApiResource = { list: ( tenantId: string, params?: Record ) => Promise[]>; get?: (tenantId: string, id: string) => Promise>; create?: (tenantId: string, body: Record) => Promise; update?: (tenantId: string, id: string, body: Record) => Promise; remove?: (tenantId: string, id: string) => Promise; }; export type ExperienceListConfig = { title: string; description?: string; breadcrumb?: string; resourceKey: string; permission?: string; api: ApiResource; columns: ExperienceColumnConfig[]; createFields?: ExperienceFieldConfig[]; editFields?: ExperienceFieldConfig[]; readOnly?: boolean; detailHref?: (row: Record) => string | null; rowActions?: { label: string; onClick: (tenantId: string, row: Record) => Promise; variant?: "default" | "outline" | "danger"; }[]; }; function buildSchema(fields: ExperienceFieldConfig[]) { 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: ExperienceColumnConfig, 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); } } if (col.type === "link" && col.linkPrefix && row.id) { return ( {val != null ? String(val) : "—"} ); } return val != null ? String(val) : "—"; } function renderField( field: ExperienceFieldConfig, register: ReturnType["register"], mode: "create" | "edit", lookups: { workspaces: Record[]; sites: Record[] } ) { if (mode === "create" && field.editOnly) return null; if (mode === "edit" && field.createOnly) return null; if (field.type === "textarea") { return (