@fayz-ai/plugin-forms 0.8.0 → 0.9.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/{FormBuilder-KDANEL6Z.js → FormBuilder-7SWZQKFN.js} +130 -23
  2. package/dist/FormBuilder-7SWZQKFN.js.map +1 -0
  3. package/dist/agent-tools.d.ts +4 -0
  4. package/dist/agent-tools.d.ts.map +1 -0
  5. package/dist/chunk-W2ZNJGQC.js +438 -0
  6. package/dist/chunk-W2ZNJGQC.js.map +1 -0
  7. package/dist/components/DocumentFormDialog.d.ts +3 -1
  8. package/dist/components/DocumentFormDialog.d.ts.map +1 -1
  9. package/dist/components/DocumentList.d.ts.map +1 -1
  10. package/dist/components/FieldConfigDialog.d.ts.map +1 -1
  11. package/dist/components/FormBuilder.d.ts.map +1 -1
  12. package/dist/components/FormViewer.d.ts.map +1 -1
  13. package/dist/components/PersonDocumentsWidget.d.ts.map +1 -1
  14. package/dist/data/supabase.d.ts.map +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +142 -54
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/person-fields.d.ts +16 -0
  19. package/dist/lib/person-fields.d.ts.map +1 -0
  20. package/dist/lib/print.d.ts +17 -0
  21. package/dist/lib/print.d.ts.map +1 -0
  22. package/dist/lib/tenant.d.ts.map +1 -1
  23. package/dist/locales/en.d.ts.map +1 -1
  24. package/dist/locales/pt-BR.d.ts.map +1 -1
  25. package/dist/migrations/index.d.ts +1 -0
  26. package/dist/migrations/index.d.ts.map +1 -1
  27. package/dist/types.d.ts +14 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -1
  30. package/dist/views/TemplateListView.d.ts.map +1 -1
  31. package/package.json +13 -11
  32. package/src/agent-tools.ts +73 -0
  33. package/src/components/DocumentFormDialog.tsx +37 -16
  34. package/src/components/DocumentList.tsx +12 -8
  35. package/src/components/FieldConfigDialog.tsx +40 -0
  36. package/src/components/FormBuilder.tsx +104 -22
  37. package/src/components/FormViewer.tsx +11 -1
  38. package/src/components/PersonDocumentsWidget.tsx +10 -1
  39. package/src/data/supabase.ts +2 -0
  40. package/src/index.ts +21 -0
  41. package/src/lib/person-fields.ts +67 -0
  42. package/src/lib/print.ts +207 -0
  43. package/src/lib/tenant.ts +6 -2
  44. package/src/locales/en.ts +20 -0
  45. package/src/locales/pt-BR.ts +20 -0
  46. package/src/migrations/003_agent_rpcs.sql +174 -0
  47. package/src/migrations/index.ts +178 -1
  48. package/src/types.ts +14 -0
  49. package/src/views/CustomFormsSettingsTab.tsx +23 -16
  50. package/src/views/TemplateListView.tsx +29 -18
  51. package/dist/FormBuilder-4VKYVZAC.cjs +0 -619
  52. package/dist/FormBuilder-4VKYVZAC.cjs.map +0 -1
  53. package/dist/FormBuilder-KDANEL6Z.js.map +0 -1
  54. package/dist/chunk-32I43T37.js +0 -198
  55. package/dist/chunk-32I43T37.js.map +0 -1
  56. package/dist/chunk-XLUULIVL.cjs +0 -200
  57. package/dist/chunk-XLUULIVL.cjs.map +0 -1
  58. package/dist/index.cjs +0 -1629
  59. package/dist/index.cjs.map +0 -1
