// Admin-detail tab content for the cockpit: a numbered section panel (read view) plus a // per-section editor dialog that persists via updateProjectDetail() (shallow content merge). import { useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { Pencil } from 'lucide-react'; import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, updateProjectDetail, } from '@ump/shared'; import { displayValue, readContent, type DetailField, type DetailSection, } from './detailConfig'; import { Field, FieldGrid, SectionPanel } from './DetailPrimitives'; type Content = Record; /** One administrative section, read view + an inline ✎ edit affordance (owner/admin only). */ export function DetailSectionPanel({ section, content, projectId, canEdit, }: { section: DetailSection; content: Content; projectId: string; canEdit: boolean; }) { const [editing, setEditing] = useState(false); return ( <> setEditing(true)}> Chỉnh sửa ) : undefined } > {section.fields.map((f) => ( ))} {editing && ( setEditing(false)} /> )} ); } /** Coerce a draft string back to the type the BE/JSONB should hold. */ function coerce(field: DetailField, raw: string): unknown { if (field.type === 'money' || field.type === 'number' || field.type === 'months') { const n = Number(raw); return raw.trim() === '' || Number.isNaN(n) ? '' : n; } return raw; } export function DetailEditorDialog({ section, content, projectId, onClose, }: { section: DetailSection; content: Content; projectId: string; onClose: () => void; }) { const qc = useQueryClient(); const [draft, setDraft] = useState>(() => { const d: Record = {}; for (const f of section.fields) { const v = readContent(content, f.key); d[f.key] = v === undefined || v === null ? '' : String(v); } return d; }); const save = useMutation({ mutationFn: () => { const patch: Record = {}; for (const f of section.fields) patch[f.key] = coerce(f, draft[f.key] ?? ''); return updateProjectDetail(projectId, patch); }, onSuccess: () => { qc.invalidateQueries({ queryKey: ['cockpit', projectId] }); toast.success('Đã lưu.'); onClose(); }, onError: () => toast.error('Lưu thất bại.'), }); const set = (k: string, v: string) => setDraft((d) => ({ ...d, [k]: v })); return ( !o && onClose()}> {section.index}. {section.title}
{section.fields.map((f) => (
{f.type === 'textarea' ? (