@duke333/builder-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1397 @@
1
+ // src/admin/BuilderEditorPage.tsx
2
+ import { useEffect } from "react";
3
+ import {
4
+ FormProvider,
5
+ useFieldArray,
6
+ useForm,
7
+ useWatch
8
+ } from "react-hook-form";
9
+
10
+ // src/admin/editorState.ts
11
+ var BUILDER_EDITOR_NONE_VALUE = "__none";
12
+ function builderEditorOptionsToText(options) {
13
+ return (options ?? []).map((option) => `${option.label}:${option.value}`).join("\n");
14
+ }
15
+ function builderEditorTextToOptions(value) {
16
+ const rows = (value ?? "").split("\n").map((line) => line.trim()).filter(Boolean);
17
+ if (rows.length === 0) return void 0;
18
+ return rows.map((line) => {
19
+ const [label, ...rest] = line.split(":");
20
+ return { label: label.trim(), value: rest.join(":").trim() || label.trim() };
21
+ });
22
+ }
23
+ function builderEditorSearchFieldsToText(fields) {
24
+ return (fields ?? []).join("\n");
25
+ }
26
+ function builderEditorTextToSearchFields(value) {
27
+ const fields = (value ?? "").split("\n").map((line) => line.trim()).filter(Boolean);
28
+ return fields.length > 0 ? fields : void 0;
29
+ }
30
+ function getBuilderEditorTriggerRoute(config) {
31
+ const route = config.route ?? config.path ?? config.url;
32
+ return typeof route === "string" ? route : "";
33
+ }
34
+ function valuesFromTriggers(triggers) {
35
+ return triggers.filter((trigger) => trigger.action === "callApi" || trigger.action === "webhook").map((trigger) => ({
36
+ event: trigger.event,
37
+ route: getBuilderEditorTriggerRoute(trigger.config),
38
+ enabled: trigger.enabled,
39
+ blocking: trigger.blocking
40
+ })).filter((trigger) => trigger.route);
41
+ }
42
+ function valuesFromFieldTriggers(triggers) {
43
+ return (triggers ?? []).map((trigger) => ({
44
+ event: trigger.event,
45
+ action: trigger.action,
46
+ enabled: trigger.enabled,
47
+ debounceMs: trigger.debounceMs
48
+ }));
49
+ }
50
+ function clampLayoutSpan(span, columns) {
51
+ if (!span || Number.isNaN(span)) return columns;
52
+ return Math.max(1, Math.min(columns, Math.trunc(span)));
53
+ }
54
+ function builderFormEditorDefaultSpan(field) {
55
+ if (field.type === "textarea" || field.type === "checkbox") return 12;
56
+ return 6;
57
+ }
58
+ function builderFormEditorDefaultLayoutGroups(fields, subforms = []) {
59
+ return [
60
+ {
61
+ id: "main",
62
+ title: "\u04AE\u043D\u0434\u0441\u044D\u043D",
63
+ columns: 12,
64
+ fields: [
65
+ ...fields.map((field) => ({
66
+ key: field.key,
67
+ span: builderFormEditorDefaultSpan(field)
68
+ })),
69
+ ...subforms.map((subform) => ({ key: subform.key, span: 12 }))
70
+ ]
71
+ }
72
+ ];
73
+ }
74
+ function normalizeBuilderFormEditorLayoutGroups(groups, fields, subforms = []) {
75
+ if (fields.length === 0 && subforms.length === 0) return [];
76
+ const layoutItems = [
77
+ ...fields.map((field) => ({
78
+ key: field.key,
79
+ span: builderFormEditorDefaultSpan(field)
80
+ })),
81
+ ...subforms.map((subform) => ({ key: subform.key, span: 12 }))
82
+ ];
83
+ const itemKeys = new Set(layoutItems.map((item) => item.key));
84
+ const usedKeys = /* @__PURE__ */ new Set();
85
+ const normalized = (groups ?? []).map((group, index) => {
86
+ const columns = group.columns === 6 || group.columns === 8 ? group.columns : 12;
87
+ const groupFields = (group.fields ?? []).filter((field) => itemKeys.has(field.key) && !usedKeys.has(field.key)).map((field) => {
88
+ usedKeys.add(field.key);
89
+ return { key: field.key, span: clampLayoutSpan(field.span, columns) };
90
+ });
91
+ return {
92
+ id: group.id || `group${index + 1}`,
93
+ title: group.title || `Group ${index + 1}`,
94
+ columns,
95
+ fields: groupFields
96
+ };
97
+ }).filter((group) => group.fields.length > 0 || group.title.trim());
98
+ const missingFields = layoutItems.filter((item) => !usedKeys.has(item.key));
99
+ if (normalized.length === 0) {
100
+ return builderFormEditorDefaultLayoutGroups(fields, subforms);
101
+ }
102
+ if (missingFields.length > 0) {
103
+ normalized.push({
104
+ id: "ungrouped",
105
+ title: "\u0411\u0443\u0441\u0430\u0434",
106
+ columns: 12,
107
+ fields: missingFields
108
+ });
109
+ }
110
+ return normalized;
111
+ }
112
+ function builderFormEditorValuesFromEntity(form, emptyValues) {
113
+ if (!form) return emptyValues;
114
+ const source = form.source;
115
+ const fields = form.schema.fields.map((field) => ({
116
+ key: field.key,
117
+ label: field.label,
118
+ type: field.type,
119
+ required: field.required,
120
+ hidden: field.hidden ?? false,
121
+ disabled: field.disabled ?? false,
122
+ placeholder: field.placeholder ?? "",
123
+ optionsText: builderEditorOptionsToText(field.options),
124
+ optionsMode: field.optionsSource?.type === "dbTable" ? "table" : "static",
125
+ optionsTable: field.optionsSource?.type === "dbTable" ? field.optionsSource.tableName : BUILDER_EDITOR_NONE_VALUE,
126
+ optionsValueField: field.optionsSource?.type === "dbTable" ? field.optionsSource.valueField : BUILDER_EDITOR_NONE_VALUE,
127
+ optionsLabelField: field.optionsSource?.type === "dbTable" ? field.optionsSource.labelField : BUILDER_EDITOR_NONE_VALUE,
128
+ optionsSearchFieldsText: field.optionsSource?.type === "dbTable" ? builderEditorSearchFieldsToText(field.optionsSource.searchFields) : "",
129
+ optionsDependsOn: field.optionsSource?.type === "dbTable" ? field.optionsSource.filterBy?.valueFromField ?? "" : "",
130
+ optionsFilterField: field.optionsSource?.type === "dbTable" ? field.optionsSource.filterBy?.field ?? "" : "",
131
+ triggers: valuesFromFieldTriggers(field.triggers)
132
+ }));
133
+ const subforms = (form.schema.subforms ?? []).map((subform) => ({
134
+ key: subform.key,
135
+ label: subform.label,
136
+ viewId: subform.viewId,
137
+ parentField: subform.parentField,
138
+ childField: subform.childField,
139
+ buttonLabel: subform.buttonLabel ?? "",
140
+ icon: subform.icon ?? ""
141
+ }));
142
+ return {
143
+ title: form.title,
144
+ description: form.description ?? "",
145
+ sourceType: source?.type ?? "none",
146
+ sourceTable: source?.type === "dbTable" ? source.tableName : BUILDER_EDITOR_NONE_VALUE,
147
+ apiBasePath: source?.type === "apiEndpoint" ? source.basePath : "",
148
+ fields,
149
+ layoutGroups: normalizeBuilderFormEditorLayoutGroups(
150
+ form.schema.layout?.groups,
151
+ fields,
152
+ subforms
153
+ ),
154
+ subforms,
155
+ triggers: valuesFromTriggers(form.triggers),
156
+ version: form.version
157
+ };
158
+ }
159
+ function builderListEditorValuesFromEntity(list, emptyValues) {
160
+ if (!list) return emptyValues;
161
+ const source = list.source;
162
+ return {
163
+ title: list.title,
164
+ description: list.description ?? "",
165
+ sourceType: source?.type ?? "none",
166
+ sourceTable: source?.type === "dbTable" ? source.tableName : BUILDER_EDITOR_NONE_VALUE,
167
+ apiBasePath: source?.type === "apiEndpoint" ? source.basePath : "",
168
+ columns: list.schema.columns.map((column) => ({
169
+ key: column.key,
170
+ label: column.label,
171
+ sortable: column.sortable,
172
+ align: column.align
173
+ })),
174
+ triggers: valuesFromTriggers(list.triggers),
175
+ version: list.version
176
+ };
177
+ }
178
+ function builderViewEditorValuesFromEntity(view, emptyValues, defaultPermission) {
179
+ if (!view) return emptyValues;
180
+ return {
181
+ title: view.title,
182
+ description: view.description ?? "",
183
+ formId: view.formId ?? BUILDER_EDITOR_NONE_VALUE,
184
+ listId: view.listId ?? BUILDER_EDITOR_NONE_VALUE,
185
+ permissionRead: view.permissionRead ?? defaultPermission,
186
+ permissionWrite: view.permissionWrite ?? defaultPermission,
187
+ triggersText: JSON.stringify(view.triggers ?? [], null, 2),
188
+ version: view.version
189
+ };
190
+ }
191
+ function builderFormEditorFieldsFromTable(table) {
192
+ return table.columns.map((column) => ({
193
+ key: column.name,
194
+ label: column.label,
195
+ type: column.fieldType,
196
+ required: !column.nullable,
197
+ hidden: false,
198
+ disabled: false,
199
+ placeholder: column.label,
200
+ optionsText: "",
201
+ optionsMode: "static",
202
+ optionsTable: BUILDER_EDITOR_NONE_VALUE,
203
+ optionsValueField: BUILDER_EDITOR_NONE_VALUE,
204
+ optionsLabelField: BUILDER_EDITOR_NONE_VALUE,
205
+ optionsSearchFieldsText: "",
206
+ optionsDependsOn: "",
207
+ optionsFilterField: "",
208
+ triggers: []
209
+ }));
210
+ }
211
+ function builderListEditorColumnsFromTable(table) {
212
+ return table.columns.map((column) => ({
213
+ key: column.name,
214
+ label: column.label,
215
+ sortable: column.sortable,
216
+ align: column.fieldType === "number" || column.fieldType === "money" ? "right" : "left"
217
+ }));
218
+ }
219
+ function builderFormEditorInputFromValues(values) {
220
+ const source = values.sourceType === "dbTable" && values.sourceTable !== BUILDER_EDITOR_NONE_VALUE ? { type: "dbTable", tableName: values.sourceTable } : values.sourceType === "apiEndpoint" && values.apiBasePath ? { type: "apiEndpoint", basePath: values.apiBasePath } : null;
221
+ return {
222
+ title: values.title,
223
+ description: values.description || null,
224
+ source,
225
+ schema: {
226
+ fields: values.fields.map((field) => ({
227
+ key: field.key,
228
+ label: field.label,
229
+ type: field.type,
230
+ required: field.required,
231
+ hidden: field.hidden,
232
+ disabled: field.disabled,
233
+ placeholder: field.placeholder?.trim() || field.label,
234
+ options: field.type === "select" && field.optionsMode === "static" ? builderEditorTextToOptions(field.optionsText) : void 0,
235
+ optionsSource: field.type === "select" && field.optionsMode === "table" && field.optionsTable !== BUILDER_EDITOR_NONE_VALUE && field.optionsValueField !== BUILDER_EDITOR_NONE_VALUE && field.optionsLabelField !== BUILDER_EDITOR_NONE_VALUE ? {
236
+ type: "dbTable",
237
+ tableName: field.optionsTable,
238
+ valueField: field.optionsValueField,
239
+ labelField: field.optionsLabelField,
240
+ searchFields: builderEditorTextToSearchFields(field.optionsSearchFieldsText),
241
+ filterBy: field.optionsDependsOn && field.optionsFilterField ? {
242
+ field: field.optionsFilterField,
243
+ valueFromField: field.optionsDependsOn
244
+ } : void 0
245
+ } : void 0,
246
+ triggers: field.triggers.map((trigger) => ({
247
+ event: trigger.event,
248
+ action: trigger.action,
249
+ enabled: trigger.enabled,
250
+ debounceMs: trigger.debounceMs,
251
+ config: {}
252
+ }))
253
+ })),
254
+ layout: {
255
+ groups: normalizeBuilderFormEditorLayoutGroups(
256
+ values.layoutGroups,
257
+ values.fields,
258
+ values.subforms ?? []
259
+ )
260
+ },
261
+ subforms: (values.subforms ?? []).filter(
262
+ (subform) => subform.key && subform.label && subform.viewId !== BUILDER_EDITOR_NONE_VALUE && subform.parentField && subform.childField
263
+ ).map((subform) => ({
264
+ key: subform.key,
265
+ label: subform.label,
266
+ viewId: subform.viewId,
267
+ parentField: subform.parentField,
268
+ childField: subform.childField,
269
+ buttonLabel: subform.buttonLabel?.trim() || void 0,
270
+ icon: subform.icon?.trim() || void 0
271
+ }))
272
+ },
273
+ triggers: values.triggers.map((trigger) => ({
274
+ event: trigger.event,
275
+ action: "callApi",
276
+ enabled: trigger.enabled,
277
+ blocking: trigger.blocking,
278
+ config: { route: trigger.route }
279
+ })),
280
+ version: values.version
281
+ };
282
+ }
283
+ function builderListEditorInputFromValues(values) {
284
+ const source = values.sourceType === "dbTable" && values.sourceTable !== BUILDER_EDITOR_NONE_VALUE ? { type: "dbTable", tableName: values.sourceTable } : values.sourceType === "apiEndpoint" && values.apiBasePath ? { type: "apiEndpoint", basePath: values.apiBasePath } : null;
285
+ return {
286
+ title: values.title,
287
+ description: values.description || null,
288
+ source,
289
+ schema: { columns: values.columns },
290
+ triggers: values.triggers.map((trigger) => ({
291
+ event: trigger.event,
292
+ action: "callApi",
293
+ enabled: trigger.enabled,
294
+ blocking: trigger.blocking,
295
+ config: { route: trigger.route }
296
+ })),
297
+ version: values.version
298
+ };
299
+ }
300
+ function builderViewEditorInputFromValues(values, parseTriggers) {
301
+ return {
302
+ title: values.title,
303
+ description: values.description || null,
304
+ formId: values.formId === BUILDER_EDITOR_NONE_VALUE ? null : values.formId,
305
+ listId: values.listId === BUILDER_EDITOR_NONE_VALUE ? null : values.listId,
306
+ permissionRead: values.permissionRead,
307
+ permissionWrite: values.permissionWrite,
308
+ triggers: values.triggersText?.trim() ? parseTriggers(values.triggersText) : [],
309
+ version: values.version
310
+ };
311
+ }
312
+
313
+ // src/admin/BuilderEditorPage.tsx
314
+ import { jsx } from "react/jsx-runtime";
315
+ function createBuilderFormEditorPageComponent(deps) {
316
+ return function BuilderFormEditorPage({ formId }) {
317
+ const Body = deps.Body;
318
+ const entityQuery = deps.hooks.useEntity(formId);
319
+ const tables = deps.hooks.useTables();
320
+ const save = deps.hooks.useSave();
321
+ const form = useForm({
322
+ resolver: deps.resolver,
323
+ defaultValues: deps.emptyValues
324
+ });
325
+ const fieldArray = useFieldArray({ control: form.control, name: "fields" });
326
+ const subformArray = useFieldArray({ control: form.control, name: "subforms" });
327
+ const triggerArray = useFieldArray({ control: form.control, name: "triggers" });
328
+ const fields = useWatch({ control: form.control, name: "fields" }) ?? [];
329
+ const subforms = useWatch({ control: form.control, name: "subforms" }) ?? [];
330
+ const sourceType = useWatch({ control: form.control, name: "sourceType" });
331
+ const sourceTable = useWatch({ control: form.control, name: "sourceTable" });
332
+ useEffect(() => {
333
+ if (formId && !entityQuery.data) return;
334
+ form.reset(builderFormEditorValuesFromEntity(entityQuery.data ?? null, deps.emptyValues));
335
+ }, [entityQuery.data, form, formId]);
336
+ const importFieldsFromTable = () => {
337
+ const table = tables.data?.find((item) => item.name === sourceTable);
338
+ if (!table) return;
339
+ const nextFields = builderFormEditorFieldsFromTable(table);
340
+ fieldArray.replace(nextFields);
341
+ form.setValue("layoutGroups", builderFormEditorDefaultLayoutGroups(nextFields), {
342
+ shouldDirty: true
343
+ });
344
+ };
345
+ const onSubmit = form.handleSubmit(async (values) => {
346
+ try {
347
+ const saved = await save.mutateAsync({ id: formId, input: deps.buildInput(values) });
348
+ deps.notifications.success(formId ? deps.labels.saved : deps.labels.created);
349
+ if (!formId) deps.navigateAfterCreate(saved);
350
+ } catch (error) {
351
+ deps.notifications.error(error);
352
+ }
353
+ });
354
+ if (formId && entityQuery.isPending) {
355
+ return /* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-sm", children: deps.states.loading });
356
+ }
357
+ if (formId && entityQuery.isError) {
358
+ return /* @__PURE__ */ jsx("div", { className: "text-destructive text-sm", children: deps.states.error });
359
+ }
360
+ return /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsx("form", { onSubmit, noValidate: true, className: "space-y-6", children: /* @__PURE__ */ jsx(
361
+ Body,
362
+ {
363
+ formId,
364
+ form,
365
+ fieldArray,
366
+ subformArray,
367
+ triggerArray,
368
+ fields,
369
+ subforms,
370
+ sourceType,
371
+ sourceTable,
372
+ tables: tables.data ?? [],
373
+ emptyField: deps.emptyField,
374
+ emptyTrigger: deps.emptyTrigger,
375
+ isSaving: save.isPending,
376
+ importFieldsFromTable
377
+ }
378
+ ) }) });
379
+ };
380
+ }
381
+ function createBuilderListEditorPageComponent(deps) {
382
+ return function BuilderListEditorPage({ listId }) {
383
+ const Body = deps.Body;
384
+ const entityQuery = deps.hooks.useEntity(listId);
385
+ const tables = deps.hooks.useTables();
386
+ const save = deps.hooks.useSave();
387
+ const form = useForm({
388
+ resolver: deps.resolver,
389
+ defaultValues: deps.emptyValues
390
+ });
391
+ const columnArray = useFieldArray({ control: form.control, name: "columns" });
392
+ const triggerArray = useFieldArray({ control: form.control, name: "triggers" });
393
+ const sourceType = useWatch({ control: form.control, name: "sourceType" });
394
+ const sourceTable = useWatch({ control: form.control, name: "sourceTable" });
395
+ useEffect(() => {
396
+ if (listId && !entityQuery.data) return;
397
+ form.reset(builderListEditorValuesFromEntity(entityQuery.data ?? null, deps.emptyValues));
398
+ }, [entityQuery.data, form, listId]);
399
+ const importColumnsFromTable = () => {
400
+ const table = tables.data?.find((item) => item.name === sourceTable);
401
+ if (!table) return;
402
+ columnArray.replace(builderListEditorColumnsFromTable(table));
403
+ };
404
+ const onSubmit = form.handleSubmit(async (values) => {
405
+ try {
406
+ const saved = await save.mutateAsync({ id: listId, input: deps.buildInput(values) });
407
+ deps.notifications.success(listId ? deps.labels.saved : deps.labels.created);
408
+ if (!listId) deps.navigateAfterCreate(saved);
409
+ } catch (error) {
410
+ deps.notifications.error(error);
411
+ }
412
+ });
413
+ if (listId && entityQuery.isPending) {
414
+ return /* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-sm", children: deps.states.loading });
415
+ }
416
+ if (listId && entityQuery.isError) {
417
+ return /* @__PURE__ */ jsx("div", { className: "text-destructive text-sm", children: deps.states.error });
418
+ }
419
+ return /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsx("form", { onSubmit, noValidate: true, className: "space-y-6", children: /* @__PURE__ */ jsx(
420
+ Body,
421
+ {
422
+ listId,
423
+ form,
424
+ columnArray,
425
+ triggerArray,
426
+ sourceType,
427
+ sourceTable,
428
+ tables: tables.data ?? [],
429
+ emptyColumn: deps.emptyColumn,
430
+ emptyTrigger: deps.emptyTrigger,
431
+ isSaving: save.isPending,
432
+ importColumnsFromTable
433
+ }
434
+ ) }) });
435
+ };
436
+ }
437
+
438
+ // src/admin/BuilderEntityManager.tsx
439
+ import { useCallback, useMemo, useState } from "react";
440
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
441
+ function createBuilderEntityManagerComponent(deps) {
442
+ return function BuilderEntityManager() {
443
+ const [pendingDelete, setPendingDelete] = useState(null);
444
+ const state = deps.useDataTableState({ pageSize: 20 });
445
+ const query = deps.hooks.useEntities(state.query);
446
+ const remove = deps.hooks.useDelete();
447
+ const replicate = deps.hooks.useReplicate();
448
+ const columns = useMemo(
449
+ () => deps.buildColumns({
450
+ onEdit: deps.navigateToEdit,
451
+ onReplicate: async (entity) => {
452
+ const title = await deps.requestReplicateTitle(entity);
453
+ if (!title) return;
454
+ await replicate.mutateAsync({ id: entity.id, input: { title } });
455
+ },
456
+ onDelete: setPendingDelete,
457
+ runtimeHref: deps.runtimeHref
458
+ }),
459
+ [replicate]
460
+ );
461
+ const controller = deps.useDataTable({
462
+ columns,
463
+ data: query.data?.items ?? [],
464
+ rowCount: query.data?.total ?? 0,
465
+ state,
466
+ getRowId: (row) => row.id
467
+ });
468
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
469
+ /* @__PURE__ */ jsx2(
470
+ deps.DataGrid,
471
+ {
472
+ title: deps.labels.title,
473
+ description: deps.labels.description,
474
+ controller,
475
+ isLoading: query.isPending,
476
+ searchPlaceholder: deps.labels.searchPlaceholder,
477
+ emptyTitle: deps.labels.emptyTitle,
478
+ emptyDescription: deps.labels.emptyDescription,
479
+ onRowClick: deps.navigateToEdit,
480
+ toolbarExtra: /* @__PURE__ */ jsx2(deps.AddButton, { href: deps.createHref, label: deps.labels.addLabel })
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsx2(
484
+ deps.DeleteDialog,
485
+ {
486
+ entity: pendingDelete,
487
+ title: deps.labels.deleteTitle,
488
+ description: deps.labels.deleteDescription(pendingDelete),
489
+ isDeleting: remove.isPending,
490
+ onClose: () => setPendingDelete(null),
491
+ onConfirm: async () => {
492
+ if (!pendingDelete) return;
493
+ await remove.mutateAsync(pendingDelete.id);
494
+ setPendingDelete(null);
495
+ }
496
+ }
497
+ )
498
+ ] });
499
+ };
500
+ }
501
+ function createBuilderEntityDialogManagerComponent(deps) {
502
+ return function BuilderEntityDialogManager() {
503
+ const [editing, setEditing] = useState(null);
504
+ const [isEditorOpen, setEditorOpen] = useState(false);
505
+ const [pendingDelete, setPendingDelete] = useState(null);
506
+ const state = deps.useDataTableState({ pageSize: 20 });
507
+ const query = deps.hooks.useEntities(state.query);
508
+ const remove = deps.hooks.useDelete();
509
+ const replicate = deps.hooks.useReplicate();
510
+ const openEditor = useCallback((entity) => {
511
+ setEditing(entity);
512
+ setEditorOpen(true);
513
+ }, []);
514
+ const columns = useMemo(
515
+ () => deps.buildColumns({
516
+ onEdit: (entity) => openEditor(entity),
517
+ onReplicate: async (entity) => {
518
+ const title = await deps.requestReplicateTitle(entity);
519
+ if (!title) return;
520
+ await replicate.mutateAsync({ id: entity.id, input: { title } });
521
+ },
522
+ onDelete: setPendingDelete,
523
+ runtimeHref: deps.runtimeHref
524
+ }),
525
+ [replicate, openEditor]
526
+ );
527
+ const controller = deps.useDataTable({
528
+ columns,
529
+ data: query.data?.items ?? [],
530
+ rowCount: query.data?.total ?? 0,
531
+ state,
532
+ getRowId: (row) => row.id
533
+ });
534
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
535
+ /* @__PURE__ */ jsx2(
536
+ deps.DataGrid,
537
+ {
538
+ title: deps.labels.title,
539
+ description: deps.labels.description,
540
+ controller,
541
+ isLoading: query.isPending,
542
+ searchPlaceholder: deps.labels.searchPlaceholder,
543
+ emptyTitle: deps.labels.emptyTitle,
544
+ emptyDescription: deps.labels.emptyDescription,
545
+ onRowClick: openEditor,
546
+ toolbarExtra: /* @__PURE__ */ jsx2(deps.AddButton, { label: deps.labels.addLabel, onAdd: () => openEditor(null) })
547
+ }
548
+ ),
549
+ /* @__PURE__ */ jsx2(
550
+ deps.EditorDialog,
551
+ {
552
+ open: isEditorOpen,
553
+ editing,
554
+ onClose: () => setEditorOpen(false)
555
+ }
556
+ ),
557
+ /* @__PURE__ */ jsx2(
558
+ deps.DeleteDialog,
559
+ {
560
+ entity: pendingDelete,
561
+ title: deps.labels.deleteTitle,
562
+ description: deps.labels.deleteDescription(pendingDelete),
563
+ isDeleting: remove.isPending,
564
+ onClose: () => setPendingDelete(null),
565
+ onConfirm: async () => {
566
+ if (!pendingDelete) return;
567
+ await remove.mutateAsync(pendingDelete.id);
568
+ setPendingDelete(null);
569
+ }
570
+ }
571
+ )
572
+ ] });
573
+ };
574
+ }
575
+
576
+ // src/admin/BuilderViewDialog.tsx
577
+ import { useEffect as useEffect2 } from "react";
578
+ import { FormProvider as FormProvider2, useForm as useForm2 } from "react-hook-form";
579
+ import { jsx as jsx3 } from "react/jsx-runtime";
580
+ function createBuilderViewDialogComponent(deps) {
581
+ return function BuilderViewDialog({
582
+ open,
583
+ editing,
584
+ forms,
585
+ lists,
586
+ onClose,
587
+ onSave,
588
+ isSaving
589
+ }) {
590
+ const form = useForm2({
591
+ resolver: deps.resolver,
592
+ defaultValues: deps.emptyValues
593
+ });
594
+ useEffect2(() => {
595
+ if (!open) return;
596
+ form.reset(
597
+ builderViewEditorValuesFromEntity(editing, deps.emptyValues, deps.defaultPermission)
598
+ );
599
+ }, [editing, form, open]);
600
+ const onSubmit = form.handleSubmit(async (values) => {
601
+ try {
602
+ await onSave({ id: editing?.id, input: deps.buildInput(values) });
603
+ onClose();
604
+ deps.notifications.success(editing ? deps.labels.saved : deps.labels.created);
605
+ } catch (error) {
606
+ deps.notifications.error(error);
607
+ }
608
+ });
609
+ return /* @__PURE__ */ jsx3(deps.Shell, { open, editing, onClose, children: /* @__PURE__ */ jsx3(FormProvider2, { ...form, children: /* @__PURE__ */ jsx3("form", { onSubmit, noValidate: true, children: deps.Body({
610
+ form,
611
+ editing,
612
+ forms,
613
+ lists,
614
+ permissionOptions: deps.permissionOptions,
615
+ noneValue: deps.noneValue,
616
+ isSaving,
617
+ onClose
618
+ }) }) }) });
619
+ };
620
+ }
621
+
622
+ // src/api/createBuilderApiClient.ts
623
+ function queryParams(query) {
624
+ return { ...query };
625
+ }
626
+ function fieldOptionsQueryParams(query) {
627
+ const flatQuery = { search: query.search };
628
+ for (const [key, value] of Object.entries(query.depends ?? {})) {
629
+ flatQuery[`depends.${key}`] = value;
630
+ }
631
+ return flatQuery;
632
+ }
633
+ function createBuilderApiClient(transport) {
634
+ return {
635
+ listBuilderForms: (query) => transport.get("/builder/forms", {
636
+ query: queryParams(query)
637
+ }),
638
+ listBuilderDbTables: () => transport.get("/builder/datasources/tables"),
639
+ listBuilderFormOptions: () => transport.get("/builder/forms/options"),
640
+ getBuilderForm: (id) => transport.get(`/builder/forms/${encodeURIComponent(id)}`),
641
+ createBuilderForm: (input) => transport.post("/builder/forms", input),
642
+ updateBuilderForm: (id, input) => transport.patch(`/builder/forms/${encodeURIComponent(id)}`, input),
643
+ replicateBuilderForm: (id, input) => transport.post(`/builder/forms/${encodeURIComponent(id)}/replicate`, input),
644
+ deleteBuilderForm: (id) => transport.delete(`/builder/forms/${encodeURIComponent(id)}`),
645
+ listBuilderLists: (query) => transport.get("/builder/lists", {
646
+ query: queryParams(query)
647
+ }),
648
+ listBuilderListOptions: () => transport.get("/builder/lists/options"),
649
+ getBuilderList: (id) => transport.get(`/builder/lists/${encodeURIComponent(id)}`),
650
+ createBuilderList: (input) => transport.post("/builder/lists", input),
651
+ updateBuilderList: (id, input) => transport.patch(`/builder/lists/${encodeURIComponent(id)}`, input),
652
+ replicateBuilderList: (id, input) => transport.post(`/builder/lists/${encodeURIComponent(id)}/replicate`, input),
653
+ deleteBuilderList: (id) => transport.delete(`/builder/lists/${encodeURIComponent(id)}`),
654
+ listBuilderViews: (query) => transport.get("/builder/views", {
655
+ query: queryParams(query)
656
+ }),
657
+ getBuilderView: (id) => transport.get(`/builder/views/${encodeURIComponent(id)}`),
658
+ createBuilderView: (input) => transport.post("/builder/views", input),
659
+ updateBuilderView: (id, input) => transport.patch(`/builder/views/${encodeURIComponent(id)}`, input),
660
+ replicateBuilderView: (id, input) => transport.post(`/builder/views/${encodeURIComponent(id)}/replicate`, input),
661
+ deleteBuilderView: (id) => transport.delete(`/builder/views/${encodeURIComponent(id)}`),
662
+ getRuntimeBuilderForm: (id) => transport.get(`/builder/runtime/forms/${encodeURIComponent(id)}`),
663
+ createRuntimeBuilderFormEntry: (formId, input) => transport.post(
664
+ `/builder/runtime/forms/${encodeURIComponent(formId)}/entries`,
665
+ input
666
+ ),
667
+ listRuntimeBuilderFormFieldOptions: (formId, fieldKey, query) => transport.get(
668
+ `/builder/runtime/forms/${encodeURIComponent(formId)}/fields/${encodeURIComponent(fieldKey)}/options`,
669
+ { query: fieldOptionsQueryParams(query) }
670
+ ),
671
+ runRuntimeBuilderFormFieldTrigger: (formId, fieldKey, event, input) => transport.post(
672
+ `/builder/runtime/forms/${encodeURIComponent(formId)}/fields/${encodeURIComponent(fieldKey)}/triggers/${encodeURIComponent(event)}`,
673
+ input
674
+ ),
675
+ getRuntimeBuilderList: (id) => transport.get(`/builder/runtime/lists/${encodeURIComponent(id)}`),
676
+ listRuntimeBuilderListEntries: (listId, query) => transport.get(
677
+ `/builder/runtime/lists/${encodeURIComponent(listId)}/entries`,
678
+ { query: queryParams(query) }
679
+ ),
680
+ getRuntimeBuilderView: (id) => transport.get(`/builder/runtime/views/${encodeURIComponent(id)}`),
681
+ runRuntimeBuilderViewFieldTrigger: (viewId, fieldKey, event, input) => transport.post(
682
+ `/builder/runtime/views/${encodeURIComponent(viewId)}/fields/${encodeURIComponent(fieldKey)}/triggers/${encodeURIComponent(event)}`,
683
+ input
684
+ ),
685
+ listBuilderViewEntries: (viewId, query) => transport.get(
686
+ `/builder/runtime/views/${encodeURIComponent(viewId)}/entries`,
687
+ { query: queryParams(query) }
688
+ ),
689
+ createBuilderViewEntry: (viewId, input) => transport.post(
690
+ `/builder/runtime/views/${encodeURIComponent(viewId)}/entries`,
691
+ input
692
+ ),
693
+ createCompositeBuilderViewEntry: (viewId, input) => transport.post(
694
+ `/builder/runtime/views/${encodeURIComponent(viewId)}/composite-entries`,
695
+ input
696
+ ),
697
+ updateCompositeBuilderViewEntry: (viewId, id, input) => transport.patch(
698
+ `/builder/runtime/views/${encodeURIComponent(viewId)}/composite-entries/${encodeURIComponent(id)}`,
699
+ input
700
+ ),
701
+ updateBuilderViewEntry: (viewId, id, input) => transport.patch(
702
+ `/builder/runtime/views/${encodeURIComponent(viewId)}/entries/${encodeURIComponent(id)}`,
703
+ input
704
+ ),
705
+ deleteBuilderViewEntry: (viewId, id) => transport.delete(
706
+ `/builder/runtime/views/${encodeURIComponent(viewId)}/entries/${encodeURIComponent(id)}`
707
+ )
708
+ };
709
+ }
710
+
711
+ // src/components/DynamicForm.tsx
712
+ import { useMemo as useMemo2 } from "react";
713
+ import { FormProvider as FormProvider3, useForm as useForm3 } from "react-hook-form";
714
+
715
+ // src/runtime/runtimeUtils.ts
716
+ function emptyFieldValue(field) {
717
+ return field.type === "checkbox" ? false : "";
718
+ }
719
+ function createBuilderFormDefaultValues(schema) {
720
+ const values = {};
721
+ for (const field of schema?.fields ?? []) {
722
+ values[field.key] = emptyFieldValue(field);
723
+ }
724
+ return { values };
725
+ }
726
+ function createBuilderEntryDefaultValues(view, editing) {
727
+ const values = {};
728
+ for (const field of view.form?.schema.fields ?? []) {
729
+ const existing = editing?.values[field.key];
730
+ values[field.key] = existing === void 0 ? emptyFieldValue(field) : existing;
731
+ }
732
+ return { values, version: editing?.version ?? 0 };
733
+ }
734
+ function formatBuilderRuntimeValue(value, locale = "mn-MN") {
735
+ if (value === null || value === void 0 || value === "") return "\u2014";
736
+ if (typeof value === "boolean") return value ? "\u0422\u0438\u0439\u043C" : "\u04AE\u0433\u04AF\u0439";
737
+ if (typeof value === "number") return new Intl.NumberFormat(locale).format(value);
738
+ return String(value);
739
+ }
740
+
741
+ // src/components/DynamicForm.tsx
742
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
743
+ function createDynamicFormComponent(deps) {
744
+ return function DynamicForm({ formId }) {
745
+ const query = deps.hooks.useRuntimeBuilderForm(formId);
746
+ const save = deps.hooks.useSaveRuntimeBuilderFormEntry(formId);
747
+ const formValues = useMemo2(
748
+ () => query.data ? createBuilderFormDefaultValues(query.data.schema) : void 0,
749
+ [query.data]
750
+ );
751
+ const form = useForm3({
752
+ defaultValues: createBuilderFormDefaultValues(query.data?.schema),
753
+ values: formValues
754
+ });
755
+ const onSubmit = form.handleSubmit(async (values) => {
756
+ try {
757
+ await save.mutateAsync(values);
758
+ form.reset(createBuilderFormDefaultValues(query.data?.schema));
759
+ deps.notifications.success("Form \u0445\u0430\u0434\u0433\u0430\u043B\u0430\u0433\u0434\u043B\u0430\u0430");
760
+ } catch (error) {
761
+ deps.notifications.error(deps.notifications.errorMessage(error, "\u0425\u0430\u0434\u0433\u0430\u043B\u0430\u0445\u0430\u0434 \u0430\u043B\u0434\u0430\u0430 \u0433\u0430\u0440\u043B\u0430\u0430"));
762
+ }
763
+ });
764
+ if (query.isError) {
765
+ return /* @__PURE__ */ jsx4(
766
+ deps.ErrorState,
767
+ {
768
+ title: "Form \u043D\u044D\u044D\u0433\u0434\u0441\u044D\u043D\u0433\u04AF\u0439",
769
+ description: `\xAB${formId}\xBB id-\u0442\u0430\u0439 form \u043E\u043B\u0434\u0441\u043E\u043D\u0433\u04AF\u0439.`
770
+ }
771
+ );
772
+ }
773
+ if (!query.data) return null;
774
+ return /* @__PURE__ */ jsx4(deps.FormShell, { title: query.data.title, children: /* @__PURE__ */ jsx4(FormProvider3, { ...form, children: /* @__PURE__ */ jsxs2("form", { onSubmit, noValidate: true, className: "space-y-6", children: [
775
+ /* @__PURE__ */ jsx4(deps.FormFields, { formId, schema: query.data.schema, namePrefix: "values" }),
776
+ /* @__PURE__ */ jsx4(deps.SubmitButton, { isSaving: save.isPending })
777
+ ] }) }) });
778
+ };
779
+ }
780
+
781
+ // src/components/DynamicList.tsx
782
+ import { useMemo as useMemo3 } from "react";
783
+ import { jsx as jsx5 } from "react/jsx-runtime";
784
+ function createDynamicListComponent(deps) {
785
+ return function DynamicList({ listId }) {
786
+ const query = deps.hooks.useRuntimeBuilderList(listId);
787
+ const state = deps.useDataTableState({ pageSize: 20 });
788
+ const entriesQuery = deps.hooks.useRuntimeBuilderListEntries(listId, state.query);
789
+ const columns = useMemo3(() => query.data ? deps.buildColumns(query.data) : [], [query.data]);
790
+ const entries = entriesQuery.data?.items ?? [];
791
+ const controller = deps.useDataTable({
792
+ columns,
793
+ data: entries,
794
+ rowCount: entriesQuery.data?.total ?? 0,
795
+ state,
796
+ getRowId: (row) => row.id
797
+ });
798
+ if (query.isError) {
799
+ return /* @__PURE__ */ jsx5(
800
+ deps.ErrorState,
801
+ {
802
+ title: "List \u043D\u044D\u044D\u0433\u0434\u0441\u044D\u043D\u0433\u04AF\u0439",
803
+ description: `\xAB${listId}\xBB id-\u0442\u0430\u0439 list \u043E\u043B\u0434\u0441\u043E\u043D\u0433\u04AF\u0439.`
804
+ }
805
+ );
806
+ }
807
+ return /* @__PURE__ */ jsx5(
808
+ deps.DataGrid,
809
+ {
810
+ title: query.data?.title ?? "Dynamic List",
811
+ description: query.data?.description ?? `/winu/list?id=${listId}`,
812
+ controller,
813
+ isLoading: query.isPending || entriesQuery.isPending,
814
+ searchPlaceholder: "\u041C\u04E9\u0440 \u0445\u0430\u0439\u0445...",
815
+ emptyTitle: "\u041C\u04E9\u0440 \u0430\u043B\u0433\u0430",
816
+ emptyDescription: "\u042D\u043D\u044D list-\u0438\u0439\u043D datasource \u0434\u044D\u044D\u0440 \u043C\u04E9\u0440 \u0430\u043B\u0433\u0430."
817
+ }
818
+ );
819
+ };
820
+ }
821
+
822
+ // src/components/DynamicView.tsx
823
+ import { useMemo as useMemo4, useState as useState2 } from "react";
824
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
825
+ function createDynamicViewComponent(deps) {
826
+ return function DynamicView({ viewId }) {
827
+ const [editing, setEditing] = useState2(null);
828
+ const [isDialogOpen, setDialogOpen] = useState2(false);
829
+ const [pendingDelete, setPendingDelete] = useState2(null);
830
+ const viewQuery = deps.hooks.useRuntimeBuilderView(viewId);
831
+ const state = deps.useDataTableState({ pageSize: 20 });
832
+ const entriesQuery = deps.hooks.useBuilderViewEntries(viewId, state.query);
833
+ const save = deps.hooks.useSaveBuilderViewEntry(viewId);
834
+ const remove = deps.hooks.useDeleteBuilderViewEntry(viewId);
835
+ const view = viewQuery.data;
836
+ const columns = useMemo4(
837
+ () => view ? deps.buildColumns(view, {
838
+ onEdit: (entry) => {
839
+ setEditing(entry);
840
+ setDialogOpen(true);
841
+ },
842
+ onDelete: setPendingDelete
843
+ }) : [],
844
+ [view]
845
+ );
846
+ const controller = deps.useDataTable({
847
+ columns,
848
+ data: entriesQuery.data?.items ?? [],
849
+ rowCount: entriesQuery.data?.total ?? 0,
850
+ state,
851
+ getRowId: (row) => row.id
852
+ });
853
+ if (viewQuery.isError) {
854
+ return /* @__PURE__ */ jsx6(
855
+ deps.ErrorState,
856
+ {
857
+ title: "View \u043D\u044D\u044D\u0433\u0434\u0441\u044D\u043D\u0433\u04AF\u0439",
858
+ description: `\xAB${viewId}\xBB id-\u0442\u0430\u0439 view \u043E\u043B\u0434\u0441\u043E\u043D\u0433\u04AF\u0439.`
859
+ }
860
+ );
861
+ }
862
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-6", children: [
863
+ /* @__PURE__ */ jsx6(
864
+ deps.DataGrid,
865
+ {
866
+ title: view?.title ?? "Dynamic View",
867
+ description: view?.description ?? `/winu/view?id=${viewId}`,
868
+ controller,
869
+ isLoading: viewQuery.isPending || entriesQuery.isPending,
870
+ searchPlaceholder: "\u041C\u04E9\u0440 \u0445\u0430\u0439\u0445...",
871
+ emptyTitle: "\u041C\u04E9\u0440 \u0430\u043B\u0433\u0430",
872
+ emptyDescription: "\u042D\u043D\u044D view \u0434\u044D\u044D\u0440 \u044D\u0445\u043D\u0438\u0439 \u043C\u04E9\u0440\u04E9\u04E9 \u043D\u044D\u043C\u044D\u044D\u0440\u044D\u0439.",
873
+ onRowClick: (entry) => {
874
+ setEditing(entry);
875
+ setDialogOpen(true);
876
+ },
877
+ toolbarExtra: /* @__PURE__ */ jsx6(
878
+ deps.AddButton,
879
+ {
880
+ disabled: !view?.form,
881
+ onAdd: () => {
882
+ setEditing(null);
883
+ setDialogOpen(true);
884
+ }
885
+ }
886
+ )
887
+ }
888
+ ),
889
+ view ? /* @__PURE__ */ jsx6(
890
+ deps.EntryDialog,
891
+ {
892
+ open: isDialogOpen,
893
+ view,
894
+ editing,
895
+ onClose: () => setDialogOpen(false),
896
+ onSave: save.mutateAsync,
897
+ isSaving: save.isPending
898
+ }
899
+ ) : null,
900
+ /* @__PURE__ */ jsx6(
901
+ deps.DeleteDialog,
902
+ {
903
+ entry: pendingDelete,
904
+ isDeleting: remove.isPending,
905
+ onClose: () => setPendingDelete(null),
906
+ onConfirm: async () => {
907
+ if (!pendingDelete) return;
908
+ await remove.mutateAsync(pendingDelete.id);
909
+ setPendingDelete(null);
910
+ }
911
+ }
912
+ )
913
+ ] });
914
+ };
915
+ }
916
+
917
+ // src/components/RuntimeEntryDialog.tsx
918
+ import { useEffect as useEffect3, useMemo as useMemo5, useState as useState3 } from "react";
919
+ import { FormProvider as FormProvider4, useForm as useForm4 } from "react-hook-form";
920
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
921
+ function isDbTableSource(source) {
922
+ return source?.type === "dbTable" && "tableName" in source;
923
+ }
924
+ function runtimeViewTableName(view) {
925
+ const listSource = view.list?.source;
926
+ if (isDbTableSource(listSource)) return listSource.tableName;
927
+ const formSource = view.form?.source;
928
+ return isDbTableSource(formSource) ? formSource.tableName : void 0;
929
+ }
930
+ function cleanRowValues(values) {
931
+ return Object.fromEntries(
932
+ Object.entries(values).filter(([key]) => key === "_delete" || !key.startsWith("_"))
933
+ );
934
+ }
935
+ function childRowPayload(row, includeIdentity) {
936
+ const values = cleanRowValues(row.values);
937
+ if (!includeIdentity || row.values._draftNew === true) return values;
938
+ return {
939
+ id: row.id,
940
+ version: row.version,
941
+ ...values
942
+ };
943
+ }
944
+ function createRuntimeEntryDialogComponent(deps) {
945
+ return function RuntimeEntryDialog({
946
+ open,
947
+ view,
948
+ editing,
949
+ onClose,
950
+ onSave,
951
+ isSaving
952
+ }) {
953
+ const [draftSubforms, setDraftSubforms] = useState3({});
954
+ const compositeCreate = deps.useCreateCompositeEntry?.(view.id);
955
+ const compositeUpdate = deps.useUpdateCompositeEntry?.(view.id);
956
+ const form = useForm4({
957
+ defaultValues: createBuilderEntryDefaultValues(view, editing),
958
+ values: open ? createBuilderEntryDefaultValues(view, editing) : void 0
959
+ });
960
+ const mainTableName = runtimeViewTableName(view);
961
+ const hasDraftRows = useMemo5(
962
+ () => Object.values(draftSubforms).some((draft) => draft.rows.length > 0),
963
+ [draftSubforms]
964
+ );
965
+ useEffect3(() => {
966
+ if (open) setDraftSubforms({});
967
+ }, [open]);
968
+ const onSubmit = form.handleSubmit(async (values) => {
969
+ try {
970
+ if (!editing && hasDraftRows) {
971
+ if (!mainTableName) throw new Error("Main form datasource table \u043E\u043B\u0434\u0441\u043E\u043D\u0433\u04AF\u0439");
972
+ if (!compositeCreate) throw new Error("Composite create handler \u0442\u043E\u0445\u0438\u0440\u0443\u0443\u043B\u0430\u0433\u0434\u0430\u0430\u0433\u04AF\u0439 \u0431\u0430\u0439\u043D\u0430");
973
+ await compositeCreate.mutateAsync({
974
+ data: {
975
+ [mainTableName]: values.values,
976
+ ...Object.fromEntries(
977
+ Object.values(draftSubforms).map((draft) => [
978
+ draft.dataKey,
979
+ draft.rows.filter((row) => row.values._delete !== true).map((row) => childRowPayload(row, false))
980
+ ])
981
+ )
982
+ }
983
+ });
984
+ } else if (editing && hasDraftRows) {
985
+ if (!mainTableName) throw new Error("Main form datasource table \u043E\u043B\u0434\u0441\u043E\u043D\u0433\u04AF\u0439");
986
+ if (!compositeUpdate) throw new Error("Composite update handler \u0442\u043E\u0445\u0438\u0440\u0443\u0443\u043B\u0430\u0433\u0434\u0430\u0430\u0433\u04AF\u0439 \u0431\u0430\u0439\u043D\u0430");
987
+ await compositeUpdate.mutateAsync({
988
+ id: editing.id,
989
+ input: {
990
+ data: {
991
+ [mainTableName]: {
992
+ id: editing.id,
993
+ version: values.version,
994
+ ...values.values
995
+ },
996
+ ...Object.fromEntries(
997
+ Object.values(draftSubforms).map((draft) => [
998
+ draft.dataKey,
999
+ draft.rows.map((row) => childRowPayload(row, true))
1000
+ ])
1001
+ )
1002
+ }
1003
+ }
1004
+ });
1005
+ } else {
1006
+ await onSave({ id: editing?.id, input: values });
1007
+ }
1008
+ onClose();
1009
+ deps.notifications.success(editing ? "\u041C\u04E9\u0440 \u0445\u0430\u0434\u0433\u0430\u043B\u0430\u0433\u0434\u043B\u0430\u0430" : "\u041C\u04E9\u0440 \u043D\u044D\u043C\u044D\u0433\u0434\u043B\u044D\u044D");
1010
+ } catch (error) {
1011
+ deps.notifications.error(deps.notifications.errorMessage(error, "\u0425\u0430\u0434\u0433\u0430\u043B\u0430\u0445\u0430\u0434 \u0430\u043B\u0434\u0430\u0430 \u0433\u0430\u0440\u043B\u0430\u0430"));
1012
+ }
1013
+ });
1014
+ const saving = isSaving || Boolean(compositeCreate?.isPending || compositeUpdate?.isPending);
1015
+ const extraItems = deps.SubformField && view.form?.schema.subforms?.length ? view.form.schema.subforms.map((subform) => ({
1016
+ key: subform.key,
1017
+ render: () => deps.SubformField ? /* @__PURE__ */ jsx7(
1018
+ deps.SubformField,
1019
+ {
1020
+ subform,
1021
+ parentEntry: editing,
1022
+ draftRows: draftSubforms[subform.key]?.rows ?? [],
1023
+ onDraftRowsChange: ({ subformKey, dataKey, rows }) => {
1024
+ setDraftSubforms((current) => ({
1025
+ ...current,
1026
+ [subformKey]: { dataKey, rows }
1027
+ }));
1028
+ }
1029
+ }
1030
+ ) : null
1031
+ })) : void 0;
1032
+ return /* @__PURE__ */ jsx7(
1033
+ deps.DialogShell,
1034
+ {
1035
+ open,
1036
+ title: editing ? "\u041C\u04E9\u0440 \u0437\u0430\u0441\u0430\u0445" : "\u041C\u04E9\u0440 \u043D\u044D\u043C\u044D\u0445",
1037
+ description: view.title,
1038
+ onClose,
1039
+ children: /* @__PURE__ */ jsx7(FormProvider4, { ...form, children: /* @__PURE__ */ jsxs4("form", { onSubmit, noValidate: true, className: deps.formClassName, children: [
1040
+ view.form ? /* @__PURE__ */ jsx7(
1041
+ deps.FormFields,
1042
+ {
1043
+ formId: view.form.id,
1044
+ viewId: view.id,
1045
+ entry: editing,
1046
+ schema: view.form.schema,
1047
+ namePrefix: "values",
1048
+ extraItems
1049
+ }
1050
+ ) : null,
1051
+ /* @__PURE__ */ jsx7(deps.Actions, { isSaving: saving, onClose })
1052
+ ] }) })
1053
+ }
1054
+ );
1055
+ };
1056
+ }
1057
+
1058
+ // src/components/RuntimeSubformField.tsx
1059
+ import { useEffect as useEffect4, useMemo as useMemo6, useRef, useState as useState4 } from "react";
1060
+ import { FormProvider as FormProvider5, useForm as useForm5 } from "react-hook-form";
1061
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1062
+
1063
+ // src/hooks/createBuilderHooks.ts
1064
+ import { keepPreviousData, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
1065
+ function createBuilderKeys() {
1066
+ const builderKeys = {
1067
+ forms: ["builder", "forms"],
1068
+ dbTables: () => ["builder", "datasources", "tables"],
1069
+ formList: (query) => [...builderKeys.forms, "list", query],
1070
+ form: (id) => [...builderKeys.forms, "detail", id],
1071
+ formOptions: () => [...builderKeys.forms, "options"],
1072
+ lists: ["builder", "lists"],
1073
+ listList: (query) => [...builderKeys.lists, "list", query],
1074
+ list: (id) => [...builderKeys.lists, "detail", id],
1075
+ listOptions: () => [...builderKeys.lists, "options"],
1076
+ views: ["builder", "views"],
1077
+ viewList: (query) => [...builderKeys.views, "list", query],
1078
+ runtimeForm: (id) => ["builder", "runtime", "form", id],
1079
+ runtimeFieldOptions: (formId, fieldKey, query) => [...builderKeys.runtimeForm(formId), "fields", fieldKey, "options", query],
1080
+ runtimeFormEntries: (id) => [...builderKeys.runtimeForm(id), "entries"],
1081
+ runtimeList: (id) => ["builder", "runtime", "list", id],
1082
+ runtimeListEntries: (id, query) => [...builderKeys.runtimeList(id), "entries", query],
1083
+ runtimeView: (id) => ["builder", "runtime", "view", id],
1084
+ entries: (id, query) => [...builderKeys.runtimeView(id), "entries", query]
1085
+ };
1086
+ return builderKeys;
1087
+ }
1088
+ function createBuilderHooks(client) {
1089
+ const builderKeys = createBuilderKeys();
1090
+ function invalidateRuntimeViewEntries(queryClient, viewId) {
1091
+ return Promise.all([
1092
+ queryClient.invalidateQueries({ queryKey: builderKeys.runtimeView(viewId) }),
1093
+ queryClient.invalidateQueries({ queryKey: [...builderKeys.runtimeView(viewId), "entries"] })
1094
+ ]);
1095
+ }
1096
+ function useBuilderDbTables() {
1097
+ return useQuery({ queryKey: builderKeys.dbTables(), queryFn: client.listBuilderDbTables });
1098
+ }
1099
+ function useBuilderForms(query) {
1100
+ return useQuery({
1101
+ queryKey: builderKeys.formList(query),
1102
+ queryFn: () => client.listBuilderForms(query),
1103
+ placeholderData: keepPreviousData
1104
+ });
1105
+ }
1106
+ function useBuilderFormOptions() {
1107
+ return useQuery({
1108
+ queryKey: builderKeys.formOptions(),
1109
+ queryFn: client.listBuilderFormOptions
1110
+ });
1111
+ }
1112
+ function useBuilderForm(id) {
1113
+ return useQuery({
1114
+ queryKey: builderKeys.form(id ?? ""),
1115
+ queryFn: () => client.getBuilderForm(id ?? ""),
1116
+ enabled: Boolean(id)
1117
+ });
1118
+ }
1119
+ function useSaveBuilderForm() {
1120
+ const queryClient = useQueryClient();
1121
+ return useMutation({
1122
+ mutationFn: ({ id, input }) => id ? client.updateBuilderForm(id, input) : client.createBuilderForm(input),
1123
+ onSuccess: (form) => {
1124
+ queryClient.invalidateQueries({ queryKey: builderKeys.forms });
1125
+ queryClient.invalidateQueries({ queryKey: builderKeys.form(form.id) });
1126
+ queryClient.invalidateQueries({ queryKey: builderKeys.runtimeForm(form.id) });
1127
+ }
1128
+ });
1129
+ }
1130
+ function useReplicateBuilderForm() {
1131
+ const queryClient = useQueryClient();
1132
+ return useMutation({
1133
+ mutationFn: ({ id, input }) => client.replicateBuilderForm(id, input),
1134
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: builderKeys.forms })
1135
+ });
1136
+ }
1137
+ function useDeleteBuilderForm() {
1138
+ const queryClient = useQueryClient();
1139
+ return useMutation({
1140
+ mutationFn: client.deleteBuilderForm,
1141
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: builderKeys.forms })
1142
+ });
1143
+ }
1144
+ function useBuilderLists(query) {
1145
+ return useQuery({
1146
+ queryKey: builderKeys.listList(query),
1147
+ queryFn: () => client.listBuilderLists(query),
1148
+ placeholderData: keepPreviousData
1149
+ });
1150
+ }
1151
+ function useBuilderListOptions() {
1152
+ return useQuery({
1153
+ queryKey: builderKeys.listOptions(),
1154
+ queryFn: client.listBuilderListOptions
1155
+ });
1156
+ }
1157
+ function useBuilderList(id) {
1158
+ return useQuery({
1159
+ queryKey: builderKeys.list(id ?? ""),
1160
+ queryFn: () => client.getBuilderList(id ?? ""),
1161
+ enabled: Boolean(id)
1162
+ });
1163
+ }
1164
+ function useSaveBuilderList() {
1165
+ const queryClient = useQueryClient();
1166
+ return useMutation({
1167
+ mutationFn: ({ id, input }) => id ? client.updateBuilderList(id, input) : client.createBuilderList(input),
1168
+ onSuccess: (list) => {
1169
+ queryClient.invalidateQueries({ queryKey: builderKeys.lists });
1170
+ queryClient.invalidateQueries({ queryKey: builderKeys.list(list.id) });
1171
+ queryClient.invalidateQueries({ queryKey: builderKeys.runtimeList(list.id) });
1172
+ }
1173
+ });
1174
+ }
1175
+ function useReplicateBuilderList() {
1176
+ const queryClient = useQueryClient();
1177
+ return useMutation({
1178
+ mutationFn: ({ id, input }) => client.replicateBuilderList(id, input),
1179
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: builderKeys.lists })
1180
+ });
1181
+ }
1182
+ function useDeleteBuilderList() {
1183
+ const queryClient = useQueryClient();
1184
+ return useMutation({
1185
+ mutationFn: client.deleteBuilderList,
1186
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: builderKeys.lists })
1187
+ });
1188
+ }
1189
+ function useBuilderViews(query) {
1190
+ return useQuery({
1191
+ queryKey: builderKeys.viewList(query),
1192
+ queryFn: () => client.listBuilderViews(query),
1193
+ placeholderData: keepPreviousData
1194
+ });
1195
+ }
1196
+ function useSaveBuilderView() {
1197
+ const queryClient = useQueryClient();
1198
+ return useMutation({
1199
+ mutationFn: ({ id, input }) => id ? client.updateBuilderView(id, input) : client.createBuilderView(input),
1200
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: builderKeys.views })
1201
+ });
1202
+ }
1203
+ function useReplicateBuilderView() {
1204
+ const queryClient = useQueryClient();
1205
+ return useMutation({
1206
+ mutationFn: ({ id, input }) => client.replicateBuilderView(id, input),
1207
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: builderKeys.views })
1208
+ });
1209
+ }
1210
+ function useDeleteBuilderView() {
1211
+ const queryClient = useQueryClient();
1212
+ return useMutation({
1213
+ mutationFn: client.deleteBuilderView,
1214
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: builderKeys.views })
1215
+ });
1216
+ }
1217
+ function useRuntimeBuilderForm(id) {
1218
+ return useQuery({
1219
+ queryKey: builderKeys.runtimeForm(id),
1220
+ queryFn: () => client.getRuntimeBuilderForm(id)
1221
+ });
1222
+ }
1223
+ function useRuntimeBuilderFormFieldOptions(formId, fieldKey, query, enabled = true) {
1224
+ return useQuery({
1225
+ queryKey: builderKeys.runtimeFieldOptions(formId ?? "", fieldKey, query),
1226
+ queryFn: () => client.listRuntimeBuilderFormFieldOptions(formId ?? "", fieldKey, query),
1227
+ enabled: Boolean(formId) && enabled
1228
+ });
1229
+ }
1230
+ function useRunRuntimeBuilderFieldTrigger() {
1231
+ return useMutation({
1232
+ mutationFn: (args) => {
1233
+ if (args.viewId) {
1234
+ return client.runRuntimeBuilderViewFieldTrigger(
1235
+ args.viewId,
1236
+ args.fieldKey,
1237
+ args.event,
1238
+ args.input
1239
+ );
1240
+ }
1241
+ if (args.formId) {
1242
+ return client.runRuntimeBuilderFormFieldTrigger(
1243
+ args.formId,
1244
+ args.fieldKey,
1245
+ args.event,
1246
+ args.input
1247
+ );
1248
+ }
1249
+ throw new Error("formId \u044D\u0441\u0432\u044D\u043B viewId \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
1250
+ }
1251
+ });
1252
+ }
1253
+ function useRuntimeBuilderList(id) {
1254
+ return useQuery({
1255
+ queryKey: builderKeys.runtimeList(id),
1256
+ queryFn: () => client.getRuntimeBuilderList(id)
1257
+ });
1258
+ }
1259
+ function useRuntimeBuilderListEntries(id, query) {
1260
+ return useQuery({
1261
+ queryKey: builderKeys.runtimeListEntries(id, query),
1262
+ queryFn: () => client.listRuntimeBuilderListEntries(id, query),
1263
+ placeholderData: keepPreviousData
1264
+ });
1265
+ }
1266
+ function useSaveRuntimeBuilderFormEntry(formId) {
1267
+ const queryClient = useQueryClient();
1268
+ return useMutation({
1269
+ mutationFn: (input) => client.createRuntimeBuilderFormEntry(formId, input),
1270
+ onSuccess: () => {
1271
+ queryClient.invalidateQueries({ queryKey: builderKeys.runtimeFormEntries(formId) });
1272
+ queryClient.invalidateQueries({ queryKey: builderKeys.runtimeForm(formId) });
1273
+ queryClient.invalidateQueries({ queryKey: ["builder", "runtime", "list"] });
1274
+ }
1275
+ });
1276
+ }
1277
+ function useRuntimeBuilderView(id, enabled = true) {
1278
+ return useQuery({
1279
+ queryKey: builderKeys.runtimeView(id),
1280
+ queryFn: () => client.getRuntimeBuilderView(id),
1281
+ enabled: Boolean(id) && enabled
1282
+ });
1283
+ }
1284
+ function useBuilderViewEntries(id, query, enabled = true) {
1285
+ return useQuery({
1286
+ queryKey: builderKeys.entries(id, query),
1287
+ queryFn: () => client.listBuilderViewEntries(id, query),
1288
+ placeholderData: keepPreviousData,
1289
+ enabled: Boolean(id) && enabled
1290
+ });
1291
+ }
1292
+ function useSaveBuilderViewEntry(viewId) {
1293
+ const queryClient = useQueryClient();
1294
+ return useMutation({
1295
+ mutationFn: ({ id, input }) => id ? client.updateBuilderViewEntry(viewId, id, input) : client.createBuilderViewEntry(
1296
+ viewId,
1297
+ input
1298
+ ),
1299
+ onSuccess: () => invalidateRuntimeViewEntries(queryClient, viewId)
1300
+ });
1301
+ }
1302
+ function useCreateCompositeBuilderViewEntry(viewId) {
1303
+ const queryClient = useQueryClient();
1304
+ return useMutation({
1305
+ mutationFn: (input) => client.createCompositeBuilderViewEntry(viewId, input),
1306
+ onSuccess: () => invalidateRuntimeViewEntries(queryClient, viewId)
1307
+ });
1308
+ }
1309
+ function useUpdateCompositeBuilderViewEntry(viewId) {
1310
+ const queryClient = useQueryClient();
1311
+ return useMutation({
1312
+ mutationFn: ({
1313
+ id,
1314
+ input
1315
+ }) => client.updateCompositeBuilderViewEntry(viewId, id, input),
1316
+ onSuccess: () => invalidateRuntimeViewEntries(queryClient, viewId)
1317
+ });
1318
+ }
1319
+ function useDeleteBuilderViewEntry(id) {
1320
+ const queryClient = useQueryClient();
1321
+ return useMutation({
1322
+ mutationFn: (entryId) => client.deleteBuilderViewEntry(id, entryId),
1323
+ onSuccess: () => invalidateRuntimeViewEntries(queryClient, id)
1324
+ });
1325
+ }
1326
+ return {
1327
+ builderKeys,
1328
+ useBuilderDbTables,
1329
+ useBuilderForm,
1330
+ useBuilderFormOptions,
1331
+ useBuilderForms,
1332
+ useBuilderList,
1333
+ useBuilderListOptions,
1334
+ useBuilderLists,
1335
+ useBuilderViewEntries,
1336
+ useBuilderViews,
1337
+ useDeleteBuilderForm,
1338
+ useDeleteBuilderList,
1339
+ useDeleteBuilderView,
1340
+ useDeleteBuilderViewEntry,
1341
+ useCreateCompositeBuilderViewEntry,
1342
+ useReplicateBuilderForm,
1343
+ useReplicateBuilderList,
1344
+ useReplicateBuilderView,
1345
+ useRuntimeBuilderForm,
1346
+ useRuntimeBuilderFormFieldOptions,
1347
+ useRuntimeBuilderList,
1348
+ useRuntimeBuilderListEntries,
1349
+ useRuntimeBuilderView,
1350
+ useRunRuntimeBuilderFieldTrigger,
1351
+ useSaveBuilderForm,
1352
+ useSaveBuilderList,
1353
+ useSaveBuilderView,
1354
+ useSaveBuilderViewEntry,
1355
+ useSaveRuntimeBuilderFormEntry,
1356
+ useUpdateCompositeBuilderViewEntry
1357
+ };
1358
+ }
1359
+
1360
+ // src/index.ts
1361
+ var builderUiPackage = {
1362
+ name: "@duke333/builder-ui",
1363
+ version: "0.1.0"
1364
+ };
1365
+ export {
1366
+ BUILDER_EDITOR_NONE_VALUE,
1367
+ builderEditorOptionsToText,
1368
+ builderEditorSearchFieldsToText,
1369
+ builderEditorTextToOptions,
1370
+ builderEditorTextToSearchFields,
1371
+ builderFormEditorFieldsFromTable,
1372
+ builderFormEditorInputFromValues,
1373
+ builderFormEditorValuesFromEntity,
1374
+ builderListEditorColumnsFromTable,
1375
+ builderListEditorInputFromValues,
1376
+ builderListEditorValuesFromEntity,
1377
+ builderUiPackage,
1378
+ builderViewEditorInputFromValues,
1379
+ builderViewEditorValuesFromEntity,
1380
+ createBuilderApiClient,
1381
+ createBuilderEntityDialogManagerComponent,
1382
+ createBuilderEntityManagerComponent,
1383
+ createBuilderEntryDefaultValues,
1384
+ createBuilderFormDefaultValues,
1385
+ createBuilderFormEditorPageComponent,
1386
+ createBuilderHooks,
1387
+ createBuilderKeys,
1388
+ createBuilderListEditorPageComponent,
1389
+ createBuilderViewDialogComponent,
1390
+ createDynamicFormComponent,
1391
+ createDynamicListComponent,
1392
+ createDynamicViewComponent,
1393
+ createRuntimeEntryDialogComponent,
1394
+ formatBuilderRuntimeValue,
1395
+ getBuilderEditorTriggerRoute
1396
+ };
1397
+ //# sourceMappingURL=index.js.map