@fayz-ai/plugin-forms 0.9.0 → 0.9.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 (59) hide show
  1. package/README.md +3 -3
  2. package/dist/{FormBuilder-7SWZQKFN.js → FormBuilder-JCNHXQXT.js} +4 -4
  3. package/dist/{FormBuilder-7SWZQKFN.js.map → FormBuilder-JCNHXQXT.js.map} +1 -1
  4. package/dist/{chunk-W2ZNJGQC.js → chunk-JCAWSDX6.js} +3 -3
  5. package/dist/chunk-JCAWSDX6.js.map +1 -0
  6. package/dist/components/AddDocumentDropdown.d.ts +1 -1
  7. package/dist/components/AddDocumentDropdown.d.ts.map +1 -1
  8. package/dist/{CustomFormsContext.d.ts → context.d.ts} +1 -1
  9. package/dist/context.d.ts.map +1 -0
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/agent-tools.d.ts.map +1 -0
  15. package/dist/lib/document-types.d.ts.map +1 -0
  16. package/package.json +5 -7
  17. package/dist/CustomFormsContext.d.ts.map +0 -1
  18. package/dist/agent-tools.d.ts.map +0 -1
  19. package/dist/chunk-W2ZNJGQC.js.map +0 -1
  20. package/dist/document-types.d.ts.map +0 -1
  21. package/src/CustomFormsContext.tsx +0 -28
  22. package/src/agent-tools.ts +0 -73
  23. package/src/components/AddDocumentDropdown.tsx +0 -92
  24. package/src/components/BuilderCanvas.tsx +0 -87
  25. package/src/components/BuilderField.tsx +0 -81
  26. package/src/components/DocumentFormDialog.tsx +0 -155
  27. package/src/components/DocumentList.tsx +0 -254
  28. package/src/components/FieldConfigDialog.tsx +0 -243
  29. package/src/components/FieldPalette.tsx +0 -81
  30. package/src/components/FormBuilder.tsx +0 -427
  31. package/src/components/FormRenderer.tsx +0 -256
  32. package/src/components/FormViewer.tsx +0 -94
  33. package/src/components/PersonDocumentsWidget.tsx +0 -229
  34. package/src/components/TemplateSelector.tsx +0 -91
  35. package/src/config.ts +0 -43
  36. package/src/data/index.ts +0 -3
  37. package/src/data/mock.ts +0 -193
  38. package/src/data/supabase.ts +0 -408
  39. package/src/data/tables.ts +0 -7
  40. package/src/data/types.ts +0 -33
  41. package/src/document-types.ts +0 -51
  42. package/src/index.ts +0 -234
  43. package/src/lib/person-fields.ts +0 -67
  44. package/src/lib/print.ts +0 -207
  45. package/src/lib/tenant.ts +0 -9
  46. package/src/locales/en.ts +0 -95
  47. package/src/locales/index.ts +0 -7
  48. package/src/locales/pt-BR.ts +0 -95
  49. package/src/migrations/000_plg_rename.sql +0 -21
  50. package/src/migrations/001_frm_base.sql +0 -174
  51. package/src/migrations/002_document_archetype.sql +0 -223
  52. package/src/migrations/003_agent_rpcs.sql +0 -174
  53. package/src/migrations/index.ts +0 -610
  54. package/src/store.ts +0 -167
  55. package/src/types.ts +0 -217
  56. package/src/views/CustomFormsSettingsTab.tsx +0 -105
  57. package/src/views/TemplateListView.tsx +0 -174
  58. /package/dist/{agent-tools.d.ts → lib/agent-tools.d.ts} +0 -0
  59. /package/dist/{document-types.d.ts → lib/document-types.d.ts} +0 -0
