@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
package/dist/index.js ADDED
@@ -0,0 +1,1605 @@
1
+ import { FormRenderer } from './chunk-32I43T37.js';
2
+ import React2, { useState, useCallback, useRef, useEffect } from 'react';
3
+ import { registerTranslations, createSafeDataProvider, useTranslation, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
4
+ import { create } from 'zustand';
5
+ import { FileText, Image, Paperclip, ArrowLeft, Loader2, Save, Pencil, Plus, ChevronDown, Upload, Eye, Trash2, Search } from 'lucide-react';
6
+ import { useSaveBar, Button, Badge, Dropdown, DropdownTrigger, DropdownContent, DropdownSeparator, DropdownLabel, DropdownItem, Input, Card, CardContent } from '@fayz-ai/ui';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ // src/data/mock.ts
10
+ function uuid() {
11
+ return crypto.randomUUID();
12
+ }
13
+ function now() {
14
+ return (/* @__PURE__ */ new Date()).toISOString();
15
+ }
16
+ function createMockFormsProvider() {
17
+ const templates = [];
18
+ const documents = [];
19
+ const files = [];
20
+ return {
21
+ // ── Templates ──────────────────────────────────────────
22
+ async getTemplates(query) {
23
+ let filtered = templates.filter((t) => !t.isDeleted && t.isCurrent);
24
+ if (query.category) {
25
+ filtered = filtered.filter((t) => t.category === query.category);
26
+ }
27
+ if (query.search) {
28
+ const s = query.search.toLowerCase();
29
+ filtered = filtered.filter((t) => t.name.toLowerCase().includes(s));
30
+ }
31
+ const page = query.page ?? 1;
32
+ const pageSize = query.pageSize ?? 25;
33
+ const start = (page - 1) * pageSize;
34
+ return { data: filtered.slice(start, start + pageSize), total: filtered.length };
35
+ },
36
+ async getTemplateById(id) {
37
+ return templates.find((t) => t.id === id && !t.isDeleted) ?? null;
38
+ },
39
+ async createTemplate(input) {
40
+ const template = {
41
+ id: uuid(),
42
+ tenantId: "mock-tenant",
43
+ name: input.name,
44
+ description: input.description,
45
+ category: input.category,
46
+ version: 1,
47
+ isCurrent: true,
48
+ schema: input.schema,
49
+ specialty: input.specialty,
50
+ tags: input.tags ?? [],
51
+ metadata: input.metadata ?? {},
52
+ isActive: true,
53
+ isDeleted: false,
54
+ createdAt: now(),
55
+ updatedAt: now()
56
+ };
57
+ templates.push(template);
58
+ return template;
59
+ },
60
+ async updateTemplate(id, input) {
61
+ const idx = templates.findIndex((t) => t.id === id);
62
+ if (idx === -1) throw new Error("Template not found");
63
+ const existing = templates[idx];
64
+ const updated = {
65
+ ...existing,
66
+ ...input,
67
+ tags: input.tags ?? existing.tags,
68
+ metadata: input.metadata ?? existing.metadata,
69
+ schema: input.schema ?? existing.schema,
70
+ updatedAt: now()
71
+ };
72
+ templates[idx] = updated;
73
+ return updated;
74
+ },
75
+ async deleteTemplate(id) {
76
+ const idx = templates.findIndex((t) => t.id === id);
77
+ if (idx !== -1) templates[idx] = { ...templates[idx], isDeleted: true };
78
+ },
79
+ // ── Documents ──────────────────────────────────────────
80
+ async getDocuments(query) {
81
+ let filtered = documents.filter((d) => !d.isDeleted);
82
+ if (query.personId) {
83
+ filtered = filtered.filter((d) => d.personId === query.personId);
84
+ }
85
+ if (query.templateId) {
86
+ filtered = filtered.filter((d) => d.templateId === query.templateId);
87
+ }
88
+ if (query.status) {
89
+ const statuses = Array.isArray(query.status) ? query.status : [query.status];
90
+ filtered = filtered.filter((d) => statuses.includes(d.status));
91
+ }
92
+ if (query.search) {
93
+ const s = query.search.toLowerCase();
94
+ filtered = filtered.filter(
95
+ (d) => d.title?.toLowerCase().includes(s) || d.templateName?.toLowerCase().includes(s)
96
+ );
97
+ }
98
+ filtered.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
99
+ const page = query.page ?? 1;
100
+ const pageSize = query.pageSize ?? 25;
101
+ const start = (page - 1) * pageSize;
102
+ return { data: filtered.slice(start, start + pageSize), total: filtered.length };
103
+ },
104
+ async getDocumentById(id) {
105
+ return documents.find((d) => d.id === id && !d.isDeleted) ?? null;
106
+ },
107
+ async createDocument(input) {
108
+ const template = templates.find((t) => t.id === input.templateId);
109
+ const doc = {
110
+ id: uuid(),
111
+ tenantId: "mock-tenant",
112
+ templateId: input.templateId ?? "",
113
+ templateName: template?.name,
114
+ templateCategory: template?.category,
115
+ personId: input.personId,
116
+ title: input.title,
117
+ data: input.data ?? {},
118
+ kind: input.kind ?? (input.templateId ? "form" : "attachment"),
119
+ status: input.status ?? "draft",
120
+ metadata: {},
121
+ isDeleted: false,
122
+ createdAt: now(),
123
+ updatedAt: now()
124
+ };
125
+ documents.push(doc);
126
+ return doc;
127
+ },
128
+ async updateDocument(id, input) {
129
+ const idx = documents.findIndex((d) => d.id === id);
130
+ if (idx === -1) throw new Error("Document not found");
131
+ const existing = documents[idx];
132
+ const updated = {
133
+ ...existing,
134
+ ...input,
135
+ data: input.data ?? existing.data,
136
+ updatedAt: now()
137
+ };
138
+ documents[idx] = updated;
139
+ return updated;
140
+ },
141
+ async deleteDocument(id) {
142
+ const idx = documents.findIndex((d) => d.id === id);
143
+ if (idx !== -1) documents[idx] = { ...documents[idx], isDeleted: true };
144
+ },
145
+ // ── Files ──────────────────────────────────────────────
146
+ async getDocumentFiles(documentId) {
147
+ return files.filter((f) => f.documentId === documentId);
148
+ },
149
+ async uploadFile(documentId, fieldKey, file) {
150
+ const docFile = {
151
+ id: uuid(),
152
+ tenantId: "mock-tenant",
153
+ documentId,
154
+ fieldKey,
155
+ fileUrl: URL.createObjectURL(file),
156
+ fileName: file.name,
157
+ fileSize: file.size,
158
+ mimeType: file.type,
159
+ sortOrder: files.filter((f) => f.documentId === documentId && f.fieldKey === fieldKey).length,
160
+ metadata: {},
161
+ createdAt: now()
162
+ };
163
+ files.push(docFile);
164
+ return docFile;
165
+ },
166
+ async deleteFile(fileId) {
167
+ const idx = files.findIndex((f) => f.id === fileId);
168
+ if (idx !== -1) files.splice(idx, 1);
169
+ }
170
+ };
171
+ }
172
+
173
+ // src/lib/tenant.ts
174
+ var currentTenantId;
175
+ function getFormsTenantId() {
176
+ return currentTenantId;
177
+ }
178
+ function getTenantId() {
179
+ return getActiveTenantId() ?? "";
180
+ }
181
+ function getClient() {
182
+ const supabase = getSupabaseClientOptional();
183
+ if (!supabase) throw new Error("Supabase not initialized");
184
+ return supabase;
185
+ }
186
+ function mapTemplate(r) {
187
+ return {
188
+ id: r.id,
189
+ tenantId: r.tenant_id,
190
+ name: r.name,
191
+ description: r.description,
192
+ category: r.category,
193
+ version: r.version,
194
+ isCurrent: r.is_current,
195
+ parentId: r.parent_id,
196
+ schema: r.schema,
197
+ specialty: r.specialty,
198
+ tags: r.tags ?? [],
199
+ metadata: r.metadata ?? {},
200
+ isActive: r.is_active,
201
+ isDeleted: r.is_deleted,
202
+ createdBy: r.created_by,
203
+ updatedBy: r.updated_by,
204
+ createdAt: r.created_at,
205
+ updatedAt: r.updated_at
206
+ };
207
+ }
208
+ function mapDocument(r) {
209
+ return {
210
+ id: r.id,
211
+ tenantId: r.tenant_id,
212
+ templateId: r.template_id ?? "",
213
+ templateName: r.template_name,
214
+ templateCategory: r.template_category,
215
+ personId: r.person_id,
216
+ personName: r.person_name,
217
+ title: r.title,
218
+ data: r.form_data ?? {},
219
+ status: r.status,
220
+ signedAt: r.signed_at,
221
+ signedBy: r.signed_by,
222
+ notes: r.notes,
223
+ metadata: r.metadata ?? {},
224
+ isDeleted: false,
225
+ createdBy: r.created_by,
226
+ updatedBy: r.updated_by,
227
+ createdAt: r.created_at,
228
+ updatedAt: r.updated_at,
229
+ // Extra fields from core document
230
+ kind: r.kind,
231
+ fileUrl: r.file_url,
232
+ fileName: r.file_name,
233
+ fileSize: r.file_size,
234
+ mimeType: r.mime_type
235
+ };
236
+ }
237
+ function mapFile(r) {
238
+ return {
239
+ id: r.id,
240
+ tenantId: r.tenant_id,
241
+ documentId: r.document_id,
242
+ fieldKey: r.field_key,
243
+ fileUrl: r.file_url,
244
+ fileName: r.file_name,
245
+ fileSize: r.file_size,
246
+ mimeType: r.mime_type,
247
+ sortOrder: r.sort_order,
248
+ metadata: r.metadata ?? {},
249
+ createdAt: r.created_at
250
+ };
251
+ }
252
+ function createSupabaseFormsProvider() {
253
+ return {
254
+ // ── Templates ──────────────────────────────────────────
255
+ async getTemplates(query) {
256
+ const db = getClient();
257
+ let qb = db.from("frm_templates").select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_current", true).eq("is_deleted", false);
258
+ if (query.category) qb = qb.eq("category", query.category);
259
+ if (query.search) qb = qb.ilike("name", `%${query.search}%`);
260
+ const page = query.page ?? 1;
261
+ const pageSize = query.pageSize ?? 25;
262
+ qb = qb.order("updated_at", { ascending: false }).range((page - 1) * pageSize, page * pageSize - 1);
263
+ const { data, count, error } = await qb;
264
+ if (error) throw new Error(error.message);
265
+ return { data: (data ?? []).map(mapTemplate), total: count ?? 0 };
266
+ },
267
+ async getTemplateById(id) {
268
+ const db = getClient();
269
+ const { data, error } = await db.from("frm_templates").select("*").eq("id", id).eq("is_deleted", false).single();
270
+ if (error || !data) return null;
271
+ return mapTemplate(data);
272
+ },
273
+ async createTemplate(input) {
274
+ const db = getClient();
275
+ const tenantId = getTenantId();
276
+ const { data, error } = await db.from("frm_templates").insert({
277
+ tenant_id: tenantId,
278
+ name: input.name,
279
+ description: input.description,
280
+ category: input.category,
281
+ schema: input.schema,
282
+ specialty: input.specialty,
283
+ tags: input.tags ?? [],
284
+ metadata: input.metadata ?? {}
285
+ }).select().single();
286
+ if (error) throw new Error(error.message);
287
+ return mapTemplate(data);
288
+ },
289
+ async updateTemplate(id, input) {
290
+ const db = getClient();
291
+ const tenantId = getTenantId();
292
+ const { count } = await db.from("frm_documents").select("document_id", { count: "exact", head: true }).eq("template_id", id);
293
+ const hasDocuments = (count ?? 0) > 0;
294
+ if (hasDocuments && input.schema) {
295
+ const existing = await this.getTemplateById(id);
296
+ if (!existing) throw new Error("Template not found");
297
+ const { data: newRow, error: insertError } = await db.from("frm_templates").insert({
298
+ tenant_id: tenantId,
299
+ name: input.name ?? existing.name,
300
+ description: input.description ?? existing.description,
301
+ category: input.category ?? existing.category,
302
+ schema: input.schema,
303
+ specialty: input.specialty ?? existing.specialty,
304
+ tags: input.tags ?? existing.tags,
305
+ metadata: input.metadata ?? existing.metadata,
306
+ is_active: input.isActive ?? existing.isActive,
307
+ version: existing.version + 1,
308
+ is_current: true,
309
+ parent_id: existing.parentId ?? existing.id
310
+ }).select().single();
311
+ if (insertError) throw new Error(insertError.message);
312
+ await db.from("frm_templates").update({ is_current: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
313
+ return mapTemplate(newRow);
314
+ }
315
+ const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
316
+ if (input.name !== void 0) updates.name = input.name;
317
+ if (input.description !== void 0) updates.description = input.description;
318
+ if (input.category !== void 0) updates.category = input.category;
319
+ if (input.schema !== void 0) updates.schema = input.schema;
320
+ if (input.specialty !== void 0) updates.specialty = input.specialty;
321
+ if (input.tags !== void 0) updates.tags = input.tags;
322
+ if (input.metadata !== void 0) updates.metadata = input.metadata;
323
+ if (input.isActive !== void 0) updates.is_active = input.isActive;
324
+ const { data, error } = await db.from("frm_templates").update(updates).eq("id", id).select().single();
325
+ if (error) throw new Error(error.message);
326
+ return mapTemplate(data);
327
+ },
328
+ async deleteTemplate(id) {
329
+ const db = getClient();
330
+ const { error } = await db.from("frm_templates").update({ is_deleted: true, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
331
+ if (error) throw new Error(error.message);
332
+ },
333
+ // ── Documents (saas_core.documents + frm_documents extension) ──
334
+ async getDocuments(query) {
335
+ const db = getClient();
336
+ let qb = db.from("v_documents").select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_active", true);
337
+ if (query.personId) qb = qb.eq("person_id", query.personId);
338
+ if (query.templateId) qb = qb.eq("template_id", query.templateId);
339
+ if (query.status) {
340
+ const statuses = Array.isArray(query.status) ? query.status : [query.status];
341
+ qb = qb.in("status", statuses);
342
+ }
343
+ if (query.search) qb = qb.or(`title.ilike.%${query.search}%,template_name.ilike.%${query.search}%`);
344
+ const page = query.page ?? 1;
345
+ const pageSize = query.pageSize ?? 50;
346
+ qb = qb.order("created_at", { ascending: false }).range((page - 1) * pageSize, page * pageSize - 1);
347
+ const { data, count, error } = await qb;
348
+ if (error) throw new Error(error.message);
349
+ return { data: (data ?? []).map(mapDocument), total: count ?? 0 };
350
+ },
351
+ async getDocumentById(id) {
352
+ const db = getClient();
353
+ const { data, error } = await db.from("v_documents").select("*").eq("id", id).single();
354
+ if (error || !data) return null;
355
+ return mapDocument(data);
356
+ },
357
+ async createDocument(input) {
358
+ const tenantId = getTenantId();
359
+ const isForm = !!input.templateId;
360
+ const { data: coreDoc, error: coreError } = await getClient().from("frm_core_documents").insert({
361
+ tenant_id: tenantId,
362
+ kind: input.kind ?? (isForm ? "form" : "attachment"),
363
+ person_id: input.personId,
364
+ title: input.title,
365
+ status: input.status ?? "draft",
366
+ file_url: input.fileUrl,
367
+ file_name: input.fileName,
368
+ file_size: input.fileSize,
369
+ mime_type: input.mimeType,
370
+ metadata: {}
371
+ }).select().single();
372
+ if (coreError) throw new Error(coreError.message);
373
+ if (isForm && input.templateId) {
374
+ const db = getClient();
375
+ const { error: extError } = await db.from("frm_documents").insert({
376
+ document_id: coreDoc.id,
377
+ tenant_id: tenantId,
378
+ template_id: input.templateId,
379
+ data: input.data ?? {}
380
+ });
381
+ if (extError) {
382
+ await getClient().from("frm_core_documents").delete().eq("id", coreDoc.id);
383
+ throw new Error(extError.message);
384
+ }
385
+ }
386
+ const doc = await this.getDocumentById(coreDoc.id);
387
+ return doc;
388
+ },
389
+ async updateDocument(id, input) {
390
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
391
+ const coreUpdates = { updated_at: now2 };
392
+ if (input.status !== void 0) coreUpdates.status = input.status;
393
+ if (input.title !== void 0) coreUpdates.title = input.title;
394
+ if (input.notes !== void 0) coreUpdates.notes = input.notes;
395
+ const { error: coreError } = await getClient().from("frm_core_documents").update(coreUpdates).eq("id", id);
396
+ if (coreError) throw new Error(coreError.message);
397
+ if (input.data !== void 0) {
398
+ const db = getClient();
399
+ await db.from("frm_documents").update({ data: input.data, updated_at: now2 }).eq("document_id", id);
400
+ }
401
+ const doc = await this.getDocumentById(id);
402
+ return doc;
403
+ },
404
+ async deleteDocument(id) {
405
+ const { error } = await getClient().from("frm_core_documents").update({ is_active: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
406
+ if (error) throw new Error(error.message);
407
+ },
408
+ // ── Files ──────────────────────────────────────────────
409
+ async getDocumentFiles(documentId) {
410
+ const db = getClient();
411
+ const { data, error } = await db.from("frm_document_files").select("*").eq("document_id", documentId).order("sort_order");
412
+ if (error) throw new Error(error.message);
413
+ return (data ?? []).map(mapFile);
414
+ },
415
+ async uploadFile(documentId, fieldKey, file) {
416
+ const db = getClient();
417
+ const tenantId = getTenantId();
418
+ const ext = file.name.split(".").pop() ?? "bin";
419
+ const storagePath = `${tenantId}/forms/${documentId}/${fieldKey}/${crypto.randomUUID()}.${ext}`;
420
+ const { error: uploadError } = await db.storage.from("documents").upload(storagePath, file, { contentType: file.type });
421
+ if (uploadError) throw new Error(uploadError.message);
422
+ const { data: urlData } = db.storage.from("documents").getPublicUrl(storagePath);
423
+ const { data, error } = await db.from("frm_document_files").insert({
424
+ tenant_id: tenantId,
425
+ document_id: documentId,
426
+ field_key: fieldKey,
427
+ file_url: urlData.publicUrl,
428
+ file_name: file.name,
429
+ file_size: file.size,
430
+ mime_type: file.type
431
+ }).select().single();
432
+ if (error) throw new Error(error.message);
433
+ return mapFile(data);
434
+ },
435
+ async deleteFile(fileId) {
436
+ const db = getClient();
437
+ const { error } = await db.from("frm_document_files").delete().eq("id", fileId);
438
+ if (error) throw new Error(error.message);
439
+ }
440
+ };
441
+ }
442
+ function createCustomFormsStore(provider) {
443
+ return create((set) => ({
444
+ templates: [],
445
+ templatesTotal: 0,
446
+ templatesLoading: false,
447
+ templatesError: null,
448
+ documents: [],
449
+ documentsTotal: 0,
450
+ documentsLoading: false,
451
+ documentsError: null,
452
+ activeTemplate: null,
453
+ activeTemplateLoading: false,
454
+ activeDocument: null,
455
+ activeDocumentLoading: false,
456
+ async fetchTemplates(query) {
457
+ set({ templatesLoading: true, templatesError: null });
458
+ try {
459
+ const result = await provider.getTemplates(query ?? {});
460
+ set({ templates: result.data, templatesTotal: result.total });
461
+ } catch (e) {
462
+ set({ templatesError: e.message ?? "Failed to fetch templates" });
463
+ } finally {
464
+ set({ templatesLoading: false });
465
+ }
466
+ },
467
+ async fetchTemplateById(id) {
468
+ set({ activeTemplateLoading: true });
469
+ try {
470
+ const template = await provider.getTemplateById(id);
471
+ set({ activeTemplate: template });
472
+ return template;
473
+ } catch {
474
+ set({ activeTemplate: null });
475
+ return null;
476
+ } finally {
477
+ set({ activeTemplateLoading: false });
478
+ }
479
+ },
480
+ async createTemplate(input) {
481
+ const template = await provider.createTemplate(input);
482
+ set((s) => ({ templates: [template, ...s.templates], templatesTotal: s.templatesTotal + 1 }));
483
+ return template;
484
+ },
485
+ async updateTemplate(id, input) {
486
+ const template = await provider.updateTemplate(id, input);
487
+ set((s) => ({
488
+ templates: s.templates.map((t) => t.id === id ? template : t),
489
+ activeTemplate: s.activeTemplate?.id === id ? template : s.activeTemplate
490
+ }));
491
+ return template;
492
+ },
493
+ async deleteTemplate(id) {
494
+ await provider.deleteTemplate(id);
495
+ set((s) => ({
496
+ templates: s.templates.filter((t) => t.id !== id),
497
+ templatesTotal: s.templatesTotal - 1
498
+ }));
499
+ },
500
+ async fetchDocuments(query) {
501
+ set({ documentsLoading: true, documentsError: null });
502
+ try {
503
+ const result = await provider.getDocuments(query ?? {});
504
+ set({ documents: result.data, documentsTotal: result.total });
505
+ } catch (e) {
506
+ set({ documentsError: e.message ?? "Failed to fetch documents" });
507
+ } finally {
508
+ set({ documentsLoading: false });
509
+ }
510
+ },
511
+ async fetchDocumentById(id) {
512
+ set({ activeDocumentLoading: true });
513
+ try {
514
+ const doc = await provider.getDocumentById(id);
515
+ set({ activeDocument: doc });
516
+ return doc;
517
+ } catch {
518
+ set({ activeDocument: null });
519
+ return null;
520
+ } finally {
521
+ set({ activeDocumentLoading: false });
522
+ }
523
+ },
524
+ async createDocument(input) {
525
+ const doc = await provider.createDocument(input);
526
+ set((s) => ({ documents: [doc, ...s.documents], documentsTotal: s.documentsTotal + 1 }));
527
+ return doc;
528
+ },
529
+ async updateDocument(id, input) {
530
+ const doc = await provider.updateDocument(id, input);
531
+ set((s) => ({
532
+ documents: s.documents.map((d) => d.id === id ? doc : d),
533
+ activeDocument: s.activeDocument?.id === id ? doc : s.activeDocument
534
+ }));
535
+ return doc;
536
+ },
537
+ async deleteDocument(id) {
538
+ await provider.deleteDocument(id);
539
+ set((s) => ({
540
+ documents: s.documents.filter((d) => d.id !== id),
541
+ documentsTotal: s.documentsTotal - 1
542
+ }));
543
+ }
544
+ }));
545
+ }
546
+
547
+ // src/locales/en.ts
548
+ var en = {
549
+ // Settings
550
+ "customForms.settingsTitle": "Forms & Documents",
551
+ "customForms.settingsSubtitle": "Create and manage custom forms for your business",
552
+ // Templates
553
+ "customForms.templates": "Templates",
554
+ "customForms.newTemplate": "New Template",
555
+ "customForms.editTemplate": "Edit Template",
556
+ "customForms.templateName": "Form name",
557
+ "customForms.templateDescription": "Description",
558
+ "customForms.templateCategory": "Category",
559
+ "customForms.templateSpecialty": "Specialty",
560
+ "customForms.noTemplates": "No form templates yet",
561
+ "customForms.noTemplatesDescription": "Create your first custom form to start collecting structured data.",
562
+ "customForms.deleteTemplateConfirm": "Delete this form template? Existing documents will be preserved.",
563
+ // Categories
564
+ "customForms.category.anamnesis": "Anamnesis",
565
+ "customForms.category.evolution": "Evolution Note",
566
+ "customForms.category.report": "Report",
567
+ "customForms.category.contract": "Contract",
568
+ "customForms.category.general": "General",
569
+ // Builder
570
+ "customForms.builder.fields": "Fields",
571
+ "customForms.builder.fieldCount": "{{count}} fields",
572
+ "customForms.builder.settings": "Settings",
573
+ "customForms.builder.save": "Save",
574
+ "customForms.builder.saving": "Saving...",
575
+ "customForms.builder.saved": "Form saved",
576
+ "customForms.builder.emptyCanvas": "Drag fields from the left panel to start building your form",
577
+ "customForms.builder.fieldLabel": "Field label",
578
+ "customForms.builder.fieldPlaceholder": "Placeholder",
579
+ "customForms.builder.fieldRequired": "Required",
580
+ "customForms.builder.fieldOptions": "Options",
581
+ "customForms.builder.addOption": "Add option",
582
+ "customForms.builder.colSpan": "Column span",
583
+ "customForms.builder.deleteField": "Remove field",
584
+ // Field types
585
+ "customForms.fieldType.title": "Title",
586
+ "customForms.fieldType.text": "Simple Text",
587
+ "customForms.fieldType.memo": "Memo",
588
+ "customForms.fieldType.richtext": "Rich Text",
589
+ "customForms.fieldType.date": "Date",
590
+ "customForms.fieldType.select": "Select",
591
+ "customForms.fieldType.radio": "Radio",
592
+ "customForms.fieldType.tags": "Tags",
593
+ "customForms.fieldType.checkbox": "Checkbox",
594
+ "customForms.fieldType.image": "Image / Drawing",
595
+ "customForms.fieldType.gallery": "Gallery",
596
+ "customForms.fieldType.budget": "Budget",
597
+ // Documents
598
+ "customForms.documents": "Documents",
599
+ "customForms.addDocument": "Add Document",
600
+ "customForms.selectTemplate": "Select a form",
601
+ "customForms.noDocuments": "No documents yet",
602
+ "customForms.noDocumentsDescription": "Add a document to start recording information.",
603
+ "customForms.documentTitle": "Document title",
604
+ "customForms.saveAsDraft": "Save as Draft",
605
+ "customForms.saveAndComplete": "Save & Complete",
606
+ "customForms.deleteDocumentConfirm": "Delete this document?",
607
+ // Registry
608
+ "registry.form-categories": "Categories",
609
+ "registry.form-categories.description": "Categories for organizing form templates",
610
+ // Status
611
+ "customForms.status.draft": "Draft",
612
+ "customForms.status.completed": "Completed",
613
+ "customForms.status.signed": "Signed",
614
+ "customForms.status.archived": "Archived"
615
+ };
616
+
617
+ // src/locales/pt-BR.ts
618
+ var ptBR = {
619
+ // Settings
620
+ "customForms.settingsTitle": "Formul\xE1rios e Documentos",
621
+ "customForms.settingsSubtitle": "Crie e gerencie formul\xE1rios personalizados para o seu neg\xF3cio",
622
+ // Templates
623
+ "customForms.templates": "Modelos",
624
+ "customForms.newTemplate": "Novo Modelo",
625
+ "customForms.editTemplate": "Editar Modelo",
626
+ "customForms.templateName": "Nome do formul\xE1rio",
627
+ "customForms.templateDescription": "Descri\xE7\xE3o",
628
+ "customForms.templateCategory": "Categoria",
629
+ "customForms.templateSpecialty": "Especialidade",
630
+ "customForms.noTemplates": "Nenhum modelo criado",
631
+ "customForms.noTemplatesDescription": "Crie seu primeiro formul\xE1rio personalizado para come\xE7ar a coletar dados estruturados.",
632
+ "customForms.deleteTemplateConfirm": "Excluir este modelo de formul\xE1rio? Documentos existentes ser\xE3o preservados.",
633
+ // Categories
634
+ "customForms.category.anamnesis": "Anamnese",
635
+ "customForms.category.evolution": "Evolu\xE7\xE3o",
636
+ "customForms.category.report": "Laudo",
637
+ "customForms.category.contract": "Contrato",
638
+ "customForms.category.general": "Geral",
639
+ // Builder
640
+ "customForms.builder.fields": "Campos",
641
+ "customForms.builder.fieldCount": "{{count}} campos",
642
+ "customForms.builder.settings": "Configura\xE7\xF5es",
643
+ "customForms.builder.save": "Salvar",
644
+ "customForms.builder.saving": "Salvando...",
645
+ "customForms.builder.saved": "Formul\xE1rio salvo",
646
+ "customForms.builder.emptyCanvas": "Arraste os campos do painel esquerdo para come\xE7ar a montar seu formul\xE1rio",
647
+ "customForms.builder.fieldLabel": "R\xF3tulo do campo",
648
+ "customForms.builder.fieldPlaceholder": "Placeholder",
649
+ "customForms.builder.fieldRequired": "Obrigat\xF3rio",
650
+ "customForms.builder.fieldOptions": "Op\xE7\xF5es",
651
+ "customForms.builder.addOption": "Adicionar op\xE7\xE3o",
652
+ "customForms.builder.colSpan": "Largura em colunas",
653
+ "customForms.builder.deleteField": "Remover campo",
654
+ // Field types
655
+ "customForms.fieldType.title": "T\xEDtulo",
656
+ "customForms.fieldType.text": "Texto Simples",
657
+ "customForms.fieldType.memo": "Memorando",
658
+ "customForms.fieldType.richtext": "Texto Rico",
659
+ "customForms.fieldType.date": "Data",
660
+ "customForms.fieldType.select": "Sele\xE7\xE3o",
661
+ "customForms.fieldType.radio": "Radio",
662
+ "customForms.fieldType.tags": "Tags",
663
+ "customForms.fieldType.checkbox": "Checkbox",
664
+ "customForms.fieldType.image": "Imagem / Desenho",
665
+ "customForms.fieldType.gallery": "Galeria",
666
+ "customForms.fieldType.budget": "Or\xE7amento",
667
+ // Documents
668
+ "customForms.documents": "Documentos",
669
+ "customForms.addDocument": "Novo Documento",
670
+ "customForms.selectTemplate": "Selecione um formul\xE1rio",
671
+ "customForms.noDocuments": "Nenhum documento",
672
+ "customForms.noDocumentsDescription": "Adicione um documento para come\xE7ar a registrar informa\xE7\xF5es.",
673
+ "customForms.documentTitle": "T\xEDtulo do documento",
674
+ "customForms.saveAsDraft": "Salvar Rascunho",
675
+ "customForms.saveAndComplete": "Salvar e Finalizar",
676
+ "customForms.deleteDocumentConfirm": "Excluir este documento?",
677
+ // Registry
678
+ "registry.form-categories": "Categorias",
679
+ "registry.form-categories.description": "Categorias para organizar modelos de formul\xE1rios",
680
+ // Status
681
+ "customForms.status.draft": "Rascunho",
682
+ "customForms.status.completed": "Finalizado",
683
+ "customForms.status.signed": "Assinado",
684
+ "customForms.status.archived": "Arquivado"
685
+ };
686
+
687
+ // src/locales/index.ts
688
+ var customFormsLocales = {
689
+ en,
690
+ "pt-BR": ptBR
691
+ };
692
+
693
+ // src/config.ts
694
+ var DEFAULT_LABELS = {
695
+ pageTitle: "Custom Forms",
696
+ settingsLabel: "Forms & Documents",
697
+ templates: "Templates",
698
+ documents: "Documents",
699
+ newTemplate: "New Template",
700
+ addDocument: "Add Document"
701
+ };
702
+ function resolveConfig(options) {
703
+ return {
704
+ labels: { ...DEFAULT_LABELS, ...options?.labels }
705
+ };
706
+ }
707
+ var CATEGORY_COLORS = {
708
+ anamnesis: "bg-info-soft text-info-soft-foreground",
709
+ evolution: "bg-success-soft text-success-soft-foreground",
710
+ report: "bg-magic-soft text-magic-soft-foreground",
711
+ contract: "bg-warning-soft text-warning-soft-foreground",
712
+ general: "bg-muted text-muted-foreground"
713
+ };
714
+ function TemplateListView({ store, config, onEdit, onNew }) {
715
+ const t = useTranslation();
716
+ const [search, setSearch] = useState("");
717
+ const templates = store((s) => s.templates);
718
+ const loading = store((s) => s.templatesLoading);
719
+ const orgId = getFormsTenantId();
720
+ useEffect(() => {
721
+ return;
722
+ }, [orgId, search]);
723
+ const handleDelete = useCallback(async (template) => {
724
+ if (!window.confirm(t("customForms.deleteTemplateConfirm"))) return;
725
+ await store.getState().deleteTemplate(template.id);
726
+ }, [store, t]);
727
+ const categoryLabel = (cat) => t(`customForms.category.${cat}`);
728
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
729
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
730
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1 max-w-xs", children: [
731
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" }),
732
+ /* @__PURE__ */ jsx(
733
+ Input,
734
+ {
735
+ value: search,
736
+ onChange: (e) => setSearch(e.target.value),
737
+ placeholder: t("common.search"),
738
+ className: "h-9 pl-8 text-sm"
739
+ }
740
+ )
741
+ ] }),
742
+ /* @__PURE__ */ jsxs(Button, { onClick: onNew, size: "sm", className: "h-9", children: [
743
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 mr-1.5" }),
744
+ t("customForms.newTemplate")
745
+ ] })
746
+ ] }),
747
+ !loading && templates.length === 0 && /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col items-center justify-center py-16 text-center", children: [
748
+ /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted mb-4", children: /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 text-muted-foreground" }) }),
749
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: t("customForms.noTemplates") }),
750
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1 max-w-sm", children: t("customForms.noTemplatesDescription") }),
751
+ /* @__PURE__ */ jsxs(Button, { onClick: onNew, size: "sm", className: "mt-4", children: [
752
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 mr-1.5" }),
753
+ t("customForms.newTemplate")
754
+ ] })
755
+ ] }) }),
756
+ templates.length > 0 && /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: templates.map((template) => /* @__PURE__ */ jsx(
757
+ Card,
758
+ {
759
+ className: "group cursor-pointer hover:shadow-md transition-shadow",
760
+ onClick: () => onEdit(template),
761
+ children: /* @__PURE__ */ jsxs(CardContent, { className: "p-4", children: [
762
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
763
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
764
+ /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm truncate", children: template.name }),
765
+ template.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 line-clamp-2", children: template.description })
766
+ ] }),
767
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity", children: [
768
+ /* @__PURE__ */ jsx(
769
+ "button",
770
+ {
771
+ onClick: (e) => {
772
+ e.stopPropagation();
773
+ onEdit(template);
774
+ },
775
+ className: "p-1.5 rounded-md hover:bg-muted",
776
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5 text-muted-foreground" })
777
+ }
778
+ ),
779
+ /* @__PURE__ */ jsx(
780
+ "button",
781
+ {
782
+ onClick: (e) => {
783
+ e.stopPropagation();
784
+ handleDelete(template);
785
+ },
786
+ className: "p-1.5 rounded-md hover:bg-destructive/10",
787
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5 text-destructive" })
788
+ }
789
+ )
790
+ ] })
791
+ ] }),
792
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-3", children: [
793
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${CATEGORY_COLORS[template.category]}`, children: categoryLabel(template.category) }),
794
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
795
+ template.schema.fields.length,
796
+ " ",
797
+ t("customForms.builder.fields").toLowerCase()
798
+ ] }),
799
+ template.version > 1 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
800
+ "v",
801
+ template.version
802
+ ] })
803
+ ] })
804
+ ] })
805
+ },
806
+ template.id
807
+ )) }),
808
+ loading && /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "p-4 space-y-3", children: [
809
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-2/3 bg-muted animate-pulse rounded" }),
810
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-full bg-muted animate-pulse rounded" }),
811
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
812
+ /* @__PURE__ */ jsx("div", { className: "h-5 w-16 bg-muted animate-pulse rounded-full" }),
813
+ /* @__PURE__ */ jsx("div", { className: "h-5 w-12 bg-muted animate-pulse rounded-full" })
814
+ ] })
815
+ ] }) }, i)) })
816
+ ] });
817
+ }
818
+ function CustomFormsSettingsTab({ config, provider, store, registries }) {
819
+ const t = useTranslation();
820
+ const [builderTemplateId, setBuilderTemplateId] = useState(null);
821
+ const handleNew = useCallback(() => {
822
+ setBuilderTemplateId("__new__");
823
+ }, []);
824
+ const handleEdit = useCallback((template) => {
825
+ setBuilderTemplateId(template.id);
826
+ }, []);
827
+ const handleBackFromBuilder = useCallback(() => {
828
+ setBuilderTemplateId(null);
829
+ store.getState().fetchTemplates();
830
+ }, [store]);
831
+ React2.useEffect(() => {
832
+ function checkHash() {
833
+ const hash = window.location.hash.slice(1) || "/";
834
+ const builderMatch = hash.match(/\/settings\/custom_forms\/templates\/(.+)/);
835
+ if (builderMatch && builderMatch[1] !== "new") {
836
+ setBuilderTemplateId(builderMatch[1]);
837
+ } else if (builderMatch && builderMatch[1] === "new") {
838
+ setBuilderTemplateId("__new__");
839
+ }
840
+ }
841
+ checkHash();
842
+ }, []);
843
+ const templatesContent = builderTemplateId ? /* @__PURE__ */ jsx(
844
+ BuilderWrapper,
845
+ {
846
+ templateId: builderTemplateId === "__new__" ? void 0 : builderTemplateId,
847
+ store,
848
+ provider,
849
+ config,
850
+ onBack: handleBackFromBuilder
851
+ }
852
+ ) : /* @__PURE__ */ jsx(
853
+ TemplateListView,
854
+ {
855
+ store,
856
+ config,
857
+ onEdit: handleEdit,
858
+ onNew: handleNew
859
+ }
860
+ );
861
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "24px" }, children: [
862
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
863
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "20px", fontWeight: 600, margin: 0 }, children: t("customForms.settingsTitle") }),
864
+ /* @__PURE__ */ jsx("p", { style: { color: "var(--muted-foreground, #6b7280)", margin: "4px 0 0", fontSize: "14px" }, children: t("customForms.settingsSubtitle") })
865
+ ] }),
866
+ templatesContent
867
+ ] });
868
+ }
869
+ function BuilderWrapper({
870
+ templateId,
871
+ store,
872
+ provider,
873
+ config,
874
+ onBack
875
+ }) {
876
+ const FormBuilderLazy = React2.lazy(
877
+ () => import('./FormBuilder-KDANEL6Z.js').then((m) => ({ default: m.FormBuilder }))
878
+ );
879
+ return /* @__PURE__ */ jsx(
880
+ React2.Suspense,
881
+ {
882
+ fallback: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "h-6 w-6 border-2 border-primary border-t-transparent animate-spin rounded-full" }) }),
883
+ children: /* @__PURE__ */ jsx(
884
+ FormBuilderLazy,
885
+ {
886
+ templateId,
887
+ store,
888
+ provider,
889
+ config,
890
+ onBack
891
+ }
892
+ )
893
+ }
894
+ );
895
+ }
896
+ var STATUS_COLORS = {
897
+ draft: "bg-warning-soft text-warning-soft-foreground",
898
+ completed: "bg-success-soft text-success-soft-foreground",
899
+ signed: "bg-info-soft text-info-soft-foreground",
900
+ archived: "bg-muted text-muted-foreground"
901
+ };
902
+ var KIND_ICONS = {
903
+ form: FileText,
904
+ image: Image,
905
+ attachment: Paperclip,
906
+ prescription: FileText,
907
+ contract: FileText
908
+ };
909
+ function formatDateLabel(dateStr) {
910
+ const date = new Date(dateStr);
911
+ const now2 = /* @__PURE__ */ new Date();
912
+ const today = new Date(now2.getFullYear(), now2.getMonth(), now2.getDate());
913
+ const docDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
914
+ const diffDays = Math.round((today.getTime() - docDate.getTime()) / (1e3 * 60 * 60 * 24));
915
+ if (diffDays === 0) return "Hoje";
916
+ if (diffDays === 1) return "Ontem";
917
+ if (diffDays < 7) return `${diffDays} dias atr\xE1s`;
918
+ return date.toLocaleDateString(void 0, { day: "numeric", month: "long", year: "numeric" });
919
+ }
920
+ function groupByDate(docs) {
921
+ const groups = /* @__PURE__ */ new Map();
922
+ for (const doc of docs) {
923
+ const dateKey = doc.createdAt.slice(0, 10);
924
+ if (!groups.has(dateKey)) groups.set(dateKey, []);
925
+ groups.get(dateKey).push(doc);
926
+ }
927
+ return Array.from(groups.entries()).sort(([a], [b]) => b.localeCompare(a)).map(([date, items]) => ({
928
+ date,
929
+ label: formatDateLabel(date),
930
+ items
931
+ }));
932
+ }
933
+ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
934
+ const t = useTranslation();
935
+ const [documents, setDocuments] = useState([]);
936
+ const [loading, setLoading] = useState(true);
937
+ const [isDragOver, setIsDragOver] = useState(false);
938
+ const dragCounter = useRef(0);
939
+ const fetchDocs = useCallback(async () => {
940
+ setLoading(true);
941
+ try {
942
+ const result = await provider.getDocuments({ personId });
943
+ setDocuments(result.data);
944
+ } finally {
945
+ setLoading(false);
946
+ }
947
+ }, [personId, provider]);
948
+ useEffect(() => {
949
+ const timer = setTimeout(() => fetchDocs(), 50);
950
+ return () => clearTimeout(timer);
951
+ }, [fetchDocs]);
952
+ useEffect(() => {
953
+ const unsub = store.subscribe(() => fetchDocs());
954
+ return unsub;
955
+ }, [store, fetchDocs]);
956
+ const handleDelete = useCallback(async (doc) => {
957
+ if (!window.confirm(t("customForms.deleteDocumentConfirm"))) return;
958
+ await provider.deleteDocument(doc.id);
959
+ setDocuments((prev) => prev.filter((d) => d.id !== doc.id));
960
+ }, [provider, t]);
961
+ const handleDragEnter = useCallback((e) => {
962
+ e.preventDefault();
963
+ dragCounter.current++;
964
+ setIsDragOver(true);
965
+ }, []);
966
+ const handleDragLeave = useCallback((e) => {
967
+ e.preventDefault();
968
+ dragCounter.current--;
969
+ if (dragCounter.current === 0) setIsDragOver(false);
970
+ }, []);
971
+ const handleDragOver = useCallback((e) => {
972
+ e.preventDefault();
973
+ }, []);
974
+ const handleDrop = useCallback((e) => {
975
+ e.preventDefault();
976
+ dragCounter.current = 0;
977
+ setIsDragOver(false);
978
+ const files = Array.from(e.dataTransfer.files);
979
+ if (files.length > 0 && onFileDrop) {
980
+ onFileDrop(files);
981
+ }
982
+ }, [onFileDrop]);
983
+ const groups = groupByDate(documents);
984
+ if (loading) {
985
+ return /* @__PURE__ */ jsx("div", { className: "space-y-4 py-4", children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
986
+ /* @__PURE__ */ jsx("div", { className: "h-9 w-9 rounded-lg bg-muted animate-pulse shrink-0" }),
987
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
988
+ /* @__PURE__ */ jsx("div", { className: "h-3.5 w-2/3 bg-muted animate-pulse rounded" }),
989
+ /* @__PURE__ */ jsx("div", { className: "h-2.5 w-1/3 bg-muted animate-pulse rounded" })
990
+ ] })
991
+ ] }, i)) });
992
+ }
993
+ return /* @__PURE__ */ jsxs(
994
+ "div",
995
+ {
996
+ className: `relative min-h-[200px] rounded-xl border-2 border-dashed transition-colors ${isDragOver ? "border-primary bg-primary/5" : documents.length === 0 ? "border-border" : "border-transparent"}`,
997
+ onDragEnter: handleDragEnter,
998
+ onDragLeave: handleDragLeave,
999
+ onDragOver: handleDragOver,
1000
+ onDrop: handleDrop,
1001
+ children: [
1002
+ isDragOver && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-10 flex flex-col items-center justify-center rounded-xl bg-primary/5", children: [
1003
+ /* @__PURE__ */ jsx(Upload, { className: "h-8 w-8 text-primary/50 mb-2" }),
1004
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-primary/70", children: "Solte os arquivos aqui" })
1005
+ ] }),
1006
+ documents.length === 0 && !isDragOver && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [
1007
+ /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted mb-3", children: /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 text-muted-foreground" }) }),
1008
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("customForms.noDocuments") }),
1009
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 max-w-xs", children: t("customForms.noDocumentsDescription") }),
1010
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground mt-3 flex items-center gap-1", children: [
1011
+ /* @__PURE__ */ jsx(Upload, { className: "h-3 w-3" }),
1012
+ "Arraste arquivos para c\xE1"
1013
+ ] })
1014
+ ] }),
1015
+ groups.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-5 py-2", children: groups.map((group) => /* @__PURE__ */ jsxs("div", { children: [
1016
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
1017
+ /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" }),
1018
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground px-1", children: group.label }),
1019
+ /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" })
1020
+ ] }),
1021
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: group.items.map((doc) => {
1022
+ const Icon = KIND_ICONS[doc.kind ?? "form"] ?? FileText;
1023
+ const time = new Date(doc.createdAt).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
1024
+ return /* @__PURE__ */ jsxs(
1025
+ "div",
1026
+ {
1027
+ className: "group flex items-center gap-3 rounded-lg p-2.5 hover:bg-muted/50 cursor-pointer transition-colors",
1028
+ onClick: () => onView(doc),
1029
+ children: [
1030
+ /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 shrink-0", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-primary" }) }),
1031
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1032
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: doc.title ?? doc.templateName ?? "Documento" }),
1033
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mt-0.5", children: [
1034
+ doc.templateName && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: doc.templateName }),
1035
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: time })
1036
+ ] })
1037
+ ] }),
1038
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] shrink-0 ${STATUS_COLORS[doc.status] ?? ""}`, children: t(`customForms.status.${doc.status}`) }),
1039
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity", children: [
1040
+ /* @__PURE__ */ jsx(
1041
+ "button",
1042
+ {
1043
+ onClick: (e) => {
1044
+ e.stopPropagation();
1045
+ onView(doc);
1046
+ },
1047
+ className: "p-1.5 rounded-md hover:bg-muted",
1048
+ children: /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5 text-muted-foreground" })
1049
+ }
1050
+ ),
1051
+ /* @__PURE__ */ jsx(
1052
+ "button",
1053
+ {
1054
+ onClick: (e) => {
1055
+ e.stopPropagation();
1056
+ handleDelete(doc);
1057
+ },
1058
+ className: "p-1.5 rounded-md hover:bg-destructive/10",
1059
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5 text-destructive" })
1060
+ }
1061
+ )
1062
+ ] })
1063
+ ]
1064
+ },
1065
+ doc.id
1066
+ );
1067
+ }) })
1068
+ ] }, group.date)) })
1069
+ ]
1070
+ }
1071
+ );
1072
+ }
1073
+ function generateTitle(templateName, personName) {
1074
+ const date = (/* @__PURE__ */ new Date()).toLocaleDateString();
1075
+ return personName ? `${templateName} \u2014 ${personName} \u2014 ${date}` : `${templateName} \u2014 ${date}`;
1076
+ }
1077
+ function DocumentFormDialog({
1078
+ template: initialTemplate,
1079
+ existingDocument,
1080
+ personId,
1081
+ personName,
1082
+ provider,
1083
+ store,
1084
+ onSaved,
1085
+ onBack
1086
+ }) {
1087
+ const t = useTranslation();
1088
+ const [template, setTemplate] = useState(initialTemplate);
1089
+ const [data, setData] = useState(existingDocument?.data ?? {});
1090
+ const [saving, setSaving] = useState(false);
1091
+ useEffect(() => {
1092
+ if (existingDocument && !template) {
1093
+ provider.getTemplateById(existingDocument.templateId).then(setTemplate);
1094
+ }
1095
+ }, [existingDocument, template, provider]);
1096
+ const handleSave = useCallback(
1097
+ async (status) => {
1098
+ if (!template) return;
1099
+ setSaving(true);
1100
+ try {
1101
+ const autoTitle = generateTitle(template.name, personName);
1102
+ if (existingDocument) {
1103
+ await store.getState().updateDocument(existingDocument.id, { data, status });
1104
+ } else {
1105
+ await store.getState().createDocument({
1106
+ templateId: template.id,
1107
+ personId,
1108
+ title: autoTitle,
1109
+ data,
1110
+ status
1111
+ });
1112
+ }
1113
+ onSaved();
1114
+ } finally {
1115
+ setSaving(false);
1116
+ }
1117
+ },
1118
+ [template, existingDocument, data, personId, personName, store, onSaved]
1119
+ );
1120
+ const snapshot = React2.useRef(null);
1121
+ useEffect(() => {
1122
+ if (template && snapshot.current === null) snapshot.current = JSON.stringify(data);
1123
+ }, [template]);
1124
+ const dirty = snapshot.current !== null && JSON.stringify(data) !== snapshot.current;
1125
+ useSaveBar({
1126
+ dirty,
1127
+ saving,
1128
+ onSave: () => {
1129
+ void handleSave("completed");
1130
+ },
1131
+ onDiscard: () => onBack(),
1132
+ saveLabel: t("customForms.saveAndComplete")
1133
+ });
1134
+ if (!template) {
1135
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "h-5 w-5 border-2 border-primary border-t-transparent animate-spin rounded-full" }) });
1136
+ }
1137
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1138
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1139
+ /* @__PURE__ */ jsx(
1140
+ "button",
1141
+ {
1142
+ onClick: onBack,
1143
+ className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
1144
+ children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
1145
+ }
1146
+ ),
1147
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1148
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm", children: template.name }),
1149
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1150
+ personName && /* @__PURE__ */ jsxs("span", { children: [
1151
+ personName,
1152
+ " \xB7 "
1153
+ ] }),
1154
+ (/* @__PURE__ */ new Date()).toLocaleDateString()
1155
+ ] })
1156
+ ] })
1157
+ ] }),
1158
+ /* @__PURE__ */ jsx("div", { className: "rounded-xl border p-4", children: /* @__PURE__ */ jsx(
1159
+ FormRenderer,
1160
+ {
1161
+ schema: template.schema,
1162
+ data,
1163
+ onChange: setData
1164
+ }
1165
+ ) }),
1166
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end gap-2", children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => handleSave("draft"), disabled: saving, children: [
1167
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 mr-1.5 animate-spin" }) : /* @__PURE__ */ jsx(Save, { className: "h-3.5 w-3.5 mr-1.5" }),
1168
+ t("customForms.saveAsDraft")
1169
+ ] }) })
1170
+ ] });
1171
+ }
1172
+ var STATUS_COLORS2 = {
1173
+ draft: "bg-warning-soft text-warning-soft-foreground",
1174
+ completed: "bg-success-soft text-success-soft-foreground",
1175
+ signed: "bg-info-soft text-info-soft-foreground",
1176
+ archived: "bg-muted text-muted-foreground"
1177
+ };
1178
+ function FormViewer({ document: doc, provider, onBack, onEdit }) {
1179
+ const t = useTranslation();
1180
+ const [template, setTemplate] = useState(null);
1181
+ useEffect(() => {
1182
+ if (!doc.templateId) return;
1183
+ const timer = setTimeout(() => {
1184
+ provider.getTemplateById(doc.templateId).then(setTemplate);
1185
+ }, 50);
1186
+ return () => clearTimeout(timer);
1187
+ }, [doc.templateId, provider]);
1188
+ if (!template) {
1189
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "h-5 w-5 border-2 border-primary border-t-transparent animate-spin rounded-full" }) });
1190
+ }
1191
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1192
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1193
+ /* @__PURE__ */ jsx(
1194
+ "button",
1195
+ {
1196
+ onClick: onBack,
1197
+ className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
1198
+ children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
1199
+ }
1200
+ ),
1201
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1202
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm truncate", children: doc.title ?? template.name }),
1203
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: new Date(doc.createdAt).toLocaleDateString() })
1204
+ ] }),
1205
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${STATUS_COLORS2[doc.status] ?? ""}`, children: t(`customForms.status.${doc.status}`) }),
1206
+ onEdit && doc.status === "draft" && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => onEdit(doc), children: [
1207
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3 mr-1" }),
1208
+ t("common.edit")
1209
+ ] })
1210
+ ] }),
1211
+ /* @__PURE__ */ jsx("div", { className: "rounded-xl border p-4", children: /* @__PURE__ */ jsx(
1212
+ FormRenderer,
1213
+ {
1214
+ schema: template.schema,
1215
+ data: doc.data,
1216
+ onChange: () => {
1217
+ },
1218
+ readOnly: true
1219
+ }
1220
+ ) })
1221
+ ] });
1222
+ }
1223
+
1224
+ // src/document-types.ts
1225
+ var providers = [];
1226
+ function registerDocumentTypeProvider(provider) {
1227
+ if (providers.find((p) => p.id === provider.id)) return;
1228
+ providers.push(provider);
1229
+ }
1230
+ async function getAllDocumentTypes(personId) {
1231
+ const results = await Promise.all(
1232
+ providers.map((p) => Promise.resolve(p.getTypes(personId)))
1233
+ );
1234
+ const all = results.flat();
1235
+ all.sort((a, b) => (a.groupOrder ?? 50) - (b.groupOrder ?? 50) || (a.order ?? 0) - (b.order ?? 0));
1236
+ return all;
1237
+ }
1238
+ function AddDocumentDropdown({ personId, onSelect }) {
1239
+ const t = useTranslation();
1240
+ const [options, setOptions] = useState([]);
1241
+ const [loaded, setLoaded] = useState(false);
1242
+ const loadingRef = useRef(false);
1243
+ const handleOpen = useCallback((open) => {
1244
+ if (!open || loaded || loadingRef.current) return;
1245
+ loadingRef.current = true;
1246
+ getAllDocumentTypes(personId).then((opts) => {
1247
+ setOptions(opts);
1248
+ setLoaded(true);
1249
+ loadingRef.current = false;
1250
+ });
1251
+ }, [personId, loaded]);
1252
+ const groups = [];
1253
+ for (const opt of options) {
1254
+ let group = groups.find((g) => g.label === opt.group);
1255
+ if (!group) {
1256
+ group = { label: opt.group, items: [] };
1257
+ groups.push(group);
1258
+ }
1259
+ group.items.push(opt);
1260
+ }
1261
+ return /* @__PURE__ */ jsxs(Dropdown, { onOpenChange: handleOpen, children: [
1262
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { size: "sm", className: "h-8", children: [
1263
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 mr-1" }),
1264
+ t("customForms.addDocument"),
1265
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 ml-1 opacity-50" })
1266
+ ] }) }),
1267
+ /* @__PURE__ */ jsxs(DropdownContent, { align: "end", className: "w-56", children: [
1268
+ groups.map((group, gi) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
1269
+ gi > 0 && /* @__PURE__ */ jsx(DropdownSeparator, {}),
1270
+ /* @__PURE__ */ jsx(DropdownLabel, { className: "text-[10px] uppercase tracking-wider", children: group.label }),
1271
+ group.items.map((opt) => {
1272
+ const Icon = opt.icon;
1273
+ return /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => onSelect(opt), className: "gap-2", children: [
1274
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1275
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1276
+ /* @__PURE__ */ jsx("p", { className: "text-sm truncate", children: opt.label }),
1277
+ opt.description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground truncate", children: opt.description })
1278
+ ] })
1279
+ ] }, opt.id);
1280
+ })
1281
+ ] }, group.label)),
1282
+ !loaded && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-3", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-4 border-2 border-primary border-t-transparent animate-spin rounded-full" }) }),
1283
+ loaded && options.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customForms.noTemplates") })
1284
+ ] })
1285
+ ] });
1286
+ }
1287
+ function PersonDocumentsWidget({ item, config, provider, store }) {
1288
+ const t = useTranslation();
1289
+ const [view, setView] = useState({ type: "list" });
1290
+ const fileInputRef = useRef(null);
1291
+ const [pendingFileAccept, setPendingFileAccept] = useState("*/*");
1292
+ const handleDocTypeSelect = useCallback(async (option) => {
1293
+ if (option.id.startsWith("template:")) {
1294
+ const templateId = option.id.replace("template:", "");
1295
+ const template = await provider.getTemplateById(templateId);
1296
+ if (template) {
1297
+ setView({ type: "fill", template });
1298
+ }
1299
+ return;
1300
+ }
1301
+ if (option.id === "core:image") {
1302
+ setPendingFileAccept("image/*");
1303
+ setTimeout(() => fileInputRef.current?.click(), 0);
1304
+ return;
1305
+ }
1306
+ if (option.id === "core:attachment") {
1307
+ setPendingFileAccept("*/*");
1308
+ setTimeout(() => fileInputRef.current?.click(), 0);
1309
+ return;
1310
+ }
1311
+ }, [provider]);
1312
+ const handleFiles = useCallback(async (files) => {
1313
+ for (const file of files) {
1314
+ const isImage = file.type.startsWith("image/");
1315
+ await store.getState().createDocument({
1316
+ kind: isImage ? "image" : "attachment",
1317
+ personId: item.id,
1318
+ title: file.name,
1319
+ fileName: file.name,
1320
+ fileSize: file.size,
1321
+ mimeType: file.type,
1322
+ status: "completed"
1323
+ });
1324
+ }
1325
+ }, [store, item.id]);
1326
+ const handleFileInputChange = useCallback((e) => {
1327
+ const files = Array.from(e.target.files ?? []);
1328
+ if (files.length > 0) handleFiles(files);
1329
+ e.target.value = "";
1330
+ }, [handleFiles]);
1331
+ const handleViewDoc = useCallback((doc) => {
1332
+ setView({ type: "view", document: doc });
1333
+ }, []);
1334
+ const handleEditDoc = useCallback((doc) => {
1335
+ setView({ type: "edit", document: doc });
1336
+ }, []);
1337
+ const handleBack = useCallback(() => {
1338
+ setView({ type: "list" });
1339
+ }, []);
1340
+ const handleSaved = useCallback(() => {
1341
+ setView({ type: "list" });
1342
+ }, []);
1343
+ if (view.type === "fill") {
1344
+ return /* @__PURE__ */ jsx(
1345
+ DocumentFormDialog,
1346
+ {
1347
+ template: view.template,
1348
+ personId: item.id,
1349
+ personName: item.name,
1350
+ provider,
1351
+ store,
1352
+ onSaved: handleSaved,
1353
+ onBack: handleBack
1354
+ }
1355
+ );
1356
+ }
1357
+ if (view.type === "view") {
1358
+ const doc = view.document;
1359
+ const isForm = doc.kind === "form" || !doc.kind && doc.templateId;
1360
+ if (!isForm) {
1361
+ return /* @__PURE__ */ jsx(FileDocumentViewer, { document: doc, onBack: handleBack });
1362
+ }
1363
+ return /* @__PURE__ */ jsx(
1364
+ FormViewer,
1365
+ {
1366
+ document: doc,
1367
+ provider,
1368
+ onBack: handleBack,
1369
+ onEdit: handleEditDoc
1370
+ }
1371
+ );
1372
+ }
1373
+ if (view.type === "edit") {
1374
+ return /* @__PURE__ */ jsx(
1375
+ DocumentFormDialog,
1376
+ {
1377
+ template: null,
1378
+ existingDocument: view.document,
1379
+ personId: item.id,
1380
+ personName: item.name,
1381
+ provider,
1382
+ store,
1383
+ onSaved: handleSaved,
1384
+ onBack: handleBack
1385
+ }
1386
+ );
1387
+ }
1388
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1389
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1390
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t("customForms.documents") }),
1391
+ /* @__PURE__ */ jsx(AddDocumentDropdown, { personId: item.id, onSelect: handleDocTypeSelect })
1392
+ ] }),
1393
+ /* @__PURE__ */ jsx(
1394
+ DocumentList,
1395
+ {
1396
+ personId: item.id,
1397
+ provider,
1398
+ store,
1399
+ onView: handleViewDoc,
1400
+ onFileDrop: handleFiles
1401
+ }
1402
+ ),
1403
+ /* @__PURE__ */ jsx(
1404
+ "input",
1405
+ {
1406
+ ref: fileInputRef,
1407
+ type: "file",
1408
+ className: "hidden",
1409
+ accept: pendingFileAccept,
1410
+ multiple: true,
1411
+ onChange: handleFileInputChange
1412
+ }
1413
+ )
1414
+ ] });
1415
+ }
1416
+ function FileDocumentViewer({ document: doc, onBack }) {
1417
+ const isImage = doc.kind === "image" || doc.mimeType?.startsWith("image/");
1418
+ const Icon = isImage ? Image : Paperclip;
1419
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1420
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1421
+ /* @__PURE__ */ jsx(
1422
+ "button",
1423
+ {
1424
+ onClick: onBack,
1425
+ className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
1426
+ children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
1427
+ }
1428
+ ),
1429
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1430
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm truncate", children: doc.title ?? doc.fileName ?? "Document" }),
1431
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: new Date(doc.createdAt).toLocaleDateString() })
1432
+ ] }),
1433
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-[10px] bg-success/15 text-success", children: doc.status === "completed" ? "Finalizado" : doc.status })
1434
+ ] }),
1435
+ /* @__PURE__ */ jsx("div", { className: "rounded-xl border p-6 flex flex-col items-center gap-4", children: isImage && doc.fileUrl ? /* @__PURE__ */ jsx("img", { src: doc.fileUrl, alt: doc.fileName ?? "", className: "max-w-full max-h-[500px] rounded-lg object-contain" }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 py-8", children: [
1436
+ /* @__PURE__ */ jsx("div", { className: "flex h-16 w-16 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(Icon, { className: "h-7 w-7 text-muted-foreground" }) }),
1437
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: doc.fileName ?? doc.title ?? "Arquivo" }),
1438
+ doc.fileSize && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1439
+ (doc.fileSize / 1024).toFixed(1),
1440
+ " KB"
1441
+ ] })
1442
+ ] }) })
1443
+ ] });
1444
+ }
1445
+ function registerBuiltInProviders(formsProvider) {
1446
+ registerDocumentTypeProvider({
1447
+ id: "custom_forms:templates",
1448
+ async getTypes() {
1449
+ try {
1450
+ const result = await formsProvider.getTemplates({});
1451
+ return result.data.map((tpl) => ({
1452
+ id: `template:${tpl.id}`,
1453
+ label: tpl.name,
1454
+ icon: FileText,
1455
+ group: "Formul\xE1rios",
1456
+ groupOrder: 10,
1457
+ order: 0,
1458
+ description: tpl.description,
1459
+ _templateId: tpl.id
1460
+ }));
1461
+ } catch {
1462
+ return [];
1463
+ }
1464
+ }
1465
+ });
1466
+ registerDocumentTypeProvider({
1467
+ id: "core:files",
1468
+ getTypes() {
1469
+ return [
1470
+ { id: "core:image", label: "Imagem", icon: Image, group: "Arquivos", groupOrder: 90, order: 0 },
1471
+ { id: "core:attachment", label: "Anexo", icon: Paperclip, group: "Arquivos", groupOrder: 90, order: 1 }
1472
+ ];
1473
+ }
1474
+ });
1475
+ }
1476
+ function createCustomFormsPlugin(options) {
1477
+ registerTranslations(customFormsLocales);
1478
+ const config = resolveConfig(options);
1479
+ const provider = options?.dataProvider ?? createSafeDataProvider(
1480
+ () => createSupabaseFormsProvider(),
1481
+ () => createMockFormsProvider()
1482
+ );
1483
+ const store = createCustomFormsStore(provider);
1484
+ registerBuiltInProviders(provider);
1485
+ const formRegistries = [
1486
+ {
1487
+ id: "form-categories",
1488
+ entity: {
1489
+ name: "Category",
1490
+ namePlural: "Categories",
1491
+ icon: "FolderOpen",
1492
+ displayField: "name",
1493
+ defaultSort: "sort_order",
1494
+ fields: [
1495
+ { key: "name", label: "Name", type: "text", required: true, showInTable: true },
1496
+ { key: "icon", label: "Icon", type: "text", showInTable: true },
1497
+ { key: "color", label: "Color", type: "color", showInTable: true },
1498
+ { key: "sortOrder", label: "Order", type: "number", showInTable: true, defaultValue: 0 },
1499
+ { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true, inlineToggle: true }
1500
+ ],
1501
+ data: {
1502
+ table: "frm_categories",
1503
+ tenantScoped: true
1504
+ }
1505
+ },
1506
+ icon: "FolderOpen",
1507
+ description: "Categories for organizing form templates",
1508
+ seedData: [
1509
+ { name: "Anamnese", sortOrder: 1, isActive: true },
1510
+ { name: "Evolu\xE7\xE3o", sortOrder: 2, isActive: true },
1511
+ { name: "Laudo", sortOrder: 3, isActive: true },
1512
+ { name: "Contrato", sortOrder: 4, isActive: true },
1513
+ { name: "Geral", sortOrder: 5, isActive: true }
1514
+ ]
1515
+ }
1516
+ ];
1517
+ const SettingsComponent = () => React2.createElement(CustomFormsSettingsTab, { config, provider, store, registries: formRegistries });
1518
+ SettingsComponent.displayName = "CustomFormsSettingsTab";
1519
+ const PersonDocumentsWidget2 = (props) => React2.createElement(PersonDocumentsWidget, { ...props, config, provider, store });
1520
+ PersonDocumentsWidget2.displayName = "PersonDocumentsWidget";
1521
+ return {
1522
+ id: "custom_forms",
1523
+ name: config.labels.pageTitle,
1524
+ icon: "FileText",
1525
+ version: "1.0.0",
1526
+ scope: options?.scope ?? "universal",
1527
+ verticalId: options?.verticalId,
1528
+ defaultEnabled: true,
1529
+ dependencies: [],
1530
+ declaredFeatures: [
1531
+ { id: "custom_forms", label: config.labels.pageTitle, group: config.labels.pageTitle }
1532
+ ],
1533
+ navigation: [],
1534
+ routes: [],
1535
+ settings: [
1536
+ {
1537
+ id: "custom_forms",
1538
+ label: config.labels.settingsLabel,
1539
+ icon: "FileText",
1540
+ component: SettingsComponent,
1541
+ order: 15
1542
+ }
1543
+ ],
1544
+ widgets: [
1545
+ {
1546
+ id: "person-documents-tab",
1547
+ zone: "person.detail.documents",
1548
+ component: PersonDocumentsWidget2,
1549
+ order: 0
1550
+ }
1551
+ ],
1552
+ registries: formRegistries,
1553
+ aiTools: [
1554
+ {
1555
+ id: "custom_forms.list-templates",
1556
+ name: "listFormTemplates",
1557
+ description: "Lists available form templates for the current tenant.",
1558
+ icon: "FileText",
1559
+ mode: "read",
1560
+ category: "Forms",
1561
+ parameters: {
1562
+ type: "object",
1563
+ properties: {
1564
+ category: {
1565
+ type: "string",
1566
+ description: "Filter by category: anamnesis, evolution, report, contract, general"
1567
+ }
1568
+ }
1569
+ },
1570
+ suggestions: [
1571
+ { label: "What forms do we have?" },
1572
+ { label: "Show me anamnesis templates" }
1573
+ ],
1574
+ permission: { feature: "custom_forms", action: "read" }
1575
+ },
1576
+ {
1577
+ id: "custom_forms.list-documents",
1578
+ name: "listDocuments",
1579
+ description: "Lists filled documents, optionally filtered by person or status.",
1580
+ icon: "FileText",
1581
+ mode: "read",
1582
+ category: "Forms",
1583
+ parameters: {
1584
+ type: "object",
1585
+ properties: {
1586
+ personId: { type: "string", description: "Person UUID to filter by" },
1587
+ status: {
1588
+ type: "string",
1589
+ description: "Status filter: draft, completed, signed, archived"
1590
+ }
1591
+ }
1592
+ },
1593
+ suggestions: [
1594
+ { label: "Show this client's documents" }
1595
+ ],
1596
+ permission: { feature: "custom_forms", action: "read" }
1597
+ }
1598
+ ],
1599
+ locales: customFormsLocales
1600
+ };
1601
+ }
1602
+
1603
+ export { createCustomFormsPlugin, registerDocumentTypeProvider };
1604
+ //# sourceMappingURL=index.js.map
1605
+ //# sourceMappingURL=index.js.map