'use client'; import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { type ColumnDef } from '@tanstack/react-table'; import { Pencil, Trash2, Plus } from 'lucide-react'; import { DataTable } from '@/components/shared/data-table'; import { PageHeader } from '@/components/shared/page-header'; import { ConfirmationDialog } from '@/components/shared/confirmation-dialog'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { apiFetch } from '@/lib/api/client'; import { CustomFieldForm, type CustomFieldDefinition } from './custom-field-form'; // ─── Types ──────────────────────────────────────────────────────────────────── type EntityTab = 'client' | 'interest' | 'berth'; const TAB_LABELS: Record = { client: 'Clients', interest: 'Interests', berth: 'Berths', }; const FIELD_TYPE_LABELS: Record = { text: 'Text', number: 'Number', date: 'Date', boolean: 'Yes / No', select: 'Dropdown', }; // ─── Component ──────────────────────────────────────────────────────────────── const FIELDS_QUERY_KEY = ['admin', 'custom-fields'] as const; export function CustomFieldsManager() { const queryClient = useQueryClient(); const [activeTab, setActiveTab] = useState('client'); const [formOpen, setFormOpen] = useState(false); const [editingField, setEditingField] = useState(null); const { data: fields = [], isLoading: loading } = useQuery({ queryKey: FIELDS_QUERY_KEY, queryFn: () => apiFetch<{ data: CustomFieldDefinition[] }>('/api/v1/admin/custom-fields').then( (r) => r.data, ), }); const fetchFields = () => queryClient.invalidateQueries({ queryKey: FIELDS_QUERY_KEY }); const deleteMutation = useMutation({ mutationFn: (id: string) => apiFetch(`/api/v1/admin/custom-fields/${id}`, { method: 'DELETE' }), onSuccess: () => fetchFields(), }); const deletingId = deleteMutation.isPending ? deleteMutation.variables : null; const filteredFields = fields.filter((f) => f.entityType === activeTab); function handleCreate() { setEditingField(null); setFormOpen(true); } function handleEdit(field: CustomFieldDefinition) { setEditingField(field); setFormOpen(true); } function handleDelete(id: string) { deleteMutation.mutate(id); } function getDeleteDescription(field: CustomFieldDefinition): string { return `Are you sure you want to delete "${field.fieldLabel}" (${field.fieldName})? All stored values for this field will also be permanently deleted.`; } const columns: ColumnDef[] = [ { accessorKey: 'fieldName', header: 'Name', cell: ({ row }) => {row.original.fieldName}, }, { accessorKey: 'fieldLabel', header: 'Label', cell: ({ row }) => {row.original.fieldLabel}, }, { accessorKey: 'fieldType', header: 'Type', cell: ({ row }) => ( {FIELD_TYPE_LABELS[row.original.fieldType] ?? row.original.fieldType} ), }, { accessorKey: 'isRequired', header: 'Required', cell: ({ row }) => row.original.isRequired ? ( Required ) : ( Optional ), }, { accessorKey: 'sortOrder', header: 'Order', cell: ({ row }) => ( {row.original.sortOrder} ), }, { id: 'actions', header: '', cell: ({ row }) => (
Delete } title="Delete Custom Field" description={getDeleteDescription(row.original)} confirmLabel="Delete Field" onConfirm={() => handleDelete(row.original.id)} loading={deletingId === row.original.id} />
), enableSorting: false, size: 80, }, ]; return (
New Field } />
Heads up: custom fields render in detail-page sidebars and the entity export, and merge-tokens of the form{' '} {`{{custom.fieldName}}`} now expand in EOI/contract/email templates for client/interest/berth contexts. They still don’t plug into the global search index, the berth recommender, or the entity-diff audit log — use them for rep-only annotations and template-merge values, but anything load-bearing for the deal flow still needs a first-class column.
setActiveTab(v as EntityTab)}> {(Object.keys(TAB_LABELS) as EntityTab[]).map((tab) => ( {TAB_LABELS[tab]} ))} {(Object.keys(TAB_LABELS) as EntityTab[]).map((tab) => ( row.id} emptyState={

No custom fields for {TAB_LABELS[tab]} yet.

} />
))}
); }