feat(web): ObjectForm (core + dynamic flexible fields, RHF, validation)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Controller, type UseFormReturn } from "react-hook-form";
|
||||
import { Controller, type Path, type UseFormReturn } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { components } from "../api/schema";
|
||||
@@ -10,7 +10,13 @@ import { Label } from "@/components/ui/label";
|
||||
type FieldDefinitionView = components["schemas"]["FieldDefinitionView"];
|
||||
type LabelView = components["schemas"]["LabelView"];
|
||||
|
||||
type FieldForm = UseFormReturn<{ fields: Record<string, unknown> }>;
|
||||
type FieldForm<TValues extends { fields: Record<string, unknown> }> = UseFormReturn<TValues>;
|
||||
|
||||
function fieldPath<TValues extends { fields: Record<string, unknown> }>(
|
||||
key: string,
|
||||
): Path<TValues> {
|
||||
return `fields.${key}` as Path<TValues>;
|
||||
}
|
||||
|
||||
function labelIn(labels: LabelView[], lang: string): string {
|
||||
return (
|
||||
@@ -56,17 +62,17 @@ function OptionsSelect({
|
||||
);
|
||||
}
|
||||
|
||||
export function FieldInput({
|
||||
export function FieldInput<TValues extends { fields: Record<string, unknown> }>({
|
||||
definition,
|
||||
form,
|
||||
}: {
|
||||
definition: FieldDefinitionView;
|
||||
form: FieldForm;
|
||||
form: FieldForm<TValues>;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
|
||||
const label = labelIn(definition.labels, lang);
|
||||
const name = `fields.${definition.key}` as `fields.${string}`;
|
||||
const name = fieldPath<TValues>(definition.key);
|
||||
const placeholder = t("form.selectPlaceholder");
|
||||
|
||||
switch (definition.data_type) {
|
||||
@@ -133,7 +139,7 @@ export function FieldInput({
|
||||
|
||||
<Input
|
||||
id={`${definition.key}-en`}
|
||||
{...form.register(`fields.${definition.key}.en` as `fields.${string}`, { required: definition.required })}
|
||||
{...form.register(fieldPath<TValues>(`${definition.key}.en`), { required: definition.required })}
|
||||
/>
|
||||
|
||||
<Label
|
||||
@@ -145,7 +151,7 @@ export function FieldInput({
|
||||
|
||||
<Input
|
||||
id={`${definition.key}-sv`}
|
||||
{...form.register(`fields.${definition.key}.sv` as `fields.${string}`)}
|
||||
{...form.register(fieldPath<TValues>(`${definition.key}.sv`))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -187,7 +193,7 @@ export function FieldInput({
|
||||
}
|
||||
}
|
||||
|
||||
function TermField({
|
||||
function TermField<TValues extends { fields: Record<string, unknown> }>({
|
||||
definition,
|
||||
form,
|
||||
label,
|
||||
@@ -195,7 +201,7 @@ function TermField({
|
||||
placeholder,
|
||||
}: {
|
||||
definition: FieldDefinitionView;
|
||||
form: FieldForm;
|
||||
form: FieldForm<TValues>;
|
||||
label: string;
|
||||
lang: string;
|
||||
placeholder: string;
|
||||
@@ -208,7 +214,7 @@ function TermField({
|
||||
|
||||
<Controller
|
||||
control={form.control}
|
||||
name={`fields.${definition.key}` as `fields.${string}`}
|
||||
name={fieldPath<TValues>(definition.key)}
|
||||
rules={{ required: definition.required }}
|
||||
render={({ field }) => (
|
||||
<OptionsSelect
|
||||
@@ -225,7 +231,7 @@ function TermField({
|
||||
);
|
||||
}
|
||||
|
||||
function AuthorityField({
|
||||
function AuthorityField<TValues extends { fields: Record<string, unknown> }>({
|
||||
definition,
|
||||
form,
|
||||
label,
|
||||
@@ -233,7 +239,7 @@ function AuthorityField({
|
||||
placeholder,
|
||||
}: {
|
||||
definition: FieldDefinitionView;
|
||||
form: FieldForm;
|
||||
form: FieldForm<TValues>;
|
||||
label: string;
|
||||
lang: string;
|
||||
placeholder: string;
|
||||
@@ -246,7 +252,7 @@ function AuthorityField({
|
||||
|
||||
<Controller
|
||||
control={form.control}
|
||||
name={`fields.${definition.key}` as `fields.${string}`}
|
||||
name={fieldPath<TValues>(definition.key)}
|
||||
rules={{ required: definition.required }}
|
||||
render={({ field }) => (
|
||||
<OptionsSelect
|
||||
|
||||
Reference in New Issue
Block a user