@fayz-ai/plugin-forms 0.8.1 → 0.9.0-next.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.
@@ -1,726 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkQM2AF3DZ_cjs = require('./chunk-QM2AF3DZ.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 saas = require('@fayz-ai/saas');
12
- var jsxRuntime = require('react/jsx-runtime');
13
- var utilities = require('@dnd-kit/utilities');
14
- var reactDom = require('react-dom');
15
-
16
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
-
18
- var React2__default = /*#__PURE__*/_interopDefault(React2);
19
-
20
- var DEFAULT_COL_SPAN = {
21
- title: 12,
22
- memo: 12,
23
- richtext: 12,
24
- gallery: 12,
25
- budget: 12,
26
- image: 6,
27
- text: 6,
28
- date: 4,
29
- select: 6,
30
- radio: 6,
31
- tags: 8,
32
- checkbox: 4
33
- };
34
- var FIELD_TYPES = [
35
- { type: "title", icon: lucideReact.Heading },
36
- { type: "text", icon: lucideReact.Type },
37
- { type: "memo", icon: lucideReact.AlignLeft },
38
- { type: "richtext", icon: lucideReact.FileEdit },
39
- { type: "date", icon: lucideReact.CalendarDays },
40
- { type: "select", icon: lucideReact.ChevronDown },
41
- { type: "radio", icon: lucideReact.CircleDot },
42
- { type: "tags", icon: lucideReact.Tags },
43
- { type: "checkbox", icon: lucideReact.CheckSquare },
44
- { type: "image", icon: lucideReact.Image },
45
- { type: "gallery", icon: lucideReact.GalleryHorizontalEnd },
46
- { type: "budget", icon: lucideReact.Receipt }
47
- ];
48
- function DraggableFieldType({ item }) {
49
- const t = core.useTranslation();
50
- const { attributes, listeners, setNodeRef, isDragging } = core$1.useDraggable({
51
- id: `palette-${item.type}`,
52
- data: { source: "palette", fieldType: item.type }
53
- });
54
- const Icon = item.icon;
55
- return /* @__PURE__ */ jsxRuntime.jsxs(
56
- "button",
57
- {
58
- ref: setNodeRef,
59
- ...listeners,
60
- ...attributes,
61
- 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" : ""}`,
62
- children: [
63
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
64
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: t(`customForms.fieldType.${item.type}`) })
65
- ]
66
- }
67
- );
68
- }
69
- function FieldPalette() {
70
- const t = core.useTranslation();
71
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-44 shrink-0 space-y-1.5", children: [
72
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground px-1 mb-2", children: t("customForms.builder.fields") }),
73
- FIELD_TYPES.map((item) => /* @__PURE__ */ jsxRuntime.jsx(DraggableFieldType, { item }, item.type))
74
- ] });
75
- }
76
- function BuilderField({ field, onSelect, onRemove, isSelected }) {
77
- const t = core.useTranslation();
78
- const {
79
- attributes,
80
- listeners,
81
- setNodeRef,
82
- transform,
83
- transition,
84
- isDragging
85
- } = sortable.useSortable({ id: field.id });
86
- const style = {
87
- transform: utilities.CSS.Transform.toString(transform),
88
- transition,
89
- gridColumn: `span ${field.colSpan}`,
90
- opacity: isDragging ? 0.5 : 1
91
- };
92
- const fieldTypeDef = FIELD_TYPES.find((ft) => ft.type === field.type);
93
- const Icon = fieldTypeDef?.icon;
94
- return /* @__PURE__ */ jsxRuntime.jsxs(
95
- "div",
96
- {
97
- ref: setNodeRef,
98
- style,
99
- 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"}`,
100
- onClick: () => onSelect(field),
101
- children: [
102
- /* @__PURE__ */ jsxRuntime.jsx(
103
- "button",
104
- {
105
- ...attributes,
106
- ...listeners,
107
- className: "mt-0.5 cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-muted-foreground",
108
- onClick: (e) => e.stopPropagation(),
109
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripVertical, { className: "h-4 w-4" })
110
- }
111
- ),
112
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
113
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
114
- Icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
115
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: t(`customForms.fieldType.${field.type}`) })
116
- ] }),
117
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium mt-0.5 truncate", children: field.label }),
118
- field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-destructive", children: "*" })
119
- ] }),
120
- /* @__PURE__ */ jsxRuntime.jsx(
121
- "button",
122
- {
123
- onClick: (e) => {
124
- e.stopPropagation();
125
- onRemove(field.id);
126
- },
127
- className: "absolute top-1.5 right-1.5 p-1 rounded-md opacity-0 group-hover:opacity-100 hover:bg-destructive/10 transition-opacity",
128
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3 text-destructive" })
129
- }
130
- )
131
- ]
132
- }
133
- );
134
- }
135
- function DropPreview({ fieldType }) {
136
- const t = core.useTranslation();
137
- const def = FIELD_TYPES.find((ft) => ft.type === fieldType);
138
- if (!def) return null;
139
- const Icon = def.icon;
140
- return /* @__PURE__ */ jsxRuntime.jsxs(
141
- "div",
142
- {
143
- className: "flex items-center gap-2 rounded-lg border-2 border-dashed border-primary/40 bg-primary/5 p-3",
144
- style: { gridColumn: `span ${DEFAULT_COL_SPAN[fieldType] ?? 12}` },
145
- children: [
146
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-primary/50" }),
147
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-primary/50 font-medium", children: t(`customForms.fieldType.${fieldType}`) })
148
- ]
149
- }
150
- );
151
- }
152
- function BuilderCanvas({
153
- fields,
154
- selectedFieldId,
155
- onSelectField,
156
- onRemoveField,
157
- draggingFieldType
158
- }) {
159
- const t = core.useTranslation();
160
- const { setNodeRef, isOver } = core$1.useDroppable({ id: "builder-canvas" });
161
- const sortedFields = [...fields].sort((a, b) => a.row - b.row || a.col - b.col);
162
- const showPreview = isOver && draggingFieldType !== null;
163
- return /* @__PURE__ */ jsxRuntime.jsxs(
164
- "div",
165
- {
166
- ref: setNodeRef,
167
- 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"}`,
168
- style: {
169
- 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%)",
170
- backgroundSize: "100% 100%"
171
- },
172
- children: [
173
- 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") }),
174
- /* @__PURE__ */ jsxRuntime.jsx(sortable.SortableContext, { items: sortedFields.map((f) => f.id), strategy: sortable.verticalListSortingStrategy, children: /* @__PURE__ */ jsxRuntime.jsxs(
175
- "div",
176
- {
177
- className: "grid gap-3",
178
- style: { gridTemplateColumns: "repeat(12, 1fr)" },
179
- children: [
180
- sortedFields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
181
- BuilderField,
182
- {
183
- field,
184
- isSelected: field.id === selectedFieldId,
185
- onSelect: onSelectField,
186
- onRemove: onRemoveField
187
- },
188
- field.id
189
- )),
190
- showPreview && /* @__PURE__ */ jsxRuntime.jsx(DropPreview, { fieldType: draggingFieldType })
191
- ]
192
- }
193
- ) })
194
- ]
195
- }
196
- );
197
- }
198
- var MAPPABLE_TYPES = ["text", "memo", "richtext", "date"];
199
- var MAP_NONE = "__none__";
200
- var SIZE_OPTIONS = [
201
- { span: 3, label: "1/4" },
202
- { span: 4, label: "1/3" },
203
- { span: 6, label: "1/2" },
204
- { span: 8, label: "2/3" },
205
- { span: 9, label: "3/4" },
206
- { span: 12, label: "Full" }
207
- ];
208
- function FieldConfigDrawer({ field, onSave, onUpdate, onClose }) {
209
- const t = core.useTranslation();
210
- const [label, setLabel] = React2.useState("");
211
- const [placeholder, setPlaceholder] = React2.useState("");
212
- const [required, setRequired] = React2.useState(false);
213
- const [colSpan, setColSpan] = React2.useState(12);
214
- const [options, setOptions] = React2.useState([]);
215
- const [map, setMap] = React2.useState("");
216
- React2.useEffect(() => {
217
- if (!field) return;
218
- setLabel(field.label);
219
- setPlaceholder(field.placeholder ?? "");
220
- setRequired(field.required ?? false);
221
- setColSpan(field.colSpan);
222
- setOptions(field.options ?? []);
223
- setMap(field.map ?? "");
224
- }, [field?.id]);
225
- if (!field) return null;
226
- const hasOptions = ["select", "radio", "tags"].includes(field.type);
227
- const canMap = MAPPABLE_TYPES.includes(field.type);
228
- const fieldTypeDef = FIELD_TYPES.find((ft) => ft.type === field.type);
229
- const Icon = fieldTypeDef?.icon;
230
- const handleSave = () => {
231
- onSave({
232
- ...field,
233
- label,
234
- placeholder: placeholder || void 0,
235
- required,
236
- colSpan: Math.max(1, Math.min(12, colSpan)),
237
- options: hasOptions ? options.filter((o) => o.label.trim()) : field.options,
238
- map: canMap && map ? map : void 0
239
- });
240
- };
241
- const addOption = () => {
242
- setOptions([...options, { label: "", value: "" }]);
243
- };
244
- const updateOption = (index, newLabel) => {
245
- const updated = [...options];
246
- updated[index] = { label: newLabel, value: newLabel.toLowerCase().replace(/\s+/g, "_") };
247
- setOptions(updated);
248
- };
249
- const removeOption = (index) => {
250
- setOptions(options.filter((_, i) => i !== index));
251
- };
252
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Sheet, { open: !!field, onOpenChange: (open) => {
253
- if (!open) onClose();
254
- }, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetContent, { className: "w-80 sm:w-96", overlay: "none", children: [
255
- /* @__PURE__ */ jsxRuntime.jsx(ui.SheetHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetTitle, { className: "flex items-center gap-2 text-sm", children: [
256
- Icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-muted-foreground" }),
257
- t(`customForms.fieldType.${field.type}`)
258
- ] }) }),
259
- /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetBody, { className: "space-y-4 py-4", children: [
260
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
261
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.fieldLabel") }),
262
- /* @__PURE__ */ jsxRuntime.jsx(
263
- ui.Input,
264
- {
265
- value: label,
266
- onChange: (e) => setLabel(e.target.value),
267
- className: "h-9 text-sm",
268
- autoFocus: true
269
- }
270
- )
271
- ] }),
272
- !["title", "checkbox", "image", "gallery"].includes(field.type) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
273
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.fieldPlaceholder") }),
274
- /* @__PURE__ */ jsxRuntime.jsx(
275
- ui.Input,
276
- {
277
- value: placeholder,
278
- onChange: (e) => setPlaceholder(e.target.value),
279
- className: "h-9 text-sm"
280
- }
281
- )
282
- ] }),
283
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
284
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.colSpan") }),
285
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2", children: SIZE_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
286
- "button",
287
- {
288
- onClick: () => {
289
- setColSpan(opt.span);
290
- if (onUpdate && field) {
291
- onUpdate({ ...field, colSpan: opt.span });
292
- }
293
- },
294
- 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"}`,
295
- children: [
296
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-px h-3 mb-1.5", children: [
297
- /* @__PURE__ */ jsxRuntime.jsx(
298
- "div",
299
- {
300
- className: `rounded-sm ${colSpan === opt.span ? "bg-primary/60" : "bg-muted-foreground/30 group-hover:bg-primary/30"}`,
301
- style: { width: `${opt.span / 12 * 100}%` }
302
- }
303
- ),
304
- opt.span < 12 && /* @__PURE__ */ jsxRuntime.jsx(
305
- "div",
306
- {
307
- className: "rounded-sm bg-muted/60",
308
- style: { width: `${(12 - opt.span) / 12 * 100}%` }
309
- }
310
- )
311
- ] }),
312
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] font-medium ${colSpan === opt.span ? "text-primary" : "text-muted-foreground"}`, children: opt.label })
313
- ]
314
- },
315
- opt.span
316
- )) })
317
- ] }),
318
- field.type !== "title" && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
319
- /* @__PURE__ */ jsxRuntime.jsx(
320
- "input",
321
- {
322
- type: "checkbox",
323
- checked: required,
324
- onChange: (e) => setRequired(e.target.checked),
325
- className: "rounded border-input accent-primary"
326
- }
327
- ),
328
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: t("customForms.builder.fieldRequired") })
329
- ] }),
330
- canMap && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
331
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.mapToPerson") }),
332
- /* @__PURE__ */ jsxRuntime.jsxs(
333
- ui.Select,
334
- {
335
- value: map || MAP_NONE,
336
- onValueChange: (v) => setMap(v === MAP_NONE ? "" : v),
337
- children: [
338
- /* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { className: "h-9 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
339
- /* @__PURE__ */ jsxRuntime.jsxs(ui.SelectContent, { children: [
340
- /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: MAP_NONE, children: t("customForms.builder.mapNone") }),
341
- chunkQM2AF3DZ_cjs.PERSON_FIELD_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: opt.value, children: t(opt.labelKey) }, opt.value))
342
- ] })
343
- ]
344
- }
345
- ),
346
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground", children: t("customForms.builder.mapHint") })
347
- ] }),
348
- hasOptions && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
349
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium", children: t("customForms.builder.fieldOptions") }),
350
- options.map((opt, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
351
- /* @__PURE__ */ jsxRuntime.jsx(
352
- ui.Input,
353
- {
354
- value: opt.label,
355
- onChange: (e) => updateOption(i, e.target.value),
356
- placeholder: `Option ${i + 1}`,
357
- className: "h-8 text-sm flex-1"
358
- }
359
- ),
360
- /* @__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" }) })
361
- ] }, i)),
362
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: addOption, className: "h-8 text-xs w-full", children: [
363
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3 w-3 mr-1" }),
364
- t("customForms.builder.addOption")
365
- ] })
366
- ] })
367
- ] }),
368
- /* @__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" }) })
369
- ] }) });
370
- }
371
- var TEMPLATE_CATEGORIES = ["anamnesis", "evolution", "report", "contract", "general"];
372
- function FormBuilder({ templateId, store, provider, config, onBack }) {
373
- const t = core.useTranslation();
374
- const guardTemplates = saas.useLimitGuard("form_templates");
375
- const [name, setName] = React2.useState("");
376
- const [description, setDescription] = React2.useState("");
377
- const [category, setCategory] = React2.useState("general");
378
- const [fields, setFields] = React2.useState([]);
379
- const [selectedFieldId, setSelectedFieldId] = React2.useState(null);
380
- const [saving, setSaving] = React2.useState(false);
381
- const [showPreview, setShowPreview] = React2.useState(false);
382
- const [previewMode, setPreviewMode] = React2.useState("editor");
383
- const [previewData, setPreviewData] = React2.useState({});
384
- const [draggingFieldType, setDraggingFieldType] = React2.useState(null);
385
- const [templateName, setTemplateName] = React2.useState(null);
386
- React2.useEffect(() => {
387
- if (!templateId) return;
388
- let cancelled = false;
389
- provider.getTemplateById(templateId).then((tpl) => {
390
- if (cancelled || !tpl) return;
391
- setName(tpl.name);
392
- setTemplateName(tpl.name);
393
- setDescription(tpl.description ?? "");
394
- setCategory(tpl.category);
395
- setFields(tpl.schema.fields);
396
- });
397
- return () => {
398
- cancelled = true;
399
- };
400
- }, [templateId, provider]);
401
- const sensors = core$1.useSensors(
402
- core$1.useSensor(core$1.PointerSensor, { activationConstraint: { distance: 5 } })
403
- );
404
- const handleDragStart = React2.useCallback((event) => {
405
- const data = event.active.data.current;
406
- if (data?.source === "palette") {
407
- setDraggingFieldType(data.fieldType);
408
- }
409
- }, []);
410
- const handleDragEnd = React2.useCallback((event) => {
411
- setDraggingFieldType(null);
412
- const { active, over } = event;
413
- if (active.data.current?.source === "palette" && over) {
414
- const fieldType = active.data.current.fieldType;
415
- const typeDef = FIELD_TYPES.find((ft) => ft.type === fieldType);
416
- if (!typeDef) return;
417
- const newField = {
418
- id: crypto.randomUUID(),
419
- type: fieldType,
420
- label: t(`customForms.fieldType.${fieldType}`),
421
- col: 0,
422
- row: fields.length,
423
- colSpan: DEFAULT_COL_SPAN[fieldType] ?? 12
424
- };
425
- if (["select", "radio", "tags"].includes(fieldType)) {
426
- newField.options = [
427
- { label: "Option 1", value: "option_1" },
428
- { label: "Option 2", value: "option_2" }
429
- ];
430
- }
431
- setFields((prev) => [...prev, newField]);
432
- return;
433
- }
434
- if (active.id !== over?.id && over) {
435
- setFields((prev) => {
436
- const oldIndex = prev.findIndex((f) => f.id === active.id);
437
- const newIndex = prev.findIndex((f) => f.id === over.id);
438
- if (oldIndex === -1 || newIndex === -1) return prev;
439
- const reordered = sortable.arrayMove(prev, oldIndex, newIndex);
440
- return reordered.map((f, i) => ({ ...f, row: i }));
441
- });
442
- }
443
- }, [fields, t]);
444
- const handleSelectField = React2.useCallback((field) => {
445
- setSelectedFieldId(field.id);
446
- }, []);
447
- const handleRemoveField = React2.useCallback((fieldId) => {
448
- setFields((prev) => prev.filter((f) => f.id !== fieldId).map((f, i) => ({ ...f, row: i })));
449
- if (selectedFieldId === fieldId) setSelectedFieldId(null);
450
- }, [selectedFieldId]);
451
- const handleFieldUpdate = React2.useCallback((updated) => {
452
- setFields((prev) => prev.map((f) => f.id === updated.id ? updated : f));
453
- }, []);
454
- const surfaceStateRef = React2__default.default.useRef({ templateId, name, description, category, fields });
455
- surfaceStateRef.current = { templateId, name, description, category, fields };
456
- const agentSurface = React2__default.default.useMemo(
457
- () => ({
458
- id: "custom_forms.builder",
459
- pluginId: "custom_forms",
460
- instructions: chunkQM2AF3DZ_cjs.FORM_BUILDER_INSTRUCTIONS,
461
- describe: () => {
462
- const o = surfaceStateRef.current;
463
- return {
464
- title: `Construtor de formul\xE1rio \u2014 ${o.name?.trim() || "novo modelo"}`,
465
- state: {
466
- // Pass this as `templateId` to createOrUpdateForm when editing THIS
467
- // form. Null = an unsaved new form (create, then it gets an id).
468
- templateId: o.templateId ?? null,
469
- unsaved: !o.templateId,
470
- name: o.name ?? "",
471
- category: o.category ?? "general",
472
- description: o.description ?? "",
473
- fieldCount: o.fields.length,
474
- fields: o.fields.map((f) => ({
475
- id: f.id,
476
- type: f.type,
477
- label: f.label,
478
- required: !!f.required,
479
- ...f.options?.length ? { options: f.options.map((x) => x.label) } : {}
480
- }))
481
- }
482
- };
483
- }
484
- }),
485
- []
486
- );
487
- saas.useAgentSurface(agentSurface);
488
- const handleFieldConfigSave = React2.useCallback((updated) => {
489
- handleFieldUpdate(updated);
490
- setSelectedFieldId(null);
491
- }, [handleFieldUpdate]);
492
- const handleSave = React2.useCallback(async () => {
493
- if (!name.trim()) {
494
- ui.toast.error(t("common.formIncomplete"));
495
- return;
496
- }
497
- if (!templateId && await guardTemplates() === "blocked") return;
498
- setSaving(true);
499
- try {
500
- const schema = { fields, layout: { columns: 12 } };
501
- if (templateId) {
502
- await store.getState().updateTemplate(templateId, { name, description, category, schema });
503
- } else {
504
- await store.getState().createTemplate({ name, description, category, schema });
505
- saas.invalidateLimit("form_templates");
506
- }
507
- onBack();
508
- } finally {
509
- setSaving(false);
510
- }
511
- }, [templateId, name, description, category, fields, store, onBack, guardTemplates]);
512
- const currentFields = { name, description, category, fields };
513
- const snapshot = React2__default.default.useRef(null);
514
- const loaded = !templateId || templateName !== null;
515
- React2.useEffect(() => {
516
- if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(currentFields);
517
- }, [loaded]);
518
- const dirty = snapshot.current !== null && JSON.stringify(currentFields) !== snapshot.current;
519
- ui.useSaveBar({
520
- dirty,
521
- saving,
522
- onSave: () => {
523
- void handleSave();
524
- },
525
- onDiscard: () => onBack(),
526
- saveLabel: t("customForms.builder.save")
527
- });
528
- const selectedField = selectedFieldId ? fields.find((f) => f.id === selectedFieldId) ?? null : null;
529
- const isNew = !templateId;
530
- const breadcrumbLabel = isNew ? t("customForms.newTemplate") : templateName ?? t("customForms.editTemplate");
531
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
532
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [
533
- /* @__PURE__ */ jsxRuntime.jsxs(
534
- "button",
535
- {
536
- onClick: onBack,
537
- className: "flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors",
538
- children: [
539
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "h-3.5 w-3.5" }),
540
- t("customForms.templates")
541
- ]
542
- }
543
- ),
544
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "/" }),
545
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: breadcrumbLabel })
546
- ] }),
547
- /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.CardContent, { className: "p-5 space-y-4", children: [
548
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
549
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
550
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "text-sm font-medium", children: [
551
- t("customForms.templateName"),
552
- " ",
553
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive", children: "*" })
554
- ] }),
555
- /* @__PURE__ */ jsxRuntime.jsx(
556
- ui.Input,
557
- {
558
- value: name,
559
- onChange: (e) => setName(e.target.value),
560
- placeholder: t("customForms.templateName"),
561
- className: "h-9 text-sm",
562
- autoFocus: isNew
563
- }
564
- )
565
- ] }),
566
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
567
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: t("customForms.templateCategory") }),
568
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: category, onValueChange: (v) => setCategory(v), children: [
569
- /* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { className: "h-9 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
570
- /* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: TEMPLATE_CATEGORIES.map((cat) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: cat, children: t(`customForms.category.${cat}`) }, cat)) })
571
- ] })
572
- ] })
573
- ] }),
574
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
575
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: t("customForms.templateDescription") }),
576
- /* @__PURE__ */ jsxRuntime.jsx(
577
- ui.Input,
578
- {
579
- value: description,
580
- onChange: (e) => setDescription(e.target.value),
581
- placeholder: t("customForms.templateDescription"),
582
- className: "h-9 text-sm"
583
- }
584
- )
585
- ] })
586
- ] }) }),
587
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
588
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
589
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: t("customForms.builder.fields") }),
590
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "secondary", className: "text-[10px]", children: t("customForms.builder.fieldCount", { count: String(fields.length) }) })
591
- ] }),
592
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
593
- ui.Button,
594
- {
595
- variant: "outline",
596
- size: "sm",
597
- className: "h-9",
598
- disabled: fields.length === 0,
599
- onClick: () => {
600
- setPreviewData({});
601
- setPreviewMode("editor");
602
- setShowPreview(true);
603
- },
604
- children: [
605
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "h-3.5 w-3.5 mr-1.5" }),
606
- "Preview"
607
- ]
608
- }
609
- ) })
610
- ] }),
611
- /* @__PURE__ */ jsxRuntime.jsxs(core$1.DndContext, { sensors, collisionDetection: core$1.pointerWithin, onDragStart: handleDragStart, onDragEnd: handleDragEnd, children: [
612
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
613
- /* @__PURE__ */ jsxRuntime.jsx(FieldPalette, {}),
614
- /* @__PURE__ */ jsxRuntime.jsx(
615
- BuilderCanvas,
616
- {
617
- fields,
618
- selectedFieldId,
619
- onSelectField: handleSelectField,
620
- onRemoveField: handleRemoveField,
621
- draggingFieldType
622
- }
623
- )
624
- ] }),
625
- /* @__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: (() => {
626
- const def = FIELD_TYPES.find((ft) => ft.type === draggingFieldType);
627
- if (!def) return null;
628
- const Icon = def.icon;
629
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
630
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-primary" }),
631
- t(`customForms.fieldType.${draggingFieldType}`)
632
- ] });
633
- })() }) : null })
634
- ] }),
635
- /* @__PURE__ */ jsxRuntime.jsx(
636
- FieldConfigDrawer,
637
- {
638
- field: selectedField,
639
- onSave: handleFieldConfigSave,
640
- onUpdate: handleFieldUpdate,
641
- onClose: () => setSelectedFieldId(null)
642
- }
643
- ),
644
- showPreview && reactDom.createPortal(
645
- /* @__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(
646
- "div",
647
- {
648
- className: "relative w-full max-w-3xl bg-white rounded-modal shadow-lg",
649
- style: { minHeight: "80vh" },
650
- onClick: (e) => e.stopPropagation(),
651
- children: [
652
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sticky top-0 z-10 flex items-center justify-center border-b bg-white/95 backdrop-blur px-4 py-2.5 rounded-t-modal", children: [
653
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex rounded-lg border p-0.5 text-xs", children: [
654
- /* @__PURE__ */ jsxRuntime.jsxs(
655
- "button",
656
- {
657
- onClick: () => setPreviewMode("editor"),
658
- className: `inline-flex items-center gap-1.5 px-3 py-1 rounded-md font-medium transition-colors ${previewMode === "editor" ? "bg-neutral-900 text-white" : "text-neutral-500 hover:text-neutral-800"}`,
659
- children: [
660
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-3.5 w-3.5" }),
661
- t("customForms.preview.editor")
662
- ]
663
- }
664
- ),
665
- /* @__PURE__ */ jsxRuntime.jsxs(
666
- "button",
667
- {
668
- onClick: () => setPreviewMode("print"),
669
- className: `inline-flex items-center gap-1.5 px-3 py-1 rounded-md font-medium transition-colors ${previewMode === "print" ? "bg-neutral-900 text-white" : "text-neutral-500 hover:text-neutral-800"}`,
670
- children: [
671
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Printer, { className: "h-3.5 w-3.5" }),
672
- t("customForms.preview.print")
673
- ]
674
- }
675
- )
676
- ] }),
677
- /* @__PURE__ */ jsxRuntime.jsx(
678
- "button",
679
- {
680
- onClick: () => setShowPreview(false),
681
- className: "absolute right-3 top-1/2 -translate-y-1/2 flex h-8 w-8 items-center justify-center rounded-full text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700 transition-colors",
682
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg", children: "\u2715" })
683
- }
684
- )
685
- ] }),
686
- previewMode === "editor" ? (
687
- /* Editor — fillable form (same renderer used on the person's ficha) */
688
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-10 py-8 sm:px-14 sm:py-10 text-neutral-900", children: [
689
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold mb-1", children: name || t("customForms.newTemplate") }),
690
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-neutral-500 mb-8", children: description }),
691
- !description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-8" }),
692
- /* @__PURE__ */ jsxRuntime.jsx(
693
- chunkQM2AF3DZ_cjs.FormRenderer,
694
- {
695
- schema: { fields, layout: { columns: 12 } },
696
- data: previewData,
697
- onChange: setPreviewData
698
- }
699
- )
700
- ] })
701
- ) : (
702
- /* Impressão — exact print/PDF output rendered in an isolated iframe */
703
- /* @__PURE__ */ jsxRuntime.jsx(
704
- "iframe",
705
- {
706
- title: "print-preview",
707
- className: "w-full rounded-b-modal",
708
- style: { height: "75vh", border: "none" },
709
- srcDoc: chunkQM2AF3DZ_cjs.buildPrintHtml(
710
- { id: "preview", name: name || t("customForms.newTemplate"), schema: { fields, layout: { columns: 12 } } },
711
- { title: name || t("customForms.newTemplate"), createdAt: (/* @__PURE__ */ new Date()).toISOString(), status: "draft", data: previewData }
712
- )
713
- }
714
- )
715
- )
716
- ]
717
- }
718
- ) }),
719
- document.body
720
- )
721
- ] });
722
- }
723
-
724
- exports.FormBuilder = FormBuilder;
725
- //# sourceMappingURL=FormBuilder-YB75LEEH.cjs.map
726
- //# sourceMappingURL=FormBuilder-YB75LEEH.cjs.map