@@ -1,81 +0,0 @@
1
- import React from 'react'
2
- import { useDraggable } from '@dnd-kit/core'
3
- import {
4
- Heading, Type, AlignLeft, FileEdit, CalendarDays,
5
- ChevronDown, CircleDot, Tags, CheckSquare,
6
- Image, GalleryHorizontalEnd, Receipt,
7
- } from 'lucide-react'
8
- import { useTranslation } from '@fayz-ai/core'
9
- import type { FormFieldType } from '../types'
10
-
11
- export interface FieldTypeItem {
12
- type: FormFieldType
13
- icon: React.ElementType
14
- }
15
-
16
- export const DEFAULT_COL_SPAN: Record<string, number> = {
17
- title: 12,
18
- memo: 12,
19
- richtext: 12,
20
- gallery: 12,
21
- budget: 12,
22
- image: 6,
23
- text: 6,
24
- date: 4,
25
- select: 6,
26
- radio: 6,
27
- tags: 8,
28
- checkbox: 4,
29
- }
30
-
31
- export const FIELD_TYPES: FieldTypeItem[] = [
32
- { type: 'title', icon: Heading },
33
- { type: 'text', icon: Type },
34
- { type: 'memo', icon: AlignLeft },
35
- { type: 'richtext', icon: FileEdit },
36
- { type: 'date', icon: CalendarDays },
37
- { type: 'select', icon: ChevronDown },
38
- { type: 'radio', icon: CircleDot },
39
- { type: 'tags', icon: Tags },
40
- { type: 'checkbox', icon: CheckSquare },
41
- { type: 'image', icon: Image },
42
- { type: 'gallery', icon: GalleryHorizontalEnd },
43
- { type: 'budget', icon: Receipt },
44
- ]
45
-
46
- function DraggableFieldType({ item }: { item: FieldTypeItem }) {
47
- const t = useTranslation()
48
- const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
49
- id: `palette-${item.type}`,
50
- data: { source: 'palette', fieldType: item.type },
51
- })
52
-
53
- const Icon = item.icon
54
-
55
- return (
56
- <button
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
- >
62
- <Icon className="h-4 w-4 text-muted-foreground shrink-0" />
63
- <span className="truncate">{t(`customForms.fieldType.${item.type}`)}</span>
64
- </button>
65
- )
66
- }
67
-
68
- export function FieldPalette() {
69
- const t = useTranslation()
70
-
71
- return (
72
- <div className="w-44 shrink-0 space-y-1.5">
73
- <p className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground px-1 mb-2">
74
- {t('customForms.builder.fields')}
75
- </p>
76
- {FIELD_TYPES.map((item) => (
77
- <DraggableFieldType key={item.type} item={item} />
78
- ))}
79
- </div>
80
- )
81
- }
@@ -1,427 +0,0 @@
1
- import React, { useState, useCallback, useEffect } from 'react'
2
- import {
3
- DndContext,
4
- DragEndEvent,
5
- DragOverlay,
6
- DragStartEvent,
7
- PointerSensor,
8
- pointerWithin,
9
- useSensor,
10
- useSensors,
11
- } from '@dnd-kit/core'
12
- import { arrayMove } from '@dnd-kit/sortable'
13
- import { snapCenterToCursor } from '@dnd-kit/modifiers'
14
- import { ArrowLeft, Eye, Pencil, Printer } from 'lucide-react'
15
- import { Button, useSaveBar, toast } from '@fayz-ai/ui'
16
- import { Input } from '@fayz-ai/ui'
17
- import { Card, CardContent } from '@fayz-ai/ui'
18
- import { Badge } from '@fayz-ai/ui'
19
- import {
20
- Select,
21
- SelectContent,
22
- SelectItem,
23
- SelectTrigger,
24
- SelectValue,
25
- } from '@fayz-ai/ui'
26
- import { useTranslation } from '@fayz-ai/core'
27
- import { useLimitGuard, invalidateLimit, useAgentSurface, type AgentSurfaceContribution } from '@fayz-ai/saas'
28
- import type { CustomFormsStore } from '../store'
29
- import type { CustomFormsDataProvider } from '../data/types'
30
- import type { CustomFormsConfig } from '../config'
31
- import type { FormFieldDef, FormSchema, TemplateCategory } from '../types'
32
- import { FieldPalette, FIELD_TYPES, DEFAULT_COL_SPAN } from './FieldPalette'
33
- import { BuilderCanvas } from './BuilderCanvas'
34
- import { FieldConfigDrawer } from './FieldConfigDialog'
35
- import { createPortal } from 'react-dom'
36
- import { FormRenderer } from './FormRenderer'
37
- import { buildPrintHtml } from '../lib/print'
38
- import { FORM_BUILDER_INSTRUCTIONS } from '../agent-tools'
39
-
40
- const TEMPLATE_CATEGORIES: TemplateCategory[] = ['anamnesis', 'evolution', 'report', 'contract', 'general']
41
-
42
- interface FormBuilderProps {
43
- templateId?: string
44
- store: CustomFormsStore
45
- provider: CustomFormsDataProvider
46
- config: CustomFormsConfig
47
- onBack: () => void
48
- }
49
-
50
- export function FormBuilder({ templateId, store, provider, config, onBack }: FormBuilderProps) {
51
- const t = useTranslation()
52
- const guardTemplates = useLimitGuard('form_templates')
53
-
54
- const [name, setName] = useState('')
55
- const [description, setDescription] = useState('')
56
- const [category, setCategory] = useState<TemplateCategory>('general')
57
- const [fields, setFields] = useState<FormFieldDef[]>([])
58
- const [selectedFieldId, setSelectedFieldId] = useState<string | null>(null)
59
- const [saving, setSaving] = useState(false)
60
- const [showPreview, setShowPreview] = useState(false)
61
- const [previewMode, setPreviewMode] = useState<'editor' | 'print'>('editor')
62
- const [previewData, setPreviewData] = useState<Record<string, unknown>>({})
63
- const [draggingFieldType, setDraggingFieldType] = useState<import('../types').FormFieldType | null>(null)
64
- const [templateName, setTemplateName] = useState<string | null>(null)
65
-
66
- // Load existing template
67
- useEffect(() => {
68
- if (!templateId) return
69
- let cancelled = false
70
- provider.getTemplateById(templateId).then((tpl) => {
71
- if (cancelled || !tpl) return
72
- setName(tpl.name)
73
- setTemplateName(tpl.name)
74
- setDescription(tpl.description ?? '')
75
- setCategory(tpl.category)
76
- setFields(tpl.schema.fields)
77
- })
78
- return () => { cancelled = true }
79
- }, [templateId, provider])
80
-
81
- const sensors = useSensors(
82
- useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
83
- )
84
-
85
- const handleDragStart = useCallback((event: DragStartEvent) => {
86
- const data = event.active.data.current
87
- if (data?.source === 'palette') {
88
- setDraggingFieldType(data.fieldType)
89
- }
90
- }, [])
91
-
92
- const handleDragEnd = useCallback((event: DragEndEvent) => {
93
- setDraggingFieldType(null)
94
- const { active, over } = event
95
-
96
- // Dropped from palette onto canvas
97
- if (active.data.current?.source === 'palette' && over) {
98
- const fieldType = active.data.current.fieldType
99
- const typeDef = FIELD_TYPES.find((ft) => ft.type === fieldType)
100
- if (!typeDef) return
101
-
102
- const newField: FormFieldDef = {
103
- id: crypto.randomUUID(),
104
- type: fieldType,
105
- label: t(`customForms.fieldType.${fieldType}`),
106
- col: 0,
107
- row: fields.length,
108
- colSpan: DEFAULT_COL_SPAN[fieldType] ?? 12,
109
- }
110
-
111
- if (['select', 'radio', 'tags'].includes(fieldType)) {
112
- newField.options = [
113
- { label: 'Option 1', value: 'option_1' },
114
- { label: 'Option 2', value: 'option_2' },
115
- ]
116
- }
117
-
118
- setFields((prev) => [...prev, newField])
119
- return
120
- }
121
-
122
- // Reorder within canvas
123
- if (active.id !== over?.id && over) {
124
- setFields((prev) => {
125
- const oldIndex = prev.findIndex((f) => f.id === active.id)
126
- const newIndex = prev.findIndex((f) => f.id === over.id)
127
- if (oldIndex === -1 || newIndex === -1) return prev
128
- const reordered = arrayMove(prev, oldIndex, newIndex)
129
- return reordered.map((f, i) => ({ ...f, row: i }))
130
- })
131
- }
132
- }, [fields, t])
133
-
134
- const handleSelectField = useCallback((field: FormFieldDef) => {
135
- setSelectedFieldId(field.id)
136
- }, [])
137
-
138
- const handleRemoveField = useCallback((fieldId: string) => {
139
- setFields((prev) => prev.filter((f) => f.id !== fieldId).map((f, i) => ({ ...f, row: i })))
140
- if (selectedFieldId === fieldId) setSelectedFieldId(null)
141
- }, [selectedFieldId])
142
-
143
- const handleFieldUpdate = useCallback((updated: FormFieldDef) => {
144
- setFields((prev) => prev.map((f) => (f.id === updated.id ? updated : f)))
145
- }, [])
146
-
147
- // --- Assistant surface: publish WHAT THE USER IS EDITING as context --------
148
- // The build action is the server-plane createOrUpdateForm tool (a pool RPC),
149
- // which works from any page. This surface only tells the model which template
150
- // is open and its current fields+ids, so "add an allergies field to THIS
151
- // form" resolves to the right templateId. No client-side mutation here.
152
- const surfaceStateRef = React.useRef({ templateId, name, description, category, fields })
153
- surfaceStateRef.current = { templateId, name, description, category, fields }
154
-
155
- const agentSurface = React.useMemo<AgentSurfaceContribution>(
156
- () => ({
157
- id: 'custom_forms.builder',
158
- pluginId: 'custom_forms',
159
- instructions: FORM_BUILDER_INSTRUCTIONS,
160
- describe: () => {
161
- const o = surfaceStateRef.current
162
- return {
163
- title: `Construtor de formulário — ${o.name?.trim() || 'novo modelo'}`,
164
- state: {
165
- // Pass this as `templateId` to createOrUpdateForm when editing THIS
166
- // form. Null = an unsaved new form (create, then it gets an id).
167
- templateId: o.templateId ?? null,
168
- unsaved: !o.templateId,
169
- name: o.name ?? '',
170
- category: o.category ?? 'general',
171
- description: o.description ?? '',
172
- fieldCount: o.fields.length,
173
- fields: o.fields.map((f) => ({
174
- id: f.id,
175
- type: f.type,
176
- label: f.label,
177
- required: !!f.required,
178
- ...(f.options?.length ? { options: f.options.map((x) => x.label) } : {}),
179
- })),
180
- },
181
- }
182
- },
183
- }),
184
- [],
185
- )
186
- useAgentSurface(agentSurface)
187
- // ---------------------------------------------------------------------------
188
-
189
- const handleFieldConfigSave = useCallback((updated: FormFieldDef) => {
190
- handleFieldUpdate(updated)
191
- setSelectedFieldId(null)
192
- }, [handleFieldUpdate])
193
-
194
- const handleSave = useCallback(async () => {
195
- if (!name.trim()) { toast.error(t('common.formIncomplete')); return }
196
- // Plan quantity guard (client-side, before the provider call) — only on
197
- // create. Opens the global UpgradeModal and aborts when the cap is reached.
198
- if (!templateId && (await guardTemplates()) === 'blocked') return
199
- setSaving(true)
200
- try {
201
- const schema: FormSchema = { fields, layout: { columns: 12 } }
202
-
203
- if (templateId) {
204
- await store.getState().updateTemplate(templateId, { name, description, category, schema })
205
- } else {
206
- await store.getState().createTemplate({ name, description, category, schema })
207
- invalidateLimit('form_templates')
208
- }
209
- onBack()
210
- } finally {
211
- setSaving(false)
212
- }
213
- }, [templateId, name, description, category, fields, store, onBack, guardTemplates])
214
-
215
- // Dirty detection — snapshot editable fields once async load settles
216
- const currentFields = { name, description, category, fields }
217
- const snapshot = React.useRef<string | null>(null)
218
- const loaded = !templateId || templateName !== null
219
- useEffect(() => {
220
- if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(currentFields)
221
- }, [loaded])
222
- const dirty = snapshot.current !== null && JSON.stringify(currentFields) !== snapshot.current
223
-
224
- useSaveBar({
225
- dirty,
226
- saving,
227
- onSave: () => { void handleSave() },
228
- onDiscard: () => onBack(),
229
- saveLabel: t('customForms.builder.save'),
230
- })
231
-
232
- const selectedField = selectedFieldId
233
- ? fields.find((f) => f.id === selectedFieldId) ?? null
234
- : null
235
-
236
- const isNew = !templateId
237
- const breadcrumbLabel = isNew
238
- ? t('customForms.newTemplate')
239
- : templateName ?? t('customForms.editTemplate')
240
-
241
- return (
242
- <div className="space-y-4">
243
- {/* Breadcrumb */}
244
- <div className="flex items-center gap-1.5 text-sm">
245
- <button
246
- onClick={onBack}
247
- className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
248
- >
249
- <ArrowLeft className="h-3.5 w-3.5" />
250
- {t('customForms.templates')}
251
- </button>
252
- <span className="text-muted-foreground">/</span>
253
- <span className="font-medium">{breadcrumbLabel}</span>
254
- </div>
255
-
256
- {/* Form meta — card layout matching CRUD form */}
257
- <Card>
258
- <CardContent className="p-5 space-y-4">
259
- <div className="grid gap-4 sm:grid-cols-2">
260
- <div className="space-y-1.5">
261
- <label className="text-sm font-medium">
262
- {t('customForms.templateName')} <span className="text-destructive">*</span>
263
- </label>
264
- <Input
265
- value={name}
266
- onChange={(e) => setName(e.target.value)}
267
- placeholder={t('customForms.templateName')}
268
- className="h-9 text-sm"
269
- autoFocus={isNew}
270
- />
271
- </div>
272
- <div className="space-y-1.5">
273
- <label className="text-sm font-medium">{t('customForms.templateCategory')}</label>
274
- <Select value={category} onValueChange={(v) => setCategory(v as TemplateCategory)}>
275
- <SelectTrigger className="h-9 text-sm">
276
- <SelectValue />
277
- </SelectTrigger>
278
- <SelectContent>
279
- {TEMPLATE_CATEGORIES.map((cat) => (
280
- <SelectItem key={cat} value={cat}>
281
- {t(`customForms.category.${cat}`)}
282
- </SelectItem>
283
- ))}
284
- </SelectContent>
285
- </Select>
286
- </div>
287
- </div>
288
- <div className="space-y-1.5">
289
- <label className="text-sm font-medium">{t('customForms.templateDescription')}</label>
290
- <Input
291
- value={description}
292
- onChange={(e) => setDescription(e.target.value)}
293
- placeholder={t('customForms.templateDescription')}
294
- className="h-9 text-sm"
295
- />
296
- </div>
297
- </CardContent>
298
- </Card>
299
-
300
- {/* Builder section header */}
301
- <div className="flex items-center justify-between">
302
- <div className="flex items-center gap-2">
303
- <h3 className="text-sm font-semibold">{t('customForms.builder.fields')}</h3>
304
- <Badge variant="secondary" className="text-[10px]">
305
- {t('customForms.builder.fieldCount', { count: String(fields.length) })}
306
- </Badge>
307
- </div>
308
- <div className="flex items-center gap-2">
309
- <Button
310
- variant="outline"
311
- size="sm"
312
- className="h-9"
313
- disabled={fields.length === 0}
314
- onClick={() => { setPreviewData({}); setPreviewMode('editor'); setShowPreview(true) }}
315
- >
316
- <Eye className="h-3.5 w-3.5 mr-1.5" />
317
- Preview
318
- </Button>
319
- </div>
320
- </div>
321
-
322
- {/* Builder body — drag palette + canvas */}
323
- <DndContext sensors={sensors} collisionDetection={pointerWithin} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
324
- <div className="flex gap-4">
325
- <FieldPalette />
326
- <BuilderCanvas
327
- fields={fields}
328
- selectedFieldId={selectedFieldId}
329
- onSelectField={handleSelectField}
330
- onRemoveField={handleRemoveField}
331
- draggingFieldType={draggingFieldType}
332
- />
333
- </div>
334
-
335
- <DragOverlay dropAnimation={null} modifiers={[snapCenterToCursor]}>
336
- {draggingFieldType ? (
337
- <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">
338
- {(() => {
339
- const def = FIELD_TYPES.find((ft) => ft.type === draggingFieldType)
340
- if (!def) return null
341
- const Icon = def.icon
342
- return (
343
- <>
344
- <Icon className="h-4 w-4 text-primary" />
345
- {t(`customForms.fieldType.${draggingFieldType}`)}
346
- </>
347
- )
348
- })()}
349
- </div>
350
- ) : null}
351
- </DragOverlay>
352
- </DndContext>
353
-
354
- {/* Field config drawer (right side) */}
355
- <FieldConfigDrawer
356
- field={selectedField}
357
- onSave={handleFieldConfigSave}
358
- onUpdate={handleFieldUpdate}
359
- onClose={() => setSelectedFieldId(null)}
360
- />
361
-
362
- {/* Preview — A4 paper overlay (portaled to body) */}
363
- {showPreview && createPortal(
364
- <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)}>
365
- <div
366
- className="relative w-full max-w-3xl bg-white rounded-modal shadow-lg"
367
- style={{ minHeight: '80vh' }}
368
- onClick={(e) => e.stopPropagation()}
369
- >
370
- {/* Toggle Editor ⟷ Impressão (centered) + close, on a sticky top bar. */}
371
- <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">
372
- <div className="inline-flex rounded-lg border p-0.5 text-xs">
373
- <button
374
- onClick={() => setPreviewMode('editor')}
375
- 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'}`}
376
- >
377
- <Pencil className="h-3.5 w-3.5" />
378
- {t('customForms.preview.editor')}
379
- </button>
380
- <button
381
- onClick={() => setPreviewMode('print')}
382
- 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'}`}
383
- >
384
- <Printer className="h-3.5 w-3.5" />
385
- {t('customForms.preview.print')}
386
- </button>
387
- </div>
388
- <button
389
- onClick={() => setShowPreview(false)}
390
- 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"
391
- >
392
- <span className="text-lg">✕</span>
393
- </button>
394
- </div>
395
-
396
- {previewMode === 'editor' ? (
397
- /* Editor — fillable form (same renderer used on the person's ficha) */
398
- <div className="px-10 py-8 sm:px-14 sm:py-10 text-neutral-900">
399
- <h1 className="text-2xl font-bold mb-1">{name || t('customForms.newTemplate')}</h1>
400
- {description && <p className="text-sm text-neutral-500 mb-8">{description}</p>}
401
- {!description && <div className="mb-8" />}
402
-
403
- <FormRenderer
404
- schema={{ fields, layout: { columns: 12 } }}
405
- data={previewData}
406
- onChange={setPreviewData}
407
- />
408
- </div>
409
- ) : (
410
- /* Impressão — exact print/PDF output rendered in an isolated iframe */
411
- <iframe
412
- title="print-preview"
413
- className="w-full rounded-b-modal"
414
- style={{ height: '75vh', border: 'none' }}
415
- srcDoc={buildPrintHtml(
416
- { id: 'preview', name: name || t('customForms.newTemplate'), schema: { fields, layout: { columns: 12 } } } as any,
417
- { title: name || t('customForms.newTemplate'), createdAt: new Date().toISOString(), status: 'draft', data: previewData } as any,
418
- )}
419
- />
420
- )}
421
- </div>
422
- </div>,
423
- document.body,
424
- )}
425
- </div>
426
- )
427
- }