2026-10-09 18:03:56 +00:00
|
|
|
import { Button } from "@repo/shadcn-ui/components/button";
|
|
|
|
|
import { Input } from "@repo/shadcn-ui/components/input";
|
|
|
|
|
import { Label } from "@repo/shadcn-ui/components/label";
|
|
|
|
|
import { Textarea } from "@repo/shadcn-ui/components/textarea";
|
|
|
|
|
import { ArrowDownIcon, ArrowUpIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
|
|
|
|
import { useId } from "react";
|
|
|
|
|
|
|
|
|
|
import { removeTemplateSection } from "../lib/template-sections";
|
|
|
|
|
|
|
|
|
|
import type { FieldSection } from "./field-sections";
|
|
|
|
|
import { TemplateSectionFields } from "./template-section-fields";
|
|
|
|
|
|
|
|
|
|
export type SectionBinding = { fieldId: string; required: boolean; sectionId?: string | undefined };
|
|
|
|
|
export type SectionEditorLabels = {
|
|
|
|
|
title: string;
|
|
|
|
|
add: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description: string;
|
|
|
|
|
ungrouped: string;
|
|
|
|
|
moveUp: string;
|
|
|
|
|
moveDown: string;
|
|
|
|
|
remove: string;
|
|
|
|
|
removeField: string;
|
|
|
|
|
required: string;
|
|
|
|
|
assign: string;
|
|
|
|
|
help: string;
|
|
|
|
|
empty: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function TemplateSectionsEditor({
|
|
|
|
|
sections,
|
|
|
|
|
fields,
|
|
|
|
|
fieldNames,
|
|
|
|
|
fieldNotes,
|
|
|
|
|
labels,
|
|
|
|
|
disabled = false,
|
|
|
|
|
createId,
|
|
|
|
|
onChange,
|
|
|
|
|
}: {
|
|
|
|
|
sections: readonly FieldSection[];
|
|
|
|
|
fields: readonly SectionBinding[];
|
|
|
|
|
fieldNames: Readonly<Record<string, string>>;
|
|
|
|
|
fieldNotes?: Readonly<Record<string, string>> | undefined;
|
|
|
|
|
labels: SectionEditorLabels;
|
|
|
|
|
disabled?: boolean;
|
|
|
|
|
createId: () => string;
|
|
|
|
|
onChange: (value: { sections: FieldSection[]; fields: SectionBinding[] }) => void;
|
|
|
|
|
}) {
|
|
|
|
|
const prefix = useId();
|
|
|
|
|
const saveSections = (next: FieldSection[]) => onChange({ sections: next, fields: [...fields] });
|
|
|
|
|
const moveSection = (index: number, offset: number) => {
|
|
|
|
|
const next = [...sections];
|
|
|
|
|
const first = next[index];
|
|
|
|
|
const second = next[index + offset];
|
|
|
|
|
if (first && second) {
|
|
|
|
|
next[index] = second;
|
|
|
|
|
next[index + offset] = first;
|
|
|
|
|
saveSections(next);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const removeSection = (id: string) => onChange(removeTemplateSection(sections, fields, id));
|
|
|
|
|
const fieldList = (sectionId?: string | undefined) => (
|
|
|
|
|
<TemplateSectionFields
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
fieldNames={fieldNames}
|
|
|
|
|
fieldNotes={fieldNotes}
|
|
|
|
|
fields={fields}
|
|
|
|
|
labels={labels}
|
|
|
|
|
onChange={(next) => onChange({ sections: [...sections], fields: next })}
|
|
|
|
|
sectionId={sectionId}
|
|
|
|
|
sections={sections}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
return (
|
|
|
|
|
<section className="space-y-3">
|
|
|
|
|
<div className="flex items-center justify-between gap-3">
|
|
|
|
|
<h3 className="text-sm font-medium">{labels.title}</h3>
|
|
|
|
|
<Button
|
|
|
|
|
disabled={disabled || sections.length >= 30}
|
|
|
|
|
onClick={() => saveSections([...sections, { id: createId(), name: "", description: "" }])}
|
|
|
|
|
size="sm"
|
|
|
|
|
type="button"
|
|
|
|
|
variant="outline"
|
|
|
|
|
>
|
|
|
|
|
<PlusIcon aria-hidden="true" />
|
|
|
|
|
{labels.add}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-muted-foreground">{labels.help}</p>
|
2026-10-09 18:30:41 +00:00
|
|
|
<div className="grid items-start gap-4 @min-[900px]:grid-cols-2">
|
|
|
|
|
{sections.map((section, index) => (
|
|
|
|
|
<fieldset
|
|
|
|
|
className="space-y-3 rounded-md border p-3"
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
key={section.id}
|
|
|
|
|
>
|
|
|
|
|
<legend className="sr-only">{section.name || labels.name}</legend>
|
|
|
|
|
<div className="flex items-end gap-2">
|
|
|
|
|
<div className="min-w-0 flex-1 space-y-2">
|
|
|
|
|
<Label htmlFor={`${prefix}-${section.id}-name`}>{labels.name}</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id={`${prefix}-${section.id}-name`}
|
|
|
|
|
maxLength={100}
|
|
|
|
|
onChange={(event) =>
|
|
|
|
|
saveSections(
|
|
|
|
|
sections.map((item) =>
|
|
|
|
|
item.id === section.id ? { ...item, name: event.target.value } : item
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
required
|
|
|
|
|
value={section.name}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label={`${labels.moveUp}: ${section.name}`}
|
|
|
|
|
disabled={disabled || index === 0}
|
|
|
|
|
onClick={() => moveSection(index, -1)}
|
|
|
|
|
size="icon-sm"
|
|
|
|
|
type="button"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
>
|
|
|
|
|
<ArrowUpIcon />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label={`${labels.moveDown}: ${section.name}`}
|
|
|
|
|
disabled={disabled || index === sections.length - 1}
|
|
|
|
|
onClick={() => moveSection(index, 1)}
|
|
|
|
|
size="icon-sm"
|
|
|
|
|
type="button"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
>
|
|
|
|
|
<ArrowDownIcon />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
aria-label={`${labels.remove}: ${section.name}`}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onClick={() => removeSection(section.id)}
|
|
|
|
|
size="icon-sm"
|
|
|
|
|
type="button"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
>
|
|
|
|
|
<Trash2Icon />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor={`${prefix}-${section.id}-description`}>{labels.description}</Label>
|
|
|
|
|
<Textarea
|
|
|
|
|
id={`${prefix}-${section.id}-description`}
|
|
|
|
|
maxLength={1000}
|
2026-10-09 18:03:56 +00:00
|
|
|
onChange={(event) =>
|
|
|
|
|
saveSections(
|
|
|
|
|
sections.map((item) =>
|
2026-10-09 18:30:41 +00:00
|
|
|
item.id === section.id ? { ...item, description: event.target.value } : item
|
2026-10-09 18:03:56 +00:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-10-09 18:30:41 +00:00
|
|
|
rows={2}
|
|
|
|
|
value={section.description}
|
2026-10-09 18:03:56 +00:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-10-09 18:30:41 +00:00
|
|
|
{fieldList(section.id)}
|
|
|
|
|
</fieldset>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-10-09 18:03:56 +00:00
|
|
|
{fieldList()}
|
|
|
|
|
</section>
|
|
|
|
|
);
|
|
|
|
|
}
|