Initial state for GitNexus analysis
This commit is contained in:
123
packages/features/module-builder/src/components/module-form.tsx
Normal file
123
packages/features/module-builder/src/components/module-form.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { FieldRenderer } from './field-renderer';
|
||||
|
||||
import type { CmsFieldType } from '../schema/module.schema';
|
||||
|
||||
interface FieldDefinition {
|
||||
name: string;
|
||||
display_name: string;
|
||||
field_type: CmsFieldType;
|
||||
is_required: boolean;
|
||||
placeholder?: string | null;
|
||||
help_text?: string | null;
|
||||
is_readonly: boolean;
|
||||
select_options?: Array<{ label: string; value: string }> | null;
|
||||
section: string;
|
||||
sort_order: number;
|
||||
show_in_form: boolean;
|
||||
width: string;
|
||||
}
|
||||
|
||||
interface ModuleFormProps {
|
||||
fields: FieldDefinition[];
|
||||
initialData?: Record<string, unknown>;
|
||||
onSubmit: (data: Record<string, unknown>) => Promise<void>;
|
||||
isLoading?: boolean;
|
||||
errors?: Array<{ field: string; message: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic form component driven by module field definitions.
|
||||
* Replaces the legacy my_modulklasse form rendering.
|
||||
*/
|
||||
export function ModuleForm({
|
||||
fields,
|
||||
initialData = {},
|
||||
onSubmit,
|
||||
isLoading = false,
|
||||
errors = [],
|
||||
}: ModuleFormProps) {
|
||||
const [formData, setFormData] = useState<Record<string, unknown>>(initialData);
|
||||
|
||||
const visibleFields = fields
|
||||
.filter((f) => f.show_in_form)
|
||||
.sort((a, b) => a.sort_order - b.sort_order);
|
||||
|
||||
// Group fields by section
|
||||
const sections = new Map<string, FieldDefinition[]>();
|
||||
for (const field of visibleFields) {
|
||||
const section = field.section || 'default';
|
||||
if (!sections.has(section)) {
|
||||
sections.set(section, []);
|
||||
}
|
||||
sections.get(section)!.push(field);
|
||||
}
|
||||
|
||||
const handleFieldChange = (fieldName: string, value: unknown) => {
|
||||
setFormData((prev) => ({ ...prev, [fieldName]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
await onSubmit(formData);
|
||||
};
|
||||
|
||||
const getFieldError = (fieldName: string) => {
|
||||
return errors.find((e) => e.field === fieldName)?.message;
|
||||
};
|
||||
|
||||
const getWidthClass = (width: string) => {
|
||||
switch (width) {
|
||||
case 'half': return 'col-span-1';
|
||||
case 'third': return 'col-span-1';
|
||||
case 'quarter': return 'col-span-1';
|
||||
default: return 'col-span-full';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{Array.from(sections.entries()).map(([sectionName, sectionFields]) => (
|
||||
<div key={sectionName} className="space-y-4">
|
||||
{sectionName !== 'default' && (
|
||||
<h3 className="text-lg font-semibold border-b pb-2">
|
||||
{sectionName}
|
||||
</h3>
|
||||
)}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{sectionFields.map((field) => (
|
||||
<div key={field.name} className={getWidthClass(field.width)}>
|
||||
<FieldRenderer
|
||||
name={field.name}
|
||||
displayName={field.display_name}
|
||||
fieldType={field.field_type}
|
||||
value={formData[field.name]}
|
||||
onChange={(value) => handleFieldChange(field.name, value)}
|
||||
placeholder={field.placeholder ?? undefined}
|
||||
helpText={field.help_text ?? undefined}
|
||||
required={field.is_required}
|
||||
readonly={field.is_readonly}
|
||||
error={getFieldError(field.name)}
|
||||
selectOptions={field.select_options ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4 border-t">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Wird gespeichert...' : 'Speichern'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user