@fayz-ai/plugin-forms 0.2.0 → 0.8.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 (69) hide show
  1. package/README.md +2 -0
  2. package/dist/{FormBuilder-KDANEL6Z.js → FormBuilder-7SWZQKFN.js} +130 -23
  3. package/dist/FormBuilder-7SWZQKFN.js.map +1 -0
  4. package/dist/{FormBuilder-4VKYVZAC.cjs → FormBuilder-YB75LEEH.cjs} +129 -22
  5. package/dist/FormBuilder-YB75LEEH.cjs.map +1 -0
  6. package/dist/agent-tools.d.ts +4 -0
  7. package/dist/agent-tools.d.ts.map +1 -0
  8. package/dist/chunk-QM2AF3DZ.cjs +446 -0
  9. package/dist/chunk-QM2AF3DZ.cjs.map +1 -0
  10. package/dist/chunk-W2ZNJGQC.js +438 -0
  11. package/dist/chunk-W2ZNJGQC.js.map +1 -0
  12. package/dist/components/DocumentFormDialog.d.ts +3 -1
  13. package/dist/components/DocumentFormDialog.d.ts.map +1 -1
  14. package/dist/components/DocumentList.d.ts.map +1 -1
  15. package/dist/components/FieldConfigDialog.d.ts.map +1 -1
  16. package/dist/components/FormBuilder.d.ts.map +1 -1
  17. package/dist/components/FormViewer.d.ts.map +1 -1
  18. package/dist/components/PersonDocumentsWidget.d.ts.map +1 -1
  19. package/dist/data/supabase.d.ts.map +1 -1
  20. package/dist/data/tables.d.ts +7 -0
  21. package/dist/data/tables.d.ts.map +1 -0
  22. package/dist/index.cjs +168 -72
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +168 -72
  26. package/dist/index.js.map +1 -1
  27. package/dist/lib/person-fields.d.ts +16 -0
  28. package/dist/lib/person-fields.d.ts.map +1 -0
  29. package/dist/lib/print.d.ts +17 -0
  30. package/dist/lib/print.d.ts.map +1 -0
  31. package/dist/lib/tenant.d.ts.map +1 -1
  32. package/dist/locales/en.d.ts.map +1 -1
  33. package/dist/locales/pt-BR.d.ts.map +1 -1
  34. package/dist/migrations/index.d.ts +9 -0
  35. package/dist/migrations/index.d.ts.map +1 -0
  36. package/dist/types.d.ts +15 -1
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -1
  39. package/dist/views/TemplateListView.d.ts.map +1 -1
  40. package/package.json +10 -4
  41. package/src/agent-tools.ts +73 -0
  42. package/src/components/DocumentFormDialog.tsx +37 -16
  43. package/src/components/DocumentList.tsx +12 -8
  44. package/src/components/FieldConfigDialog.tsx +40 -0
  45. package/src/components/FormBuilder.tsx +104 -22
  46. package/src/components/FormViewer.tsx +11 -1
  47. package/src/components/PersonDocumentsWidget.tsx +10 -1
  48. package/src/data/supabase.ts +22 -19
  49. package/src/data/tables.ts +7 -0
  50. package/src/index.ts +23 -1
  51. package/src/lib/person-fields.ts +67 -0
  52. package/src/lib/print.ts +207 -0
  53. package/src/lib/tenant.ts +6 -2
  54. package/src/locales/en.ts +20 -0
  55. package/src/locales/pt-BR.ts +20 -0
  56. package/src/migrations/000_plg_rename.sql +21 -0
  57. package/src/migrations/001_frm_base.sql +92 -56
  58. package/src/migrations/002_document_archetype.sql +130 -42
  59. package/src/migrations/003_agent_rpcs.sql +174 -0
  60. package/src/migrations/index.ts +610 -0
  61. package/src/types.ts +18 -4
  62. package/src/views/CustomFormsSettingsTab.tsx +23 -16
  63. package/src/views/TemplateListView.tsx +29 -18
  64. package/dist/FormBuilder-4VKYVZAC.cjs.map +0 -1
  65. package/dist/FormBuilder-KDANEL6Z.js.map +0 -1
  66. package/dist/chunk-32I43T37.js +0 -198
  67. package/dist/chunk-32I43T37.js.map +0 -1
  68. package/dist/chunk-XLUULIVL.cjs +0 -200
  69. package/dist/chunk-XLUULIVL.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkXLUULIVL_cjs = require('./chunk-XLUULIVL.cjs');
3
+ var chunkQM2AF3DZ_cjs = require('./chunk-QM2AF3DZ.cjs');
4
4
  var React2 = require('react');
5
5
  var core = require('@fayz-ai/core');
