212 lines
5.4 KiB
TypeScript
212 lines
5.4 KiB
TypeScript
import { Controller, type Path, type UseFormReturn } from "react-hook-form";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import type { components } from "../api/schema";
|
|
import { useAuthorities, useTerms } from "../api/queries";
|
|
import { useConfig } from "../config/config-context";
|
|
import { labelText } from "../lib/labels";
|
|
import { OptionsCombobox } from "./options-combobox";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
type FieldDefinitionView = components["schemas"]["FieldDefinitionView"];
|
|
|
|
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>;
|
|
}
|
|
|
|
export function FieldInput<TValues extends { fields: Record<string, unknown> }>({
|
|
definition,
|
|
form,
|
|
}: {
|
|
definition: FieldDefinitionView;
|
|
form: FieldForm<TValues>;
|
|
}) {
|
|
const { t, i18n } = useTranslation();
|
|
const { default_language } = useConfig();
|
|
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
|
|
const label = labelText(definition.labels, lang);
|
|
const name = fieldPath<TValues>(definition.key);
|
|
const placeholder = t("form.selectPlaceholder");
|
|
|
|
switch (definition.data_type) {
|
|
case "integer":
|
|
return (
|
|
<div className="space-y-1">
|
|
<Label htmlFor={definition.key}>{label}</Label>
|
|
|
|
<Input
|
|
id={definition.key}
|
|
type="number"
|
|
{...form.register(name, {
|
|
valueAsNumber: true,
|
|
required: definition.required,
|
|
})}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
case "date":
|
|
return (
|
|
<div className="space-y-1">
|
|
<Label htmlFor={definition.key}>{label}</Label>
|
|
|
|
<Input
|
|
id={definition.key}
|
|
type="date"
|
|
{...form.register(name, { required: definition.required })}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
case "boolean":
|
|
// A checkbox always has a boolean value, so `required` is a no-op here.
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<Controller
|
|
control={form.control}
|
|
name={name}
|
|
render={({ field }) => (
|
|
<Checkbox
|
|
id={definition.key}
|
|
checked={!!field.value}
|
|
onCheckedChange={(checked) => field.onChange(checked === true)}
|
|
/>
|
|
)}
|
|
/>
|
|
|
|
<Label htmlFor={definition.key}>{label}</Label>
|
|
</div>
|
|
);
|
|
|
|
case "localized_text":
|
|
return (
|
|
<div className="space-y-1">
|
|
<Label htmlFor={definition.key}>{label}</Label>
|
|
<Input
|
|
id={definition.key}
|
|
{...form.register(fieldPath<TValues>(`${definition.key}.${default_language}`), {
|
|
required: definition.required,
|
|
})}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
case "term":
|
|
return (
|
|
<TermField
|
|
definition={definition}
|
|
form={form}
|
|
label={label}
|
|
lang={lang}
|
|
placeholder={placeholder}
|
|
/>
|
|
);
|
|
|
|
case "authority":
|
|
return (
|
|
<AuthorityField
|
|
definition={definition}
|
|
form={form}
|
|
label={label}
|
|
lang={lang}
|
|
placeholder={placeholder}
|
|
/>
|
|
);
|
|
|
|
case "text":
|
|
default:
|
|
return (
|
|
<div className="space-y-1">
|
|
<Label htmlFor={definition.key}>{label}</Label>
|
|
|
|
<Input
|
|
id={definition.key}
|
|
{...form.register(name, { required: definition.required })}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
function TermField<TValues extends { fields: Record<string, unknown> }>({
|
|
definition,
|
|
form,
|
|
label,
|
|
lang,
|
|
placeholder,
|
|
}: {
|
|
definition: FieldDefinitionView;
|
|
form: FieldForm<TValues>;
|
|
label: string;
|
|
lang: string;
|
|
placeholder: string;
|
|
}) {
|
|
const { data: terms } = useTerms(definition.vocabulary_id);
|
|
|
|
return (
|
|
<div className="space-y-1">
|
|
<Label htmlFor={definition.key}>{label}</Label>
|
|
|
|
<Controller
|
|
control={form.control}
|
|
name={fieldPath<TValues>(definition.key)}
|
|
rules={{ required: definition.required }}
|
|
render={({ field }) => (
|
|
<OptionsCombobox
|
|
id={definition.key}
|
|
value={(field.value as string) ?? ""}
|
|
onChange={field.onChange}
|
|
options={terms ?? []}
|
|
lang={lang}
|
|
placeholder={placeholder}
|
|
/>
|
|
)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AuthorityField<TValues extends { fields: Record<string, unknown> }>({
|
|
definition,
|
|
form,
|
|
label,
|
|
lang,
|
|
placeholder,
|
|
}: {
|
|
definition: FieldDefinitionView;
|
|
form: FieldForm<TValues>;
|
|
label: string;
|
|
lang: string;
|
|
placeholder: string;
|
|
}) {
|
|
const { data: authorities } = useAuthorities(definition.authority_kind);
|
|
|
|
return (
|
|
<div className="space-y-1">
|
|
<Label htmlFor={definition.key}>{label}</Label>
|
|
|
|
<Controller
|
|
control={form.control}
|
|
name={fieldPath<TValues>(definition.key)}
|
|
rules={{ required: definition.required }}
|
|
render={({ field }) => (
|
|
<OptionsCombobox
|
|
id={definition.key}
|
|
value={(field.value as string) ?? ""}
|
|
onChange={field.onChange}
|
|
options={authorities ?? []}
|
|
lang={lang}
|
|
placeholder={placeholder}
|
|
/>
|
|
)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|