@@ -1,619 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkXLUULIVL_cjs = require('./chunk-XLUULIVL.cjs');
4
- var React2 = require('react');
5
- var core$1 = require('@dnd-kit/core');
6
- var sortable = require('@dnd-kit/sortable');
7
- var modifiers = require('@dnd-kit/modifiers');
8
- var lucideReact = require('lucide-react');
9
- var ui = require('@fayz-ai/ui');
10
- var core = require('@fayz-ai/core');
11
- var jsxRuntime = require('react/jsx-runtime');
12
- var utilities = require('@dnd-kit/utilities');
13
- var reactDom = require('react-dom');
14
-
15
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
-
17
- var React2__default = /*#__PURE__*/_interopDefault(React2);
18
-
19
- var DEFAULT_COL_SPAN = {
20
- title: 12,
21
- memo: 12,
22
- richtext: 12,
23
- gallery: 12,
24
- budget: 12,
25
- image: 6,
26
- text: 6,
27
- date: 4,
28
- select: 6,
29
- radio: 6,
30
- tags: 8,
31
- checkbox: 4
32
- };
33
- var FIELD_TYPES = [
34
- { type: "title", icon: lucideReact.Heading },
35
- { type: "text", icon: lucideReact.Type },
36
- { type: "memo", icon: lucideReact.AlignLeft },
37
- { type: "richtext", icon: lucideReact.FileEdit },
38
- { type: "date", icon: lucideReact.CalendarDays },
39
- { type: "select", icon: lucideReact.ChevronDown },
40
- { type: "radio", icon: lucideReact.CircleDot },
41
- { type: "tags", icon: lucideReact.Tags },
42
- { type: "checkbox", icon: lucideReact.CheckSquare },
43
- { type: "image", icon: lucideReact.Image },
44
- { type: "gallery", icon: lucideReact.GalleryHorizontalEnd },
45
- { type: "budget", icon: lucideReact.Receipt }
46
- ];
47
- function DraggableFieldType({ item }) {
48
- const t = core.useTranslation();
49
- const { attributes, listeners, setNodeRef, isDragging } = core$1.useDraggable({
50
- id: `palette-${item.type}`,
51
- data: { source: "palette", fieldType: item.type }
52
- });
53
- const Icon = item.icon;
54
- return /* @__PURE__ */ jsxRuntime.jsxs(
55
- "button",
56
- {
57
- ref: setNodeRef,
58
- ...listeners,
59
- ...attributes,
60
- className: `flex items-center gap-2 w-full px-3 py-2 rounded-lg border bg-card text-sm font-medium transition-all hover:shadow-sm hover:border-primary/30 cursor-grab active:cursor-grabbing ${isDragging ? "opacity-30" : ""}`,
61
- children: [
62
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
63
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: t(`customForms.fieldType.${item.type}`) })
64
- ]
65
- }
66
- );
67
- }
68
- function FieldPalette() {
69
- const t = core.useTranslation();
70
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-44 shrink-0 space-y-1.5", children: [
71
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground px-1 mb-2", children: t("customForms.builder.fields") }),
72
- FIELD_TYPES.map((item) => /* @__PURE__ */ jsxRuntime.jsx(DraggableFieldType, { item }, item.type))
73
- ] });
74
- }
75
- function BuilderField({ field, onSelect, onRemove, isSelected }) {
76
- const t = core.useTranslation();
77
- const {
78
- attributes,
79
- listeners,
80
- setNodeRef,
81
- transform,
82
- transition,
83
- isDragging
84
- } = sortable.useSortable({ id: field.id });
85
- const style = {
86
- transform: utilities.CSS.Transform.toString(transform),
87
- transition,
88
- gridColumn: `span ${field.colSpan}`,
89
- opacity: isDragging ? 0.5 : 1
90
- };
91
- const fieldTypeDef = FIELD_TYPES.find((ft) => ft.type === field.type);
92
- const Icon = fieldTypeDef?.icon;
93
- return /* @__PURE__ */ jsxRuntime.jsxs(
94
- "div",
95
- {
96
- ref: setNodeRef,
97
- style,
98
- className: `group relative flex items-start gap-2 rounded-lg border-2 bg-card p-3 transition-colors cursor-pointer ${isSelected ? "border-primary ring-1 ring-primary/20" : "border-border hover:border-primary/30"}`,
99
- onClick: () => onSelect(field),
100
- children: [
101
- /* @__PURE__ */ jsxRuntime.jsx(
102
- "button",
103
- {
104
- ...attributes,
105
- ...listeners,
106
- className: "mt-0.5 cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-muted-foreground",
107
- onClick: (e) => e.stopPropagation(),
108
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripVertical, { className: "h-4 w-4" })
109
- }
110
- ),
111
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
112
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
113
- Icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
114
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: t(`customForms.fieldType.${field.type}`) })
115
- ] }),
116
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium mt-0.5 truncate", children: field.label }),
117
- field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-destructive", children: "*" })
118
- ] }),
119
- /* @__PURE__ */ jsxRuntime.jsx(
120
- "button",
121
- {
122
- onClick: (e) => {
123
- e.stopPropagation();
124
- onRemove(field.id);
125
- },
126
- className: "absolute top-1.5 right-1.5 p-1 rounded-md opacity-0 group-hover:opacity-100 hover:bg-destructive/10 transition-opacity",
127
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3 text-destructive" })
128
- }
129
- )
130
- ]
131
- }
132
- );
133
- }
134
- function DropPreview({ fieldType }) {
135
- const t = core.useTranslation();
136
- const def = FIELD_TYPES.find((ft) => ft.type === fieldType);
137
- if (!def) return null;
138
- const Icon = def.icon;
139
- return /* @__PURE__ */ jsxRuntime.jsxs(
140
- "div",
141
- {
142
- className: "flex items-center gap-2 rounded-lg border-2 border-dashed border-primary/40 bg-primary/5 p-3",
143
- style: { gridColumn: `span ${DEFAULT_COL_SPAN[fieldType] ?? 12}` },
144
- children: [
145
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-primary/50" }),
146
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-primary/50 font-medium", children: t(`customForms.fieldType.${fieldType}`) })
147
- ]
148
- }
149
- );
150
- }
151
- function BuilderCanvas({
152
- fields,
153
- selectedFieldId,
154
- onSelectField,
155
- onRemoveField,
156
- draggingFieldType
157
- }) {
158
- const t = core.useTranslation();
159
- const { setNodeRef, isOver } = core$1.useDroppable({ id: "builder-canvas" });
160
- const sortedFields = [...fields].sort((a, b) => a.row - b.row || a.col - b.col);
161
- const showPreview = isOver && draggingFieldType !== null;
162
- return /* @__PURE__ */ jsxRuntime.jsxs(
163
- "div",
164
- {
165
- ref: setNodeRef,
166
- className: `flex-1 min-h-[400px] rounded-xl border-2 border-dashed p-4 transition-colors ${isOver ? "border-primary/50 bg-primary/5" : "border-border bg-muted/20"}`,
167
- style: {
168
- backgroundImage: "repeating-linear-gradient(90deg, transparent, transparent calc(8.333% - 1px), hsl(var(--border) / 0.3) calc(8.333% - 1px), hsl(var(--border) / 0.3) 8.333%)",
169
- backgroundSize: "100% 100%"
170
- },
171
- children: [
172
- fields.length === 0 && !showPreview && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-muted-foreground text-sm", children: t("customForms.builder.emptyCanvas") }),
173
- /* @__PURE__ */ jsxRuntime.jsx(sortable.SortableContext, { items: sortedFields.map((f) => f.id), strategy: sortable.verticalListSortingStrategy, children: /* @__PURE__ */ jsxRuntime.jsxs(
174
- "div",
175
- {
176
- className: "grid gap-3",
177
- style: { gridTemplateColumns: "repeat(12, 1fr)" },
178
- children: [
179
- sortedFields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
180
- BuilderField,
181
- {
182
- field,
183
- isSelected: field.id === selectedFieldId,
184
- onSelect: onSelectField,
185
- onRemove: onRemoveField
186
- },
187
- field.id
188
- )),
189
- showPreview && /* @__PURE__ */ jsxRuntime.jsx(DropPreview, { fieldType: draggingFieldType })
190
- ]
191
- }
192
- ) })
193
- ]
194
- }
195
- );
196
- }
197
- var SIZE_OPTIONS = [
198
- { span: 3, label: "1/4" },
199
- { span: 4, label: "1/3" },
200
- { span: 6, label: "1/2" },
201
- { span: 8, label: "2/3" },
202
- { span: 9, label: "3/4" },
203
- { span: 12, label: "Full" }
204
- ];
205
- function FieldConfigDrawer({ field, onSave, onUpdate, onClose }) {
206
- const t = core.useTranslation();
207
- const [label, setLabel] = React2.useState("");
208
- const [placeholder, setPlaceholder] = React2.useState("");
209
- const [required, setRequired] = React2.useState(false);
210
- const [colSpan, setColSpan] = React2.useState(12);
211
- const [options, setOptions] = React2.useState([]);
212
- React2.useEffect(() => {
213
- if (!field) return;
214
- setLabel(field.label);
215
- setPlaceholder(field.placeholder ?? "");
216
- setRequired(field.required ?? false);
217
- setColSpan(field.colSpan);
218
- setOptions(field.options ?? []);
219
- }, [field?.id]);
220
- if (!field) return null;
221
- const hasOptions = ["select", "radio", "tags"].includes(field.type);
222
- const fieldTypeDef = FIELD_TYPES.find((ft) => ft.type === field.type);
223
- const Icon = fieldTypeDef?.icon;
224
- const handleSave = () => {
225
- onSave({
226
- ...field,
227
- label,
228
- placeholder: placeholder || void 0,
229
- required,
230
- colSpan: Math.max(1, Math.min(12, colSpan)),
231
- options: hasOptions ? options.filter((o) => o.label.trim()) : field.options
232
- });
233
- };
234
- const addOption = () => {
235
- setOptions([...options, { label: "", value: "" }]);
236
- };
237
- const updateOption = (index, newLabel) => {
238
- const updated = [...options];
239
- updated[index] = { label: newLabel, value: newLabel.toLowerCase().replace(/\s+/g, "_") };
240
- setOptions(updated);
241
- };
242
- const removeOption = (index) => {
243
- setOptions(options.filter((_, i) => i !== index));
244
- };
245
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Sheet, { open: !!field, onOpenChange: (open) => {
246
- if (!open) onClose();
247
- }, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetContent, { className: "w-80 sm:w-96", overlay: "none", children: [
248
- /* @__PURE__ */ jsxRuntime.jsx(ui.SheetHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetTitle, { className: "flex items-center gap-2 text-sm", children: [
249
- Icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-muted-foreground" }),
250
- t(`customForms.fieldType.${field.type}`)
251
- ] }) }),
252
- /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetBody, { className: "space-y-4 py-4", children: [
253
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
254
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.fieldLabel") }),
255
- /* @__PURE__ */ jsxRuntime.jsx(
256
- ui.Input,
257
- {
258
- value: label,
259
- onChange: (e) => setLabel(e.target.value),
260
- className: "h-9 text-sm",
261
- autoFocus: true
262
- }
263
- )
264
- ] }),
265
- !["title", "checkbox", "image", "gallery"].includes(field.type) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
266
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.fieldPlaceholder") }),
267
- /* @__PURE__ */ jsxRuntime.jsx(
268
- ui.Input,
269
- {
270
- value: placeholder,
271
- onChange: (e) => setPlaceholder(e.target.value),
272
- className: "h-9 text-sm"
273
- }
274
- )
275
- ] }),
276
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
277
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.colSpan") }),
278
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2", children: SIZE_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
279
- "button",
280
- {
281
- onClick: () => {
282
- setColSpan(opt.span);
283
- if (onUpdate && field) {
284
- onUpdate({ ...field, colSpan: opt.span });
285
- }
286
- },
287
- className: `group relative rounded-lg border-2 p-2 transition-all ${colSpan === opt.span ? "border-primary bg-primary/5" : "border-border hover:border-primary/30"}`,
288
- children: [
289
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-px h-3 mb-1.5", children: [
290
- /* @__PURE__ */ jsxRuntime.jsx(
291
- "div",
292
- {
293
- className: `rounded-sm ${colSpan === opt.span ? "bg-primary/60" : "bg-muted-foreground/30 group-hover:bg-primary/30"}`,
294
- style: { width: `${opt.span / 12 * 100}%` }
295
- }
296
- ),
297
- opt.span < 12 && /* @__PURE__ */ jsxRuntime.jsx(
298
- "div",
299
- {
300
- className: "rounded-sm bg-muted/60",
301
- style: { width: `${(12 - opt.span) / 12 * 100}%` }
302
- }
303
- )
304
- ] }),
305
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] font-medium ${colSpan === opt.span ? "text-primary" : "text-muted-foreground"}`, children: opt.label })
306
- ]
307
- },
308
- opt.span
309
- )) })
310
- ] }),
311
- field.type !== "title" && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
312
- /* @__PURE__ */ jsxRuntime.jsx(
313
- "input",
314
- {
315
- type: "checkbox",
316
- checked: required,
317
- onChange: (e) => setRequired(e.target.checked),
318
- className: "rounded border-input accent-primary"
319
- }
320
- ),
321
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: t("customForms.builder.fieldRequired") })
322
- ] }),
323
- hasOptions && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
324
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.fieldOptions") }),
325
- options.map((opt, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
326
- /* @__PURE__ */ jsxRuntime.jsx(
327
- ui.Input,
328
- {
329
- value: opt.label,
330
- onChange: (e) => updateOption(i, e.target.value),
331
- placeholder: `Option ${i + 1}`,
332
- className: "h-8 text-sm flex-1"
333
- }
334
- ),
335
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => removeOption(i), className: "p-1 hover:bg-destructive/10 rounded", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5 text-destructive" }) })
336
- ] }, i)),
337
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: addOption, className: "h-8 text-xs w-full", children: [
338
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3 w-3 mr-1" }),
339
- t("customForms.builder.addOption")
340
- ] })
341
- ] })
342
- ] }),
343
- /* @__PURE__ */ jsxRuntime.jsx(ui.SheetFooter, { className: "pt-4 border-t", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "sm", onClick: handleSave, className: "w-full", children: "OK" }) })
344
- ] }) });
345
- }
346
- var TEMPLATE_CATEGORIES = ["anamnesis", "evolution", "report", "contract", "general"];
347
- function FormBuilder({ templateId, store, provider, config, onBack }) {
348
- const t = core.useTranslation();
349
- const [name, setName] = React2.useState("");
350
- const [description, setDescription] = React2.useState("");
351
- const [category, setCategory] = React2.useState("general");
352
- const [fields, setFields] = React2.useState([]);
353
- const [selectedFieldId, setSelectedFieldId] = React2.useState(null);
354
- const [saving, setSaving] = React2.useState(false);
355
- const [showPreview, setShowPreview] = React2.useState(false);
356
- const [previewData, setPreviewData] = React2.useState({});
357
- const [draggingFieldType, setDraggingFieldType] = React2.useState(null);
358
- const [templateName, setTemplateName] = React2.useState(null);
359
- React2.useEffect(() => {
360
- if (!templateId) return;
361
- let cancelled = false;
362
- provider.getTemplateById(templateId).then((tpl) => {
363
- if (cancelled || !tpl) return;
364
- setName(tpl.name);
365
- setTemplateName(tpl.name);
366
- setDescription(tpl.description ?? "");
367
- setCategory(tpl.category);
368
- setFields(tpl.schema.fields);
369
- });
370
- return () => {
371
- cancelled = true;
372
- };
373
- }, [templateId, provider]);
374
- const sensors = core$1.useSensors(
375
- core$1.useSensor(core$1.PointerSensor, { activationConstraint: { distance: 5 } })
376
- );
377
- const handleDragStart = React2.useCallback((event) => {
378
- const data = event.active.data.current;
379
- if (data?.source === "palette") {
380
- setDraggingFieldType(data.fieldType);
381
- }
382
- }, []);
383
- const handleDragEnd = React2.useCallback((event) => {
384
- setDraggingFieldType(null);
385
- const { active, over } = event;
386
- if (active.data.current?.source === "palette" && over) {
387
- const fieldType = active.data.current.fieldType;
388
- const typeDef = FIELD_TYPES.find((ft) => ft.type === fieldType);
389
- if (!typeDef) return;
390
- const newField = {
391
- id: crypto.randomUUID(),
392
- type: fieldType,
393
- label: t(`customForms.fieldType.${fieldType}`),
394
- col: 0,
395
- row: fields.length,
396
- colSpan: DEFAULT_COL_SPAN[fieldType] ?? 12
397
- };
398
- if (["select", "radio", "tags"].includes(fieldType)) {
399
- newField.options = [
400
- { label: "Option 1", value: "option_1" },
401
- { label: "Option 2", value: "option_2" }
402
- ];
403
- }
404
- setFields((prev) => [...prev, newField]);
405
- return;
406
- }
407
- if (active.id !== over?.id && over) {
408
- setFields((prev) => {
409
- const oldIndex = prev.findIndex((f) => f.id === active.id);
410
- const newIndex = prev.findIndex((f) => f.id === over.id);
411
- if (oldIndex === -1 || newIndex === -1) return prev;
412
- const reordered = sortable.arrayMove(prev, oldIndex, newIndex);
413
- return reordered.map((f, i) => ({ ...f, row: i }));
414
- });
415
- }
416
- }, [fields, t]);
417
- const handleSelectField = React2.useCallback((field) => {
418
- setSelectedFieldId(field.id);
419
- }, []);
420
- const handleRemoveField = React2.useCallback((fieldId) => {
421
- setFields((prev) => prev.filter((f) => f.id !== fieldId).map((f, i) => ({ ...f, row: i })));
422
- if (selectedFieldId === fieldId) setSelectedFieldId(null);
423
- }, [selectedFieldId]);
424
- const handleFieldUpdate = React2.useCallback((updated) => {
425
- setFields((prev) => prev.map((f) => f.id === updated.id ? updated : f));
426
- }, []);
427
- const handleFieldConfigSave = React2.useCallback((updated) => {
428
- handleFieldUpdate(updated);
429
- setSelectedFieldId(null);
430
- }, [handleFieldUpdate]);
431
- const handleSave = React2.useCallback(async () => {
432
- if (!name.trim()) {
433
- ui.toast.error(t("common.formIncomplete"));
434
- return;
435
- }
436
- setSaving(true);
437
- try {
438
- const schema = { fields, layout: { columns: 12 } };
439
- if (templateId) {
440
- await store.getState().updateTemplate(templateId, { name, description, category, schema });
441
- } else {
442
- await store.getState().createTemplate({ name, description, category, schema });
443
- }
444
- onBack();
445
- } finally {
446
- setSaving(false);
447
- }
448
- }, [templateId, name, description, category, fields, store, onBack]);
449
- const currentFields = { name, description, category, fields };
450
- const snapshot = React2__default.default.useRef(null);
451
- const loaded = !templateId || templateName !== null;
452
- React2.useEffect(() => {
453
- if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(currentFields);
454
- }, [loaded]);
455
- const dirty = snapshot.current !== null && JSON.stringify(currentFields) !== snapshot.current;
456
- ui.useSaveBar({
457
- dirty,
458
- saving,
459
- onSave: () => {
460
- void handleSave();
461
- },
462
- onDiscard: () => onBack(),
463
- saveLabel: t("customForms.builder.save")
464
- });
465
- const selectedField = selectedFieldId ? fields.find((f) => f.id === selectedFieldId) ?? null : null;
466
- const isNew = !templateId;
467
- const breadcrumbLabel = isNew ? t("customForms.newTemplate") : templateName ?? t("customForms.editTemplate");
468
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
469
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [
470
- /* @__PURE__ */ jsxRuntime.jsxs(
471
- "button",
472
- {
473
- onClick: onBack,
474
- className: "flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors",
475
- children: [
476
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "h-3.5 w-3.5" }),
477
- t("customForms.templates")
478
- ]
479
- }
480
- ),
481
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "/" }),
482
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: breadcrumbLabel })
483
- ] }),
484
- /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.CardContent, { className: "p-5 space-y-4", children: [
485
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
486
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
487
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "text-sm font-medium", children: [
488
- t("customForms.templateName"),
489
- " ",
490
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive", children: "*" })
491
- ] }),
492
- /* @__PURE__ */ jsxRuntime.jsx(
493
- ui.Input,
494
- {
495
- value: name,
496
- onChange: (e) => setName(e.target.value),
497
- placeholder: t("customForms.templateName"),
498
- className: "h-9 text-sm",
499
- autoFocus: isNew
500
- }
501
- )
502
- ] }),
503
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
504
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: t("customForms.templateCategory") }),
505
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: category, onValueChange: (v) => setCategory(v), children: [
506
- /* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { className: "h-9 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
507
- /* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: TEMPLATE_CATEGORIES.map((cat) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: cat, children: t(`customForms.category.${cat}`) }, cat)) })
508
- ] })
509
- ] })
510
- ] }),
511
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
512
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: t("customForms.templateDescription") }),
513
- /* @__PURE__ */ jsxRuntime.jsx(
514
- ui.Input,
515
- {
516
- value: description,
517
- onChange: (e) => setDescription(e.target.value),
518
- placeholder: t("customForms.templateDescription"),
519
- className: "h-9 text-sm"
520
- }
521
- )
522
- ] })
523
- ] }) }),
524
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
525
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
526
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: t("customForms.builder.fields") }),
527
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "secondary", className: "text-[10px]", children: t("customForms.builder.fieldCount", { count: String(fields.length) }) })
528
- ] }),
529
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
530
- ui.Button,
531
- {
532
- variant: "outline",
533
- size: "sm",
534
- className: "h-9",
535
- disabled: fields.length === 0,
536
- onClick: () => {
537
- setPreviewData({});
538
- setShowPreview(true);
539
- },
540
- children: [
541
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "h-3.5 w-3.5 mr-1.5" }),
542
- "Preview"
543
- ]
544
- }
545
- ) })
546
- ] }),
547
- /* @__PURE__ */ jsxRuntime.jsxs(core$1.DndContext, { sensors, collisionDetection: core$1.pointerWithin, onDragStart: handleDragStart, onDragEnd: handleDragEnd, children: [
548
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
549
- /* @__PURE__ */ jsxRuntime.jsx(FieldPalette, {}),
550
- /* @__PURE__ */ jsxRuntime.jsx(
551
- BuilderCanvas,
552
- {
553
- fields,
554
- selectedFieldId,
555
- onSelectField: handleSelectField,
556
- onRemoveField: handleRemoveField,
557
- draggingFieldType
558
- }
559
- )
560
- ] }),
561
- /* @__PURE__ */ jsxRuntime.jsx(core$1.DragOverlay, { dropAnimation: null, modifiers: [modifiers.snapCenterToCursor], children: draggingFieldType ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg border bg-card shadow-xl text-sm font-medium opacity-90 pointer-events-none", children: (() => {
562
- const def = FIELD_TYPES.find((ft) => ft.type === draggingFieldType);
563
- if (!def) return null;
564
- const Icon = def.icon;
565
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
566
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-primary" }),
567
- t(`customForms.fieldType.${draggingFieldType}`)
568
- ] });
569
- })() }) : null })
570
- ] }),
571
- /* @__PURE__ */ jsxRuntime.jsx(
572
- FieldConfigDrawer,
573
- {
574
- field: selectedField,
575
- onSave: handleFieldConfigSave,
576
- onUpdate: handleFieldUpdate,
577
- onClose: () => setSelectedFieldId(null)
578
- }
579
- ),
580
- showPreview && reactDom.createPortal(
581
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[100] flex items-start justify-center bg-black/60 overflow-y-auto py-10 px-4", onClick: () => setShowPreview(false), children: /* @__PURE__ */ jsxRuntime.jsxs(
582
- "div",
583
- {
584
- className: "relative w-full max-w-3xl bg-white rounded-modal shadow-lg",
585
- style: { minHeight: "80vh" },
586
- onClick: (e) => e.stopPropagation(),
587
- children: [
588
- /* @__PURE__ */ jsxRuntime.jsx(
589
- "button",
590
- {
591
- onClick: () => setShowPreview(false),
592
- className: "absolute top-3 right-3 z-10 flex h-8 w-8 items-center justify-center rounded-full text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700 transition-colors",
593
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg", children: "\u2715" })
594
- }
595
- ),
596
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-10 py-8 sm:px-14 sm:py-10 text-neutral-900", children: [
597
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold mb-1", children: name || t("customForms.newTemplate") }),
598
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-neutral-500 mb-8", children: description }),
599
- !description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-8" }),
600
- /* @__PURE__ */ jsxRuntime.jsx(
601
- chunkXLUULIVL_cjs.FormRenderer,
602
- {
603
- schema: { fields, layout: { columns: 12 } },
604
- data: previewData,
605
- onChange: setPreviewData
606
- }
607
- )
608
- ] })
609
- ]
610
- }
611
- ) }),
612
- document.body
613
- )
614
- ] });
615
- }
616
-
617
- exports.FormBuilder = FormBuilder;
618
- //# sourceMappingURL=FormBuilder-4VKYVZAC.cjs.map
619
- //# sourceMappingURL=FormBuilder-4VKYVZAC.cjs.map