Initial state for GitNexus analysis

This commit is contained in:
Zaid Marzguioui
2026-03-29 19:44:57 +02:00
parent 9d7c7f8030
commit 61ff48cb73
155 changed files with 23483 additions and 1722 deletions

View 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>
);
}