Files
myeasycms-v2/packages/features/module-builder/src/components/module-form.tsx
T. Zehetbauer bbb33aa63d
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 5m43s
Workflow / ⚫️ Test (push) Has been skipped
feat: add file upload and management features; enhance pagination and permissions handling
2026-04-01 20:13:15 +02:00

130 lines
3.7 KiB
TypeScript

'use client';
import { useState } from 'react';
import { Button } from '@kit/ui/button';
import type { CmsFieldType } from '../schema/module.schema';
import { FieldRenderer } from './field-renderer';
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="border-b pb-2 text-lg font-semibold">
{sectionName}
</h3>
)}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{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 border-t pt-4">
<Button
type="submit"
disabled={isLoading}
data-test="module-record-submit-btn"
>
{isLoading ? 'Wird gespeichert...' : 'Speichern'}
</Button>
</div>
</form>
);
}