Render forms from indexed form elements
This commit is contained in:
@@ -3049,7 +3049,7 @@ function FormDesignerPanel({
|
||||
const form = objectForms.find((item) => item.form.lineage_id === selectedFormId) ?? objectForms[0];
|
||||
const commands = form?.commands.slice(0, 6) ?? [];
|
||||
const formKey = form?.form.lineage_id ?? "draft";
|
||||
const baseElements = useMemo(() => buildIdeFormElements(data, form), [data, form]);
|
||||
const baseElements = useMemo(() => buildIdeFormElements(form), [form]);
|
||||
const elements = elementDrafts[formKey] ?? baseElements;
|
||||
const formTitle = titleByForm[formKey] ?? form?.form.name ?? "ФормаДокумента";
|
||||
const formObjectCaption = language === "ru" ? `${formTitle} (форма 1С 8.5)` : `${formTitle} (1C 8.5 form)`;
|
||||
@@ -3057,9 +3057,9 @@ function FormDesignerPanel({
|
||||
|
||||
useEffect(() => {
|
||||
if (form && !elementDrafts[form.form.lineage_id]) {
|
||||
setElementDrafts((current) => ({ ...current, [form.form.lineage_id]: buildIdeFormElements(data, form) }));
|
||||
setElementDrafts((current) => ({ ...current, [form.form.lineage_id]: buildIdeFormElements(form) }));
|
||||
}
|
||||
}, [data, elementDrafts, form]);
|
||||
}, [elementDrafts, form]);
|
||||
|
||||
const updateElement = (id: string, patch: Partial<IdeFormElementDraft>) => {
|
||||
setElementDrafts((current) => ({
|
||||
@@ -3175,9 +3175,23 @@ function FormDesignerPanel({
|
||||
"grid grid-cols-12 gap-x-3 gap-y-2 p-5",
|
||||
layout === "compact" ? "gap-y-1" : ""
|
||||
].join(" ")}>
|
||||
{elements.map((element) => (
|
||||
<IdeFormControl element={element} forceHalf={layout === "columns"} key={element.id} />
|
||||
))}
|
||||
{elements.length ? (
|
||||
elements.map((element) => (
|
||||
<IdeFormControl element={element} forceHalf={layout === "columns"} key={element.id} />
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-12 border border-dashed border-[#aeb8c6] bg-white px-4 py-6 text-sm text-slate-600" data-ide-form-empty>
|
||||
<div className="font-semibold text-slate-900">{language === "ru" ? "Структура элементов формы не загружена" : "Form element structure is not loaded"}</div>
|
||||
<div className="mt-1 text-xs">
|
||||
{form?.form.qualified_name ?? form?.form.name ?? data.projectId}
|
||||
</div>
|
||||
<div className="mt-3 text-xs">
|
||||
{language === "ru"
|
||||
? "В текущем индексе для этой формы нет узлов элементов. SFERA не подставляет шаблонные поля, чтобы не искажать объект 1С."
|
||||
: "The current index has no element nodes for this form. SFERA does not insert template fields because that would distort the 1C object."}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-auto border-t border-[#ccd4df] bg-white px-4 py-3">
|
||||
@@ -3306,7 +3320,7 @@ function ideFormControlInput(element: IdeFormElementDraft) {
|
||||
if (element.controlKind === "text") {
|
||||
return <textarea className="min-h-16 resize-none border border-[#aeb8c6] bg-white px-2 py-1 text-xs" readOnly value={element.binding} />;
|
||||
}
|
||||
return <input className="h-7 border border-[#aeb8c6] bg-white px-2 text-xs" readOnly value={element.controlKind === "date" ? "21.05.2026 0:00:00" : element.binding} />;
|
||||
return <input className="h-7 border border-[#aeb8c6] bg-white px-2 text-xs" readOnly value={element.binding} />;
|
||||
}
|
||||
|
||||
function IdeFormMetric({ label, value }: Readonly<{ label: string; value: number }>) {
|
||||
@@ -3318,7 +3332,7 @@ function IdeFormMetric({ label, value }: Readonly<{ label: string; value: number
|
||||
);
|
||||
}
|
||||
|
||||
function buildIdeFormElements(data: ProjectWorkspaceData, form: ProjectWorkspaceData["forms"][number] | undefined): IdeFormElementDraft[] {
|
||||
function buildIdeFormElements(form: ProjectWorkspaceData["forms"][number] | undefined): IdeFormElementDraft[] {
|
||||
const explicitElements = form?.elements ?? [];
|
||||
if (explicitElements.length) {
|
||||
return explicitElements.map((element, index) => ({
|
||||
@@ -3330,38 +3344,7 @@ function buildIdeFormElements(data: ProjectWorkspaceData, form: ProjectWorkspace
|
||||
width: "stretch"
|
||||
}));
|
||||
}
|
||||
const attributes = data.selectedObjectSchema?.attributes ?? [];
|
||||
const tabularSections = data.selectedObjectSchema?.tabular_sections ?? [];
|
||||
const fromSchema: IdeFormElementDraft[] = [
|
||||
...attributes.map((attribute, index) => ({
|
||||
id: attribute.lineage_id || `attribute.${index}`,
|
||||
name: attribute.name,
|
||||
caption: attribute.name,
|
||||
controlKind: controlKindForFormNode(attribute.name, attribute.kind),
|
||||
binding: attribute.name,
|
||||
width: index < 2 ? "half" : "stretch"
|
||||
} satisfies IdeFormElementDraft)),
|
||||
...tabularSections.map((section, index) => ({
|
||||
id: section.tabular_section.lineage_id || `table.${index}`,
|
||||
name: section.tabular_section.name,
|
||||
caption: section.tabular_section.name,
|
||||
controlKind: "table" as const,
|
||||
binding: section.tabular_section.name,
|
||||
width: "stretch" as const
|
||||
}))
|
||||
];
|
||||
if (fromSchema.length) {
|
||||
return fromSchema;
|
||||
}
|
||||
const formName = form?.form.name ?? "ФормаДокумента";
|
||||
if (formName.includes("ФормаДокумента")) {
|
||||
return [
|
||||
{ id: "default.number", name: "Номер", caption: "Номер", controlKind: "input", binding: "Объект.Номер", width: "half" },
|
||||
{ id: "default.date", name: "Дата", caption: "Дата", controlKind: "date", binding: "Объект.Дата", width: "half" },
|
||||
{ id: "default.table", name: "Товары", caption: "Товары", controlKind: "table", binding: "Объект.Товары", width: "stretch" }
|
||||
];
|
||||
}
|
||||
return [{ id: "default.name", name: "Наименование", caption: "Наименование", controlKind: "input", binding: "Объект.Наименование", width: "stretch" }];
|
||||
return [];
|
||||
}
|
||||
|
||||
function controlKindForFormNode(name: string, kind: string): IdeFormElementDraft["controlKind"] {
|
||||
|
||||
@@ -5531,7 +5531,7 @@ function FormDesignerPanel({ projectId, object }: Readonly<{ projectId: string;
|
||||
const [extraElementsByForm, setExtraElementsByForm] = useState<Record<string, FormDesignerElementDraft[]>>({});
|
||||
const [newElementName, setNewElementName] = useState("");
|
||||
const [newElementKind, setNewElementKind] = useState<FormDesignerElementDraft["kind"]>("input");
|
||||
const baseElements = activeForm ? buildFormDesignerElements(object, activeForm) : [];
|
||||
const baseElements = activeForm ? buildFormDesignerElements(activeForm) : [];
|
||||
const elements = [...baseElements, ...(extraElementsByForm[activeFormKey] ?? [])].map((element) => ({
|
||||
...element,
|
||||
...(elementOverrides[element.id] ?? {})
|
||||
@@ -5617,9 +5617,17 @@ function FormDesignerPanel({ projectId, object }: Readonly<{ projectId: string;
|
||||
className={`grid grid-cols-12 gap-x-3 gap-y-2 p-4 ${layout === "compact" ? "gap-y-1" : ""}`}
|
||||
data-legacy-form-layout={layout}
|
||||
>
|
||||
{elements.map((element) => (
|
||||
<LegacyFormControl key={element.id} element={element} forceHalf={layout === "columns"} />
|
||||
))}
|
||||
{elements.length ? (
|
||||
elements.map((element) => (
|
||||
<LegacyFormControl key={element.id} element={element} forceHalf={layout === "columns"} />
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-12 border border-dashed border-[#aeb8c6] bg-white px-4 py-6 text-sm text-muted-foreground">
|
||||
<div className="font-semibold text-foreground">Структура элементов формы не загружена</div>
|
||||
<div className="mt-1 text-xs">{formQualifiedName(object, activeForm)}</div>
|
||||
<div className="mt-3 text-xs">SFERA не подставляет реквизиты объекта вместо элементов формы, чтобы не искажать объект 1С.</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5763,29 +5771,48 @@ function legacyFormControlInput(element: FormDesignerElementDraft): ReactNode {
|
||||
if (element.kind === "text") {
|
||||
return <textarea value={element.binding} readOnly className="min-h-16 resize-none border border-[#aeb8c6] bg-white px-2 py-1 text-xs" />;
|
||||
}
|
||||
return <input value={element.kind === "date" ? "21.05.2026 0:00:00" : element.binding} readOnly className="h-7 border border-[#aeb8c6] bg-white px-2 text-xs" />;
|
||||
return <input value={element.binding} readOnly className="h-7 border border-[#aeb8c6] bg-white px-2 text-xs" />;
|
||||
}
|
||||
|
||||
function buildFormDesignerElements(object: NormalizedMetadataObject, form: ObjectPart): FormDesignerElementDraft[] {
|
||||
const fields = [...object.attributes, ...object.tabular_sections];
|
||||
if (fields.length) {
|
||||
return fields.map((field, index) => ({
|
||||
id: `${form.name}-${field.kind}-${field.name}`,
|
||||
name: field.name,
|
||||
caption: field.name,
|
||||
kind: field.kind === "TABULAR_SECTION" ? "table" : field.name.toLowerCase().includes("дата") ? "date" : "input",
|
||||
binding: field.kind === "TABULAR_SECTION" ? `Объект.${field.name}` : field.name,
|
||||
width: field.kind === "TABULAR_SECTION" ? "stretch" : index < 2 ? "half" : "stretch"
|
||||
}));
|
||||
function buildFormDesignerElements(form: ObjectPart): FormDesignerElementDraft[] {
|
||||
const rawElements = form.attributes.elements;
|
||||
if (Array.isArray(rawElements)) {
|
||||
return rawElements
|
||||
.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null)
|
||||
.map((item, index) => {
|
||||
const name = String(item.name ?? item.caption ?? `Элемент${index + 1}`);
|
||||
const kind = String(item.control_kind ?? item.control ?? item.type ?? item.kind ?? "");
|
||||
return {
|
||||
id: String(item.lineage_id ?? item.id ?? `${form.name}-${name}-${index}`),
|
||||
name,
|
||||
caption: String(item.caption ?? name),
|
||||
kind: formDesignerKindFor(kind, name),
|
||||
binding: String(item.binding ?? item.path ?? name),
|
||||
width: String(item.width ?? "") === "half" ? "half" : String(item.width ?? "") === "third" ? "third" : "stretch"
|
||||
};
|
||||
});
|
||||
}
|
||||
if (form.name.includes("ФормаДокумента")) {
|
||||
return [
|
||||
{ id: `${form.name}-number`, name: "Номер", caption: "Номер", kind: "input", binding: "Объект.Номер", width: "half" },
|
||||
{ id: `${form.name}-date`, name: "Дата", caption: "Дата", kind: "date", binding: "Объект.Дата", width: "half" },
|
||||
{ id: `${form.name}-table`, name: "Товары", caption: "Товары", kind: "table", binding: "Объект.Товары", width: "stretch" }
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
function formDesignerKindFor(kind: string, name: string): FormDesignerElementDraft["kind"] {
|
||||
const raw = `${kind} ${name}`.toLowerCase();
|
||||
if (raw.includes("table") || raw.includes("табли") || raw.includes("список")) {
|
||||
return "table";
|
||||
}
|
||||
return [{ id: `${form.name}-name`, name: "Наименование", caption: "Наименование", kind: "input", binding: "Объект.Наименование", width: "stretch" }];
|
||||
if (raw.includes("check") || raw.includes("boolean") || raw.includes("флаж") || raw.includes("булево")) {
|
||||
return "checkbox";
|
||||
}
|
||||
if (raw.includes("date") || raw.includes("дата")) {
|
||||
return "date";
|
||||
}
|
||||
if (raw.includes("group") || raw.includes("груп")) {
|
||||
return "group";
|
||||
}
|
||||
if (raw.includes("text") || raw.includes("надпись")) {
|
||||
return "text";
|
||||
}
|
||||
return "input";
|
||||
}
|
||||
|
||||
function formQualifiedName(object: NormalizedMetadataObject, form: ObjectPart): string {
|
||||
|
||||
Reference in New Issue
Block a user