@fayz-ai/plugin-forms 0.1.1

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