'use client'; import type { CmsFieldType } from '../schema/module.schema'; import { Input } from '@kit/ui/input'; import { Textarea } from '@kit/ui/textarea'; import { Checkbox } from '@kit/ui/checkbox'; import { Label } from '@kit/ui/label'; interface FieldRendererProps { name: string; displayName: string; fieldType: CmsFieldType; value: unknown; onChange: (value: unknown) => void; placeholder?: string; helpText?: string; required?: boolean; readonly?: boolean; error?: string; selectOptions?: Array<{ label: string; value: string }>; } /** * Maps cms_field_type to the appropriate Shadcn UI component. * Replaces legacy PHP field rendering from my_modulklasse. */ export function FieldRenderer({ name, displayName, fieldType, value, onChange, placeholder, helpText, required, readonly, error, selectOptions, }: FieldRendererProps) { const fieldValue = value != null ? String(value) : ''; const renderField = () => { switch (fieldType) { case 'text': case 'phone': case 'url': case 'color': return ( onChange(e.target.value)} placeholder={placeholder} readOnly={readonly} required={required} /> ); case 'email': return ( onChange(e.target.value)} placeholder={placeholder ?? 'email@example.de'} readOnly={readonly} required={required} /> ); case 'password': return ( onChange(e.target.value)} placeholder={placeholder} readOnly={readonly} required={required} /> ); case 'textarea': return ( onChange(e.target.value)} placeholder={placeholder} readOnly={readonly} required={required} rows={4} /> ); case 'richtext': // Phase 3 enhancement: TipTap editor return ( onChange(e.target.value)} placeholder={placeholder ?? 'Formatierter Text...'} readOnly={readonly} rows={6} /> ); case 'integer': return ( onChange(parseInt(e.target.value, 10) || '')} placeholder={placeholder} readOnly={readonly} required={required} /> ); case 'decimal': case 'currency': return ( onChange(parseFloat(e.target.value) || '')} placeholder={placeholder ?? (fieldType === 'currency' ? '0,00 €' : undefined)} readOnly={readonly} required={required} /> ); case 'date': return ( onChange(e.target.value)} readOnly={readonly} required={required} /> ); case 'time': return ( onChange(e.target.value)} readOnly={readonly} required={required} /> ); case 'checkbox': return ( onChange(checked)} disabled={readonly} /> {displayName} ); case 'select': case 'radio': return ( onChange(e.target.value)} disabled={readonly} required={required} className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background" > {placeholder ?? 'Bitte wählen...'} {selectOptions?.map((opt) => ( {opt.label} ))} ); case 'iban': return ( onChange(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, ''))} placeholder={placeholder ?? 'DE89 3704 0044 0532 0130 00'} readOnly={readonly} required={required} maxLength={34} /> ); case 'file': return ( { const file = e.target.files?.[0]; if (file) onChange(file); }} disabled={readonly} required={required} /> ); case 'hidden': return ; case 'computed': return ( {fieldValue || '—'} ); default: return ( onChange(e.target.value)} placeholder={placeholder} readOnly={readonly} /> ); } }; // Checkbox renders its own label if (fieldType === 'checkbox') { return ( {renderField()} {helpText && {helpText}} {error && {error}} ); } return ( {displayName} {required && *} {renderField()} {helpText && {helpText}} {error && {error}} ); }
{helpText}
{error}