Files
biggus-dickus/web/src/objects/field-input.tsx
T

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