import { useState, useEffect } from 'react' import Layout from '@/components/layout/Layout' import { Card, EmptyState, Btn, Modal, Field, Input, Select, Textarea, showToast, Tag, StatusDot, Table } from '@/components/ui' import { FormFieldList, FormFieldDef } from '@/components/forms/FieldRenderer' import { FieldType } from '@/types' const FIELD_TYPES: { value: FieldType; label: string }[] = [ { value: 'SHORT_TEXT', label: 'Short text' }, { value: 'LONG_TEXT', label: 'Long text' }, { value: 'NUMBER', label: 'Number' }, { value: 'DATE', label: 'Date' }, { value: 'SINGLE_CHOICE', label: 'Single choice' }, { value: 'MULTI_CHOICE', label: 'Multi-choice' }, { value: 'RATING', label: 'Rating (1–5)' }, { value: 'PHOTO', label: 'Photo / file' }, ] const STATUS_COLOR: Record = { DRAFT: 'purple', ACTIVE: 'green', SUSPENDED: 'amber', REVIEW_READY: 'blue', STANDARD_SET: 'teal', ARCHIVED: 'gray', } interface BuilderField extends FormFieldDef { order: number } export default function FormBuilderPage() { const [forms, setForms] = useState([]) const [loading, setLoading] = useState(true) // Builder modal const [builderOpen, setBuilderOpen] = useState(false) const [builderMode, setBuilderMode] = useState<'create' | 'edit'>('create') const [editingId, setEditingId] = useState(null) const [saving, setSaving] = useState(false) const [formMeta, setFormMeta] = useState({ name: '', product: '', description: '', minSubmissions: 10 }) const [fields, setFields] = useState([]) const [newField, setNewField] = useState({ label: '', type: 'SHORT_TEXT' as FieldType, hint: '', required: false, options: '' }) // Preview modal const [previewOpen, setPreviewOpen] = useState(false) const [previewFields, setPreviewFields] = useState([]) const [previewTitle, setPreviewTitle] = useState('') const [previewSubtitle, setPreviewSubtitle] = useState('') const [previewAnswers, setPreviewAnswers] = useState>({}) useEffect(() => { loadForms() }, []) async function loadForms() { setLoading(true) const res = await fetch('/api/forms') if (res.ok) { const { data } = await res.json() setForms(data || []) } setLoading(false) } // ── Builder open/close ────────────────────────────────────────────────── function openCreate() { setFormMeta({ name: '', product: '', description: '', minSubmissions: 10 }) setFields([]) setNewField({ label: '', type: 'SHORT_TEXT', hint: '', required: false, options: '' }) setEditingId(null) setBuilderMode('create') setBuilderOpen(true) } function openEdit(form: any) { setFormMeta({ name: form.name, product: form.product || '', description: form.description || '', minSubmissions: form.minSubmissions, }) setFields((form.fields || []).map((f: any) => ({ id: f.id, label: f.label, type: f.type, hint: f.hint || '', required: !!f.required, options: f.options || [], order: f.order ?? 0, }))) setNewField({ label: '', type: 'SHORT_TEXT', hint: '', required: false, options: '' }) setEditingId(form.id) setBuilderMode('edit') setBuilderOpen(true) } // ── Field add/remove ───────────────────────────────────────────────────── function addField() { if (!newField.label) return const opts = newField.options.split('\n').map(o => o.trim()).filter(Boolean) setFields(f => [...f, { ...newField, options: opts, order: f.length, id: `new_${Date.now()}` }]) setNewField({ label: '', type: 'SHORT_TEXT', hint: '', required: false, options: '' }) } function removeField(index: number) { setFields(fs => fs.filter((_, i) => i !== index)) } // ── Save (create or edit) ─────────────────────────────────────────────── async function saveForm() { if (!formMeta.name) return setSaving(true) const url = builderMode === 'edit' ? `/api/forms/${editingId}` : '/api/forms' const method = builderMode === 'edit' ? 'PATCH' : 'POST' const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...formMeta, fields }), }) setSaving(false) if (res.ok) { setBuilderOpen(false) showToast(builderMode === 'edit' ? 'Form updated' : 'Form created — saved as draft') loadForms() } else { showToast('Failed to save form', 'error') } } // ── Status transitions ────────────────────────────────────────────────── async function setStatus(id: string, status: string, successMsg: string) { const res = await fetch(`/api/forms/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }), }) if (res.ok) { showToast(successMsg); loadForms() } else showToast('Update failed', 'error') } // ── Clone ──────────────────────────────────────────────────────────────── async function cloneForm(id: string) { const res = await fetch(`/api/forms/${id}/clone`, { method: 'POST' }) if (res.ok) { const { data } = await res.json() showToast(`Cloned as "${data.name}" — edit to customize`) loadForms() } else { showToast('Clone failed', 'error') } } // ── Preview ────────────────────────────────────────────────────────────── function openPreview(fieldsToShow: FormFieldDef[], title: string, subtitle?: string) { setPreviewFields(fieldsToShow) setPreviewTitle(title || 'Untitled form') setPreviewSubtitle(subtitle || '') setPreviewAnswers({}) setPreviewOpen(true) } const needsOptions = ['SINGLE_CHOICE', 'MULTI_CHOICE'].includes(newField.type) return (

First build form builder

Admin only — design, edit, suspend, clone, and archive data collection forms

+ New form
{loading ? (
Loading…
) : forms.length === 0 ? ( ) : (
{forms.map((f: any) => ( ))}
{f.name}
{f.clonedFromName &&
Cloned from {f.clonedFromName}
}
{f.product || '—'} {f._count?.fields || 0} = f.minSubmissions ? '#1D9E75' : '#333' }}>{f._count?.submissions || 0} {f.minSubmissions} {f.status.replace('_', ' ')}
openPreview(f.fields || [], f.name, f.product)}>Preview openEdit(f)}>Edit cloneForm(f.id)}>Clone {f.status === 'DRAFT' && ( setStatus(f.id, 'ACTIVE', 'Form published — production team can now fill it')}>Publish )} {f.status === 'ACTIVE' && ( setStatus(f.id, 'SUSPENDED', 'Form suspended')}>Suspend )} {f.status === 'SUSPENDED' && ( setStatus(f.id, 'ACTIVE', 'Form reactivated')}>Reactivate )} {f.status === 'ARCHIVED' ? ( setStatus(f.id, 'DRAFT', 'Form restored to draft')}>Restore ) : ( setStatus(f.id, 'ARCHIVED', 'Form archived')}>Archive )}
)}
{/* Builder Modal (create / edit) */} setBuilderOpen(false)} title={builderMode === 'edit' ? `Edit form — ${formMeta.name || ''}` : 'Build new form'} width={640}>
Form details
setFormMeta(m => ({ ...m, name: e.target.value }))} placeholder="First Build Data Sheet — Line 3"/> setFormMeta(m => ({ ...m, product: e.target.value }))} placeholder="Widget A — Rev 2"/>