6
6
  var saas = require('@fayz-ai/saas');
@@ -13,6 +13,14 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
13
 
14
14
  var React2__default = /*#__PURE__*/_interopDefault(React2);
15
15
 
16
+ // src/data/tables.ts
17
+ var T = {
18
+ documents: "plg_forms_documents",
19
+ templates: "plg_forms_templates",
20
+ documentFiles: "plg_forms_document_files",
21
+ categories: "plg_forms_categories"
22
+ };
23
+
16
24
  // src/data/mock.ts
17
25
  function uuid() {
18
26
  return crypto.randomUUID();
@@ -176,14 +184,11 @@ function createMockFormsProvider() {
176
184
  }
177
185
  };
178
186
  }
179
-
180
- // src/lib/tenant.ts
181
- var currentTenantId;
182
187
  function getFormsTenantId() {
183
- return currentTenantId;
188
+ return core.getActiveTenantId();
184
189
  }
185
190
  function getTenantId() {
186
- return core.getActiveTenantId() ?? "";
191
+ return getFormsTenantId() ?? core.getActiveTenantId() ?? "";
187
192
  }
188
193
  function getClient() {
189
194
  const supabase = core.getSupabaseClientOptional();
@@ -261,7 +266,7 @@ function createSupabaseFormsProvider() {
261
266
  // ── Templates ──────────────────────────────────────────
262
267
  async getTemplates(query) {
263
268
  const db = getClient();
264
- let qb = db.from("frm_templates").select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_current", true).eq("is_deleted", false);
269
+ let qb = db.from(T.templates).select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_current", true).eq("is_deleted", false).eq("is_active", true);
265
270
  if (query.category) qb = qb.eq("category", query.category);
266
271
  if (query.search) qb = qb.ilike("name", `%${query.search}%`);
267
272
  const page = query.page ?? 1;
@@ -273,14 +278,14 @@ function createSupabaseFormsProvider() {
273
278
  },
274
279
  async getTemplateById(id) {
275
280
  const db = getClient();
276
- const { data, error } = await db.from("frm_templates").select("*").eq("id", id).eq("is_deleted", false).single();
281
+ const { data, error } = await db.from(T.templates).select("*").eq("id", id).eq("is_deleted", false).single();
277
282
  if (error || !data) return null;
278
283
  return mapTemplate(data);
279
284
  },
280
285
  async createTemplate(input) {
281
286
  const db = getClient();
282
287
  const tenantId = getTenantId();
283
- const { data, error } = await db.from("frm_templates").insert({
288
+ const { data, error } = await db.from(T.templates).insert({
284
289
  tenant_id: tenantId,
285
290
  name: input.name,
286
291
  description: input.description,
@@ -296,12 +301,12 @@ function createSupabaseFormsProvider() {
296
301
  async updateTemplate(id, input) {
297
302
  const db = getClient();
298
303
  const tenantId = getTenantId();
299
- const { count } = await db.from("frm_documents").select("document_id", { count: "exact", head: true }).eq("template_id", id);
304
+ const { count } = await db.from(T.documents).select("document_id", { count: "exact", head: true }).eq("template_id", id);
300
305
  const hasDocuments = (count ?? 0) > 0;
301
306
  if (hasDocuments && input.schema) {
302
307
  const existing = await this.getTemplateById(id);
303
308
  if (!existing) throw new Error("Template not found");
304
- const { data: newRow, error: insertError } = await db.from("frm_templates").insert({
309
+ const { data: newRow, error: insertError } = await db.from(T.templates).insert({
305
310
  tenant_id: tenantId,
306
311
  name: input.name ?? existing.name,
307
312
  description: input.description ?? existing.description,
@@ -316,7 +321,7 @@ function createSupabaseFormsProvider() {
316
321
  parent_id: existing.parentId ?? existing.id
317
322
  }).select().single();
318
323
  if (insertError) throw new Error(insertError.message);
319
- await db.from("frm_templates").update({ is_current: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
324
+ await db.from(T.templates).update({ is_current: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
320
325
  return mapTemplate(newRow);
321
326
  }
322
327
  const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
@@ -328,16 +333,16 @@ function createSupabaseFormsProvider() {
328
333
  if (input.tags !== void 0) updates.tags = input.tags;
329
334
  if (input.metadata !== void 0) updates.metadata = input.metadata;
330
335
  if (input.isActive !== void 0) updates.is_active = input.isActive;
331
- const { data, error } = await db.from("frm_templates").update(updates).eq("id", id).select().single();
336
+ const { data, error } = await db.from(T.templates).update(updates).eq("id", id).select().single();
332
337
  if (error) throw new Error(error.message);
333
338
  return mapTemplate(data);
334
339
  },
335
340
  async deleteTemplate(id) {
336
341
  const db = getClient();
337
- const { error } = await db.from("frm_templates").update({ is_deleted: true, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
342
+ const { error } = await db.from(T.templates).update({ is_deleted: true, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
338
343
  if (error) throw new Error(error.message);
339
344
  },
340
- // ── Documents (saas_core.documents + frm_documents extension) ──
345
+ // ── Documents (public.documents + plg_forms_documents extension) ──
341
346
  async getDocuments(query) {
342
347
  const db = getClient();
343
348
  let qb = db.from("v_documents").select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_active", true);
@@ -364,7 +369,7 @@ function createSupabaseFormsProvider() {
364
369
  async createDocument(input) {
365
370
  const tenantId = getTenantId();
366
371
  const isForm = !!input.templateId;
367
- const { data: coreDoc, error: coreError } = await getClient().from("frm_core_documents").insert({
372
+ const { data: coreDoc, error: coreError } = await getClient().from("documents").insert({
368
373
  tenant_id: tenantId,
369
374
  kind: input.kind ?? (isForm ? "form" : "attachment"),
370
375
  person_id: input.personId,
@@ -379,14 +384,14 @@ function createSupabaseFormsProvider() {
379
384
  if (coreError) throw new Error(coreError.message);
380
385
  if (isForm && input.templateId) {
381
386
  const db = getClient();
382
- const { error: extError } = await db.from("frm_documents").insert({
387
+ const { error: extError } = await db.from(T.documents).insert({
383
388
  document_id: coreDoc.id,
384
389
  tenant_id: tenantId,
385
390
  template_id: input.templateId,
386
391
  data: input.data ?? {}
387
392
  });
388
393
  if (extError) {
389
- await getClient().from("frm_core_documents").delete().eq("id", coreDoc.id);
394
+ await getClient().from("documents").delete().eq("id", coreDoc.id);
390
395
  throw new Error(extError.message);
391
396
  }
392
397
  }
@@ -399,23 +404,23 @@ function createSupabaseFormsProvider() {
399
404
  if (input.status !== void 0) coreUpdates.status = input.status;
400
405
  if (input.title !== void 0) coreUpdates.title = input.title;
401
406
  if (input.notes !== void 0) coreUpdates.notes = input.notes;
402
- const { error: coreError } = await getClient().from("frm_core_documents").update(coreUpdates).eq("id", id);
407
+ const { error: coreError } = await getClient().from("documents").update(coreUpdates).eq("id", id);
403
408
  if (coreError) throw new Error(coreError.message);
404
409
  if (input.data !== void 0) {
405
410
  const db = getClient();
406
- await db.from("frm_documents").update({ data: input.data, updated_at: now2 }).eq("document_id", id);
411
+ await db.from(T.documents).update({ data: input.data, updated_at: now2 }).eq("document_id", id);
407
412
  }
408
413
  const doc = await this.getDocumentById(id);
409
414
  return doc;
410
415
  },
411
416
  async deleteDocument(id) {
412
- const { error } = await getClient().from("frm_core_documents").update({ is_active: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
417
+ const { error } = await getClient().from("documents").update({ is_active: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
413
418
  if (error) throw new Error(error.message);
414
419
  },
415
420
  // ── Files ──────────────────────────────────────────────
416
421
  async getDocumentFiles(documentId) {
417
422
  const db = getClient();
418
- const { data, error } = await db.from("frm_document_files").select("*").eq("document_id", documentId).order("sort_order");
423
+ const { data, error } = await db.from(T.documentFiles).select("*").eq("document_id", documentId).order("sort_order");
419
424
  if (error) throw new Error(error.message);
420
425
  return (data ?? []).map(mapFile);
421
426
  },
@@ -427,7 +432,7 @@ function createSupabaseFormsProvider() {
427
432
  const { error: uploadError } = await db.storage.from("documents").upload(storagePath, file, { contentType: file.type });
428
433
  if (uploadError) throw new Error(uploadError.message);
429
434
  const { data: urlData } = db.storage.from("documents").getPublicUrl(storagePath);
430
- const { data, error } = await db.from("frm_document_files").insert({
435
+ const { data, error } = await db.from(T.documentFiles).insert({
431
436
  tenant_id: tenantId,
432
437
  document_id: documentId,
433
438
  field_key: fieldKey,
@@ -441,7 +446,7 @@ function createSupabaseFormsProvider() {
441
446
  },
442
447
  async deleteFile(fileId) {
443
448
  const db = getClient();
444
- const { error } = await db.from("frm_document_files").delete().eq("id", fileId);
449
+ const { error } = await db.from(T.documentFiles).delete().eq("id", fileId);
445
450
  if (error) throw new Error(error.message);
446
451
  }
447
452
  };
@@ -567,6 +572,7 @@ var en = {
567
572
  "customForms.noTemplates": "No form templates yet",
568
573
  "customForms.noTemplatesDescription": "Create your first custom form to start collecting structured data.",
569
574
  "customForms.deleteTemplateConfirm": "Delete this form template? Existing documents will be preserved.",
575
+ "customForms.archiveTemplateConfirm": "Archive this form template? It leaves the list and the document picker, but is kept and existing documents are preserved.",
570
576
  // Categories
571
577
  "customForms.category.anamnesis": "Anamnesis",
572
578
  "customForms.category.evolution": "Evolution Note",
@@ -588,6 +594,21 @@ var en = {
588
594
  "customForms.builder.addOption": "Add option",
589
595
  "customForms.builder.colSpan": "Column span",
590
596
  "customForms.builder.deleteField": "Remove field",
597
+ "customForms.builder.mapToPerson": "Auto-fill from record",
598
+ "customForms.builder.mapNone": "Don't auto-fill",
599
+ "customForms.builder.mapHint": "When creating the document from a person's profile, this field is pre-filled with the chosen value (still editable).",
600
+ "customForms.preview.editor": "Editor",
601
+ "customForms.preview.print": "Print",
602
+ // Person field mappings (auto-fill)
603
+ "customForms.personField.name": "Name",
604
+ "customForms.personField.documentNumber": "ID / Document",
605
+ "customForms.personField.email": "Email",
606
+ "customForms.personField.phone": "Phone",
607
+ "customForms.personField.dateOfBirth": "Date of Birth",
608
+ "customForms.personField.guardianName": "Guardian",
609
+ "customForms.personField.address": "Address",
610
+ "customForms.personField.city": "City",
611
+ "customForms.personField.state": "State",
591
612
  // Field types
592
613
  "customForms.fieldType.title": "Title",
593
614
  "customForms.fieldType.text": "Simple Text",
@@ -611,6 +632,9 @@ var en = {
611
632
  "customForms.saveAsDraft": "Save as Draft",
612
633
  "customForms.saveAndComplete": "Save & Complete",
613
634
  "customForms.deleteDocumentConfirm": "Delete this document?",
635
+ "customForms.archive": "Archive",
636
+ "customForms.archiveDocumentConfirm": "Archive this document? It leaves the active list but is kept on file.",
637
+ "customForms.printPdf": "Print / PDF",
614
638
  // Registry
615
639
  "registry.form-categories": "Categories",
616
640
  "registry.form-categories.description": "Categories for organizing form templates",
@@ -637,6 +661,7 @@ var ptBR = {
637
661
  "customForms.noTemplates": "Nenhum modelo criado",
638
662
  "customForms.noTemplatesDescription": "Crie seu primeiro formul\xE1rio personalizado para come\xE7ar a coletar dados estruturados.",
639
663
  "customForms.deleteTemplateConfirm": "Excluir este modelo de formul\xE1rio? Documentos existentes ser\xE3o preservados.",
664
+ "customForms.archiveTemplateConfirm": "Arquivar este modelo? Ele sai da lista e do seletor de documentos, mas fica guardado e os documentos existentes s\xE3o preservados.",
640
665
  // Categories
641
666
  "customForms.category.anamnesis": "Anamnese",
642
667
  "customForms.category.evolution": "Evolu\xE7\xE3o",
@@ -658,6 +683,21 @@ var ptBR = {
658
683
  "customForms.builder.addOption": "Adicionar op\xE7\xE3o",
659
684
  "customForms.builder.colSpan": "Largura em colunas",
660
685
  "customForms.builder.deleteField": "Remover campo",
686
+ "customForms.builder.mapToPerson": "Preencher com dado da ficha",
687
+ "customForms.builder.mapNone": "N\xE3o preencher",
688
+ "customForms.builder.mapHint": "Ao criar o documento na ficha da pessoa, este campo j\xE1 vem preenchido com o dado escolhido (edit\xE1vel).",
689
+ "customForms.preview.editor": "Editor",
690
+ "customForms.preview.print": "Impress\xE3o",
691
+ // Person field mappings (auto-fill)
692
+ "customForms.personField.name": "Nome",
693
+ "customForms.personField.documentNumber": "CPF / Documento",
694
+ "customForms.personField.email": "E-mail",
695
+ "customForms.personField.phone": "Telefone",
696
+ "customForms.personField.dateOfBirth": "Data de Nascimento",
697
+ "customForms.personField.guardianName": "Respons\xE1vel",
698
+ "customForms.personField.address": "Endere\xE7o",
699
+ "customForms.personField.city": "Cidade",
700
+ "customForms.personField.state": "Estado",
661
701
  // Field types
662
702
  "customForms.fieldType.title": "T\xEDtulo",
663
703
  "customForms.fieldType.text": "Texto Simples",
@@ -681,6 +721,9 @@ var ptBR = {
681
721
  "customForms.saveAsDraft": "Salvar Rascunho",
682
722
  "customForms.saveAndComplete": "Salvar e Finalizar",
683
723
  "customForms.deleteDocumentConfirm": "Excluir este documento?",
724
+ "customForms.archive": "Arquivar",
725
+ "customForms.archiveDocumentConfirm": "Arquivar este documento? Ele sai da lista ativa mas fica guardado.",
726
+ "customForms.printPdf": "Imprimir / PDF",
684
727
  // Registry
685
728
  "registry.form-categories": "Categorias",
686
729
  "registry.form-categories.description": "Categorias para organizar modelos de formul\xE1rios",
@@ -726,12 +769,17 @@ function TemplateListView({ store, config, onEdit, onNew }) {
726
769
  const loading = store((s) => s.templatesLoading);
727
770
  const orgId = getFormsTenantId();
728
771
  React2.useEffect(() => {
729
- return;
772
+ if (!orgId) return;
773
+ const timer = setTimeout(() => {
774
+ store.getState().fetchTemplates({ search: search || void 0 });
775
+ }, 50);
776
+ return () => clearTimeout(timer);
730
777
  }, [orgId, search]);
731
- const handleDelete = React2.useCallback(async (template) => {
732
- if (!window.confirm(t("customForms.deleteTemplateConfirm"))) return;
733
- await store.getState().deleteTemplate(template.id);
778
+ const handleArchive = React2.useCallback(async (template) => {
779
+ if (!window.confirm(t("customForms.archiveTemplateConfirm"))) return;
780
+ await store.getState().updateTemplate(template.id, { isActive: false });
734
781
  }, [store, t]);
782
+ const visibleTemplates = templates.filter((tpl) => tpl.isActive !== false);
735
783
  const categoryLabel = (cat) => t(`customForms.category.${cat}`);
736
784
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
737
785
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
@@ -747,21 +795,21 @@ function TemplateListView({ store, config, onEdit, onNew }) {
747
795
  }
748
796
  )
749
797
  ] }),
750
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: onNew, size: "sm", className: "h-9", children: [
798
+ /* @__PURE__ */ jsxRuntime.jsx(saas.PermissionGate, { feature: "custom_forms", action: "create", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: onNew, size: "sm", className: "h-9", children: [
751
799
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5 mr-1.5" }),
752
800
  t("customForms.newTemplate")
753
- ] })
801
+ ] }) })
754
802
  ] }),
755
- !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: [
803
+ !loading && visibleTemplates.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: [
756
804
  /* @__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" }) }),
757
805
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: t("customForms.noTemplates") }),
758
806
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-1 max-w-sm", children: t("customForms.noTemplatesDescription") }),
759
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: onNew, size: "sm", className: "mt-4", children: [
807
+ /* @__PURE__ */ jsxRuntime.jsx(saas.PermissionGate, { feature: "custom_forms", action: "create", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: onNew, size: "sm", className: "mt-4", children: [
760
808
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5 mr-1.5" }),
761
809
  t("customForms.newTemplate")
762
- ] })
810
+ ] }) })
763
811
  ] }) }),
764
- 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(
812
+ visibleTemplates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: visibleTemplates.map((template) => /* @__PURE__ */ jsxRuntime.jsx(
765
813
  ui.Card,
766
814
  {
767
815
  className: "group cursor-pointer hover:shadow-md transition-shadow",
@@ -789,10 +837,11 @@ function TemplateListView({ store, config, onEdit, onNew }) {
789
837
  {
790
838
  onClick: (e) => {
791
839
  e.stopPropagation();
792
- handleDelete(template);
840
+ handleArchive(template);
793
841
  },
794
- className: "p-1.5 rounded-md hover:bg-destructive/10",
795
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5 text-destructive" })
842
+ className: "p-1.5 rounded-md hover:bg-muted",
843
+ title: t("customForms.archive"),
844
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Archive, { className: "h-3.5 w-3.5 text-muted-foreground" })
796
845
  }
797
846
  )
798
847
  ] })
@@ -823,29 +872,29 @@ function TemplateListView({ store, config, onEdit, onNew }) {
823
872
  ] }) }, i)) })
824
873
  ] });
825
874
  }
875
+ var ROUTE_BASE = "/settings/custom_forms";
876
+ function readBuilderIdFromHash() {
877
+ const hash = window.location.hash.slice(1) || "/";
878
+ const m = hash.match(/\/settings\/custom_forms\/templates\/([^/]+)/);
879
+ if (!m) return null;
880
+ return m[1] === "new" ? "__new__" : m[1];
881
+ }
826
882
  function CustomFormsSettingsTab({ config, provider, store }) {
827
- const [builderTemplateId, setBuilderTemplateId] = React2.useState(null);
883
+ const [builderTemplateId, setBuilderTemplateId] = React2.useState(readBuilderIdFromHash);
828
884
  const handleNew = React2.useCallback(() => {
829
- setBuilderTemplateId("__new__");
885
+ window.location.hash = `#${ROUTE_BASE}/templates/new`;
830
886
  }, []);
831
887
  const handleEdit = React2.useCallback((template) => {
832
- setBuilderTemplateId(template.id);
888
+ window.location.hash = `#${ROUTE_BASE}/templates/${template.id}`;
833
889
  }, []);
834
890
  const handleBackFromBuilder = React2.useCallback(() => {
835
- setBuilderTemplateId(null);
891
+ window.location.hash = `#${ROUTE_BASE}/forms`;
836
892
  store.getState().fetchTemplates();
837
893
  }, [store]);
838
894
  React2__default.default.useEffect(() => {
839
- function checkHash() {
840
- const hash = window.location.hash.slice(1) || "/";
841
- const builderMatch = hash.match(/\/settings\/custom_forms\/templates\/(.+)/);
842
- if (builderMatch && builderMatch[1] !== "new") {
843
- setBuilderTemplateId(builderMatch[1]);
844
- } else if (builderMatch && builderMatch[1] === "new") {
845
- setBuilderTemplateId("__new__");
846
- }
847
- }
848
- checkHash();
895
+ const sync = () => setBuilderTemplateId(readBuilderIdFromHash());
896
+ window.addEventListener("hashchange", sync);
897
+ return () => window.removeEventListener("hashchange", sync);
849
898
  }, []);
850
899
  return builderTemplateId ? /* @__PURE__ */ jsxRuntime.jsx(
851
900
  BuilderWrapper,
@@ -874,7 +923,7 @@ function BuilderWrapper({
874
923
  onBack
875
924
  }) {
876
925
  const FormBuilderLazy = React2__default.default.lazy(
877
- () => import('./FormBuilder-4VKYVZAC.cjs').then((m) => ({ default: m.FormBuilder }))
926
+ () => import('./FormBuilder-YB75LEEH.cjs').then((m) => ({ default: m.FormBuilder }))
878
927
  );
879
928
  return /* @__PURE__ */ jsxRuntime.jsx(
880
929
  React2__default.default.Suspense,
@@ -940,7 +989,7 @@ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
940
989
  setLoading(true);
941
990
  try {
942
991
  const result = await provider.getDocuments({ personId });
943
- setDocuments(result.data);
992
+ setDocuments(result.data.filter((d) => d.status !== "archived"));
944
993
  } finally {
945
994
  setLoading(false);
946
995
  }
@@ -953,9 +1002,9 @@ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
953
1002
  const unsub = store.subscribe(() => fetchDocs());
954
1003
  return unsub;
955
1004
  }, [store, fetchDocs]);
956
- const handleDelete = React2.useCallback(async (doc) => {
957
- if (!window.confirm(t("customForms.deleteDocumentConfirm"))) return;
958
- await provider.deleteDocument(doc.id);
1005
+ const handleArchive = React2.useCallback(async (doc) => {
1006
+ if (!window.confirm(t("customForms.archiveDocumentConfirm"))) return;
1007
+ await provider.updateDocument(doc.id, { status: "archived" });
959
1008
  setDocuments((prev) => prev.filter((d) => d.id !== doc.id));
960
1009
  }, [provider, t]);
961
1010
  const handleDragEnter = React2.useCallback((e) => {
@@ -1053,10 +1102,11 @@ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
1053
1102
  {
1054
1103
  onClick: (e) => {
1055
1104
  e.stopPropagation();
1056
- handleDelete(doc);
1105
+ handleArchive(doc);
1057
1106
  },
1058
- className: "p-1.5 rounded-md hover:bg-destructive/10",
1059
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5 text-destructive" })
1107
+ className: "p-1.5 rounded-md hover:bg-muted",
1108
+ title: t("customForms.archive"),
1109
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Archive, { className: "h-3.5 w-3.5 text-muted-foreground" })
1060
1110
  }
1061
1111
  )
1062
1112
  ] })
@@ -1079,23 +1129,34 @@ function DocumentFormDialog({
1079
1129
  existingDocument,
1080
1130
  personId,
1081
1131
  personName,
1132
+ person,
1082
1133
  provider,
1083
1134
  store,
1084
1135
  onSaved,
1085
1136
  onBack
1086
1137
  }) {
1087
1138
  const t = core.useTranslation();
1139
+ const guardDocuments = saas.useLimitGuard("documents_month");
1088
1140
  const [template, setTemplate] = React2.useState(initialTemplate);
1089
- const [data, setData] = React2.useState(existingDocument?.data ?? {});
1141
+ const [data, setData] = React2.useState(
1142
+ () => existingDocument ? existingDocument.data : chunkQM2AF3DZ_cjs.prefillFromPerson(initialTemplate?.schema, person)
1143
+ );
1090
1144
  const [saving, setSaving] = React2.useState(false);
1091
1145
  React2.useEffect(() => {
1092
1146
  if (existingDocument && !template) {
1093
1147
  provider.getTemplateById(existingDocument.templateId).then(setTemplate);
1094
1148
  }
1095
1149
  }, [existingDocument, template, provider]);
1150
+ const prefilled = React2__default.default.useRef(!!initialTemplate || !!existingDocument);
1151
+ React2.useEffect(() => {
1152
+ if (prefilled.current || !template || existingDocument) return;
1153
+ prefilled.current = true;
1154
+ setData((prev) => chunkQM2AF3DZ_cjs.prefillFromPerson(template.schema, person, prev));
1155
+ }, [template, existingDocument, person]);
1096
1156
  const handleSave = React2.useCallback(
1097
1157
  async (status) => {
1098
1158
  if (!template) return;
1159
+ if (!existingDocument && await guardDocuments() === "blocked") return;
1099
1160
  setSaving(true);
1100
1161
  try {
1101
1162
  const autoTitle = generateTitle(template.name, personName);
@@ -1109,18 +1170,21 @@ function DocumentFormDialog({
1109
1170
  data,
1110
1171
  status
1111
1172
  });
1173
+ saas.invalidateLimit("documents_month");
1112
1174
  }
1113
1175
  onSaved();
1114
1176
  } finally {
1115
1177
  setSaving(false);
1116
1178
  }
1117
1179
  },
1118
- [template, existingDocument, data, personId, personName, store, onSaved]
1180
+ [template, existingDocument, data, personId, personName, store, onSaved, guardDocuments]
1119
1181
  );
1120
1182
  const snapshot = React2__default.default.useRef(null);
1121
1183
  React2.useEffect(() => {
1122
- if (template && snapshot.current === null) snapshot.current = JSON.stringify(data);
1123
- }, [template]);
1184
+ if (template && snapshot.current === null) {
1185
+ snapshot.current = JSON.stringify(existingDocument?.data ?? {});
1186
+ }
1187
+ }, [template, existingDocument]);
1124
1188
  const dirty = snapshot.current !== null && JSON.stringify(data) !== snapshot.current;
1125
1189
  ui.useSaveBar({
1126
1190
  dirty,
@@ -1156,17 +1220,13 @@ function DocumentFormDialog({
1156
1220
  ] })
1157
1221
  ] }),
1158
1222
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1159
- chunkXLUULIVL_cjs.FormRenderer,
1223
+ chunkQM2AF3DZ_cjs.FormRenderer,
1160
1224
  {
1161
1225
  schema: template.schema,
1162
1226
  data,
1163
1227
  onChange: setData
1164
1228
  }
1165
- ) }),
1166
- /* @__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: [
1167
- 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" }),
1168
- t("customForms.saveAsDraft")
1169
- ] }) })
1229
+ ) })
1170
1230
  ] });
1171
1231
  }
1172
1232
  var STATUS_COLORS2 = {
@@ -1203,13 +1263,26 @@ function FormViewer({ document: doc, provider, onBack, onEdit }) {
1203
1263
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: new Date(doc.createdAt).toLocaleDateString() })
1204
1264
  ] }),
1205
1265
  /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "secondary", className: `text-[10px] ${STATUS_COLORS2[doc.status] ?? ""}`, children: t(`customForms.status.${doc.status}`) }),
1266
+ /* @__PURE__ */ jsxRuntime.jsxs(
1267
+ ui.Button,
1268
+ {
1269
+ variant: "outline",
1270
+ size: "sm",
1271
+ className: "h-8",
1272
+ onClick: () => chunkQM2AF3DZ_cjs.printDocument(template, doc),
1273
+ children: [
1274
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Printer, { className: "h-3 w-3 mr-1" }),
1275
+ t("customForms.printPdf")
1276
+ ]
1277
+ }
1278
+ ),
1206
1279
  onEdit && doc.status === "draft" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => onEdit(doc), children: [
1207
1280
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-3 w-3 mr-1" }),
1208
1281
  t("common.edit")
1209
1282
  ] })
1210
1283
  ] }),
1211
1284
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1212
- chunkXLUULIVL_cjs.FormRenderer,
1285
+ chunkQM2AF3DZ_cjs.FormRenderer,
1213
1286
  {
1214
1287
  schema: template.schema,
1215
1288
  data: doc.data,
@@ -1286,6 +1359,7 @@ function AddDocumentDropdown({ personId, onSelect }) {
1286
1359
  }
1287
1360
  function PersonDocumentsWidget({ item, config, provider, store }) {
1288
1361
  const t = core.useTranslation();
1362
+ const guardDocuments = saas.useLimitGuard("documents_month");
1289
1363
  const [view, setView] = React2.useState({ type: "list" });
1290
1364
  const fileInputRef = React2.useRef(null);
1291
1365
  const [pendingFileAccept, setPendingFileAccept] = React2.useState("*/*");
@@ -1310,6 +1384,7 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
1310
1384
  }
1311
1385
  }, [provider]);
1312
1386
  const handleFiles = React2.useCallback(async (files) => {
1387
+ if (await guardDocuments(files.length) === "blocked") return;
1313
1388
  for (const file of files) {
1314
1389
  const isImage = file.type.startsWith("image/");
1315
1390
  await store.getState().createDocument({
@@ -1322,7 +1397,8 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
1322
1397
  status: "completed"
1323
1398
  });
1324
1399
  }
1325
- }, [store, item.id]);
1400
+ saas.invalidateLimit("documents_month");
1401
+ }, [store, item.id, guardDocuments]);
1326
1402
  const handleFileInputChange = React2.useCallback((e) => {
1327
1403
  const files = Array.from(e.target.files ?? []);
1328
1404
  if (files.length > 0) handleFiles(files);
@@ -1347,6 +1423,7 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
1347
1423
  template: view.template,
1348
1424
  personId: item.id,
1349
1425
  personName: item.name,
1426
+ person: item,
1350
1427
  provider,
1351
1428
  store,
1352
1429
  onSaved: handleSaved,
@@ -1378,6 +1455,7 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
1378
1455
  existingDocument: view.document,
1379
1456
  personId: item.id,
1380
1457
  personName: item.name,
1458
+ person: item,
1381
1459
  provider,
1382
1460
  store,
1383
1461
  onSaved: handleSaved,
@@ -1499,7 +1577,7 @@ function createCustomFormsPlugin(options) {
1499
1577
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true, inlineToggle: true }
1500
1578
  ],
1501
1579
  data: {
1502
- table: "frm_categories",
1580
+ table: T.categories,
1503
1581
  tenantScoped: true
1504
1582
  }
1505
1583
  },
@@ -1518,6 +1596,9 @@ function createCustomFormsPlugin(options) {
1518
1596
  const SettingsComponent = () => React2__default.default.createElement(saas.PluginSettingsPanel, {
1519
1597
  title: config.labels.settingsLabel,
1520
1598
  subtitle: config.labels.settingsSubtitle,
1599
+ // The settings shell already renders the section label as the page H1;
1600
+ // hide the panel's own duplicate title (keeps the subtitle).
1601
+ hideTitle: true,
1521
1602
  customTabs: [
1522
1603
  {
1523
1604
  id: "forms",
@@ -1545,6 +1626,10 @@ function createCustomFormsPlugin(options) {
1545
1626
  declaredFeatures: [
1546
1627
  { id: "custom_forms", label: config.labels.pageTitle, group: config.labels.pageTitle }
1547
1628
  ],
1629
+ declaredLimits: [
1630
+ { key: "form_templates", label: "Form templates", table: "plg_forms_templates" },
1631
+ { key: "documents_month", label: "Documents / month", table: "documents", period: "month" }
1632
+ ],
1548
1633
  navigation: [],
1549
1634
  routes: [],
1550
1635
  settings: [
@@ -1609,6 +1694,17 @@ function createCustomFormsPlugin(options) {
1609
1694
  { label: "Show this client's documents" }
1610
1695
  ],
1611
1696
  permission: { feature: "custom_forms", action: "read" }
1697
+ },
1698
+ // Server-plane write: builds/edits a form template via the pool RPC, so
1699
+ // the assistant can montar formulários from any page or channel.
1700
+ chunkQM2AF3DZ_cjs.createOrUpdateFormTool
1701
+ ],
1702
+ declaredRpcs: [
1703
+ {
1704
+ name: "agent_forms_upsert_template",
1705
+ kind: "write",
1706
+ description: "Guarded form-template upsert: agent_guard (role\u2192plan\u2192form_templates cap), grid layout computed server-side, create or edit by id, audited.",
1707
+ audits: true
1612
1708
  }
1613
1709
  ],
1614
1710
  locales: customFormsLocales