@braedonsaunders/appkit-forms 0.2.2

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.
Files changed (120) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +74 -0
  3. package/canvas-editor.d.ts +24 -0
  4. package/canvas-editor.d.ts.map +1 -0
  5. package/canvas-editor.js +115 -0
  6. package/canvas-editor.js.map +1 -0
  7. package/canvas-runtime.d.ts +12 -0
  8. package/canvas-runtime.d.ts.map +1 -0
  9. package/canvas-runtime.js +54 -0
  10. package/canvas-runtime.js.map +1 -0
  11. package/element-preview.d.ts +9 -0
  12. package/element-preview.d.ts.map +1 -0
  13. package/element-preview.js +167 -0
  14. package/element-preview.js.map +1 -0
  15. package/form-designer.d.ts +29 -0
  16. package/form-designer.d.ts.map +1 -0
  17. package/form-designer.js +214 -0
  18. package/form-designer.js.map +1 -0
  19. package/form-flow-validation.d.ts +5 -0
  20. package/form-flow-validation.d.ts.map +1 -0
  21. package/form-flow-validation.js +154 -0
  22. package/form-flow-validation.js.map +1 -0
  23. package/form-renderer.d.ts +37 -0
  24. package/form-renderer.d.ts.map +1 -0
  25. package/form-renderer.js +282 -0
  26. package/form-renderer.js.map +1 -0
  27. package/formula-builder.d.ts +25 -0
  28. package/formula-builder.d.ts.map +1 -0
  29. package/formula-builder.js +310 -0
  30. package/formula-builder.js.map +1 -0
  31. package/generated-copy.d.ts +17 -0
  32. package/generated-copy.d.ts.map +1 -0
  33. package/generated-copy.js +396 -0
  34. package/generated-copy.js.map +1 -0
  35. package/index.d.ts +27 -0
  36. package/index.d.ts.map +1 -0
  37. package/index.js +25 -0
  38. package/index.js.map +1 -0
  39. package/logic-builder.d.ts +27 -0
  40. package/logic-builder.d.ts.map +1 -0
  41. package/logic-builder.js +106 -0
  42. package/logic-builder.js.map +1 -0
  43. package/package.json +54 -0
  44. package/photo-field-state.d.ts +9 -0
  45. package/photo-field-state.d.ts.map +1 -0
  46. package/photo-field-state.js +11 -0
  47. package/photo-field-state.js.map +1 -0
  48. package/production-designer-adapter.d.ts +100 -0
  49. package/production-designer-adapter.d.ts.map +1 -0
  50. package/production-designer-adapter.js +2 -0
  51. package/production-designer-adapter.js.map +1 -0
  52. package/production-file-upload.d.ts +27 -0
  53. package/production-file-upload.d.ts.map +1 -0
  54. package/production-file-upload.js +105 -0
  55. package/production-file-upload.js.map +1 -0
  56. package/production-form-designer.d.ts +3 -0
  57. package/production-form-designer.d.ts.map +1 -0
  58. package/production-form-designer.js +1124 -0
  59. package/production-form-designer.js.map +1 -0
  60. package/production-form-renderer.d.ts +16 -0
  61. package/production-form-renderer.d.ts.map +1 -0
  62. package/production-form-renderer.js +2134 -0
  63. package/production-form-renderer.js.map +1 -0
  64. package/production-runtime-adapter.d.ts +187 -0
  65. package/production-runtime-adapter.d.ts.map +1 -0
  66. package/production-runtime-adapter.js +2 -0
  67. package/production-runtime-adapter.js.map +1 -0
  68. package/production-runtime-context.d.ts +29 -0
  69. package/production-runtime-context.d.ts.map +1 -0
  70. package/production-runtime-context.js +35 -0
  71. package/production-runtime-context.js.map +1 -0
  72. package/production-runtime-layout.d.ts +21 -0
  73. package/production-runtime-layout.d.ts.map +1 -0
  74. package/production-runtime-layout.js +33 -0
  75. package/production-runtime-layout.js.map +1 -0
  76. package/properties.d.ts +36 -0
  77. package/properties.d.ts.map +1 -0
  78. package/properties.js +133 -0
  79. package/properties.js.map +1 -0
  80. package/record-actions-panel.d.ts +28 -0
  81. package/record-actions-panel.d.ts.map +1 -0
  82. package/record-actions-panel.js +136 -0
  83. package/record-actions-panel.js.map +1 -0
  84. package/record-behavior-panel.d.ts +15 -0
  85. package/record-behavior-panel.d.ts.map +1 -0
  86. package/record-behavior-panel.js +59 -0
  87. package/record-behavior-panel.js.map +1 -0
  88. package/record-config.d.ts +44 -0
  89. package/record-config.d.ts.map +1 -0
  90. package/record-config.js +2 -0
  91. package/record-config.js.map +1 -0
  92. package/record-list-panel.d.ts +16 -0
  93. package/record-list-panel.d.ts.map +1 -0
  94. package/record-list-panel.js +86 -0
  95. package/record-list-panel.js.map +1 -0
  96. package/risk-matrix.d.ts +93 -0
  97. package/risk-matrix.d.ts.map +1 -0
  98. package/risk-matrix.js +239 -0
  99. package/risk-matrix.js.map +1 -0
  100. package/runtime-controls.d.ts +19 -0
  101. package/runtime-controls.d.ts.map +1 -0
  102. package/runtime-controls.js +61 -0
  103. package/runtime-controls.js.map +1 -0
  104. package/sketch-pad-client.d.ts +4 -0
  105. package/sketch-pad-client.d.ts.map +1 -0
  106. package/sketch-pad-client.js +54 -0
  107. package/sketch-pad-client.js.map +1 -0
  108. package/sketch-pad.d.ts +13 -0
  109. package/sketch-pad.d.ts.map +1 -0
  110. package/sketch-pad.js +17 -0
  111. package/sketch-pad.js.map +1 -0
  112. package/styles.css +7 -0
  113. package/text.d.ts +5 -0
  114. package/text.d.ts.map +1 -0
  115. package/text.js +10 -0
  116. package/text.js.map +1 -0
  117. package/workflow-properties.d.ts +16 -0
  118. package/workflow-properties.d.ts.map +1 -0
  119. package/workflow-properties.js +31 -0
  120. package/workflow-properties.js.map +1 -0
