Files
resolutionflow/frontend/src/components/procedural-editor/IntakeFieldEditor.tsx
Michael Chihlas a332a9ebab chore: run Tailwind v4 upgrade tool (Phase 1)
- Upgraded tailwindcss v3 → v4.2.1, postcss plugin to @tailwindcss/postcss
- Deleted tailwind.config.js, migrated theme to CSS @theme block in index.css
- Replaced @tailwind directives with @import 'tailwindcss'
- Added @custom-variant dark, @utility blocks for custom utilities
- Updated class names across 128 files (shadow-sm → shadow-xs, etc.)
- Removed autoprefixer (built into v4)
- Added migration plan doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:00:38 -05:00

156 lines
6.9 KiB
TypeScript

import { GripVertical, Trash2, ChevronDown, ChevronUp } from 'lucide-react'
import { useState } from 'react'
import type { IntakeFormField, IntakeFieldType } from '@/types'
const FIELD_TYPE_OPTIONS: { value: IntakeFieldType; label: string }[] = [
{ value: 'text', label: 'Text' },
{ value: 'textarea', label: 'Text Area' },
{ value: 'number', label: 'Number' },
{ value: 'ip_address', label: 'IP Address' },
{ value: 'email', label: 'Email' },
{ value: 'url', label: 'URL' },
{ value: 'select', label: 'Select (Dropdown)' },
{ value: 'multi_select', label: 'Multi-Select' },
{ value: 'checkbox', label: 'Checkbox' },
{ value: 'password', label: 'Password' },
]
interface IntakeFieldEditorProps {
field: IntakeFormField
onUpdate: (updates: Partial<IntakeFormField>) => void
onRemove: () => void
}
export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEditorProps) {
const [expanded, setExpanded] = useState(false)
const needsOptions = field.field_type === 'select' || field.field_type === 'multi_select'
return (
<div className="bg-card border border-border rounded-xl p-3">
{/* Header row */}
<div className="flex items-center gap-2">
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-muted-foreground" />
<input
type="text"
value={field.label}
onChange={(e) => onUpdate({ label: e.target.value })}
placeholder="Field label"
className="min-w-0 flex-1 rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
<select
value={field.field_type}
onChange={(e) => onUpdate({ field_type: e.target.value as IntakeFieldType })}
className="rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
{FIELD_TYPE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<label className="flex items-center gap-1 text-xs text-muted-foreground">
<input
type="checkbox"
checked={field.required}
onChange={(e) => onUpdate({ required: e.target.checked })}
className="rounded border-border"
/>
Req
</label>
<button
onClick={() => setExpanded(!expanded)}
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
{expanded ? <ChevronUp className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
</button>
<button
onClick={onRemove}
className="rounded p-1 text-muted-foreground hover:bg-red-500/20 hover:text-red-400"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
{/* Expanded details */}
{expanded && (
<div className="mt-3 grid grid-cols-2 gap-3 border-t border-border pt-3">
<div>
<label className="mb-1 block text-xs text-muted-foreground">Variable Name</label>
<input
type="text"
value={field.variable_name}
onChange={(e) => onUpdate({ variable_name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '') })}
placeholder="e.g. server_name"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
<p className="mt-0.5 text-[10px] text-muted-foreground">Used as [VAR:{field.variable_name}]</p>
</div>
<div>
<label className="mb-1 block text-xs text-muted-foreground">Placeholder</label>
<input
type="text"
value={field.placeholder || ''}
onChange={(e) => onUpdate({ placeholder: e.target.value || undefined })}
placeholder="Hint text"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
<div className="col-span-2">
<label className="mb-1 block text-xs text-muted-foreground">Help Text</label>
<input
type="text"
value={field.help_text || ''}
onChange={(e) => onUpdate({ help_text: e.target.value || undefined })}
placeholder="Description or instructions"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
<div>
<label className="mb-1 block text-xs text-muted-foreground">Default Value</label>
<input
type="text"
value={field.default_value || ''}
onChange={(e) => onUpdate({ default_value: e.target.value || undefined })}
placeholder="Pre-filled value"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
<div>
<label className="mb-1 block text-xs text-muted-foreground">Group Name</label>
<input
type="text"
value={field.group_name || ''}
onChange={(e) => onUpdate({ group_name: e.target.value || undefined })}
placeholder="e.g. Network Settings"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
{needsOptions && (
<div className="col-span-2">
<label className="mb-1 block text-xs text-muted-foreground">Options (one per line)</label>
<textarea
value={(field.options || []).join('\n')}
onChange={(e) => {
const options = e.target.value.split('\n').filter((o) => o.trim())
onUpdate({ options: options.length > 0 ? options : undefined })
}}
placeholder="Option 1&#10;Option 2&#10;Option 3"
rows={3}
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
)}
</div>
)}
</div>
)
}