@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.
- package/README.md +2 -0
- 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/data/tables.d.ts +7 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/index.cjs +168 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +168 -72
- 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 +9 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/types.d.ts +15 -1
- 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 +10 -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 +22 -19
- package/src/data/tables.ts +7 -0
- package/src/index.ts +23 -1
- 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/000_plg_rename.sql +21 -0
- package/src/migrations/001_frm_base.sql +92 -56
- package/src/migrations/002_document_archetype.sql +130 -42
- package/src/migrations/003_agent_rpcs.sql +174 -0
- package/src/migrations/index.ts +610 -0
- package/src/types.ts +18 -4
- 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');
|
|
@@ -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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
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("
|
|
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(
|
|
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("
|
|
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("
|
|
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(
|
|
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("
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
732
|
-
if (!window.confirm(t("customForms.
|
|
733
|
-
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 });
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
840
|
+
handleArchive(template);
|
|
793
841
|
},
|
|
794
|
-
className: "p-1.5 rounded-md hover:bg-
|
|
795
|
-
|
|
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(
|
|
883
|
+
const [builderTemplateId, setBuilderTemplateId] = React2.useState(readBuilderIdFromHash);
|
|
828
884
|
const handleNew = React2.useCallback(() => {
|
|
829
|
-
|
|
885
|
+
window.location.hash = `#${ROUTE_BASE}/templates/new`;
|
|
830
886
|
}, []);
|
|
831
887
|
const handleEdit = React2.useCallback((template) => {
|
|
832
|
-
|
|
888
|
+
window.location.hash = `#${ROUTE_BASE}/templates/${template.id}`;
|
|
833
889
|
}, []);
|
|
834
890
|
const handleBackFromBuilder = React2.useCallback(() => {
|
|
835
|
-
|
|
891
|
+
window.location.hash = `#${ROUTE_BASE}/forms`;
|
|
836
892
|
store.getState().fetchTemplates();
|
|
837
893
|
}, [store]);
|
|
838
894
|
React2__default.default.useEffect(() => {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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-
|
|
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
|
|
957
|
-
if (!window.confirm(t("customForms.
|
|
958
|
-
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" });
|
|
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
|
-
|
|
1105
|
+
handleArchive(doc);
|
|
1057
1106
|
},
|
|
1058
|
-
className: "p-1.5 rounded-md hover:bg-
|
|
1059
|
-
|
|
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(
|
|
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)
|
|
1123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|