@@ -0,0 +1,1124 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { GeneratedText, useGeneratedTranslations, GeneratedValue, useGeneratedValueTranslations, } from './generated-copy.js';
4
+ // Form designer.
5
+ //
6
+ // Three-pane layout:
7
+ // - Left: field palette (categorized, with icons), section list + workflow step editor
8
+ // - Middle: canvas — sections and fields, palette drops, drag reorder, click-to-select
9
+ // - Right: properties panel for the current selection (form / section / field) with
10
+ // tabs for Basic / Validation / Logic / Default / Calc
11
+ //
12
+ // All edits mutate a local copy of FormSchemaV1. Publishing writes a new
13
+ // immutable version through the host adapter.
14
+ import { useEffect, useRef, useState, useTransition, useMemo } from 'react';
15
+ import { AlignLeft, ArrowDown, ArrowUp, Calculator, Calendar, Check, CheckCircle2, CheckSquare, ChevronDown, ClipboardCheck, Eye, FileText, Globe2, GripVertical, Hash, Image as ImageIcon, ListChecks, Mail, MapPin, Mic, Minus, Pencil, Pin, PinOff, Phone, Plus, Radio, Save, ShieldCheck, Signal, Sliders, Star, ToggleLeft, Table2, Trash2, Type, Upload, User, Users, Video, } from 'lucide-react';
16
+ import { Reorder, useDragControls } from 'framer-motion';
17
+ import { Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardContent, CardHeader, CardTitle, Drawer, Input, Label, Select, Textarea, toast, } from '@braedonsaunders/appkit-ui';
18
+ import { FIELD_TYPES, entityKindForPicker, storesResponseValue, } from '@braedonsaunders/appkit-forms-core';
19
+ import { localizeText } from '@braedonsaunders/appkit-i18n';
20
+ import { RecordBehaviorPanel } from './record-behavior-panel.js';
21
+ import { RecordActionsPanel } from './record-actions-panel.js';
22
+ import { RecordListPanel } from './record-list-panel.js';
23
+ import { readText, writeText } from './text.js';
24
+ import { LogicBuilder } from './logic-builder.js';
25
+ import { FormulaBuilder } from './formula-builder.js';
26
+ import { CanvasEditor, defaultBox } from './canvas-editor.js';
27
+ import { BarChart3, Bold, Briefcase, Building2, Database, Info, LayoutGrid, ListOrdered, Map as MapIcon, MapPinned, MousePointerClick, PanelLeft, PenTool, RefreshCw, ScanLine, Send, SlidersHorizontal, Sparkles, Workflow as WorkflowIcon, } from 'lucide-react';
28
+ // --- Palette ---------------------------------------------------------------
29
+ // Field-type icon registry. Falls back to a generic Type icon for unknowns.
30
+ const FIELD_ICONS = {
31
+ text: Type,
32
+ long_text: AlignLeft,
33
+ number: Hash,
34
+ currency: Calculator,
35
+ percentage: Hash,
36
+ slider: Sliders,
37
+ gps: MapPin,
38
+ matrix: LayoutGrid,
39
+ date: Calendar,
40
+ datetime: Calendar,
41
+ time: Calendar,
42
+ email: Mail,
43
+ phone: Phone,
44
+ url: FileText,
45
+ radio: Radio,
46
+ checkbox_group: CheckSquare,
47
+ select: ChevronDown,
48
+ multi_select: ListChecks,
49
+ pass_fail_na: ShieldCheck,
50
+ rating: Star,
51
+ yes_no_comment: ToggleLeft,
52
+ traffic_light: Signal,
53
+ person_picker: User,
54
+ multi_person_picker: Users,
55
+ customer_picker: Building2,
56
+ project_picker: Briefcase,
57
+ site_picker: MapPin,
58
+ area_picker: MapIcon,
59
+ gl_account: Database,
60
+ party: Building2,
61
+ photo: ImageIcon,
62
+ photo_upload: ImageIcon,
63
+ photo_ai: Sparkles,
64
+ photo_annotated: MapPinned,
65
+ file: Upload,
66
+ video: Video,
67
+ audio: Mic,
68
+ sketch: PenTool,
69
+ signature: Pencil,
70
+ typed_attestation: CheckCircle2,
71
+ formula: Calculator,
72
+ risk_matrix: Sliders,
73
+ heading: Type,
74
+ paragraph: AlignLeft,
75
+ divider: Minus,
76
+ table: Table2,
77
+ lookup: Database,
78
+ data_table: Table2,
79
+ metric: BarChart3,
80
+ qr_scanner: ScanLine,
81
+ ranking: ListOrdered,
82
+ rich_text: Bold,
83
+ address: MapPin,
84
+ };
85
+ // One element per concept — no duplicates across groups.
86
+ const PALETTE_PRIMARY = [
87
+ {
88
+ label: 'Common',
89
+ types: [
90
+ 'text',
91
+ 'long_text',
92
+ 'number',
93
+ 'currency',
94
+ 'percentage',
95
+ 'slider',
96
+ 'date',
97
+ 'table',
98
+ 'select',
99
+ 'checkbox_group',
100
+ 'pass_fail_na',
101
+ 'signature',
102
+ 'photo',
103
+ 'file',
104
+ 'person_picker',
105
+ 'formula',
106
+ ],
107
+ },
108
+ ];
109
+ const PALETTE_MORE = [
110
+ {
111
+ label: 'Date & contact',
112
+ types: ['datetime', 'time', 'gps', 'address', 'email', 'phone', 'url', 'qr_scanner'],
113
+ },
114
+ { label: 'Choice', types: ['radio', 'multi_select', 'ranking', 'matrix'] },
115
+ { label: 'Scoring', types: ['rating', 'yes_no_comment', 'traffic_light'] },
116
+ {
117
+ label: 'Pickers',
118
+ types: [
119
+ 'multi_person_picker',
120
+ 'customer_picker',
121
+ 'project_picker',
122
+ 'site_picker',
123
+ 'area_picker',
124
+ 'gl_account',
125
+ 'party',
126
+ ],
127
+ },
128
+ { label: 'Media', types: ['photo_ai', 'photo_annotated', 'sketch', 'video', 'audio'] },
129
+ { label: 'Computed', types: ['risk_matrix', 'typed_attestation'] },
130
+ { label: 'Data', types: ['lookup', 'data_table', 'metric'] },
131
+ { label: 'Display', types: ['heading', 'paragraph', 'rich_text', 'divider'] },
132
+ ];
133
+ function newId(prefix) {
134
+ return `${prefix}_${globalThis.crypto.randomUUID()}`;
135
+ }
136
+ function emptyField(type) {
137
+ return {
138
+ id: newId('f'),
139
+ type,
140
+ label: { en: FIELD_TYPES[type].label },
141
+ required: false,
142
+ };
143
+ }
144
+ function emptyStep() {
145
+ return {
146
+ key: newId('step'),
147
+ title: { en: 'New step' },
148
+ assignee: { type: 'expression', expr: '$submitter' },
149
+ };
150
+ }
151
+ const KIND_META = {
152
+ form: { label: 'Form', cls: 'bg-primary-subtle text-primary' },
153
+ wizard: {
154
+ label: 'Wizard',
155
+ cls: 'bg-primary-subtle text-primary',
156
+ },
157
+ checklist: {
158
+ label: 'Checklist',
159
+ cls: 'bg-success-subtle text-success',
160
+ },
161
+ register: {
162
+ label: 'Register',
163
+ cls: 'bg-warning-subtle text-warning',
164
+ },
165
+ mini_app: {
166
+ label: 'Mini-app',
167
+ cls: 'bg-primary-subtle text-primary',
168
+ },
169
+ };
170
+ function normalizeDesignerSchema(schema) {
171
+ return {
172
+ ...structuredClone(schema),
173
+ workflow: schema.workflow ?? {
174
+ steps: [{ key: 'submit', title: 'Submit', assignee: { type: 'expression', expr: '$submitter' } }],
175
+ },
176
+ };
177
+ }
178
+ export function ProductionFormDesigner({ adapter, templateId, templateName, templateKind = 'form', initialSchema, currentVersion, initialSurface = 'build', overview, recordConfig, allowedRoles = [], roles = [], flows = [], recordActionAdapter, dataSources = [], dataSourcesLoading = false, onRefreshDataSources, renderFlows, renderAssistant, backHref, recordsHref, assignmentCreateHref, assignmentsHref, dataSourcesHref, publishedHref, canPin = false, pinned = false, onSchemaChange, locale, defaultLocale, enabledLocales, localeLabels, className, }) {
179
+ const tGeneratedValue = useGeneratedValueTranslations();
180
+ const tGenerated = useGeneratedTranslations();
181
+ const languages = (value) => localeLabels?.[value] ?? value.toUpperCase();
182
+ const [schema, setSchema] = useState(() => normalizeDesignerSchema(initialSchema));
183
+ const [appName, setAppName] = useState(templateName);
184
+ const [recordConfiguration, setRecordConfiguration] = useState(recordConfig ?? {});
185
+ const [isPinned, setIsPinned] = useState(pinned);
186
+ // Unified editor: left rail tab + right surface.
187
+ const [leftTab, setLeftTab] = useState('build');
188
+ const [surface, setSurface] = useState(initialSurface === 'flows' && renderFlows ? 'flows' : 'build');
189
+ const [designerTab, setDesignerTab] = useState(() => initialSchema.tabs?.[0]?.id ?? '');
190
+ const [showAiAssistant, setShowAiAssistant] = useState(false);
191
+ // The element type currently being dragged from the left palette — read by the
192
+ // canvas on drop. (HTML5 dataTransfer is also set, for the browser's drag UX.)
193
+ const dragElementRef = useRef(null);
194
+ const [dropSectionId, setDropSectionId] = useState(null);
195
+ const [selection, setSelection] = useState({ kind: 'form' });
196
+ // Which right-hand flyout is open. Preview + properties used to be permanent
197
+ // columns (too cramped); now they slide in on demand.
198
+ const [rightPanel, setRightPanel] = useState('none');
199
+ const [showPublish, setShowPublish] = useState(false);
200
+ const [pending, start] = useTransition();
201
+ const [error, setError] = useState(null);
202
+ const [changelog, setChangelog] = useState('');
203
+ const [contentLocale, setContentLocale] = useState(locale);
204
+ useEffect(() => onSchemaChange?.(schema), [onSchemaChange, schema]);
205
+ // Selectable fields for the records-list "List" tab — real answer fields only
206
+ // (content-only display elements carry no data to show in a column).
207
+ const listFields = useMemo(() => {
208
+ const skip = new Set(['heading', 'paragraph', 'divider', 'metric']);
209
+ const out = [];
210
+ for (const sec of schema.sections)
211
+ for (const f of sec.fields) {
212
+ if (skip.has(f.type))
213
+ continue;
214
+ out.push({
215
+ id: f.id,
216
+ label: localizeText(f.label, contentLocale, f.id, defaultLocale),
217
+ });
218
+ }
219
+ return out;
220
+ }, [contentLocale, defaultLocale, schema]);
221
+ // Current records-list config lives under recordConfig.list (the page passes
222
+ // the full recordConfig jsonb; its type omits `list`, but it's there at runtime).
223
+ const listConfig = recordConfiguration.list;
224
+ let selectedField = null;
225
+ if (selection.kind === 'field') {
226
+ for (const sec of schema.sections) {
227
+ const f = sec.fields.find((x) => x.id === selection.fieldId);
228
+ if (f) {
229
+ selectedField = { section: sec, field: f };
230
+ break;
231
+ }
232
+ }
233
+ }
234
+ const selectedSection = useMemo(() => {
235
+ if (selection.kind !== 'section')
236
+ return null;
237
+ return schema.sections.find((s) => s.id === selection.sectionId) ?? null;
238
+ }, [schema, selection]);
239
+ function update(fn) {
240
+ setSchema((s) => fn(structuredClone(s)));
241
+ }
242
+ // Selecting something opens the properties flyout; workflow is edited in the
243
+ // canvas so it just switches the canvas view.
244
+ function selectField(sectionId, fieldId) {
245
+ setSelection({ kind: 'field', sectionId, fieldId });
246
+ setRightPanel('props');
247
+ }
248
+ function selectSection(sectionId) {
249
+ setSelection({ kind: 'section', sectionId });
250
+ setRightPanel('props');
251
+ }
252
+ function openWorkflow() {
253
+ setSelection({ kind: 'workflow' });
254
+ setRightPanel('none');
255
+ }
256
+ function addSection() {
257
+ const id = newId('sec');
258
+ update((draft) => {
259
+ draft.sections.push({
260
+ id,
261
+ title: { en: 'New section' },
262
+ fields: [],
263
+ // New sections land in the active designer tab (if the app uses tabs).
264
+ ...(draft.tabs?.length ? { tabId: designerTab || draft.tabs[0].id } : {}),
265
+ });
266
+ return draft;
267
+ });
268
+ selectSection(id);
269
+ }
270
+ // --- Tabs (presentational app pages) -------------------------------------
271
+ function addTab() {
272
+ const id = newId('tab');
273
+ update((draft) => {
274
+ const tabs = draft.tabs ?? [];
275
+ const title = { en: `Tab ${tabs.length + 1}` };
276
+ if (tabs.length === 0) {
277
+ // First tab: pull every existing section into it so nothing is orphaned.
278
+ draft.tabs = [{ id, title }];
279
+ for (const s of draft.sections)
280
+ s.tabId = id;
281
+ }
282
+ else {
283
+ draft.tabs = [...tabs, { id, title }];
284
+ }
285
+ return draft;
286
+ });
287
+ setDesignerTab(id);
288
+ }
289
+ function renameTab(id, title) {
290
+ update((draft) => {
291
+ const t = draft.tabs?.find((x) => x.id === id);
292
+ if (t)
293
+ t.title = writeText(t.title, title, contentLocale);
294
+ return draft;
295
+ });
296
+ }
297
+ function deleteTab(id) {
298
+ update((draft) => {
299
+ const remaining = (draft.tabs ?? []).filter((t) => t.id !== id);
300
+ const fallback = remaining[0]?.id;
301
+ for (const s of draft.sections)
302
+ if (s.tabId === id)
303
+ s.tabId = fallback;
304
+ draft.tabs = remaining.length ? remaining : undefined;
305
+ return draft;
306
+ });
307
+ setDesignerTab((cur) => (cur === id ? (schema.tabs?.find((t) => t.id !== id)?.id ?? '') : cur));
308
+ }
309
+ function addField(sectionId, type) {
310
+ const f = emptyField(type);
311
+ update((draft) => {
312
+ const sec = draft.sections.find((s) => s.id === sectionId);
313
+ if (!sec)
314
+ return draft;
315
+ sec.fields.push(f);
316
+ // If the section is in canvas mode, also place the new element on the grid
317
+ // (append below everything) — otherwise a clicked element would be invisible.
318
+ if (sec.canvas) {
319
+ const box = defaultBox(type);
320
+ const bottomY = sec.canvas.items.reduce((m, it) => Math.max(m, it.y + it.h), 0);
321
+ sec.canvas = {
322
+ ...sec.canvas,
323
+ items: [
324
+ ...sec.canvas.items,
325
+ { i: f.id, x: 0, y: bottomY, w: Math.min(box.w * 2, 12), h: box.h },
326
+ ],
327
+ };
328
+ }
329
+ return draft;
330
+ });
331
+ selectField(sectionId, f.id);
332
+ }
333
+ function deleteField(sectionId, fieldId) {
334
+ update((draft) => {
335
+ const sec = draft.sections.find((s) => s.id === sectionId);
336
+ if (!sec)
337
+ return draft;
338
+ sec.fields = sec.fields.filter((f) => f.id !== fieldId);
339
+ // Keep the canvas layout in sync — drop the deleted field's box.
340
+ if (sec.canvas)
341
+ sec.canvas = { ...sec.canvas, items: sec.canvas.items.filter((it) => it.i !== fieldId) };
342
+ return draft;
343
+ });
344
+ if (selection.kind === 'field' && selection.fieldId === fieldId) {
345
+ setSelection({ kind: 'section', sectionId });
346
+ }
347
+ }
348
+ function deleteSection(sectionId) {
349
+ update((draft) => {
350
+ draft.sections = draft.sections.filter((s) => s.id !== sectionId);
351
+ return draft;
352
+ });
353
+ setSelection({ kind: 'form' });
354
+ }
355
+ function moveField(sectionId, fieldId, delta) {
356
+ update((draft) => {
357
+ const sec = draft.sections.find((s) => s.id === sectionId);
358
+ if (!sec)
359
+ return draft;
360
+ const i = sec.fields.findIndex((f) => f.id === fieldId);
361
+ const j = i + delta;
362
+ if (i < 0 || j < 0 || j >= sec.fields.length)
363
+ return draft;
364
+ [sec.fields[i], sec.fields[j]] = [sec.fields[j], sec.fields[i]];
365
+ return draft;
366
+ });
367
+ }
368
+ // Drag-to-reorder fields within a section (framer-motion Reorder). Replaces
369
+ // the schema's field order with the dragged order; the up/down arrows remain
370
+ // as a keyboard-accessible fallback.
371
+ function reorderFields(sectionId, fields) {
372
+ setSchema((s) => ({
373
+ ...s,
374
+ sections: s.sections.map((sec) => (sec.id === sectionId ? { ...sec, fields } : sec)),
375
+ }));
376
+ }
377
+ function moveSection(sectionId, delta) {
378
+ update((draft) => {
379
+ const i = draft.sections.findIndex((s) => s.id === sectionId);
380
+ const j = i + delta;
381
+ if (i < 0 || j < 0 || j >= draft.sections.length)
382
+ return draft;
383
+ [draft.sections[i], draft.sections[j]] = [draft.sections[j], draft.sections[i]];
384
+ return draft;
385
+ });
386
+ }
387
+ function updateField(sectionId, fieldId, patch) {
388
+ update((draft) => {
389
+ const sec = draft.sections.find((s) => s.id === sectionId);
390
+ if (!sec)
391
+ return draft;
392
+ const idx = sec.fields.findIndex((f) => f.id === fieldId);
393
+ if (idx < 0)
394
+ return draft;
395
+ sec.fields[idx] = { ...sec.fields[idx], ...patch };
396
+ return draft;
397
+ });
398
+ }
399
+ function updateSection(sectionId, patch) {
400
+ update((draft) => {
401
+ const i = draft.sections.findIndex((s) => s.id === sectionId);
402
+ if (i < 0)
403
+ return draft;
404
+ draft.sections[i] = { ...draft.sections[i], ...patch };
405
+ return draft;
406
+ });
407
+ }
408
+ // --- Canvas (free-form positioned layout) --------------------------------
409
+ function canvasBoxFor(type) {
410
+ return defaultBox(type);
411
+ }
412
+ // Free-form CANVAS layout is a GLOBAL, app-level mode (advanced) — not a
413
+ // per-section toggle. Turning it on auto-places every section's fields in a
414
+ // column so nothing is lost; turning it off drops the positioning back to a
415
+ // stacked layout.
416
+ function setAllCanvas(on) {
417
+ update((draft) => {
418
+ for (const sec of draft.sections) {
419
+ if (on && !sec.canvas) {
420
+ let y = 0;
421
+ sec.canvas = {
422
+ cols: 12,
423
+ rowHeight: 40,
424
+ items: sec.fields.map((f) => {
425
+ const box = canvasBoxFor(f.type);
426
+ const item = { i: f.id, x: 0, y, w: Math.min(box.w * 2, 12), h: box.h };
427
+ y += box.h;
428
+ return item;
429
+ }),
430
+ };
431
+ }
432
+ else if (!on && sec.canvas) {
433
+ delete sec.canvas;
434
+ }
435
+ }
436
+ return draft;
437
+ });
438
+ }
439
+ const canvasMode = schema.sections.length > 0 && schema.sections.every((s) => !!s.canvas);
440
+ // When the app uses tabs, the build surface shows one tab's sections at a time.
441
+ const appTabs = schema.tabs ?? [];
442
+ const visibleSections = appTabs.length
443
+ ? schema.sections.filter((s) => (s.tabId ?? appTabs[0].id) === designerTab)
444
+ : schema.sections;
445
+ function setCanvasItems(sectionId, items) {
446
+ update((draft) => {
447
+ const sec = draft.sections.find((s) => s.id === sectionId);
448
+ if (!sec || !sec.canvas)
449
+ return draft;
450
+ sec.canvas = { ...sec.canvas, items };
451
+ return draft;
452
+ });
453
+ }
454
+ function addWidgetToCanvas(sectionId, type, box) {
455
+ const f = emptyField(type);
456
+ update((draft) => {
457
+ const sec = draft.sections.find((s) => s.id === sectionId);
458
+ if (!sec)
459
+ return draft;
460
+ sec.fields.push(f);
461
+ const canvas = sec.canvas ?? { cols: 12, rowHeight: 40, items: [] };
462
+ sec.canvas = {
463
+ ...canvas,
464
+ items: [...canvas.items, { i: f.id, x: box.x, y: box.y, w: box.w, h: box.h }],
465
+ };
466
+ return draft;
467
+ });
468
+ selectField(sectionId, f.id);
469
+ }
470
+ function publish() {
471
+ setError(tGeneratedValue(null));
472
+ start(async () => {
473
+ const result = await adapter.publish({
474
+ templateId,
475
+ schema,
476
+ changelog: changelog.trim(),
477
+ });
478
+ if (!result.ok) {
479
+ setError(tGeneratedValue(result.error ?? tGenerated('m_16c73b6230c543')));
480
+ toast.error(tGeneratedValue(result.error ?? tGenerated('m_16c73b6230c543')));
481
+ return;
482
+ }
483
+ toast.success(tGenerated('m_165fbf2bc8254b', { value0: result.version }));
484
+ setShowPublish(false);
485
+ if (publishedHref)
486
+ adapter.navigate?.(publishedHref);
487
+ });
488
+ }
489
+ // Sections grouped by their workflow step assignment, used for the step
490
+ // chips in the canvas header.
491
+ const stepsCount = schema.workflow.steps.length;
492
+ return (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${className ?? ''}`, children: [_jsxs("header", { className: "flex shrink-0 items-center justify-between border-b border-border bg-surface px-4 py-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx("a", { href: backHref, className: "shrink-0 text-sm text-primary hover:underline", children: _jsx(GeneratedText, { id: "m_07f039d1ce81ec" }) }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate text-sm font-semibold", children: _jsx(GeneratedValue, { value: appName }) }), _jsx("span", { className: `rounded-full px-2 py-0.5 text-[10px] font-semibold tracking-wide uppercase ${KIND_META[templateKind].cls}`, children: _jsx(GeneratedValue, { value: KIND_META[templateKind].label }) })] }), _jsxs("div", { className: "text-xs text-fg-muted", children: [_jsx(GeneratedText, { id: "m_1d76a1713fe585" }), _jsx(GeneratedValue, { value: currentVersion }), " \u00B7", ' ', _jsx(GeneratedValue, { value: schema.sections.length }), ' ', _jsx(GeneratedText, { id: "m_02f67a0e8ba5ce" }), _jsx(GeneratedValue, { value: schema.sections.length === 1 ? '' : _jsx(GeneratedText, { id: "m_00ded356f0f424" }) })] })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("label", { className: "flex items-center gap-1.5 text-xs text-fg-muted", children: [_jsx(Globe2, { size: 14 }), _jsx(Select, { value: contentLocale, onChange: (event) => setContentLocale(event.target.value), className: "h-8 min-w-28 text-xs", "aria-label": tGenerated('m_104e7b02f49d5e'), children: enabledLocales.map((enabledLocale) => (_jsx("option", { value: enabledLocale, children: languages(enabledLocale) }, enabledLocale))) })] }), _jsx(GeneratedValue, { value: renderAssistant ? (_jsxs(Button, { size: "sm", onClick: () => setShowAiAssistant(true), className: "bg-primary text-primary-fg hover:bg-primary-hover", children: [_jsx(Sparkles, { size: 14 }), " ", _jsx(GeneratedText, { id: "m_1e0a86199c09df" })] })) : null }), _jsx("a", { href: recordsHref, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(ClipboardCheck, { size: 14 }), " ", _jsx(GeneratedText, { id: "m_0d06d42b0344eb" })] }) }), _jsxs(Button, { variant: rightPanel === 'preview' ? 'secondary' : 'outline', size: "sm", onClick: () => setRightPanel((p) => (p === 'preview' ? 'none' : 'preview')), children: [_jsx(Eye, { size: 14 }), _jsx(GeneratedText, { id: "m_11d37007232de5" })] }), _jsxs(Button, { size: "sm", onClick: () => setShowPublish(true), disabled: pending, children: [_jsx(Save, { size: 14 }), _jsx(GeneratedText, { id: "m_01b66d9dba6889" }), _jsx(GeneratedValue, { value: currentVersion + 1 })] })] })] }), _jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsxs("aside", { className: "flex w-1/3 min-w-72 max-w-[30rem] shrink-0 flex-col border-r border-border bg-surface", children: [_jsxs("div", { className: "flex shrink-0 items-center gap-0.5 border-b border-border px-2 py-1.5", children: [_jsx(LeftTab, { active: leftTab === 'overview', onClick: () => setLeftTab('overview'), icon: _jsx(Info, { size: 16 }), label: tGenerated('m_102c6abe56e4d5') }), _jsx(LeftTab, { active: leftTab === 'build', onClick: () => setLeftTab('build'), icon: _jsx(PanelLeft, { size: 16 }), label: tGenerated('m_0adae4a94c7be3') }), _jsx(LeftTab, { active: leftTab === 'record', onClick: () => setLeftTab('record'), icon: _jsx(SlidersHorizontal, { size: 16 }), label: tGenerated('m_1c1250c8c10a42') }), _jsx(LeftTab, { active: leftTab === 'list', onClick: () => setLeftTab('list'), icon: _jsx(Table2, { size: 16 }), label: tGenerated('m_02ec964c87acae') }), _jsx(LeftTab, { active: leftTab === 'actions', onClick: () => setLeftTab('actions'), icon: _jsx(MousePointerClick, { size: 16 }), label: tGenerated('m_13f79ee70f138d') }), _jsx(LeftTab, { active: leftTab === 'assignments', onClick: () => setLeftTab('assignments'), icon: _jsx(Send, { size: 16 }), label: tGenerated('m_0444d7da46d928') }), _jsx(LeftTab, { active: leftTab === 'permissions', onClick: () => setLeftTab('permissions'), icon: _jsx(ShieldCheck, { size: 16 }), label: tGenerated('m_128e83250b7fc0') })] }), _jsx("div", { className: "app-scroll min-h-0 flex-1 overflow-y-auto p-3", children: _jsx(GeneratedValue, { value: leftTab === 'overview' ? (_jsx(OverviewPanel, { name: appName, overview: overview, onSaved: setAppName, onSave: (input) => adapter.saveOverview({ templateId, ...input }), canPin: canPin, pinned: isPinned, onPin: adapter.setPinned ? async (next) => {
493
+ const result = await adapter.setPinned({ templateId, pinned: next });
494
+ if (result.ok)
495
+ setIsPinned(next);
496
+ return result;
497
+ } : undefined })) : leftTab === 'record' ? (_jsx(RecordBehaviorPanel, { value: recordConfiguration, roles: roles, onChange: setRecordConfiguration, onSave: async (next) => {
498
+ const result = await adapter.saveRecordConfig({ templateId, recordConfig: next });
499
+ if (!result.ok)
500
+ throw new Error(result.error ?? 'Record behavior could not be saved.');
501
+ } })) : leftTab === 'list' ? (_jsx(RecordListPanel, { value: listConfig, fields: listFields, onChange: (list) => setRecordConfiguration((current) => ({ ...current, list })), onSave: async (list) => {
502
+ const result = await adapter.saveListConfig({ templateId, listConfig: list });
503
+ if (!result.ok)
504
+ throw new Error(result.error ?? 'Record list could not be saved.');
505
+ } })) : leftTab === 'actions' ? (_jsx(RecordActionsPanel, { flows: flows, adapter: recordActionAdapter })) : leftTab === 'assignments' ? (_jsx(AssignmentsPanel, { createHref: assignmentCreateHref, listHref: assignmentsHref })) : leftTab === 'permissions' ? (_jsx(PermissionsPanel, { roles: roles, initial: allowedRoles, onSave: (next) => adapter.savePermissions({ templateId, allowedRoles: next }) })) : (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: openWorkflow, title: tGenerated('m_037b62ee03b335'), className: `mb-3 block w-full rounded px-2 py-1 text-left text-xs font-semibold ${selection.kind === 'workflow'
506
+ ? 'bg-primary-subtle text-primary'
507
+ : 'text-fg hover:bg-surface-hover'}`, children: [_jsx(GeneratedText, { id: "m_001f1bfc6e0cd8" }), _jsx(GeneratedValue, { value: stepsCount }), ")"] }), _jsx("h3", { className: "mb-1 text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_1a19ebd22b2247" }) }), _jsx("p", { className: "mb-2 text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_02043182fe57f9" }) }), _jsx(GeneratedValue, { value: [...PALETTE_PRIMARY, ...PALETTE_MORE].map((group) => (_jsx(FieldPaletteGroup, { group: group, onDragType: (t) => {
508
+ dragElementRef.current = t;
509
+ }, onDragEnd: () => {
510
+ dragElementRef.current = null;
511
+ setDropSectionId(null);
512
+ }, onAdd: (t) => {
513
+ const targetSection = selection.kind === 'section' || selection.kind === 'field'
514
+ ? selection.sectionId
515
+ : schema.sections[schema.sections.length - 1]?.id;
516
+ if (targetSection)
517
+ addField(targetSection, t);
518
+ } }, group.label))) })] })) }) })] }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col bg-bg-subtle bg-bg", children: [_jsxs("div", { className: "flex shrink-0 items-center gap-1 border-b border-border bg-surface px-3 py-1.5", children: [_jsx(SurfaceTab, { active: surface === 'build', onClick: () => setSurface('build'), icon: _jsx(LayoutGrid, { size: 13 }), label: tGenerated('m_0b120bda8434d5') }), renderFlows ? (_jsx(SurfaceTab, { active: surface === 'flows', onClick: () => setSurface('flows'), icon: _jsx(WorkflowIcon, { size: 13 }), label: tGenerated('m_1a4786daa752b1') })) : null, _jsx(GeneratedValue, { value: surface === 'build' ? (_jsxs("div", { className: "ml-auto flex items-center gap-2", children: [_jsx("span", { className: "hidden text-[10px] font-semibold tracking-wider text-fg-muted uppercase sm:block", title: tGenerated('m_0342c212db3a34'), children: _jsx(GeneratedText, { id: "m_174bcc6db92052" }) }), _jsxs("div", { className: "flex items-center rounded-md border border-border p-0.5", children: [_jsx("button", { type: "button", onClick: () => setAllCanvas(false), className: `rounded px-2 py-0.5 text-xs font-medium transition ${!canvasMode
519
+ ? 'bg-fg text-bg'
520
+ : 'text-fg-muted hover:bg-surface-hover'}`, children: _jsx(GeneratedText, { id: "m_1e888eaf9e8eed" }) }), _jsxs("button", { type: "button", onClick: () => setAllCanvas(true), className: `flex items-center gap-1 rounded px-2 py-0.5 text-xs font-medium transition ${canvasMode
521
+ ? 'bg-primary text-primary-fg'
522
+ : 'text-fg-muted hover:bg-surface-hover'}`, children: [_jsx(LayoutGrid, { size: 12 }), " ", _jsx(GeneratedText, { id: "m_0627e4bbe74939" })] })] })] })) : null })] }), _jsx(GeneratedValue, { value: surface === 'flows' ? (_jsx("div", { className: "min-h-0 flex-1", children: renderFlows?.({ templateId, name: appName, schema, flows }) })) : (_jsx("div", { className: "app-scroll min-h-0 flex-1 overflow-y-auto p-4 lg:p-6", children: _jsx("div", { className: "w-full", children: _jsxs("div", { className: "space-y-3", children: [_jsx(GeneratedValue, { value: selection.kind === 'workflow' ? (_jsx(WorkflowEditor, { schema: schema, locale: contentLocale, defaultLocale: defaultLocale, onChange: (steps) => setSchema((s) => ({ ...s, workflow: { steps } })) })) : null }), _jsxs("div", { className: "flex flex-wrap items-center gap-1 rounded-lg border border-border bg-surface p-1.5", children: [_jsx(GeneratedValue, { value: appTabs.map((t) => (_jsxs("button", { type: "button", onClick: () => setDesignerTab(t.id), onDoubleClick: () => {
523
+ const next = window.prompt('Rename tab', localizeText(t.title, contentLocale, '', defaultLocale));
524
+ if (next != null)
525
+ renameTab(t.id, next.trim() || 'Tab');
526
+ }, title: tGenerated('m_0d31ecacb5b2ee'), className: `group flex items-center gap-1 rounded-md px-3 py-1 text-xs font-medium transition ${designerTab === t.id
527
+ ? 'bg-primary text-primary-fg'
528
+ : 'text-fg-muted hover:bg-surface-hover'}`, children: [_jsx(GeneratedValue, { value: localizeText(t.title, contentLocale, 'Tab', defaultLocale) }), _jsx(GeneratedValue, { value: appTabs.length > 1 ? (_jsx(Trash2, { size: 11, className: designerTab === t.id
529
+ ? 'text-primary-fg/70 hover:text-primary-fg'
530
+ : 'text-fg-subtle hover:text-danger', onClick: (e) => {
531
+ e.stopPropagation();
532
+ deleteTab(t.id);
533
+ } })) : null })] }, t.id))) }), _jsxs("button", { type: "button", onClick: addTab, className: "flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium text-primary hover:bg-primary-subtle hover:bg-primary-subtle/40", title: tGeneratedValue(appTabs.length === 0
534
+ ? tGenerated('m_1b7cb35c4f57dd')
535
+ : tGenerated('m_1e7fd6a7c790fe')), children: [_jsx(Plus, { size: 12 }), ' ', _jsx(GeneratedValue, { value: appTabs.length === 0 ? (_jsx(GeneratedText, { id: "m_0cfeced513f8d1" })) : (_jsx(GeneratedText, { id: "m_1457c1cce0b63c" })) })] })] }), _jsx(GeneratedValue, { value: visibleSections.map((sec, i) => {
536
+ const active = selection.kind === 'section' && selection.sectionId === sec.id;
537
+ return (_jsxs(Card, { onDragEnter: sec.canvas
538
+ ? undefined
539
+ : (event) => {
540
+ if (!dragElementRef.current)
541
+ return;
542
+ event.preventDefault();
543
+ setDropSectionId(sec.id);
544
+ }, onDragOver: sec.canvas
545
+ ? undefined
546
+ : (event) => {
547
+ if (!dragElementRef.current)
548
+ return;
549
+ event.preventDefault();
550
+ event.dataTransfer.dropEffect = 'copy';
551
+ }, onDrop: sec.canvas
552
+ ? undefined
553
+ : (event) => {
554
+ event.preventDefault();
555
+ event.stopPropagation();
556
+ const type = dragElementRef.current;
557
+ dragElementRef.current = null;
558
+ setDropSectionId(null);
559
+ if (type)
560
+ addField(sec.id, type);
561
+ }, className: `border transition-colors ${dropSectionId === sec.id
562
+ ? 'border-primary bg-primary-subtle/70 ring-2 ring-primary/40'
563
+ : active
564
+ ? 'border-primary ring-1 ring-primary'
565
+ : 'border-border'}`, children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between gap-2 pb-2", children: [_jsx("button", { type: "button", onClick: () => selectSection(sec.id), className: "flex-1 text-left", children: _jsxs(CardTitle, { className: "text-base", children: [_jsx(GeneratedValue, { value: localizeText(sec.title, contentLocale, 'Untitled section', defaultLocale) }), _jsx(GeneratedValue, { value: ' ' }), _jsx(GeneratedValue, { value: sec.repeating ? (_jsx(Badge, { variant: "secondary", children: _jsx(GeneratedText, { id: "m_06e1ca227aca9b" }) })) : null }), _jsx(GeneratedValue, { value: sec.showIf ? (_jsx(Badge, { variant: "outline", className: "text-[10px]", children: _jsx(GeneratedText, { id: "m_02529c1532db7a" }) })) : null }), _jsx(GeneratedValue, { value: sec.step ? (_jsxs(Badge, { variant: "outline", className: "text-[10px]", children: [_jsx(GeneratedText, { id: "m_11933adf482cac" }), ' ', _jsx(GeneratedValue, { value: stepLabel(schema, sec.step, contentLocale, defaultLocale) })] })) : null })] }) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(IconButton, { title: tGenerated('m_1ec1460770eaa0'), onClick: () => moveSection(sec.id, -1), disabled: i === 0, children: _jsx(ArrowUp, { size: 14 }) }), _jsx(IconButton, { title: tGenerated('m_14ab8cefda3cf9'), onClick: () => moveSection(sec.id, 1), disabled: i === visibleSections.length - 1, children: _jsx(ArrowDown, { size: 14 }) }), _jsx(IconButton, { title: tGenerated('m_1e97527af4024b'), onClick: () => deleteSection(sec.id), children: _jsx(Trash2, { size: 14, className: "text-danger" }) })] })] }), _jsx(CardContent, { className: "pt-0", children: _jsx(GeneratedValue, { value: sec.canvas ? (_jsx(CanvasEditor, { section: sec, locale: contentLocale, defaultLocale: defaultLocale, selectedFieldId: selection.kind === 'field' &&
566
+ selection.sectionId === sec.id
567
+ ? selection.fieldId
568
+ : null, dragTypeRef: dragElementRef, onLayout: (items) => setCanvasItems(sec.id, items), onAddWidget: (type, box) => addWidgetToCanvas(sec.id, type, box), onSelect: (fieldId) => selectField(sec.id, fieldId), onDelete: (fieldId) => deleteField(sec.id, fieldId) })) : sec.fields.length === 0 ? (_jsx("div", { className: "rounded-md border border-dashed border-border-strong p-4 text-center text-xs text-fg-muted border-border", children: _jsx(GeneratedText, { id: "m_0d6735f83b77a3" }) })) : (_jsx(Reorder.Group, { axis: "y", values: sec.fields, onReorder: (fields) => reorderFields(sec.id, fields), as: "ul", className: "divide-y divide-border-subtle", children: _jsx(GeneratedValue, { value: sec.fields.map((f, j) => (_jsx(FieldRow, { field: f, locale: contentLocale, defaultLocale: defaultLocale, isSelected: selection.kind === 'field' &&
569
+ selection.fieldId === f.id, typeLabel: FIELD_TYPES[f.type]?.label ?? f.type, onSelect: () => selectField(sec.id, f.id), onMoveUp: () => moveField(sec.id, f.id, -1), onMoveDown: () => moveField(sec.id, f.id, 1), onDelete: () => deleteField(sec.id, f.id), canUp: j > 0, canDown: j < sec.fields.length - 1 }, f.id))) }) })) }) })] }, sec.id));
570
+ }) }), _jsxs(Button, { variant: "outline", onClick: addSection, className: "w-full", children: [_jsx(Plus, { size: 14 }), _jsx(GeneratedText, { id: "m_0cfd5e4e441158" })] })] }) }) })) })] })] }), _jsx(Drawer, { open: rightPanel === 'props', onClose: () => setRightPanel('none'), title: tGeneratedValue(selection.kind === 'field'
571
+ ? tGenerated('m_0478595ea3a2da')
572
+ : tGenerated('m_0d513924d97753')), size: "sm", children: _jsx(GeneratedValue, { value: selection.kind === 'field' && selectedField ? (_jsx(FieldProperties, { sectionId: selectedField.section.id, field: selectedField.field, schema: schema, locale: contentLocale, defaultLocale: defaultLocale, dataSources: dataSources, dataSourcesLoading: dataSourcesLoading, onRefreshDataSources: onRefreshDataSources, dataSourcesHref: dataSourcesHref, onChange: (patch) => updateField(selectedField.section.id, selectedField.field.id, patch) }, selectedField.field.id)) : selection.kind === 'section' && selectedSection ? (_jsx(SectionProperties, { section: selectedSection, schema: schema, locale: contentLocale, defaultLocale: defaultLocale, onChange: (patch) => updateSection(selectedSection.id, patch) }, selectedSection.id)) : null }) }), _jsx(Drawer, { open: rightPanel === 'preview', onClose: () => setRightPanel('none'), title: tGenerated('m_11d37007232de5'), description: tGenerated('m_1e28aaa8e490af'), size: "lg", children: _jsx(Preview, { schema: schema, locale: contentLocale, defaultLocale: defaultLocale }) }), _jsx(Drawer, { open: showPublish, onClose: () => setShowPublish(false), title: tGenerated('m_04073463647168', { value0: currentVersion + 1 }), description: tGenerated('m_0eced645981830'), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => setShowPublish(false), children: _jsx(GeneratedText, { id: "m_112e2e8ecda428" }) }), _jsxs(Button, { onClick: publish, disabled: pending, children: [_jsx(Check, { size: 14 }), _jsx(GeneratedValue, { value: pending ? (_jsx(GeneratedText, { id: "m_12b32266eec3e2" })) : (_jsx(GeneratedText, { id: "m_0c072fb8baf115" })) })] })] }), children: _jsxs("div", { className: "space-y-3", children: [_jsxs(Alert, { variant: "info", children: [_jsx(AlertTitle, { children: _jsx(GeneratedText, { id: "m_1c2ad488291a89" }) }), _jsxs(AlertDescription, { children: [_jsx(GeneratedText, { id: "m_16d04782fa275d" }), _jsx(GeneratedValue, { value: currentVersion + 1 }), _jsx(GeneratedText, { id: "m_025b2e6dff2c41" })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: _jsx(GeneratedText, { id: "m_155fd3bdb6e009" }) }), _jsx(Textarea, { rows: 3, value: changelog, placeholder: tGenerated('m_061739b5f07deb'), onChange: (e) => setChangelog(e.target.value) })] }), _jsx(GeneratedValue, { value: error ? (_jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: _jsx(GeneratedValue, { value: error }) }) })) : null })] }) }), renderAssistant?.({
573
+ open: showAiAssistant,
574
+ onClose: () => setShowAiAssistant(false),
575
+ templateId,
576
+ schema,
577
+ apply: (next) => {
578
+ const normalized = normalizeDesignerSchema(next);
579
+ setSchema(normalized);
580
+ if (normalized.tabs?.length)
581
+ setDesignerTab(normalized.tabs[0].id);
582
+ toast.success(tGenerated('m_180fea01504577'));
583
+ },
584
+ })] }));
585
+ }
586
+ function stepLabel(schema, stepKey, locale, defaultLocale) {
587
+ const step = schema.workflow?.steps.find((s) => s.key === stepKey);
588
+ return localizeText(step?.title, locale, stepKey, defaultLocale);
589
+ }
590
+ function FieldPaletteGroup({ group, onAdd, onDragType, onDragEnd, }) {
591
+ const tGenerated = useGeneratedTranslations();
592
+ return (_jsxs("div", { className: "mb-3", children: [_jsx("div", { className: "px-1 pb-1 text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedValue, { value: group.label }) }), _jsx("div", { className: "grid grid-cols-1 gap-1", children: _jsx(GeneratedValue, { value: group.types.map((t) => {
593
+ const Icon = FIELD_ICONS[t] ?? Type;
594
+ return (_jsxs("button", { type: "button", draggable: true, onDragStart: (e) => {
595
+ onDragType?.(t);
596
+ e.dataTransfer.setData('text/plain', t);
597
+ e.dataTransfer.effectAllowed = 'copy';
598
+ }, onDragEnd: onDragEnd, onClick: () => onAdd(t), className: "flex cursor-grab items-center gap-2 rounded border border-border px-2 py-1 text-left text-xs hover:border-primary hover:bg-primary-subtle active:cursor-grabbing hover:bg-primary-subtle/40", title: tGenerated('m_1b1fa20684f949'), children: [_jsx(Icon, { size: 12 }), _jsx(GeneratedValue, { value: FIELD_TYPES[t].label })] }, t));
599
+ }) }) })] }));
600
+ }
601
+ function IconButton({ title, onClick, disabled, children, }) {
602
+ const tGeneratedValue = useGeneratedValueTranslations();
603
+ return (_jsx("button", { type: "button", title: tGeneratedValue(title), onClick: onClick, disabled: disabled, className: "rounded p-1 text-fg-muted hover:bg-surface-hover hover:text-fg disabled:cursor-not-allowed disabled:opacity-40", children: _jsx(GeneratedValue, { value: children }) }));
604
+ }
605
+ // One draggable field row in the designer canvas. Drag is handle-only (via
606
+ // framer-motion dragControls) so clicking the row still selects the field; the
607
+ // up/down arrows remain as a keyboard-accessible fallback.
608
+ function FieldRow({ field, locale, defaultLocale, isSelected, typeLabel, onSelect, onMoveUp, onMoveDown, onDelete, canUp, canDown, }) {
609
+ const tGenerated = useGeneratedTranslations();
610
+ const controls = useDragControls();
611
+ const Icon = FIELD_ICONS[field.type] ?? Type;
612
+ return (_jsxs(Reorder.Item, { value: field, dragListener: false, dragControls: controls, as: "li", className: `flex items-center justify-between gap-2 rounded px-1 py-2 ${isSelected ? 'bg-primary-subtle' : 'bg-surface'}`, children: [_jsx("button", { type: "button", "aria-label": tGenerated('m_0b04b904ce4f9a'), onPointerDown: (e) => controls.start(e), className: "cursor-grab touch-none rounded p-0.5 text-fg-subtle hover:text-fg-muted active:cursor-grabbing", children: _jsx(GripVertical, { size: 14 }) }), _jsxs("button", { type: "button", onClick: onSelect, className: "flex flex-1 items-center gap-2 text-left", children: [_jsx(Icon, { size: 14 }), _jsx("span", { className: "w-24 truncate text-xs text-fg-muted", children: _jsx(GeneratedValue, { value: typeLabel }) }), _jsxs("span", { className: "text-sm font-medium", children: [_jsx(GeneratedValue, { value: localizeText(field.label, locale, field.id, defaultLocale) }), _jsx(GeneratedValue, { value: field.required || field.validation?.required ? (_jsx("span", { className: "text-danger", children: " *" })) : null })] }), _jsx(GeneratedValue, { value: field.showIf ? (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: _jsx(GeneratedText, { id: "m_02529c1532db7a" }) })) : null }), _jsx(GeneratedValue, { value: field.formula ? (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: _jsx(GeneratedText, { id: "m_194bb13d821775" }) })) : null })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(IconButton, { title: tGenerated('m_1ec1460770eaa0'), onClick: onMoveUp, disabled: !canUp, children: _jsx(ArrowUp, { size: 12 }) }), _jsx(IconButton, { title: tGenerated('m_14ab8cefda3cf9'), onClick: onMoveDown, disabled: !canDown, children: _jsx(ArrowDown, { size: 12 }) }), _jsx(IconButton, { title: tGenerated('m_11773f3c3f7558'), onClick: onDelete, children: _jsx(Trash2, { size: 12, className: "text-danger" }) })] })] }));
613
+ }
614
+ // --- Form-level properties -------------------------------------------------
615
+ // --- Workflow editor -------------------------------------------------------
616
+ function WorkflowEditor({ schema, locale, defaultLocale, onChange, }) {
617
+ const tGeneratedValue = useGeneratedValueTranslations();
618
+ const tGenerated = useGeneratedTranslations();
619
+ const steps = schema.workflow?.steps ?? [];
620
+ function setStep(i, patch) {
621
+ onChange(steps.map((s, j) => (j === i ? { ...s, ...patch } : s)));
622
+ }
623
+ function add() {
624
+ onChange([...steps, emptyStep()]);
625
+ }
626
+ function remove(i) {
627
+ if (steps.length === 1)
628
+ return;
629
+ onChange(steps.filter((_, j) => j !== i));
630
+ }
631
+ function move(i, delta) {
632
+ const j = i + delta;
633
+ if (j < 0 || j >= steps.length)
634
+ return;
635
+ const next = [...steps];
636
+ [next[i], next[j]] = [next[j], next[i]];
637
+ onChange(next);
638
+ }
639
+ return (_jsxs(Card, { className: "border-2 border-primary/40", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: _jsx(GeneratedText, { id: "m_1c75ba367ef317" }) }) }), _jsxs(CardContent, { className: "space-y-2", children: [_jsxs("p", { className: "text-xs text-fg-muted", children: [_jsx(GeneratedText, { id: "m_1a11f78c7d99d8" }), ' ', _jsx("em", { children: _jsx(GeneratedText, { id: "m_02b0a83932da4a" }) }), ' ', "\u2192", _jsx(GeneratedValue, { value: ' ' }), _jsx("em", { children: _jsx(GeneratedText, { id: "m_14efdd0bff7d70" }) }), ' ', "\u2192", ' ', _jsx("em", { children: _jsx(GeneratedText, { id: "m_1cc0241831296d" }) }), _jsx(GeneratedText, { id: "m_17833f54b99d38" }), _jsx(GeneratedValue, { value: ' ' }), _jsx("strong", { children: _jsx(GeneratedText, { id: "m_1c8658eb9347b4" }) }), ' ', _jsx(GeneratedText, { id: "m_0bf548b271dbc1" }), ' ', _jsx("strong", { children: _jsx(GeneratedText, { id: "m_1a4786daa752b1" }) }), ' ', _jsx(GeneratedText, { id: "m_003ae75549476b" })] }), _jsx("ul", { className: "space-y-2", children: _jsx(GeneratedValue, { value: steps.map((s, i) => (_jsx("li", { className: "rounded-md border border-border bg-surface p-2", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-1 inline-flex h-6 w-6 items-center justify-center rounded-full bg-primary-subtle text-xs font-semibold text-primary bg-primary-subtle/40", children: _jsx(GeneratedValue, { value: i + 1 }) }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-[10px]", children: _jsx(GeneratedText, { id: "m_0decefd558c355" }) }), _jsx(Input, { className: "h-7 text-xs", value: readText(s.title, locale), placeholder: tGeneratedValue(localizeText(s.title, locale, s.key, defaultLocale)), onChange: (e) => setStep(i, {
640
+ title: writeText(s.title, e.target.value, locale),
641
+ }) })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-[10px]", children: _jsx(GeneratedText, { id: "m_147b61c2aef29b" }) }), _jsx(Input, { className: "h-7 font-mono text-xs", value: s.key, disabled: true })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-[10px]", children: _jsx(GeneratedText, { id: "m_160787e531bffd" }) }), _jsxs(Select, { className: "h-7 text-xs", value: s.assignee.type, onChange: (e) => {
642
+ const t = e.target.value;
643
+ const assignee = t === 'literal'
644
+ ? { type: 'literal', userId: '' }
645
+ : t === 'role'
646
+ ? { type: 'role', role: '' }
647
+ : { type: 'expression', expr: '$submitter' };
648
+ setStep(i, { assignee });
649
+ }, children: [_jsx("option", { value: "expression", children: 'Expression' }), _jsx("option", { value: "role", children: 'Role' }), _jsx("option", { value: "literal", children: 'Specific user' })] })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-[10px]", children: _jsx(GeneratedText, { id: "m_078d7e637546f9" }) }), _jsx(Input, { className: "h-7 text-xs", value: s.assignee.type === 'literal'
650
+ ? s.assignee.userId
651
+ : s.assignee.type === 'role'
652
+ ? s.assignee.role
653
+ : s.assignee.expr, onChange: (e) => {
654
+ const v = e.target.value;
655
+ if (s.assignee.type === 'literal')
656
+ setStep(i, { assignee: { type: 'literal', userId: v } });
657
+ else if (s.assignee.type === 'role')
658
+ setStep(i, { assignee: { type: 'role', role: v } });
659
+ else
660
+ setStep(i, { assignee: { type: 'expression', expr: v } });
661
+ } })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-xs", children: [_jsx("input", { type: "checkbox", checked: s.signatureRequired ?? false, onChange: (e) => setStep(i, { signatureRequired: e.target.checked }) }), _jsx(GeneratedText, { id: "m_02ffe91f500dc8" })] })] }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx(IconButton, { title: tGenerated('m_1ec1460770eaa0'), onClick: () => move(i, -1), disabled: i === 0, children: _jsx(ArrowUp, { size: 12 }) }), _jsx(IconButton, { title: tGenerated('m_14ab8cefda3cf9'), onClick: () => move(i, 1), disabled: i === steps.length - 1, children: _jsx(ArrowDown, { size: 12 }) }), _jsx(IconButton, { title: tGenerated('m_09aebdb43a47c7'), onClick: () => remove(i), disabled: steps.length === 1, children: _jsx(Trash2, { size: 12, className: "text-danger" }) })] })] }) }, s.key))) }) }), _jsxs(Button, { variant: "outline", onClick: add, className: "w-full", children: [_jsx(Plus, { size: 14 }), " ", _jsx(GeneratedText, { id: "m_0ce705b8fa979c" })] })] })] }));
662
+ }
663
+ function FieldProperties({ sectionId, field, schema, locale, defaultLocale, dataSources, dataSourcesLoading, onRefreshDataSources, dataSourcesHref, onChange, }) {
664
+ const [tab, setTab] = useState('basic');
665
+ const isCalcField = field.type === 'formula';
666
+ const storesValue = storesResponseValue(field);
667
+ const ownerSection = schema.sections.find((section) => section.id === sectionId);
668
+ const otherFields = schema.sections
669
+ .flatMap((section) => {
670
+ if (section.repeating && section.id !== sectionId)
671
+ return [];
672
+ if (!ownerSection?.repeating && section.repeating)
673
+ return [];
674
+ return section.fields;
675
+ })
676
+ .filter((candidate) => candidate.id !== field.id && storesResponseValue(candidate))
677
+ .map((f) => ({ id: f.id, label: localizeText(f.label, locale, f.id, defaultLocale) }));
678
+ const repeatingSections = schema.sections
679
+ .filter((s) => s.repeating)
680
+ .map((s) => ({
681
+ id: s.id,
682
+ label: localizeText(s.title, locale, s.id, defaultLocale),
683
+ fields: s.fields
684
+ .filter((candidate) => storesResponseValue(candidate) ||
685
+ (!ownerSection?.repeating &&
686
+ candidate.type === 'formula' &&
687
+ candidate.formula !== undefined))
688
+ .map((f) => ({
689
+ id: f.id,
690
+ label: localizeText(f.label, locale, f.id, defaultLocale),
691
+ })),
692
+ }));
693
+ // Single-entity picker fields the formula builder's entity_attr operator
694
+ // can target. Multi-pickers are excluded because entity_attr resolves one
695
+ // entity per picker, not a list.
696
+ const pickerFields = schema.sections
697
+ .filter((s) => !s.repeating)
698
+ .flatMap((s) => s.fields)
699
+ .filter((f) => entityKindForPicker(f.type) !== null)
700
+ .map((f) => ({
701
+ id: f.id,
702
+ label: localizeText(f.label, locale, f.id, defaultLocale),
703
+ kind: entityKindForPicker(f.type),
704
+ }));
705
+ const tabs = [
706
+ { value: 'basic', label: 'Basic', show: true },
707
+ { value: 'validation', label: 'Validation', show: storesValue },
708
+ { value: 'logic', label: 'Logic', show: true },
709
+ { value: 'default', label: 'Default', show: storesValue },
710
+ { value: 'formula', label: 'Formula', show: isCalcField },
711
+ ];
712
+ return (_jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("h3", { className: "text-sm font-semibold text-fg", children: [_jsx(GeneratedText, { id: "m_0bff3b64e15427" }), ' ', _jsx(GeneratedValue, { value: FIELD_TYPES[field.type]?.label ?? field.type })] }), _jsx("div", { className: "flex gap-1 border-b border-border", children: _jsx(GeneratedValue, { value: tabs
713
+ .filter((t) => t.show)
714
+ .map((t) => (_jsx("button", { type: "button", onClick: () => setTab(t.value), className: `-mb-px border-b-2 px-2 py-1 text-xs ${tab === t.value
715
+ ? 'border-primary font-semibold text-primary'
716
+ : 'border-transparent text-fg-muted hover:text-fg'}`, children: _jsx(GeneratedValue, { value: t.label }) }, t.value))) }) }), _jsx(GeneratedValue, { value: tab === 'basic' ? (_jsx(FieldBasicTab, { sectionId: sectionId, field: field, schema: schema, locale: locale, defaultLocale: defaultLocale, dataSources: dataSources, dataSourcesLoading: dataSourcesLoading, onRefreshDataSources: onRefreshDataSources, dataSourcesHref: dataSourcesHref, onChange: onChange })) : null }), _jsx(GeneratedValue, { value: tab === 'validation' && storesValue ? (_jsx(FieldValidationTab, { field: field, onChange: onChange })) : null }), _jsx(GeneratedValue, { value: tab === 'logic' ? (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_024d5ac76425dc" }) }), _jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_0db2c879ad4429" }) }), _jsx(LogicBuilder, { rule: field.showIf, availableFields: otherFields, onChange: (rule) => onChange({ showIf: rule }) })] })) : null }), _jsx(GeneratedValue, { value: tab === 'default' && storesValue ? (_jsx(FieldDefaultTab, { field: field, onChange: onChange })) : null }), _jsx(GeneratedValue, { value: tab === 'formula' && isCalcField ? (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1f15a1cd1a64c4" }) }), _jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_0cd91fbbd3fca0" }) }), _jsx(FormulaBuilder, { value: field.formula, allFields: otherFields, repeatingSections: repeatingSections, pickerFields: pickerFields, onChange: (next) => onChange({ formula: next }) })] })) : null })] }));
717
+ }
718
+ function FieldBasicTab({ sectionId, field, schema, locale, defaultLocale, dataSources, dataSourcesLoading, onRefreshDataSources, dataSourcesHref, onChange, }) {
719
+ const tGeneratedValue = useGeneratedValueTranslations();
720
+ // Other fields in the app — targets for cascade filters + lookup auto-fill.
721
+ const ownerSection = schema.sections.find((section) => section.id === sectionId);
722
+ const otherFields = schema.sections
723
+ .flatMap((section) => {
724
+ if (section.repeating && section.id !== sectionId)
725
+ return [];
726
+ if (!ownerSection?.repeating && section.repeating)
727
+ return [];
728
+ return section.fields;
729
+ })
730
+ .filter((candidate) => candidate.id !== field.id && storesResponseValue(candidate))
731
+ .map((f) => ({ id: f.id, label: localizeText(f.label, locale, f.id, defaultLocale) }));
732
+ const autofillFields = (ownerSection?.repeating
733
+ ? ownerSection.fields
734
+ : schema.sections.filter((section) => !section.repeating).flatMap((section) => section.fields))
735
+ .filter((candidate) => candidate.id !== field.id && storesResponseValue(candidate))
736
+ .map((candidate) => ({
737
+ id: candidate.id,
738
+ label: localizeText(candidate.label, locale, candidate.id, defaultLocale),
739
+ }));
740
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_13d7101bd1f531" }) }), _jsx(Input, { value: field.id, disabled: true, className: "font-mono text-xs" })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "text-xs", children: [_jsx(GeneratedText, { id: "m_1d4f77715c6f3d" }), _jsx(GeneratedValue, { value: locale.toUpperCase() }), ")"] }), _jsx(Input, { value: readText(field.label, locale), placeholder: tGeneratedValue(localizeText(field.label, locale, field.id, defaultLocale)), onChange: (e) => onChange({ label: writeText(field.label, e.target.value, locale) }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_0d04877b1a742b" }) }), _jsx(Textarea, { rows: 2, value: readText(field.helpText, locale), placeholder: tGeneratedValue(localizeText(field.helpText, locale, '', defaultLocale)), onChange: (e) => {
741
+ onChange({
742
+ helpText: e.target.value
743
+ ? writeText(field.helpText, e.target.value, locale)
744
+ : undefined,
745
+ });
746
+ } })] }), _jsx(GeneratedValue, { value: storesResponseValue(field) ? (_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: field.required ?? false, onChange: (e) => onChange({ required: e.target.checked }) }), _jsx(GeneratedText, { id: "m_12fe2fe7a9ddad" })] })) : null }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_14ea5fb193bdaf" }) }), _jsxs(Select, { className: "h-8 text-xs", value: String(field.colSpan ?? ''), onChange: (e) => onChange({ colSpan: e.target.value ? Number(e.target.value) : undefined }), children: [_jsx("option", { value: "", children: 'Full width' }), _jsx("option", { value: "1", children: '1 column' }), _jsx("option", { value: "2", children: '2 columns' }), _jsx("option", { value: "3", children: '3 columns' }), _jsx("option", { value: "4", children: '4 columns' })] }), _jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_0f334abe43e80f" }) })] }), _jsx(GeneratedValue, { value: field.type === 'select' ||
747
+ field.type === 'radio' ||
748
+ field.type === 'multi_select' ||
749
+ field.type === 'checkbox_group' ||
750
+ field.type === 'ranking' ? (_jsx(ChoiceOptionsEditor, { field: field, locale: locale, onChange: onChange })) : null }), _jsx(GeneratedValue, { value: field.type === 'table' ? _jsx(TableConfigEditor, { field: field, onChange: onChange }) : null }), _jsx(GeneratedValue, { value: field.type === 'slider' ? _jsx(SliderConfigEditor, { field: field, onChange: onChange }) : null }), _jsx(GeneratedValue, { value: field.type === 'matrix' ? _jsx(MatrixConfigEditor, { field: field, onChange: onChange }) : null }), _jsx(GeneratedValue, { value: field.type === 'lookup' || field.type === 'data_table' || field.type === 'metric' ? (_jsx(DataBindingEditor, { field: field, otherFields: otherFields, autofillFields: autofillFields, sources: dataSources, loading: dataSourcesLoading, onRefresh: onRefreshDataSources, dataSourcesHref: dataSourcesHref, onChange: onChange })) : null })] }));
751
+ }
752
+ function SliderConfigEditor({ field, onChange, }) {
753
+ const tGenerated = useGeneratedTranslations();
754
+ const c = (field.config ?? {});
755
+ const set = (patch) => onChange({ config: { ...field.config, ...patch } });
756
+ return (_jsxs("div", { className: "space-y-2 rounded-md border border-border p-2", children: [_jsx("div", { className: "text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_03363edf51ed99" }) }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_100639ca393959" }) }), _jsx(Input, { type: "number", value: c.min ?? 0, onChange: (e) => set({ min: Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1929e34b445f83" }) }), _jsx(Input, { type: "number", value: c.max ?? 10, onChange: (e) => set({ max: Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_0cff7e37da2b3f" }) }), _jsx(Input, { type: "number", value: c.step ?? 1, onChange: (e) => set({ step: Number(e.target.value) }) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_19241615edae92" }) }), _jsx(Input, { value: c.unit ?? '', placeholder: tGenerated('m_075f330b972a3b'), onChange: (e) => set({ unit: e.target.value }) })] })] }));
757
+ }
758
+ function MatrixConfigEditor({ field, onChange, }) {
759
+ const tGenerated = useGeneratedTranslations();
760
+ const c = (field.config ?? {});
761
+ const rows = c.rows ?? [];
762
+ const scale = c.scale ?? [];
763
+ const setRows = (next) => onChange({ config: { ...field.config, rows: next } });
764
+ const setScale = (next) => onChange({ config: { ...field.config, scale: next } });
765
+ return (_jsxs("div", { className: "space-y-3 rounded-md border border-border p-2", children: [_jsxs("div", { children: [_jsxs("div", { className: "mb-1 flex items-center justify-between", children: [_jsx("span", { className: "text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_03be2202673df4" }) }), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => setRows([...rows, { key: newId('row'), label: `Row ${rows.length + 1}` }]), children: _jsx(GeneratedText, { id: "m_0d067cf371aad1" }) })] }), _jsx("div", { className: "space-y-1", children: _jsx(GeneratedValue, { value: rows.map((r, i) => (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { value: r.label, onChange: (e) => setRows(rows.map((x, j) => (j === i ? { ...x, label: e.target.value } : x))) }), _jsx(IconButton, { title: tGenerated('m_12b310a027b08a'), onClick: () => setRows(rows.filter((_, j) => j !== i)), children: _jsx(Trash2, { size: 13, className: "text-danger" }) })] }, r.key))) }) })] }), _jsxs("div", { children: [_jsxs("div", { className: "mb-1 flex items-center justify-between", children: [_jsx("span", { className: "text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_1bd1bb813826dc" }) }), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => setScale([
766
+ ...scale,
767
+ { value: String(scale.length + 1), label: String(scale.length + 1) },
768
+ ]), children: _jsx(GeneratedText, { id: "m_0f92f0f3b0d515" }) })] }), _jsx("div", { className: "space-y-1", children: _jsx(GeneratedValue, { value: scale.map((s, i) => (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { className: "w-16 font-mono text-xs", value: s.value, onChange: (e) => setScale(scale.map((x, j) => (j === i ? { ...x, value: e.target.value } : x))) }), _jsx(Input, { value: s.label, onChange: (e) => setScale(scale.map((x, j) => (j === i ? { ...x, label: e.target.value } : x))) }), _jsx(IconButton, { title: tGenerated('m_095b70fae90c39'), onClick: () => setScale(scale.filter((_, j) => j !== i)), children: _jsx(Trash2, { size: 13, className: "text-danger" }) })] }, i))) }) })] })] }));
769
+ }
770
+ function DataBindingEditor({ field, otherFields, autofillFields, sources, loading, onRefresh, dataSourcesHref, onChange, }) {
771
+ const b = field.binding;
772
+ const source = sources.find((s) => s.key === b?.sourceKey);
773
+ const cols = source?.columns ?? [];
774
+ // Persist a binding patch — but drop the binding entirely if no source is
775
+ // chosen, so the schema never carries an invalid empty sourceKey.
776
+ const patch = (p) => {
777
+ const next = { ...(b ?? {}), ...p };
778
+ if (!next.sourceKey) {
779
+ onChange({ binding: undefined });
780
+ return;
781
+ }
782
+ onChange({
783
+ binding: next,
784
+ ...(field.type === 'data_table' && p.selectable === 'none'
785
+ ? { required: undefined, validation: undefined, defaultValue: undefined }
786
+ : {}),
787
+ });
788
+ };
789
+ return (_jsxs("div", { className: "space-y-2.5 rounded-md border border-primary bg-primary-subtle/30 p-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-[10px] font-semibold tracking-wider text-primary uppercase", children: _jsx(GeneratedText, { id: "m_0d523c82eb812b" }) }), dataSourcesHref ? (_jsx("a", { href: dataSourcesHref, target: "_blank", rel: "noreferrer", className: "text-[11px] text-primary hover:underline", children: _jsx(GeneratedText, { id: "m_1154a02825ffe0" }) })) : null, onRefresh ? (_jsxs("button", { type: "button", onClick: () => void onRefresh(), className: "inline-flex items-center gap-1 text-[11px] text-primary hover:underline", children: [_jsx(RefreshCw, { size: 11 }), _jsx(GeneratedText, { id: "m_16f11d7bc7b7b4" })] })) : null] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1e51e17fccd721" }) }), _jsxs(Select, { className: "h-8 text-xs", value: b?.sourceKey ?? '', onChange: (e) => onChange({
790
+ binding: e.target.value ? { sourceKey: e.target.value } : undefined,
791
+ ...(field.type === 'data_table'
792
+ ? { required: undefined, validation: undefined, defaultValue: undefined }
793
+ : {}),
794
+ }), children: [_jsx("option", { value: "", children: loading ? 'Loading…' : '— pick a data source —' }), sources.map((s) => (_jsx("option", { value: s.key, children: s.name }, s.id)))] }), _jsx(GeneratedValue, { value: !loading && sources.length === 0 ? (_jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_19589d12bcbbf9" }) })) : null })] }), _jsx(GeneratedValue, { value: source ? (_jsxs(_Fragment, { children: [_jsx(GeneratedValue, { value: field.type === 'lookup' ? (_jsx(LookupBindingFields, { b: b, cols: cols, otherFields: otherFields, autofillFields: autofillFields, patch: patch })) : null }), _jsx(GeneratedValue, { value: field.type === 'data_table' ? (_jsx(DataTableBindingFields, { b: b, cols: cols, otherFields: otherFields, patch: patch })) : null }), _jsx(GeneratedValue, { value: field.type === 'metric' ? (_jsx(MetricBindingFields, { b: b, cols: cols, otherFields: otherFields, patch: patch })) : null })] })) : null })] }));
795
+ }
796
+ function LookupBindingFields({ b, cols, otherFields, autofillFields, patch, }) {
797
+ const tGenerated = useGeneratedTranslations();
798
+ const autofill = b?.autofill ?? [];
799
+ return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_16ba74a70fdc2f" }) }), _jsxs(Select, { className: "h-8 text-xs", value: b?.labelColumn ?? '', onChange: (e) => patch({ labelColumn: e.target.value || undefined }), children: [_jsx("option", { value: "", children: 'First column' }), cols.map((c) => (_jsx("option", { value: c.key, children: c.label }, c.key)))] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_01c681f83ca98e" }) }), _jsxs(Select, { className: "h-8 text-xs", value: b?.valueColumn ?? '', onChange: (e) => patch({ valueColumn: e.target.value || undefined }), children: [_jsx("option", { value: "", children: 'Row id' }), cols.map((c) => (_jsx("option", { value: c.key, children: c.label }, c.key)))] })] })] }), _jsx(CascadeBindingFields, { b: b, cols: cols, otherFields: otherFields, patch: patch }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_0d55cb5819a8ab" }) }), _jsx(Input, { type: "number", min: 1, max: 1000, className: "h-8 text-xs", value: b?.limit ?? '', placeholder: "50", onChange: (e) => patch({ limit: e.target.value ? Number(e.target.value) : undefined }) }), _jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_1c39dfb38ad051" }) })] }), _jsxs("div", { className: "space-y-1.5 rounded border border-border bg-surface p-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_1aa941e2f78dc5" }) }), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => patch({
800
+ autofill: [
801
+ ...autofill,
802
+ { column: cols[0]?.key ?? '', targetFieldId: autofillFields[0]?.id ?? '' },
803
+ ],
804
+ }), children: _jsx(GeneratedText, { id: "m_0e2a97913eb84b" }) })] }), _jsx(GeneratedValue, { value: autofill.length === 0 ? (_jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_125586069a681f" }) })) : (autofill.map((m, i) => (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Select, { className: "h-8 text-xs", value: m.column, onChange: (e) => patch({
805
+ autofill: autofill.map((x, j) => j === i ? { ...x, column: e.target.value } : x),
806
+ }), children: cols.map((c) => (_jsx("option", { value: c.key, children: c.label }, c.key))) }), _jsx("span", { className: "text-fg-muted", children: "\u2192" }), _jsx(Select, { className: "h-8 text-xs", value: m.targetFieldId, onChange: (e) => patch({
807
+ autofill: autofill.map((x, j) => j === i ? { ...x, targetFieldId: e.target.value } : x),
808
+ }), children: autofillFields.map((f) => (_jsx("option", { value: f.id, children: f.label }, f.id))) }), _jsx(IconButton, { title: tGenerated('m_1d82f43c88df4c'), onClick: () => patch({ autofill: autofill.filter((_, j) => j !== i) }), children: _jsx(Trash2, { size: 13, className: "text-danger" }) })] }, i)))) })] })] }));
809
+ }
810
+ function CascadeBindingFields({ b, cols, otherFields, patch, }) {
811
+ return (_jsxs("div", { className: "space-y-1.5 rounded border border-border bg-surface p-2", children: [_jsx("div", { className: "text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_171519729cccb4" }) }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_0bb347c23433ea" }) }), _jsxs(Select, { className: "h-8 text-xs", value: b?.filterByField ?? '', onChange: (e) => patch(e.target.value
812
+ ? { filterByField: e.target.value }
813
+ : { filterByField: undefined, filterColumn: undefined }), children: [_jsx("option", { value: "", children: '— none —' }), otherFields.map((f) => (_jsx("option", { value: f.id, children: f.label }, f.id)))] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_0fdf1d8380c89b" }) }), _jsxs(Select, { className: "h-8 text-xs", value: b?.filterColumn ?? '', disabled: !b?.filterByField, onChange: (e) => patch({ filterColumn: e.target.value || undefined }), children: [_jsx("option", { value: "", children: "\u2014" }), cols.map((c) => (_jsx("option", { value: c.key, children: c.label }, c.key)))] })] })] }), _jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_03f068cc4e9384" }) })] }));
814
+ }
815
+ function DataTableBindingFields({ b, cols, otherFields, patch, }) {
816
+ const allShown = !b?.columns;
817
+ const shown = b?.columns ?? cols.map((c) => c.key);
818
+ const toggle = (key) => {
819
+ const base = b?.columns ?? cols.map((c) => c.key);
820
+ const next = base.includes(key) ? base.filter((k) => k !== key) : [...base, key];
821
+ patch({ columns: next });
822
+ };
823
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx(CascadeBindingFields, { b: b, cols: cols, otherFields: otherFields, patch: patch }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_16f13907f8a65a" }) }), _jsx("div", { className: "flex flex-wrap gap-1.5 rounded border border-border bg-surface p-1.5", children: _jsx(GeneratedValue, { value: cols.map((c) => {
824
+ const on = allShown || shown.includes(c.key);
825
+ return (_jsx("button", { type: "button", onClick: () => toggle(c.key), className: `rounded px-1.5 py-0.5 text-[11px] ${on ? 'bg-primary-subtle text-primary' : 'bg-surface-hover text-fg-muted line-through'}`, children: _jsx(GeneratedValue, { value: c.label }) }, c.key));
826
+ }) }) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_14b18c80af7bd4" }) }), _jsxs(Select, { className: "h-8 text-xs", value: b?.selectable ?? 'none', onChange: (e) => patch({ selectable: e.target.value }), children: [_jsx("option", { value: "none", children: 'Display only' }), _jsx("option", { value: "single", children: 'Pick one row' }), _jsx("option", { value: "multi", children: 'Pick many rows' })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1ab6807a4e8f9e" }) }), _jsx(Input, { type: "number", min: 1, max: 1000, className: "h-8 text-xs", value: b?.limit ?? '', placeholder: "25", onChange: (e) => patch({ limit: e.target.value ? Number(e.target.value) : undefined }) })] })] })] }));
827
+ }
828
+ function MetricBindingFields({ b, cols, otherFields, patch, }) {
829
+ const tGeneratedValue = useGeneratedValueTranslations();
830
+ const agg = b?.aggregate ?? { fn: 'count' };
831
+ const setAgg = (p) => patch({ aggregate: { ...agg, ...p } });
832
+ const needsColumn = agg.fn !== 'count';
833
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx(CascadeBindingFields, { b: b, cols: cols, otherFields: otherFields, patch: patch }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_102bef82722058" }) }), _jsxs(Select, { className: "h-8 text-xs", value: agg.fn, onChange: (e) => {
834
+ const fn = e.target.value;
835
+ setAgg(fn === 'count' ? { fn, column: undefined } : { fn });
836
+ }, children: [_jsx("option", { value: "count", children: 'Count' }), _jsx("option", { value: "sum", children: 'Sum' }), _jsx("option", { value: "avg", children: 'Average' }), _jsx("option", { value: "min", children: 'Min' }), _jsx("option", { value: "max", children: 'Max' })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_166da8c01a8107" }) }), _jsxs(Select, { className: "h-8 text-xs", value: agg.column ?? '', disabled: !needsColumn, onChange: (e) => setAgg({ column: e.target.value || undefined }), children: [_jsx("option", { value: "", children: needsColumn ? '— pick —' : 'n/a' }), cols.map((c) => (_jsx("option", { value: c.key, children: c.label }, c.key)))] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_175ee59112fb66" }) }), _jsxs(Select, { className: "h-8 text-xs", value: agg.groupBy ?? '', onChange: (e) => {
837
+ const groupBy = e.target.value || undefined;
838
+ patch({
839
+ aggregate: { ...agg, groupBy },
840
+ display: groupBy ? (b?.display === 'pie' ? 'pie' : 'bar') : 'number',
841
+ });
842
+ }, children: [_jsx("option", { value: "", children: '— no grouping —' }), cols.map((c) => (_jsx("option", { value: c.key, children: c.label }, c.key)))] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_06b9949f5fa1bc" }) }), _jsx(Select, { className: "h-8 text-xs", value: b?.display ?? (agg.groupBy ? 'bar' : 'number'), onChange: (e) => patch({ display: e.target.value }), children: agg.groupBy ? (_jsxs(_Fragment, { children: [_jsx("option", { value: "bar", children: 'Bar chart' }), _jsx("option", { value: "pie", children: 'Pie chart' })] })) : (_jsx("option", { value: "number", children: 'Number' })) })] })] }), _jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_067480a82419f2" }) }), _jsx(GeneratedValue, { value: agg.groupBy ? (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_147c46b5f494d3" }) }), _jsx(Input, { type: "number", min: 1, max: 1000, className: "h-8 text-xs", value: b?.limit ?? '', placeholder: tGeneratedValue(b?.display === 'pie' ? '8' : '12'), onChange: (e) => patch({ limit: e.target.value ? Number(e.target.value) : undefined }) }), _jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_018f962bfee661" }) })] })) : null })] }));
843
+ }
844
+ function FieldValidationTab({ field, onChange, }) {
845
+ const tGenerated = useGeneratedTranslations();
846
+ const v = field.validation ?? {};
847
+ function set(patch) {
848
+ const next = { ...v, ...patch };
849
+ // Strip undefineds so the JSON stays tight.
850
+ for (const k of Object.keys(next)) {
851
+ if (next[k] === undefined || next[k] === '')
852
+ delete next[k];
853
+ }
854
+ onChange({ validation: Object.keys(next).length ? next : undefined });
855
+ }
856
+ const isNumeric = field.type === 'number' || field.type === 'rating';
857
+ const isText = field.type === 'text' ||
858
+ field.type === 'long_text' ||
859
+ field.type === 'email' ||
860
+ field.type === 'phone' ||
861
+ field.type === 'url';
862
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: v.required ?? field.required ?? false, onChange: (e) => set({ required: e.target.checked }) }), _jsx(GeneratedText, { id: "m_0316664a18ded7" })] }), _jsx(GeneratedValue, { value: isNumeric ? (_jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_100639ca393959" }) }), _jsx(Input, { type: "number", value: v.min ?? '', onChange: (e) => set({ min: e.target.value === '' ? undefined : Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1929e34b445f83" }) }), _jsx(Input, { type: "number", value: v.max ?? '', onChange: (e) => set({ max: e.target.value === '' ? undefined : Number(e.target.value) }) })] })] })) : null }), _jsx(GeneratedValue, { value: isText ? (_jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_141aeed76d15ff" }) }), _jsx(Input, { type: "number", value: v.minLength ?? '', onChange: (e) => set({ minLength: e.target.value === '' ? undefined : Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_0806911c39f2f6" }) }), _jsx(Input, { type: "number", value: v.maxLength ?? '', onChange: (e) => set({ maxLength: e.target.value === '' ? undefined : Number(e.target.value) }) })] })] })) : null }), _jsx(GeneratedValue, { value: isText ? (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1357113b889d93" }) }), _jsx(Input, { value: v.pattern ?? '', onChange: (e) => set({ pattern: e.target.value || undefined }), placeholder: tGenerated('m_16907e8a555a15') }), _jsxs("p", { className: "text-[11px] text-fg-muted", children: [_jsx(GeneratedText, { id: "m_1653ad0a199171" }), ' ', _jsx(GeneratedValue, { value: _jsx(GeneratedText, { id: "m_1a3262f89c2c42" }) }), ' ', _jsx(GeneratedText, { id: "m_00b3d8649525da" })] })] })) : null }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1a014cc338576c" }) }), _jsx(Input, { value: v.message ?? '', onChange: (e) => set({ message: e.target.value || undefined }), placeholder: tGenerated('m_1737e921e5c6c6') })] })] }));
863
+ }
864
+ function FieldDefaultTab({ field, onChange, }) {
865
+ const tGenerated = useGeneratedTranslations();
866
+ const d = field.defaultValue;
867
+ const kind = d?.kind ?? '';
868
+ function setKind(next) {
869
+ if (!next)
870
+ return onChange({ defaultValue: undefined });
871
+ switch (next) {
872
+ case 'literal':
873
+ onChange({ defaultValue: { kind: 'literal', value: '' } });
874
+ break;
875
+ case 'today':
876
+ onChange({ defaultValue: { kind: 'today' } });
877
+ break;
878
+ case 'now':
879
+ onChange({ defaultValue: { kind: 'now' } });
880
+ break;
881
+ case 'current_user_person_id':
882
+ onChange({ defaultValue: { kind: 'current_user_person_id' } });
883
+ break;
884
+ case 'current_user_name':
885
+ onChange({ defaultValue: { kind: 'current_user_name' } });
886
+ break;
887
+ }
888
+ }
889
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_0de21bd4fd99e9" }) }), _jsxs(Select, { className: "h-8 text-xs", value: kind, onChange: (e) => setKind(e.target.value), children: [_jsx("option", { value: "", children: '— No default —' }), _jsx("option", { value: "literal", children: 'Literal value' }), _jsx("option", { value: "today", children: "Today's date" }), _jsx("option", { value: "now", children: 'Right now' }), _jsx("option", { value: "current_user_person_id", children: "Current user's person id" }), _jsx("option", { value: "current_user_name", children: "Current user's name" })] }), _jsx(GeneratedValue, { value: d?.kind === 'literal' ? (_jsx(Input, { value: String(d.value ?? ''), onChange: (e) => onChange({ defaultValue: { kind: 'literal', value: e.target.value } }), placeholder: tGenerated('m_0776e7f56fd55a') })) : null })] }));
890
+ }
891
+ function ChoiceOptionsEditor({ field, locale, onChange, }) {
892
+ const tGenerated = useGeneratedTranslations();
893
+ const options = field.validation?.options ?? [];
894
+ const update = (next) => onChange({ validation: { ...field.validation, options: next } });
895
+ return (_jsxs("div", { className: "space-y-2 rounded-md border border-border bg-bg-subtle/50 p-2", children: [_jsx("div", { className: "text-xs font-semibold text-fg", children: _jsx(GeneratedText, { id: "m_0e69ebb67d27c2" }) }), _jsx(GeneratedValue, { value: options.length === 0 ? (_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_06a6b6820d522d" }) })) : (_jsx("ul", { className: "space-y-1", children: _jsx(GeneratedValue, { value: options.map((opt, i) => (_jsxs("li", { className: "flex items-center gap-1", children: [_jsx(Input, { className: "h-8 flex-1 text-xs", value: opt.value, placeholder: tGenerated('m_1ed2cc27e05841'), onChange: (e) => {
896
+ const next = [...options];
897
+ next[i] = { ...opt, value: e.target.value };
898
+ update(next);
899
+ } }), _jsx(Input, { className: "h-8 flex-1 text-xs", value: readText(opt.label, locale), placeholder: tGenerated('m_02a6c587f3d3fb'), onChange: (e) => {
900
+ const next = [...options];
901
+ next[i] = {
902
+ ...opt,
903
+ label: writeText(opt.label, e.target.value, locale),
904
+ };
905
+ update(next);
906
+ } }), _jsx("button", { onClick: () => update(options.filter((_, j) => j !== i)), className: "text-fg-muted hover:text-danger", children: _jsx(Trash2, { size: 12 }) })] }, i))) }) })) }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => update([
907
+ ...options,
908
+ {
909
+ value: `opt_${options.length + 1}`,
910
+ label: { [locale]: 'New option' },
911
+ },
912
+ ]), children: [_jsx(Plus, { size: 12 }), _jsx(GeneratedText, { id: "m_157bc1fc2157b9" })] })] }));
913
+ }
914
+ // --- Table config (column + row editor for `table` fields) ------------------
915
+ function TableConfigEditor({ field, onChange, }) {
916
+ const tGenerated = useGeneratedTranslations();
917
+ const config = (field.config ?? {});
918
+ const columns = (config.columns ?? []);
919
+ const rowMode = config.rowMode === 'fixed' ? 'fixed' : 'addable';
920
+ const fixedRows = config.rows ?? [];
921
+ function setConfig(patch) {
922
+ onChange({ config: { ...config, ...patch } });
923
+ }
924
+ const setColumns = (next) => setConfig({ columns: next });
925
+ const setRows = (next) => setConfig({ rows: next });
926
+ return (_jsxs("div", { className: "space-y-3 rounded-md border border-border bg-bg-subtle/50 p-2", children: [_jsx("div", { className: "text-xs font-semibold text-fg", children: _jsx(GeneratedText, { id: "m_0d25e53152dc0a" }) }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_03be2202673df4" }) }), _jsxs(Select, { className: "h-8 text-xs", value: rowMode, onChange: (e) => {
927
+ const nextMode = e.target.value;
928
+ setConfig(nextMode === 'fixed'
929
+ ? { rowMode: nextMode, minRows: undefined, maxRows: undefined }
930
+ : { rowMode: nextMode, rows: undefined });
931
+ }, children: [_jsx("option", { value: "addable", children: 'Addable — user adds / removes rows' }), _jsx("option", { value: "fixed", children: 'Predefined — fixed list of rows' })] })] }), _jsx(GeneratedValue, { value: rowMode === 'addable' ? (_jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_00c5b5a40a72f6" }) }), _jsx(Input, { type: "number", className: "h-8 text-xs", value: config.minRows ?? '', onChange: (e) => setConfig({
932
+ minRows: e.target.value === '' ? undefined : Number(e.target.value),
933
+ }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_199de632ae64e0" }) }), _jsx(Input, { type: "number", className: "h-8 text-xs", value: config.maxRows ?? '', onChange: (e) => setConfig({
934
+ maxRows: e.target.value === '' ? undefined : Number(e.target.value),
935
+ }) })] })] })) : null }), _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_04eacfda3069db" }) }), _jsx(GeneratedValue, { value: columns.length === 0 ? (_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_09750de58b1490" }) })) : (_jsx("ul", { className: "space-y-2", children: _jsx(GeneratedValue, { value: columns.map((c, i) => (_jsxs("li", { className: "space-y-1 rounded border border-border bg-surface p-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { className: "h-7 flex-1 text-xs", value: c.label, placeholder: tGenerated('m_1e2be77312c5b8'), onChange: (e) => setColumns(columns.map((x, idx) => idx === i ? { ...x, label: e.target.value } : x)) }), _jsxs(Select, { className: "h-7 w-24 text-xs", value: c.type, onChange: (e) => {
936
+ const type = e.target.value;
937
+ setColumns(columns.map((x, idx) => idx === i
938
+ ? {
939
+ ...x,
940
+ type,
941
+ options: type === 'select' ? (x.options ?? []) : undefined,
942
+ }
943
+ : x));
944
+ }, children: [_jsx("option", { value: "text", children: 'Text' }), _jsx("option", { value: "number", children: 'Number' }), _jsx("option", { value: "select", children: 'Dropdown' }), _jsx("option", { value: "checkbox", children: 'Checkbox' }), _jsx("option", { value: "date", children: 'Date' })] }), _jsx("button", { type: "button", onClick: () => setColumns(columns.filter((_, idx) => idx !== i)), className: "rounded p-1 text-fg-muted hover:text-danger", title: tGenerated('m_0605fd789eea1e'), children: _jsx(Trash2, { size: 12 }) })] }), _jsx(Input, { className: "h-7 font-mono text-[11px]", value: c.key, placeholder: tGenerated('m_1d19a02fd2872f'), onChange: (e) => setColumns(columns.map((x, idx) => idx === i ? { ...x, key: e.target.value.replace(/\s+/g, '_') } : x)) }), _jsx(GeneratedValue, { value: c.type === 'select' ? (_jsx(TableColumnOptions, { options: c.options ?? [], onChange: (opts) => setColumns(columns.map((x, idx) => idx === i ? { ...x, options: opts } : x)) })) : null })] }, i))) }) })) }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
945
+ const n = columns.length + 1;
946
+ setColumns([...columns, { key: `col_${n}`, label: `Column ${n}`, type: 'text' }]);
947
+ }, children: [_jsx(Plus, { size: 12 }), " ", _jsx(GeneratedText, { id: "m_059cd549852b55" })] })] }), _jsx(GeneratedValue, { value: rowMode === 'fixed' ? (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_159e1e93b7e19d" }) }), _jsx(GeneratedValue, { value: fixedRows.length === 0 ? (_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_126f736e7419f7" }) })) : (_jsx("ul", { className: "space-y-1", children: _jsx(GeneratedValue, { value: fixedRows.map((r, i) => (_jsxs("li", { className: "flex items-center gap-1", children: [_jsx(Input, { className: "h-7 flex-1 text-xs", value: r.label, placeholder: tGenerated('m_0f24da1a05270a'), onChange: (e) => setRows(fixedRows.map((x, idx) => idx === i ? { label: e.target.value } : x)) }), _jsx("button", { type: "button", onClick: () => setRows(fixedRows.filter((_, idx) => idx !== i)), className: "rounded p-1 text-fg-muted hover:text-danger", title: tGenerated('m_12b310a027b08a'), children: _jsx(Trash2, { size: 12 }) })] }, i))) }) })) }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => setRows([...fixedRows, { label: `Row ${fixedRows.length + 1}` }]), children: [_jsx(Plus, { size: 12 }), " ", _jsx(GeneratedText, { id: "m_1eabd71bbc0199" })] })] })) : null })] }));
948
+ }
949
+ function TableColumnOptions({ options, onChange, }) {
950
+ const tGenerated = useGeneratedTranslations();
951
+ return (_jsxs("div", { className: "space-y-1 rounded border border-border bg-bg-subtle p-1.5", children: [_jsx("div", { className: "text-[10px] font-semibold tracking-wider text-fg-muted uppercase", children: _jsx(GeneratedText, { id: "m_0e69ebb67d27c2" }) }), _jsx(GeneratedValue, { value: options.map((o, i) => (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { className: "h-6 flex-1 text-[11px]", value: o.label, placeholder: tGenerated('m_044a5abd7d3ad4'), onChange: (e) => onChange(options.map((x, idx) => idx === i
952
+ ? {
953
+ value: x.value || e.target.value.toLowerCase().replace(/\s+/g, '_'),
954
+ label: e.target.value,
955
+ }
956
+ : x)) }), _jsx("button", { type: "button", onClick: () => onChange(options.filter((_, idx) => idx !== i)), className: "rounded p-0.5 text-fg-muted hover:text-danger", children: _jsx(Trash2, { size: 11 }) })] }, i))) }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => onChange([
957
+ ...options,
958
+ { value: `opt_${options.length + 1}`, label: `Option ${options.length + 1}` },
959
+ ]), children: [_jsx(Plus, { size: 11 }), " ", _jsx(GeneratedText, { id: "m_00f78a864ff266" })] })] }));
960
+ }
961
+ // --- Section properties ----------------------------------------------------
962
+ function SectionProperties({ section, schema, locale, defaultLocale, onChange, }) {
963
+ const tGeneratedValue = useGeneratedValueTranslations();
964
+ const tGenerated = useGeneratedTranslations();
965
+ const allFields = schema.sections
966
+ .filter((candidate) => !candidate.repeating)
967
+ .flatMap((candidate) => candidate.fields)
968
+ .filter(storesResponseValue)
969
+ .map((field) => ({
970
+ id: field.id,
971
+ label: localizeText(field.label, locale, field.id, defaultLocale),
972
+ }));
973
+ return (_jsxs("div", { className: "space-y-3 text-sm", children: [_jsx("h3", { className: "text-sm font-semibold text-fg", children: _jsx(GeneratedText, { id: "m_0d513924d97753" }) }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_089e71eb771aca" }) }), _jsx(Input, { value: section.id, disabled: true, className: "font-mono text-xs" })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "text-xs", children: [_jsx(GeneratedText, { id: "m_1ea646f0dcd3d5" }), _jsx(GeneratedValue, { value: locale.toUpperCase() }), ")"] }), _jsx(Input, { value: readText(section.title, locale), placeholder: tGeneratedValue(localizeText(section.title, locale, section.id, defaultLocale)), onChange: (e) => onChange({ title: writeText(section.title, e.target.value, locale) }) })] }), _jsx(GeneratedValue, { value: schema.tabs?.length ? (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1457c1cce0b63c" }) }), _jsx(Select, { value: section.tabId ?? schema.tabs[0].id, onChange: (e) => onChange({ tabId: e.target.value }), children: schema.tabs.map((t) => (_jsx("option", { value: t.id, children: localizeText(t.title, locale, t.id, defaultLocale) }, t.id))) })] })) : null }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_14d923495cf14c" }) }), _jsx(Textarea, { rows: 2, value: readText(section.description, locale), placeholder: tGeneratedValue(localizeText(section.description, locale, '', defaultLocale)), onChange: (e) => {
974
+ onChange({
975
+ description: e.target.value
976
+ ? writeText(section.description, e.target.value, locale)
977
+ : undefined,
978
+ });
979
+ } })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_0664f38791aed8" }) }), _jsxs(Select, { className: "h-8 text-xs", value: section.step ?? '', onChange: (e) => onChange({ step: e.target.value || undefined }), children: [_jsx("option", { value: "", children: '— first step (default) —' }), (schema.workflow?.steps ?? []).map((s) => (_jsx("option", { value: s.key, children: localizeText(s.title, locale, s.key, defaultLocale) }, s.key)))] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1ee65e14915cf1" }) }), _jsxs(Select, { className: "h-8 text-xs", value: String(section.layout?.columns ?? 1), onChange: (e) => {
980
+ const n = Number(e.target.value);
981
+ onChange({ layout: n > 1 ? { columns: n, gap: section.layout?.gap } : undefined });
982
+ }, children: [_jsx("option", { value: "1", children: '1 column' }), _jsx("option", { value: "2", children: '2 columns' }), _jsx("option", { value: "3", children: '3 columns' }), _jsx("option", { value: "4", children: '4 columns' })] }), _jsx("p", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_1e0a540071af99" }) })] }), _jsxs("label", { className: "flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: section.repeating ?? false, onChange: (e) => onChange({ repeating: e.target.checked }) }), _jsx(GeneratedText, { id: "m_10a741ad57253f" })] }), _jsx(GeneratedValue, { value: section.repeating ? (_jsxs("div", { className: "space-y-2 rounded border border-border bg-bg-subtle/50 p-2", children: [_jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_00c5b5a40a72f6" }) }), _jsx(Input, { type: "number", value: section.minRows ?? '', onChange: (e) => onChange({
983
+ minRows: e.target.value === '' ? undefined : Number(e.target.value),
984
+ }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_199de632ae64e0" }) }), _jsx(Input, { type: "number", value: section.maxRows ?? '', onChange: (e) => onChange({
985
+ maxRows: e.target.value === '' ? undefined : Number(e.target.value),
986
+ }) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_1c4a359175c7fa" }) }), _jsx(Input, { value: section.rowLabelTemplate ?? '', placeholder: tGenerated('m_02890419bf69c5'), onChange: (e) => onChange({ rowLabelTemplate: e.target.value || undefined }) }), _jsxs("p", { className: "text-[10px] text-fg-muted", children: [_jsx(GeneratedText, { id: "m_0ea6ba70f4dcd9" }), ' ', _jsx(GeneratedValue, { value: _jsx(GeneratedText, { id: "m_126c7eab8ac9fd" }) }), ",", ' ', _jsx(GeneratedValue, { value: _jsx(GeneratedText, { id: "m_1ed8de977a458a" }) }), _jsx(GeneratedText, { id: "m_1e018b65c95ffd" }), ' ', _jsx(GeneratedValue, { value: _jsx(GeneratedText, { id: "m_1fad56c24b22ed" }) }), ' ', _jsx(GeneratedText, { id: "m_02193054995990" })] })] })] })) : null }), _jsxs("div", { className: "space-y-1 pt-2", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedText, { id: "m_024d5ac76425dc" }) }), _jsx(LogicBuilder, { rule: section.showIf, availableFields: allFields, onChange: (rule) => onChange({ showIf: rule }) })] })] }));
987
+ }
988
+ // --- Preview pane ----------------------------------------------------------
989
+ function Preview({ schema, locale, defaultLocale, }) {
990
+ const sections = schema.sections;
991
+ const groupedByStep = useMemo(() => {
992
+ const out = new Map();
993
+ const defaultStep = schema.workflow?.steps[0]?.key ?? '';
994
+ for (const sec of sections) {
995
+ const k = sec.step ?? defaultStep;
996
+ const list = out.get(k) ?? [];
997
+ list.push(sec);
998
+ out.set(k, list);
999
+ }
1000
+ return out;
1001
+ }, [sections, schema.workflow?.steps]);
1002
+ return (_jsxs(Card, { className: "border-2 border-dashed border-border-strong bg-surface border-border", children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-base", children: [_jsx(GeneratedText, { id: "m_07c67e89962d16" }), ' ', _jsx(GeneratedValue, { value: localizeText(schema.title, locale, 'Form', defaultLocale) })] }), _jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_045d47c21e1cdf" }) })] }), _jsx(CardContent, { className: "space-y-4 text-sm", children: _jsx(GeneratedValue, { value: (schema.workflow?.steps ?? []).map((step) => {
1003
+ const stepSections = groupedByStep.get(step.key) ?? [];
1004
+ if (stepSections.length === 0)
1005
+ return null;
1006
+ return (_jsxs("div", { className: "rounded-md border border-border", children: [_jsxs("div", { className: "border-b border-border bg-bg-subtle px-3 py-1.5 text-xs font-semibold text-fg bg-bg-subtle/40", children: [_jsx(GeneratedText, { id: "m_1acffe9b7aa3dd" }), ' ', _jsx(GeneratedValue, { value: localizeText(step.title, locale, step.key, defaultLocale) })] }), _jsx("div", { className: "space-y-3 p-3", children: _jsx(GeneratedValue, { value: stepSections.map((sec) => (_jsxs("div", { children: [_jsxs("h3", { className: "mb-1 text-sm font-semibold text-fg", children: [_jsx(GeneratedValue, { value: localizeText(sec.title, locale, sec.id, defaultLocale) }), _jsx(GeneratedValue, { value: ' ' }), _jsx(GeneratedValue, { value: sec.repeating ? (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: _jsx(GeneratedText, { id: "m_06e1ca227aca9b" }) })) : null })] }), _jsx(GeneratedValue, { value: localizeText(sec.description, locale, '', defaultLocale) ? (_jsx("p", { className: "mb-1 text-xs text-fg-muted", children: _jsx(GeneratedValue, { value: localizeText(sec.description, locale, '', defaultLocale) }) })) : null }), _jsx("div", { className: "space-y-2", children: _jsx(GeneratedValue, { value: sec.fields.map((f) => (_jsxs("div", { children: [_jsxs("label", { className: "block text-xs font-medium text-fg", children: [_jsx(GeneratedValue, { value: localizeText(f.label, locale, f.id, defaultLocale) }), _jsx(GeneratedValue, { value: ' ' }), _jsx(GeneratedValue, { value: f.required || f.validation?.required ? (_jsx("span", { className: "text-danger", children: "*" })) : null }), _jsx(GeneratedValue, { value: f.showIf ? (_jsx(Badge, { variant: "outline", className: "ml-1 text-[10px]", children: _jsx(GeneratedText, { id: "m_02529c1532db7a" }) })) : null })] }), _jsx(PreviewField, { type: f.type })] }, f.id))) }) })] }, sec.id))) }) })] }, step.key));
1007
+ }) }) })] }));
1008
+ }
1009
+ function PreviewField({ type }) {
1010
+ const tGenerated = useGeneratedTranslations();
1011
+ switch (type) {
1012
+ case 'long_text':
1013
+ return _jsx(Textarea, { rows: 2, disabled: true, placeholder: tGenerated('m_11d37007232de5') });
1014
+ case 'select':
1015
+ case 'radio':
1016
+ return (_jsx(Select, { disabled: true, children: _jsx("option", { children: "\u2014" }) }));
1017
+ case 'checkbox_group':
1018
+ case 'multi_select':
1019
+ return (_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_0a31fefe8144a2" }) }));
1020
+ case 'signature':
1021
+ return _jsx("div", { className: "h-16 rounded border border-dashed border-border-strong bg-bg-subtle" });
1022
+ case 'photo':
1023
+ case 'photo_upload':
1024
+ case 'file':
1025
+ case 'video':
1026
+ case 'audio':
1027
+ return (_jsx("div", { className: "rounded border border-dashed border-border-strong p-3 text-center text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_1411910c3561c0" }) }));
1028
+ case 'heading':
1029
+ return (_jsx("h4", { className: "text-base font-semibold", children: _jsx(GeneratedText, { id: "m_0f8b37ea44adb6" }) }));
1030
+ case 'paragraph':
1031
+ return (_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_0131c1cfaf2d5c" }) }));
1032
+ case 'divider':
1033
+ return _jsx("hr", { className: "border-border" });
1034
+ case 'formula':
1035
+ return _jsx(Input, { disabled: true, value: "(computed)" });
1036
+ case 'pass_fail_na':
1037
+ return (_jsx("div", { className: "flex gap-1", children: _jsx(GeneratedValue, { value: ['PASS', 'FAIL', 'N/A'].map((v) => (_jsx("span", { className: "rounded border border-border px-2 py-1 text-[10px]", children: _jsx(GeneratedValue, { value: v }) }, v))) }) }));
1038
+ case 'traffic_light':
1039
+ return (_jsx("div", { className: "flex gap-1", children: _jsx(GeneratedValue, { value: [
1040
+ ['bg-success', 'Green'],
1041
+ ['bg-warning', 'Yellow'],
1042
+ ['bg-danger', 'Red'],
1043
+ ].map(([tone, label]) => (_jsxs("span", { className: "flex items-center gap-1 rounded border border-border px-2 py-1 text-[10px]", children: [_jsx("span", { className: `inline-block h-2 w-2 rounded-full ${tone}` }), _jsx(GeneratedValue, { value: label })] }, label))) }) }));
1044
+ default:
1045
+ return _jsx(Input, { disabled: true, placeholder: tGenerated('m_11d37007232de5') });
1046
+ }
1047
+ }
1048
+ // --- Unified editor: rail tabs + left-pane panels ---------------------------
1049
+ function LabeledField({ label, children }) {
1050
+ return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: _jsx(GeneratedValue, { value: label }) }), _jsx(GeneratedValue, { value: children })] }));
1051
+ }
1052
+ function LeftTab({ active, onClick, icon, label, }) {
1053
+ const tGeneratedValue = useGeneratedValueTranslations();
1054
+ return (_jsx("button", { type: "button", onClick: onClick, title: tGeneratedValue(label), "aria-label": tGeneratedValue(label), className: `flex flex-1 items-center justify-center rounded-md px-1.5 py-2 transition ${active
1055
+ ? 'bg-primary-subtle text-primary'
1056
+ : 'text-fg-muted hover:bg-surface-hover hover:text-fg'}`, children: _jsx(GeneratedValue, { value: icon }) }));
1057
+ }
1058
+ function SurfaceTab({ active, onClick, icon, label, }) {
1059
+ return (_jsxs("button", { type: "button", onClick: onClick, className: `flex items-center gap-1.5 rounded-md px-3 py-1 text-xs font-medium transition ${active
1060
+ ? 'bg-fg text-bg'
1061
+ : 'text-fg-muted hover:bg-surface-hover'}`, children: [_jsx(GeneratedValue, { value: icon }), " ", _jsx(GeneratedValue, { value: label })] }));
1062
+ }
1063
+ function OverviewPanel({ name, overview, onSaved, onSave, canPin = false, pinned = false, onPin, }) {
1064
+ const tGeneratedValue = useGeneratedValueTranslations();
1065
+ const tGenerated = useGeneratedTranslations();
1066
+ const [n, setN] = useState(name);
1067
+ const [description, setDescription] = useState(overview?.description ?? '');
1068
+ const [surfaceAsTool, setSurfaceAsTool] = useState(overview?.surfaceAsTool ?? false);
1069
+ const [pending, start] = useTransition();
1070
+ const save = () => {
1071
+ if (n.trim().length < 2) {
1072
+ toast.error(tGenerated('m_0aa48682b14c95'));
1073
+ return;
1074
+ }
1075
+ start(async () => {
1076
+ const res = await onSave({
1077
+ name: n.trim(),
1078
+ description,
1079
+ surfaceAsTool,
1080
+ });
1081
+ if (!res.ok) {
1082
+ toast.error(tGeneratedValue(res.error ?? tGenerated('m_0af1983403d12e')));
1083
+ return;
1084
+ }
1085
+ onSaved(n.trim());
1086
+ toast.success(tGenerated('m_0964f6e2cc4375'));
1087
+ });
1088
+ };
1089
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_08114bb9803f7d" }) }), _jsx(LabeledField, { label: tGenerated('m_18a30dc84d2a64'), children: _jsx(Input, { value: n, onChange: (e) => setN(e.target.value) }) }), _jsx(LabeledField, { label: tGenerated('m_14d923495cf14c'), children: _jsx(Textarea, { rows: 4, value: description, placeholder: tGenerated('m_074251897a5932'), onChange: (e) => setDescription(e.target.value) }) }), _jsxs("label", { className: "flex items-start gap-2 rounded-md border border-border p-3 text-sm", children: [_jsx("input", { type: "checkbox", checked: surfaceAsTool, onChange: (e) => setSurfaceAsTool(e.target.checked), className: "mt-0.5 h-4 w-4" }), _jsxs("span", { children: [_jsx("span", { className: "font-medium text-fg", children: _jsx(GeneratedText, { id: "m_1f008c56f09179" }) }), _jsxs("span", { className: "block text-xs text-fg-muted", children: [_jsx(GeneratedText, { id: "m_160f2c28b65229" }), ' ', _jsx("span", { className: "font-mono", children: _jsx(GeneratedText, { id: "m_1ddad1532de2e3" }) }), ' ', _jsx(GeneratedText, { id: "m_186d665b9052f3" })] })] })] }), _jsx(Button, { onClick: save, disabled: pending, className: "w-full", children: _jsx(GeneratedValue, { value: pending ? (_jsx(GeneratedText, { id: "m_106811f2aac664" })) : (_jsx(GeneratedText, { id: "m_16f4ae27da8dbb" })) }) }), _jsx(GeneratedValue, { value: canPin ? (_jsxs("div", { className: "space-y-2 rounded-md border border-border p-3", children: [_jsx("div", { className: "text-sm font-medium text-fg", children: _jsx(GeneratedText, { id: "m_0cc4443595b87e" }) }), _jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_1dadff0d2c2898" }) }), onPin ? (_jsxs(Button, { variant: pinned ? 'secondary' : 'outline', size: "sm", disabled: pending, onClick: () => start(async () => {
1090
+ const result = await onPin(!pinned);
1091
+ if (!result.ok)
1092
+ toast.error(tGeneratedValue(result.error ?? tGenerated('m_09249e0e6a801e')));
1093
+ }), children: [pinned ? _jsx(PinOff, { size: 14 }) : _jsx(Pin, { size: 14 }), pinned ? _jsx(GeneratedText, { id: "m_09eea65fa736d3" }) : _jsx(GeneratedText, { id: "m_16b0d6e1ba7d5e" })] })) : null] })) : null })] }));
1094
+ }
1095
+ function AssignmentsPanel({ createHref, listHref }) {
1096
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs("p", { className: "text-xs text-fg-muted", children: [_jsx(GeneratedText, { id: "m_0976c514f311fb" }), ' ', _jsx("span", { className: "font-medium text-fg", children: _jsx(GeneratedText, { id: "m_096d47f60747b3" }) }), ' ', _jsx(GeneratedText, { id: "m_1bac81ba078d96" })] }), _jsx("a", { href: createHref, className: "block", children: _jsxs(Button, { className: "w-full", children: [_jsx(Send, { size: 14 }), " ", _jsx(GeneratedText, { id: "m_0b60657b166b0e" })] }) }), _jsx("a", { href: listHref, className: "block", children: _jsx(Button, { variant: "outline", className: "w-full", children: _jsx(GeneratedText, { id: "m_0c528a939be441" }) }) }), _jsx("p", { className: "text-[11px] text-fg-muted", children: _jsx(GeneratedText, { id: "m_0ad38d3d51d13e" }) })] }));
1097
+ }
1098
+ function PermissionsPanel({ roles, initial, onSave, }) {
1099
+ const tGeneratedValue = useGeneratedValueTranslations();
1100
+ const tGenerated = useGeneratedTranslations();
1101
+ const [sel, setSel] = useState(new Set(initial));
1102
+ const [pending, start] = useTransition();
1103
+ const toggle = (key) => setSel((s) => {
1104
+ const n = new Set(s);
1105
+ if (n.has(key))
1106
+ n.delete(key);
1107
+ else
1108
+ n.add(key);
1109
+ return n;
1110
+ });
1111
+ const save = () => start(async () => {
1112
+ const res = await onSave(Array.from(sel));
1113
+ if (!res.ok) {
1114
+ toast.error(tGeneratedValue(res.error ?? tGenerated('m_0af1983403d12e')));
1115
+ return;
1116
+ }
1117
+ toast.success(tGenerated('m_1e42ea374d7063'));
1118
+ });
1119
+ const restricted = sel.size > 0;
1120
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_14c557ff352d60" }) }), _jsx("div", { className: `rounded-md border px-3 py-2 text-xs ${restricted
1121
+ ? 'border-warning bg-warning-subtle text-warning'
1122
+ : 'border-success bg-success-subtle text-success'}`, children: _jsx(GeneratedValue, { value: restricted ? (_jsx(GeneratedText, { id: "m_1f688129a274f7", values: { value0: sel.size, value1: sel.size === 1 ? '' : 's' } })) : (_jsx(GeneratedText, { id: "m_1452f24ba8370a" })) }) }), _jsx(GeneratedValue, { value: roles.length === 0 ? (_jsx("p", { className: "text-xs text-fg-muted", children: _jsx(GeneratedText, { id: "m_008f5a3d7812ab" }) })) : (_jsx("ul", { className: "space-y-1", children: _jsx(GeneratedValue, { value: roles.map((r) => (_jsx("li", { children: _jsxs("label", { className: "flex cursor-pointer items-center gap-2 rounded-md border border-border px-3 py-1.5 text-sm hover:bg-bg-subtle", children: [_jsx("input", { type: "checkbox", checked: sel.has(r.key), onChange: () => toggle(r.key) }), _jsx("span", { className: "flex-1", children: _jsx(GeneratedValue, { value: r.name }) }), _jsx("span", { className: "text-[10px] text-fg-muted", children: _jsx(GeneratedValue, { value: r.key }) })] }) }, r.key))) }) })) }), _jsx(Button, { onClick: save, disabled: pending, className: "w-full", children: _jsx(GeneratedValue, { value: pending ? (_jsx(GeneratedText, { id: "m_106811f2aac664" })) : (_jsx(GeneratedText, { id: "m_044b4d57bb99ce" })) }) })] }));
1123
+ }
1124
+ //# sourceMappingURL=production-form-designer.js.map