@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,15 @@
1
+ import React from 'react';
2
+ import type { CustomFormsConfig } from './config';
3
+ import type { CustomFormsDataProvider } from './data/types';
4
+ import type { CustomFormsStore } from './store';
5
+ export interface CustomFormsContextValue {
6
+ config: CustomFormsConfig;
7
+ provider: CustomFormsDataProvider;
8
+ store: CustomFormsStore;
9
+ }
10
+ export declare function CustomFormsProvider({ value, children, }: {
11
+ value: CustomFormsContextValue;
12
+ children: React.ReactNode;
13
+ }): React.FunctionComponentElement<React.ProviderProps<CustomFormsContextValue | null>>;
14
+ export declare function useCustomFormsContext(): CustomFormsContextValue;
15
+ //# sourceMappingURL=CustomFormsContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomFormsContext.d.ts","sourceRoot":"","sources":["../src/CustomFormsContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAA;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,iBAAiB,CAAA;IACzB,QAAQ,EAAE,uBAAuB,CAAA;IACjC,KAAK,EAAE,gBAAgB,CAAA;CACxB;AAID,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,uBAAuB,CAAA;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,uFAEA;AAED,wBAAgB,qBAAqB,IAAI,uBAAuB,CAI/D"}
@@ -0,0 +1,619 @@
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