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