@fayz-ai/plugin-forms 0.8.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.
- package/dist/{FormBuilder-KDANEL6Z.js → FormBuilder-7SWZQKFN.js} +130 -23
- package/dist/FormBuilder-7SWZQKFN.js.map +1 -0
- package/dist/{FormBuilder-4VKYVZAC.cjs → FormBuilder-YB75LEEH.cjs} +129 -22
- package/dist/FormBuilder-YB75LEEH.cjs.map +1 -0
- package/dist/agent-tools.d.ts +4 -0
- package/dist/agent-tools.d.ts.map +1 -0
- package/dist/chunk-QM2AF3DZ.cjs +446 -0
- package/dist/chunk-QM2AF3DZ.cjs.map +1 -0
- package/dist/chunk-W2ZNJGQC.js +438 -0
- package/dist/chunk-W2ZNJGQC.js.map +1 -0
- package/dist/components/DocumentFormDialog.d.ts +3 -1
- package/dist/components/DocumentFormDialog.d.ts.map +1 -1
- package/dist/components/DocumentList.d.ts.map +1 -1
- package/dist/components/FieldConfigDialog.d.ts.map +1 -1
- package/dist/components/FormBuilder.d.ts.map +1 -1
- package/dist/components/FormViewer.d.ts.map +1 -1
- package/dist/components/PersonDocumentsWidget.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.cjs +142 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +142 -54
- package/dist/index.js.map +1 -1
- package/dist/lib/person-fields.d.ts +16 -0
- package/dist/lib/person-fields.d.ts.map +1 -0
- package/dist/lib/print.d.ts +17 -0
- package/dist/lib/print.d.ts.map +1 -0
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -1
- package/dist/views/TemplateListView.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/agent-tools.ts +73 -0
- package/src/components/DocumentFormDialog.tsx +37 -16
- package/src/components/DocumentList.tsx +12 -8
- package/src/components/FieldConfigDialog.tsx +40 -0
- package/src/components/FormBuilder.tsx +104 -22
- package/src/components/FormViewer.tsx +11 -1
- package/src/components/PersonDocumentsWidget.tsx +10 -1
- package/src/data/supabase.ts +2 -0
- package/src/index.ts +21 -0
- package/src/lib/person-fields.ts +67 -0
- package/src/lib/print.ts +207 -0
- package/src/lib/tenant.ts +6 -2
- package/src/locales/en.ts +20 -0
- package/src/locales/pt-BR.ts +20 -0
- package/src/migrations/003_agent_rpcs.sql +174 -0
- package/src/migrations/index.ts +178 -1
- package/src/types.ts +14 -0
- package/src/views/CustomFormsSettingsTab.tsx +23 -16
- package/src/views/TemplateListView.tsx +29 -18
- package/dist/FormBuilder-4VKYVZAC.cjs.map +0 -1
- package/dist/FormBuilder-KDANEL6Z.js.map +0 -1
- package/dist/chunk-32I43T37.js +0 -198
- package/dist/chunk-32I43T37.js.map +0 -1
- package/dist/chunk-XLUULIVL.cjs +0 -200
- 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
|
|
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');
|
|
@@ -184,14 +184,11 @@ function createMockFormsProvider() {
|
|
|
184
184
|
}
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
|
-
|
|
188
|
-
// src/lib/tenant.ts
|
|
189
|
-
var currentTenantId;
|
|
190
187
|
function getFormsTenantId() {
|
|
191
|
-
return
|
|
188
|
+
return core.getActiveTenantId();
|
|
192
189
|
}
|
|
193
190
|
function getTenantId() {
|
|
194
|
-
return core.getActiveTenantId() ?? "";
|
|
191
|
+
return getFormsTenantId() ?? core.getActiveTenantId() ?? "";
|
|
195
192
|
}
|
|
196
193
|
function getClient() {
|
|
197
194
|
const supabase = core.getSupabaseClientOptional();
|
|
@@ -269,7 +266,7 @@ function createSupabaseFormsProvider() {
|
|
|
269
266
|
// ── Templates ──────────────────────────────────────────
|
|
270
267
|
async getTemplates(query) {
|
|
271
268
|
const db = getClient();
|
|
272
|
-
let qb = db.from(T.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);
|
|
273
270
|
if (query.category) qb = qb.eq("category", query.category);
|
|
274
271
|
if (query.search) qb = qb.ilike("name", `%${query.search}%`);
|
|
275
272
|
const page = query.page ?? 1;
|
|
@@ -575,6 +572,7 @@ var en = {
|
|
|
575
572
|
"customForms.noTemplates": "No form templates yet",
|
|
576
573
|
"customForms.noTemplatesDescription": "Create your first custom form to start collecting structured data.",
|
|
577
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.",
|
|
578
576
|
// Categories
|
|
579
577
|
"customForms.category.anamnesis": "Anamnesis",
|
|
580
578
|
"customForms.category.evolution": "Evolution Note",
|
|
@@ -596,6 +594,21 @@ var en = {
|
|
|
596
594
|
"customForms.builder.addOption": "Add option",
|
|
597
595
|
"customForms.builder.colSpan": "Column span",
|
|
598
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",
|
|
599
612
|
// Field types
|
|
600
613
|
"customForms.fieldType.title": "Title",
|
|
601
614
|
"customForms.fieldType.text": "Simple Text",
|
|
@@ -619,6 +632,9 @@ var en = {
|
|
|
619
632
|
"customForms.saveAsDraft": "Save as Draft",
|
|
620
633
|
"customForms.saveAndComplete": "Save & Complete",
|
|
621
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",
|
|
622
638
|
// Registry
|
|
623
639
|
"registry.form-categories": "Categories",
|
|
624
640
|
"registry.form-categories.description": "Categories for organizing form templates",
|
|
@@ -645,6 +661,7 @@ var ptBR = {
|
|
|
645
661
|
"customForms.noTemplates": "Nenhum modelo criado",
|
|
646
662
|
"customForms.noTemplatesDescription": "Crie seu primeiro formul\xE1rio personalizado para come\xE7ar a coletar dados estruturados.",
|
|
647
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.",
|
|
648
665
|
// Categories
|
|
649
666
|
"customForms.category.anamnesis": "Anamnese",
|
|
650
667
|
"customForms.category.evolution": "Evolu\xE7\xE3o",
|
|
@@ -666,6 +683,21 @@ var ptBR = {
|
|
|
666
683
|
"customForms.builder.addOption": "Adicionar op\xE7\xE3o",
|
|
667
684
|
"customForms.builder.colSpan": "Largura em colunas",
|
|
668
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",
|
|
669
701
|
// Field types
|
|
670
702
|
"customForms.fieldType.title": "T\xEDtulo",
|
|
671
703
|
"customForms.fieldType.text": "Texto Simples",
|
|
@@ -689,6 +721,9 @@ var ptBR = {
|
|
|
689
721
|
"customForms.saveAsDraft": "Salvar Rascunho",
|
|
690
722
|
"customForms.saveAndComplete": "Salvar e Finalizar",
|
|
691
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",
|
|
692
727
|
// Registry
|
|
693
728
|
"registry.form-categories": "Categorias",
|
|
694
729
|
"registry.form-categories.description": "Categorias para organizar modelos de formul\xE1rios",
|
|
@@ -734,12 +769,17 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
734
769
|
const loading = store((s) => s.templatesLoading);
|
|
735
770
|
const orgId = getFormsTenantId();
|
|
736
771
|
React2.useEffect(() => {
|
|
737
|
-
return;
|
|
772
|
+
if (!orgId) return;
|
|
773
|
+
const timer = setTimeout(() => {
|
|
774
|
+
store.getState().fetchTemplates({ search: search || void 0 });
|
|
775
|
+
}, 50);
|
|
776
|
+
return () => clearTimeout(timer);
|
|
738
777
|
}, [orgId, search]);
|
|
739
|
-
const
|
|
740
|
-
if (!window.confirm(t("customForms.
|
|
741
|
-
await store.getState().
|
|
778
|
+
const handleArchive = React2.useCallback(async (template) => {
|
|
779
|
+
if (!window.confirm(t("customForms.archiveTemplateConfirm"))) return;
|
|
780
|
+
await store.getState().updateTemplate(template.id, { isActive: false });
|
|
742
781
|
}, [store, t]);
|
|
782
|
+
const visibleTemplates = templates.filter((tpl) => tpl.isActive !== false);
|
|
743
783
|
const categoryLabel = (cat) => t(`customForms.category.${cat}`);
|
|
744
784
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
745
785
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
@@ -755,21 +795,21 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
755
795
|
}
|
|
756
796
|
)
|
|
757
797
|
] }),
|
|
758
|
-
/* @__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: [
|
|
759
799
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5 mr-1.5" }),
|
|
760
800
|
t("customForms.newTemplate")
|
|
761
|
-
] })
|
|
801
|
+
] }) })
|
|
762
802
|
] }),
|
|
763
|
-
!loading &&
|
|
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: [
|
|
764
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" }) }),
|
|
765
805
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: t("customForms.noTemplates") }),
|
|
766
806
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-1 max-w-sm", children: t("customForms.noTemplatesDescription") }),
|
|
767
|
-
/* @__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: [
|
|
768
808
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5 mr-1.5" }),
|
|
769
809
|
t("customForms.newTemplate")
|
|
770
|
-
] })
|
|
810
|
+
] }) })
|
|
771
811
|
] }) }),
|
|
772
|
-
|
|
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(
|
|
773
813
|
ui.Card,
|
|
774
814
|
{
|
|
775
815
|
className: "group cursor-pointer hover:shadow-md transition-shadow",
|
|
@@ -797,10 +837,11 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
797
837
|
{
|
|
798
838
|
onClick: (e) => {
|
|
799
839
|
e.stopPropagation();
|
|
800
|
-
|
|
840
|
+
handleArchive(template);
|
|
801
841
|
},
|
|
802
|
-
className: "p-1.5 rounded-md hover:bg-
|
|
803
|
-
|
|
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" })
|
|
804
845
|
}
|
|
805
846
|
)
|
|
806
847
|
] })
|
|
@@ -831,29 +872,29 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
831
872
|
] }) }, i)) })
|
|
832
873
|
] });
|
|
833
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
|
+
}
|
|
834
882
|
function CustomFormsSettingsTab({ config, provider, store }) {
|
|
835
|
-
const [builderTemplateId, setBuilderTemplateId] = React2.useState(
|
|
883
|
+
const [builderTemplateId, setBuilderTemplateId] = React2.useState(readBuilderIdFromHash);
|
|
836
884
|
const handleNew = React2.useCallback(() => {
|
|
837
|
-
|
|
885
|
+
window.location.hash = `#${ROUTE_BASE}/templates/new`;
|
|
838
886
|
}, []);
|
|
839
887
|
const handleEdit = React2.useCallback((template) => {
|
|
840
|
-
|
|
888
|
+
window.location.hash = `#${ROUTE_BASE}/templates/${template.id}`;
|
|
841
889
|
}, []);
|
|
842
890
|
const handleBackFromBuilder = React2.useCallback(() => {
|
|
843
|
-
|
|
891
|
+
window.location.hash = `#${ROUTE_BASE}/forms`;
|
|
844
892
|
store.getState().fetchTemplates();
|
|
845
893
|
}, [store]);
|
|
846
894
|
React2__default.default.useEffect(() => {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (builderMatch && builderMatch[1] !== "new") {
|
|
851
|
-
setBuilderTemplateId(builderMatch[1]);
|
|
852
|
-
} else if (builderMatch && builderMatch[1] === "new") {
|
|
853
|
-
setBuilderTemplateId("__new__");
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
checkHash();
|
|
895
|
+
const sync = () => setBuilderTemplateId(readBuilderIdFromHash());
|
|
896
|
+
window.addEventListener("hashchange", sync);
|
|
897
|
+
return () => window.removeEventListener("hashchange", sync);
|
|
857
898
|
}, []);
|
|
858
899
|
return builderTemplateId ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
859
900
|
BuilderWrapper,
|
|
@@ -882,7 +923,7 @@ function BuilderWrapper({
|
|
|
882
923
|
onBack
|
|
883
924
|
}) {
|
|
884
925
|
const FormBuilderLazy = React2__default.default.lazy(
|
|
885
|
-
() => import('./FormBuilder-
|
|
926
|
+
() => import('./FormBuilder-YB75LEEH.cjs').then((m) => ({ default: m.FormBuilder }))
|
|
886
927
|
);
|
|
887
928
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
888
929
|
React2__default.default.Suspense,
|
|
@@ -948,7 +989,7 @@ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
|
|
|
948
989
|
setLoading(true);
|
|
949
990
|
try {
|
|
950
991
|
const result = await provider.getDocuments({ personId });
|
|
951
|
-
setDocuments(result.data);
|
|
992
|
+
setDocuments(result.data.filter((d) => d.status !== "archived"));
|
|
952
993
|
} finally {
|
|
953
994
|
setLoading(false);
|
|
954
995
|
}
|
|
@@ -961,9 +1002,9 @@ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
|
|
|
961
1002
|
const unsub = store.subscribe(() => fetchDocs());
|
|
962
1003
|
return unsub;
|
|
963
1004
|
}, [store, fetchDocs]);
|
|
964
|
-
const
|
|
965
|
-
if (!window.confirm(t("customForms.
|
|
966
|
-
await provider.
|
|
1005
|
+
const handleArchive = React2.useCallback(async (doc) => {
|
|
1006
|
+
if (!window.confirm(t("customForms.archiveDocumentConfirm"))) return;
|
|
1007
|
+
await provider.updateDocument(doc.id, { status: "archived" });
|
|
967
1008
|
setDocuments((prev) => prev.filter((d) => d.id !== doc.id));
|
|
968
1009
|
}, [provider, t]);
|
|
969
1010
|
const handleDragEnter = React2.useCallback((e) => {
|
|
@@ -1061,10 +1102,11 @@ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
|
|
|
1061
1102
|
{
|
|
1062
1103
|
onClick: (e) => {
|
|
1063
1104
|
e.stopPropagation();
|
|
1064
|
-
|
|
1105
|
+
handleArchive(doc);
|
|
1065
1106
|
},
|
|
1066
|
-
className: "p-1.5 rounded-md hover:bg-
|
|
1067
|
-
|
|
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" })
|
|
1068
1110
|
}
|
|
1069
1111
|
)
|
|
1070
1112
|
] })
|
|
@@ -1087,23 +1129,34 @@ function DocumentFormDialog({
|
|
|
1087
1129
|
existingDocument,
|
|
1088
1130
|
personId,
|
|
1089
1131
|
personName,
|
|
1132
|
+
person,
|
|
1090
1133
|
provider,
|
|
1091
1134
|
store,
|
|
1092
1135
|
onSaved,
|
|
1093
1136
|
onBack
|
|
1094
1137
|
}) {
|
|
1095
1138
|
const t = core.useTranslation();
|
|
1139
|
+
const guardDocuments = saas.useLimitGuard("documents_month");
|
|
1096
1140
|
const [template, setTemplate] = React2.useState(initialTemplate);
|
|
1097
|
-
const [data, setData] = React2.useState(
|
|
1141
|
+
const [data, setData] = React2.useState(
|
|
1142
|
+
() => existingDocument ? existingDocument.data : chunkQM2AF3DZ_cjs.prefillFromPerson(initialTemplate?.schema, person)
|
|
1143
|
+
);
|
|
1098
1144
|
const [saving, setSaving] = React2.useState(false);
|
|
1099
1145
|
React2.useEffect(() => {
|
|
1100
1146
|
if (existingDocument && !template) {
|
|
1101
1147
|
provider.getTemplateById(existingDocument.templateId).then(setTemplate);
|
|
1102
1148
|
}
|
|
1103
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]);
|
|
1104
1156
|
const handleSave = React2.useCallback(
|
|
1105
1157
|
async (status) => {
|
|
1106
1158
|
if (!template) return;
|
|
1159
|
+
if (!existingDocument && await guardDocuments() === "blocked") return;
|
|
1107
1160
|
setSaving(true);
|
|
1108
1161
|
try {
|
|
1109
1162
|
const autoTitle = generateTitle(template.name, personName);
|
|
@@ -1117,18 +1170,21 @@ function DocumentFormDialog({
|
|
|
1117
1170
|
data,
|
|
1118
1171
|
status
|
|
1119
1172
|
});
|
|
1173
|
+
saas.invalidateLimit("documents_month");
|
|
1120
1174
|
}
|
|
1121
1175
|
onSaved();
|
|
1122
1176
|
} finally {
|
|
1123
1177
|
setSaving(false);
|
|
1124
1178
|
}
|
|
1125
1179
|
},
|
|
1126
|
-
[template, existingDocument, data, personId, personName, store, onSaved]
|
|
1180
|
+
[template, existingDocument, data, personId, personName, store, onSaved, guardDocuments]
|
|
1127
1181
|
);
|
|
1128
1182
|
const snapshot = React2__default.default.useRef(null);
|
|
1129
1183
|
React2.useEffect(() => {
|
|
1130
|
-
if (template && snapshot.current === null)
|
|
1131
|
-
|
|
1184
|
+
if (template && snapshot.current === null) {
|
|
1185
|
+
snapshot.current = JSON.stringify(existingDocument?.data ?? {});
|
|
1186
|
+
}
|
|
1187
|
+
}, [template, existingDocument]);
|
|
1132
1188
|
const dirty = snapshot.current !== null && JSON.stringify(data) !== snapshot.current;
|
|
1133
1189
|
ui.useSaveBar({
|
|
1134
1190
|
dirty,
|
|
@@ -1164,17 +1220,13 @@ function DocumentFormDialog({
|
|
|
1164
1220
|
] })
|
|
1165
1221
|
] }),
|
|
1166
1222
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1167
|
-
|
|
1223
|
+
chunkQM2AF3DZ_cjs.FormRenderer,
|
|
1168
1224
|
{
|
|
1169
1225
|
schema: template.schema,
|
|
1170
1226
|
data,
|
|
1171
1227
|
onChange: setData
|
|
1172
1228
|
}
|
|
1173
|
-
) })
|
|
1174
|
-
/* @__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: [
|
|
1175
|
-
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" }),
|
|
1176
|
-
t("customForms.saveAsDraft")
|
|
1177
|
-
] }) })
|
|
1229
|
+
) })
|
|
1178
1230
|
] });
|
|
1179
1231
|
}
|
|
1180
1232
|
var STATUS_COLORS2 = {
|
|
@@ -1211,13 +1263,26 @@ function FormViewer({ document: doc, provider, onBack, onEdit }) {
|
|
|
1211
1263
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: new Date(doc.createdAt).toLocaleDateString() })
|
|
1212
1264
|
] }),
|
|
1213
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
|
+
),
|
|
1214
1279
|
onEdit && doc.status === "draft" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => onEdit(doc), children: [
|
|
1215
1280
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-3 w-3 mr-1" }),
|
|
1216
1281
|
t("common.edit")
|
|
1217
1282
|
] })
|
|
1218
1283
|
] }),
|
|
1219
1284
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1220
|
-
|
|
1285
|
+
chunkQM2AF3DZ_cjs.FormRenderer,
|
|
1221
1286
|
{
|
|
1222
1287
|
schema: template.schema,
|
|
1223
1288
|
data: doc.data,
|
|
@@ -1294,6 +1359,7 @@ function AddDocumentDropdown({ personId, onSelect }) {
|
|
|
1294
1359
|
}
|
|
1295
1360
|
function PersonDocumentsWidget({ item, config, provider, store }) {
|
|
1296
1361
|
const t = core.useTranslation();
|
|
1362
|
+
const guardDocuments = saas.useLimitGuard("documents_month");
|
|
1297
1363
|
const [view, setView] = React2.useState({ type: "list" });
|
|
1298
1364
|
const fileInputRef = React2.useRef(null);
|
|
1299
1365
|
const [pendingFileAccept, setPendingFileAccept] = React2.useState("*/*");
|
|
@@ -1318,6 +1384,7 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
|
|
|
1318
1384
|
}
|
|
1319
1385
|
}, [provider]);
|
|
1320
1386
|
const handleFiles = React2.useCallback(async (files) => {
|
|
1387
|
+
if (await guardDocuments(files.length) === "blocked") return;
|
|
1321
1388
|
for (const file of files) {
|
|
1322
1389
|
const isImage = file.type.startsWith("image/");
|
|
1323
1390
|
await store.getState().createDocument({
|
|
@@ -1330,7 +1397,8 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
|
|
|
1330
1397
|
status: "completed"
|
|
1331
1398
|
});
|
|
1332
1399
|
}
|
|
1333
|
-
|
|
1400
|
+
saas.invalidateLimit("documents_month");
|
|
1401
|
+
}, [store, item.id, guardDocuments]);
|
|
1334
1402
|
const handleFileInputChange = React2.useCallback((e) => {
|
|
1335
1403
|
const files = Array.from(e.target.files ?? []);
|
|
1336
1404
|
if (files.length > 0) handleFiles(files);
|
|
@@ -1355,6 +1423,7 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
|
|
|
1355
1423
|
template: view.template,
|
|
1356
1424
|
personId: item.id,
|
|
1357
1425
|
personName: item.name,
|
|
1426
|
+
person: item,
|
|
1358
1427
|
provider,
|
|
1359
1428
|
store,
|
|
1360
1429
|
onSaved: handleSaved,
|
|
@@ -1386,6 +1455,7 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
|
|
|
1386
1455
|
existingDocument: view.document,
|
|
1387
1456
|
personId: item.id,
|
|
1388
1457
|
personName: item.name,
|
|
1458
|
+
person: item,
|
|
1389
1459
|
provider,
|
|
1390
1460
|
store,
|
|
1391
1461
|
onSaved: handleSaved,
|
|
@@ -1526,6 +1596,9 @@ function createCustomFormsPlugin(options) {
|
|
|
1526
1596
|
const SettingsComponent = () => React2__default.default.createElement(saas.PluginSettingsPanel, {
|
|
1527
1597
|
title: config.labels.settingsLabel,
|
|
1528
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,
|
|
1529
1602
|
customTabs: [
|
|
1530
1603
|
{
|
|
1531
1604
|
id: "forms",
|
|
@@ -1553,6 +1626,10 @@ function createCustomFormsPlugin(options) {
|
|
|
1553
1626
|
declaredFeatures: [
|
|
1554
1627
|
{ id: "custom_forms", label: config.labels.pageTitle, group: config.labels.pageTitle }
|
|
1555
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
|
+
],
|
|
1556
1633
|
navigation: [],
|
|
1557
1634
|
routes: [],
|
|
1558
1635
|
settings: [
|
|
@@ -1617,6 +1694,17 @@ function createCustomFormsPlugin(options) {
|
|
|
1617
1694
|
{ label: "Show this client's documents" }
|
|
1618
1695
|
],
|
|
1619
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
|
|
1620
1708
|
}
|
|
1621
1709
|
],
|
|
1622
1710
|
locales: customFormsLocales
|