import React, { useState, useEffect } from 'react'
import api from '../api.js'
import {
Plus, Trash2, Edit2, X, Check, GripVertical,
Type, Hash, Phone, Mail, AlertTriangle, Columns
} from 'lucide-react'
const TYPE_OPTIONS = [
{ value: 'text', label: 'Text', icon: Type },
{ value: 'number', label: 'Number', icon: Hash },
{ value: 'phone', label: 'Phone', icon: Phone },
{ value: 'email', label: 'Email', icon: Mail },
]
const TypeIcon = ({ type, size = 14 }) => {
const opt = TYPE_OPTIONS.find(t => t.value === type)
const Icon = opt?.icon || Type
return
}
function FieldModal({ field, onClose, onSave }) {
const [form, setForm] = useState({
label: field?.label || '',
field_type: field?.field_type || 'text',
required: field?.required || false,
})
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
if (!form.label.trim()) return setError('Label is required')
setLoading(true)
try {
if (field) {
const { data } = await api.put(`/fields/${field.id}`, form)
onSave(data, 'edit')
} else {
const { data } = await api.post('/fields', form)
onSave(data, 'add')
}
onClose()
} catch (err) {
setError(err.response?.data?.error || 'Failed to save field')
} finally {
setLoading(false)
}
}
return (
{field ? 'Edit Field' : 'New Field'}
)
}
export default function Fields() {
const [fields, setFields] = useState([])
const [loading, setLoading] = useState(true)
const [modal, setModal] = useState(null) // null | 'add' | field object
const [deleteConfirm, setDeleteConfirm] = useState(null)
const [toast, setToast] = useState('')
const [dragIdx, setDragIdx] = useState(null)
const [dragOverIdx, setDragOverIdx] = useState(null)
useEffect(() => {
api.get('/fields').then(({ data }) => { setFields(data); setLoading(false) }).catch(() => setLoading(false))
}, [])
const showToast = (msg) => { setToast(msg); setTimeout(() => setToast(''), 3000) }
const handleSave = (data, type) => {
if (type === 'add') setFields(prev => [...prev, data])
else setFields(prev => prev.map(f => f.id === data.id ? data : f))
showToast(type === 'add' ? 'Field added' : 'Field updated')
}
const handleDelete = async (field) => {
try {
await api.delete(`/fields/${field.id}`)
setFields(prev => prev.filter(f => f.id !== field.id))
setDeleteConfirm(null)
showToast('Field deleted')
} catch (err) {
showToast(err.response?.data?.error || 'Failed to delete')
}
}
// Drag-to-reorder
const handleDragStart = (idx) => setDragIdx(idx)
const handleDragOver = (e, idx) => { e.preventDefault(); setDragOverIdx(idx) }
const handleDrop = async (idx) => {
if (dragIdx === null || dragIdx === idx) { setDragIdx(null); setDragOverIdx(null); return }
const reordered = [...fields]
const [moved] = reordered.splice(dragIdx, 1)
reordered.splice(idx, 0, moved)
const withOrder = reordered.map((f, i) => ({ ...f, sort_order: i + 1 }))
setFields(withOrder)
setDragIdx(null)
setDragOverIdx(null)
try {
await api.put('/fields/reorder', { order: withOrder.map(f => ({ id: f.id, sort_order: f.sort_order })) })
} catch { showToast('Failed to save order') }
}
return (
{toast && (
{toast}
)}
Fields
Manage the columns shown in the Dashboard
{/* Info banner */}
Fields define the columns in the Dashboard. You can drag rows to reorder them. Deleting a field removes its data from all existing contracts.
{loading ? (
Loading...
) : fields.length === 0 ? (
No fields defined yet
) : (
|
Label |
Key |
Type |
Required |
Actions |
{fields.map((f, idx) => (
handleDragStart(idx)}
onDragOver={(e) => handleDragOver(e, idx)}
onDrop={() => handleDrop(idx)}
onDragEnd={() => { setDragIdx(null); setDragOverIdx(null) }}
className={`border-b border-slate-800/50 transition ${
dragOverIdx === idx && dragIdx !== idx ? 'bg-indigo-500/10' : 'hover:bg-slate-800/30'
} ${dragIdx === idx ? 'opacity-40' : ''}`}
>
|
|
{f.label}
|
{f.field_key}
|
{f.field_type}
|
{f.required
? Required
: Optional
}
|
|
))}
)}
{modal && (
setModal(null)}
onSave={handleSave}
/>
)}
{deleteConfirm && (
Delete Field
Delete "{deleteConfirm.label}"? This will permanently remove this column and all its data from every contract.
)}
)
}