Initial state for GitNexus analysis
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
'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 (
|
||||
<Input
|
||||
name={name}
|
||||
type={fieldType === 'color' ? 'color' : fieldType === 'url' ? 'url' : fieldType === 'phone' ? 'tel' : 'text'}
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
readOnly={readonly}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'email':
|
||||
return (
|
||||
<Input
|
||||
name={name}
|
||||
type="email"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder ?? 'email@example.de'}
|
||||
readOnly={readonly}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'password':
|
||||
return (
|
||||
<Input
|
||||
name={name}
|
||||
type="password"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
readOnly={readonly}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'textarea':
|
||||
return (
|
||||
<Textarea
|
||||
name={name}
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
readOnly={readonly}
|
||||
required={required}
|
||||
rows={4}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'richtext':
|
||||
// Phase 3 enhancement: TipTap editor
|
||||
return (
|
||||
<Textarea
|
||||
name={name}
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder ?? 'Formatierter Text...'}
|
||||
readOnly={readonly}
|
||||
rows={6}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'integer':
|
||||
return (
|
||||
<Input
|
||||
name={name}
|
||||
type="number"
|
||||
step="1"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(parseInt(e.target.value, 10) || '')}
|
||||
placeholder={placeholder}
|
||||
readOnly={readonly}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'decimal':
|
||||
case 'currency':
|
||||
return (
|
||||
<Input
|
||||
name={name}
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(parseFloat(e.target.value) || '')}
|
||||
placeholder={placeholder ?? (fieldType === 'currency' ? '0,00 €' : undefined)}
|
||||
readOnly={readonly}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'date':
|
||||
return (
|
||||
<Input
|
||||
name={name}
|
||||
type="date"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
readOnly={readonly}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'time':
|
||||
return (
|
||||
<Input
|
||||
name={name}
|
||||
type="time"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
readOnly={readonly}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'checkbox':
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={name}
|
||||
checked={Boolean(value)}
|
||||
onCheckedChange={(checked) => onChange(checked)}
|
||||
disabled={readonly}
|
||||
/>
|
||||
<Label htmlFor={name}>{displayName}</Label>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'select':
|
||||
case 'radio':
|
||||
return (
|
||||
<select
|
||||
name={name}
|
||||
value={fieldValue}
|
||||
onChange={(e) => 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"
|
||||
>
|
||||
<option value="">{placeholder ?? 'Bitte wählen...'}</option>
|
||||
{selectOptions?.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
|
||||
case 'iban':
|
||||
return (
|
||||
<Input
|
||||
name={name}
|
||||
type="text"
|
||||
value={fieldValue}
|
||||
onChange={(e) => 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 (
|
||||
<Input
|
||||
name={name}
|
||||
type="file"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) onChange(file);
|
||||
}}
|
||||
disabled={readonly}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'hidden':
|
||||
return <input type="hidden" name={name} value={fieldValue} />;
|
||||
|
||||
case 'computed':
|
||||
return (
|
||||
<div className="rounded-md border bg-muted px-3 py-2 text-sm">
|
||||
{fieldValue || '—'}
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<Input
|
||||
name={name}
|
||||
type="text"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
readOnly={readonly}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Checkbox renders its own label
|
||||
if (fieldType === 'checkbox') {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{renderField()}
|
||||
{helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
|
||||
{error && <p className="text-xs text-destructive">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor={name}>
|
||||
{displayName}
|
||||
{required && <span className="text-destructive ml-1">*</span>}
|
||||
</Label>
|
||||
{renderField()}
|
||||
{helpText && <p className="text-xs text-muted-foreground">{helpText}</p>}
|
||||
{error && <p className="text-xs text-destructive">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user