@fayz-ai/plugin-forms 0.11.1 → 0.13.0

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 (38) hide show
  1. package/dist/{FormBuilder-YHJHF6HZ.js → FormBuilder-AO6V2YOT.js} +3 -3
  2. package/dist/{FormBuilder-YHJHF6HZ.js.map → FormBuilder-AO6V2YOT.js.map} +1 -1
  3. package/dist/{chunk-RBLXIKWB.js → chunk-2AIKIPGU.js} +51 -7
  4. package/dist/chunk-2AIKIPGU.js.map +1 -0
  5. package/dist/components/AddDocumentDropdown.d.ts +12 -1
  6. package/dist/components/AddDocumentDropdown.d.ts.map +1 -1
  7. package/dist/components/DocumentList.d.ts +4 -1
  8. package/dist/components/DocumentList.d.ts.map +1 -1
  9. package/dist/components/DocumentPreview.d.ts +10 -0
  10. package/dist/components/DocumentPreview.d.ts.map +1 -0
  11. package/dist/components/FormRenderer.d.ts.map +1 -1
  12. package/dist/components/PersonDocumentsWidget.d.ts +13 -1
  13. package/dist/components/PersonDocumentsWidget.d.ts.map +1 -1
  14. package/dist/components/PersonFormsAlert.d.ts.map +1 -1
  15. package/dist/data/mock.d.ts.map +1 -1
  16. package/dist/data/supabase.d.ts.map +1 -1
  17. package/dist/data/types.d.ts +11 -1
  18. package/dist/data/types.d.ts.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +722 -181
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/categories.d.ts +69 -0
  23. package/dist/lib/categories.d.ts.map +1 -0
  24. package/dist/lib/person-requirements.d.ts +9 -0
  25. package/dist/lib/person-requirements.d.ts.map +1 -1
  26. package/dist/lib/plain-text.d.ts +9 -0
  27. package/dist/lib/plain-text.d.ts.map +1 -0
  28. package/dist/lib/tab-counts.d.ts +12 -0
  29. package/dist/lib/tab-counts.d.ts.map +1 -0
  30. package/dist/locales/en.d.ts.map +1 -1
  31. package/dist/locales/pt-BR.d.ts.map +1 -1
  32. package/dist/migrations/index.d.ts +2 -0
  33. package/dist/migrations/index.d.ts.map +1 -1
  34. package/dist/types.d.ts +16 -0
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/views/TemplateListView.d.ts.map +1 -1
  37. package/package.json +6 -5
  38. package/dist/chunk-RBLXIKWB.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { FormRenderer, createOrUpdateFormTool, prefillFromPerson, printDocument } from './chunk-RBLXIKWB.js';
2
- import React10, { createContext, useState, useCallback, useRef, useMemo, useEffect } from 'react';
3
- import { tl, createSafeDataProvider, registerTranslations, getSupabaseClientOptional, useTranslation, getActiveTenantId, emitDomainEvent, useDataVersion } from '@fayz-ai/core';
4
- import { getFayzSupabaseClient, useOrganizationStore, PluginSettingsPanel, registerTimelineSource, useLimitGuard, invalidateLimit, askAI, PermissionGate, PageActions } from '@fayz-ai/admin';
1
+ import { FormRenderer, createOrUpdateFormTool, prefillFromPerson, printDocument, htmlToPlainText } from './chunk-2AIKIPGU.js';
2
+ import React11, { createContext, useState, useCallback, useRef, useMemo, useEffect } from 'react';
3
+ import { tl, createSafeDataProvider, registerTranslations, recordRailFields, RECORD_GROUP, activeFacet, getSupabaseClientOptional, useTranslation, getActiveTenantId, emitDomainEvent, useDataVersion } from '@fayz-ai/core';
4
+ import { getFayzSupabaseClient, useOrganizationStore, PluginSettingsPanel, registerTimelineSource, registerTabCountProvider, useLimitGuard, invalidateLimit, askAI, PermissionGate, PageActions, invalidateTabCounts, TimelineList } from '@fayz-ai/admin';
5
5
  import { create } from 'zustand';
6
- import { ClipboardList, CheckCircle2, Clock, Circle, FileText, Image, Paperclip, Plus, WandSparkles, Pencil, Archive, Search, ArrowLeft, Printer, ChevronDown, Upload, Eye } from 'lucide-react';
7
- import { Skeleton, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Badge, Switch, RowActions, Button, PageHeaderActions, Card, DataTable, useSaveBar, Dropdown, DropdownTrigger, DropdownContent, DropdownSeparator, DropdownLabel } from '@fayz-ai/ui';
6
+ import { ClipboardList, CheckCircle2, Clock, Circle, FileText, FileSignature, FileCheck2, Activity, Image, Paperclip, Plus, WandSparkles, Pencil, Archive, Search, ArrowLeft, Printer, ChevronDown, EyeOff, Eye, Upload } from 'lucide-react';
7
+ import { Skeleton, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Badge, Switch, RowActions, Button, PageHeaderActions, Card, DataTable, useSaveBar, Dropdown, DropdownTrigger, DropdownContent, DropdownSeparator, DropdownLabel, ConfirmDialog } from '@fayz-ai/ui';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
9
 
10
10
  // src/data/tables.ts
@@ -61,6 +61,7 @@ function createMockFormsProvider() {
61
61
  isActive: true,
62
62
  isDeleted: false,
63
63
  requiredOnAttendance: false,
64
+ alertWhenMissing: false,
64
65
  validityDays: null,
65
66
  createdAt: now(),
66
67
  updatedAt: now()
@@ -119,12 +120,29 @@ function createMockFormsProvider() {
119
120
  (d) => d.title?.toLowerCase().includes(s) || d.templateName?.toLowerCase().includes(s)
120
121
  );
121
122
  }
123
+ if (query.kinds?.length) {
124
+ filtered = filtered.filter((d) => query.kinds.includes(d.kind ?? "form"));
125
+ }
126
+ if (query.templateCategory) {
127
+ filtered = filtered.filter((d) => (d.templateCategory ?? "general") === query.templateCategory);
128
+ }
122
129
  filtered.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
123
130
  const page = query.page ?? 1;
124
131
  const pageSize = query.pageSize ?? 25;
125
132
  const start = (page - 1) * pageSize;
126
133
  return { data: filtered.slice(start, start + pageSize), total: filtered.length };
127
134
  },
135
+ async getDocumentCounts(query) {
136
+ const counts = {};
137
+ for (const d of documents) {
138
+ if (d.isDeleted || d.status === "archived") continue;
139
+ if (d.personId !== query.personId) continue;
140
+ if (query.kinds?.length && !query.kinds.includes(d.kind ?? "form")) continue;
141
+ const bucket = d.kind === "image" || d.kind === "attachment" ? "file" : d.templateCategory ?? "general";
142
+ counts[bucket] = (counts[bucket] ?? 0) + 1;
143
+ }
144
+ return counts;
145
+ },
128
146
  async getDocumentById(id) {
129
147
  return documents.find((d) => d.id === id && !d.isDeleted) ?? null;
130
148
  },
@@ -228,6 +246,7 @@ function mapTemplate(r) {
228
246
  return {
229
247
  requiredOnAttendance: r.required_on_attendance ?? false,
230
248
  validityDays: r.validity_days ?? null,
249
+ alertWhenMissing: r.alert_when_missing ?? false,
231
250
  id: r.id,
232
251
  tenantId: r.tenant_id,
233
252
  name: r.name,
@@ -349,15 +368,17 @@ function createSupabaseFormsProvider() {
349
368
  is_active: input.isActive ?? existing.isActive,
350
369
  version: existing.version + 1,
351
370
  is_current: true,
352
- parent_id: existing.parentId ?? existing.id
371
+ parent_id: existing.parentId ?? existing.id,
372
+ // A EXIGÊNCIA É DO MODELO, NÃO DA VERSÃO. Elas eram gravadas na
373
+ // linha que estava sendo ARQUIVADA: editar os campos de uma
374
+ // anamnese exigida devolvia uma versão nova que não exigia nada,
375
+ // e o aviso sumia sem ninguém ter desligado nada.
376
+ required_on_attendance: input.requiredOnAttendance ?? existing.requiredOnAttendance,
377
+ validity_days: input.validityDays !== void 0 ? input.validityDays : existing.validityDays,
378
+ alert_when_missing: input.alertWhenMissing ?? existing.alertWhenMissing
353
379
  }).select().single();
354
380
  if (insertError) throw new Error(insertError.message);
355
- await db.from(T.templates).update({
356
- ...input.requiredOnAttendance !== void 0 ? { required_on_attendance: input.requiredOnAttendance } : {},
357
- ...input.validityDays !== void 0 ? { validity_days: input.validityDays } : {},
358
- is_current: false,
359
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
360
- }).eq("id", id);
381
+ await db.from(T.templates).update({ is_current: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
361
382
  return mapTemplate(newRow);
362
383
  }
363
384
  const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
@@ -369,6 +390,9 @@ function createSupabaseFormsProvider() {
369
390
  if (input.tags !== void 0) updates.tags = input.tags;
370
391
  if (input.metadata !== void 0) updates.metadata = input.metadata;
371
392
  if (input.isActive !== void 0) updates.is_active = input.isActive;
393
+ if (input.requiredOnAttendance !== void 0) updates.required_on_attendance = input.requiredOnAttendance;
394
+ if (input.validityDays !== void 0) updates.validity_days = input.validityDays;
395
+ if (input.alertWhenMissing !== void 0) updates.alert_when_missing = input.alertWhenMissing;
372
396
  const { data, error } = await db.from(T.templates).update(updates).eq("id", id).select().single();
373
397
  if (error) throw new Error(error.message);
374
398
  return mapTemplate(data);
@@ -414,6 +438,8 @@ function createSupabaseFormsProvider() {
414
438
  qb = qb.in("status", statuses);
415
439
  }
416
440
  if (query.search) qb = qb.or(`title.ilike.%${query.search}%,template_name.ilike.%${query.search}%`);
441
+ if (query.kinds?.length) qb = qb.in("kind", query.kinds);
442
+ if (query.templateCategory) qb = qb.eq("template_category", query.templateCategory);
417
443
  const page = query.page ?? 1;
418
444
  const pageSize = query.pageSize ?? 50;
419
445
  qb = qb.order("created_at", { ascending: false }).range((page - 1) * pageSize, page * pageSize - 1);
@@ -421,6 +447,27 @@ function createSupabaseFormsProvider() {
421
447
  if (error) throw new Error(error.message);
422
448
  return { data: (data ?? []).map(mapDocument), total: count ?? 0 };
423
449
  },
450
+ /**
451
+ * Os totais por balde, sem trazer documento nenhum.
452
+ *
453
+ * Duas colunas e nada mais: o corpo do formulário (`form_data`) é o campo
454
+ * pesado da view, e contar não precisa dele. Agrupa no cliente porque
455
+ * PostgREST não faz `group by` — e a alternativa, um `count` por balde,
456
+ * seriam seis idas para responder uma pergunta.
457
+ */
458
+ async getDocumentCounts(query) {
459
+ const db = getClient();
460
+ let qb = db.from("v_documents").select("kind, template_category").eq("tenant_id", getTenantId()).eq("person_id", query.personId).eq("is_active", true).neq("status", "archived");
461
+ if (query.kinds?.length) qb = qb.in("kind", query.kinds);
462
+ const { data, error } = await qb;
463
+ if (error) return {};
464
+ const counts = {};
465
+ for (const row of data ?? []) {
466
+ const bucket = row.kind === "image" || row.kind === "attachment" ? "file" : row.template_category || "general";
467
+ counts[bucket] = (counts[bucket] ?? 0) + 1;
468
+ }
469
+ return counts;
470
+ },
424
471
  async getDocumentById(id) {
425
472
  const db = getClient();
426
473
  const { data, error } = await db.from("v_documents").select("*").eq("id", id).single();
@@ -725,11 +772,34 @@ var en = {
725
772
  "customForms.noResults": "No results",
726
773
  "customForms.noDocuments": "No documents yet",
727
774
  "customForms.noDocumentsDescription": "Add a document to start recording information.",
775
+ "customForms.bucket.all": "All",
776
+ "customForms.bucket.file": "Files",
777
+ "customForms.noDocumentsInFilter": "Nothing of this kind on this record.",
778
+ "customForms.documentFallbackTitle": "Document",
779
+ "customForms.recordsTitle": "Records",
780
+ "customForms.filesTitle": "Files",
781
+ "customForms.addRecord": "New",
782
+ "customForms.addFile": "Upload",
783
+ "customForms.view": "View",
784
+ "customForms.showContent": "Show content",
785
+ "customForms.hideContent": "Hide content",
786
+ "customForms.previewEmpty": "This record has no filled field.",
787
+ "customForms.previewMore": "See the other {count} fields",
788
+ "customForms.loadMore": "Load {count} more",
789
+ "customForms.loadingMore": "Loading\u2026",
790
+ "customForms.noRecords": "No records yet",
791
+ "customForms.noRecordsDescription": 'Anamnesis, evolution, report or contract \u2014 start with "New".',
792
+ "customForms.noFiles": "No files yet",
793
+ "customForms.noFilesDescription": "Keep photos, exams and scanned documents here.",
794
+ "customForms.dragFilesHere": "Drag files here",
795
+ "customForms.dropFilesHere": "Drop the files here",
728
796
  "customForms.documentTitle": "Document title",
729
797
  "customForms.saveAsDraft": "Save as Draft",
730
798
  "customForms.saveAndComplete": "Save & Complete",
731
799
  "customForms.deleteDocumentConfirm": "Delete this document?",
732
800
  "customForms.archive": "Archive",
801
+ "customForms.archiveTitle": "Archive record",
802
+ "customForms.archiveDescription": "{name} leaves this record's list but stays stored \u2014 archiving does not delete.",
733
803
  "customForms.archiveDocumentConfirm": "Archive this document? It leaves the active list but is kept on file.",
734
804
  "customForms.printPdf": "Print / PDF",
735
805
  "customForms.usageCount": "{{count}} filled in",
@@ -832,12 +902,40 @@ var ptBR = {
832
902
  "customForms.noResults": "Nenhum resultado",
833
903
  "customForms.noDocuments": "Nenhum documento",
834
904
  "customForms.noDocumentsDescription": "Adicione um documento para come\xE7ar a registrar informa\xE7\xF5es.",
905
+ // Pastilhas do prontuário — o balde "file" é o que não tem modelo (foto, anexo).
906
+ "customForms.bucket.all": "Tudo",
907
+ "customForms.bucket.file": "Arquivos",
908
+ "customForms.noDocumentsInFilter": "Nada deste tipo nesta ficha.",
909
+ "customForms.documentFallbackTitle": "Documento",
910
+ // Prontuário × Arquivos — as duas superfícies do mesmo widget.
911
+ "customForms.recordsTitle": "Registros",
912
+ "customForms.filesTitle": "Arquivos",
913
+ "customForms.addRecord": "Novo",
914
+ "customForms.addFile": "Enviar",
915
+ "customForms.view": "Ver",
916
+ // Olhinho, prévia e paginação da linha do tempo de registros.
917
+ "customForms.showContent": "Mostrar conte\xFAdo",
918
+ "customForms.hideContent": "Ocultar conte\xFAdo",
919
+ "customForms.previewEmpty": "Registro sem nenhum campo preenchido.",
920
+ "customForms.previewMore": "Ver os outros {count} campos",
921
+ "customForms.loadMore": "Carregar mais {count}",
922
+ "customForms.loadingMore": "Carregando\u2026",
923
+ "customForms.noRecords": "Nenhum registro",
924
+ "customForms.noRecordsDescription": 'Anamnese, evolu\xE7\xE3o, laudo ou contrato \u2014 comece em "Novo".',
925
+ "customForms.noFiles": "Nenhum arquivo",
926
+ "customForms.noFilesDescription": "Guarde aqui foto, exame, documento digitalizado.",
927
+ "customForms.dragFilesHere": "Arraste arquivos para c\xE1",
928
+ "customForms.dropFilesHere": "Solte os arquivos aqui",
835
929
  "customForms.documentTitle": "T\xEDtulo do documento",
836
930
  "customForms.saveAsDraft": "Salvar Rascunho",
837
931
  "customForms.saveAndComplete": "Salvar e Finalizar",
838
932
  "customForms.deleteDocumentConfirm": "Excluir este documento?",
839
933
  "customForms.archive": "Arquivar",
840
934
  "customForms.archiveDocumentConfirm": "Arquivar este documento? Ele sai da lista ativa mas fica guardado.",
935
+ // O diálogo diz DE QUAL registro se trata: "Tem certeza?" sozinho é a
936
+ // pergunta que ninguém consegue responder.
937
+ "customForms.archiveTitle": "Arquivar registro",
938
+ "customForms.archiveDescription": "{name} sai da lista desta ficha, mas continua guardado \u2014 arquivar n\xE3o apaga.",
841
939
  "customForms.printPdf": "Imprimir / PDF",
842
940
  "customForms.usageCount": "{{count}} preenchidos",
843
941
  "customForms.usageLast": "\xFAltimo em {{date}}",
@@ -941,7 +1039,7 @@ function TemplateListView({ store, config, onEdit, onNew }) {
941
1039
  await store.getState().updateTemplate(template.id, { isActive: false });
942
1040
  }, [store, t]);
943
1041
  const visibleTemplates = templates.filter((tpl) => tpl.isActive !== false);
944
- const categoryLabel = (cat) => t(`customForms.category.${cat}`);
1042
+ const categoryLabel2 = (cat) => t(`customForms.category.${cat}`);
945
1043
  const columns = useMemo(() => [
946
1044
  {
947
1045
  id: "name",
@@ -954,7 +1052,7 @@ function TemplateListView({ store, config, onEdit, onNew }) {
954
1052
  {
955
1053
  id: "category",
956
1054
  header: t("customForms.categoryColumn"),
957
- cell: ({ row }) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${CATEGORY_COLORS[row.original.category]}`, children: categoryLabel(row.original.category) })
1055
+ cell: ({ row }) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${CATEGORY_COLORS[row.original.category]}`, children: categoryLabel2(row.original.category) })
958
1056
  },
959
1057
  {
960
1058
  id: "fields",
@@ -999,6 +1097,27 @@ function TemplateListView({ store, config, onEdit, onNew }) {
999
1097
  ] })
1000
1098
  ] })
1001
1099
  },
1100
+ {
1101
+ id: "alert",
1102
+ header: "Alerta",
1103
+ // A SEGUNDA PERGUNTA DO V1, e ela é outra pergunta mesmo: exigir põe a
1104
+ // bola no checklist do atendimento — trabalho a fazer com a pessoa na
1105
+ // cadeira; alertar leva a falta para a FRENTE, na faixa âmbar do
1106
+ // agendamento e da ficha. Uma casa exige anamnese e não quer a faixa
1107
+ // toda vez que marca horário, e o V1 respeita isso desligado.
1108
+ cell: ({ row }) => /* @__PURE__ */ jsx(
1109
+ Switch,
1110
+ {
1111
+ size: "sm",
1112
+ disabled: !row.original.requiredOnAttendance,
1113
+ checked: row.original.requiredOnAttendance && row.original.alertWhenMissing,
1114
+ onCheckedChange: (on) => {
1115
+ void store.getState().updateTemplate(row.original.id, { alertWhenMissing: on });
1116
+ },
1117
+ "aria-label": `Alertar quando ${row.original.name} estiver faltando`
1118
+ }
1119
+ )
1120
+ },
1002
1121
  {
1003
1122
  id: "usage",
1004
1123
  header: t("customForms.usageColumn"),
@@ -1104,7 +1223,7 @@ function CustomFormsSettingsTab({ config, provider, store }) {
1104
1223
  window.location.hash = `#${ROUTE_BASE}/forms`;
1105
1224
  store.getState().fetchTemplates();
1106
1225
  }, [store]);
1107
- React10.useEffect(() => {
1226
+ React11.useEffect(() => {
1108
1227
  const sync = () => setBuilderTemplateId(readBuilderIdFromHash());
1109
1228
  window.addEventListener("hashchange", sync);
1110
1229
  return () => window.removeEventListener("hashchange", sync);
@@ -1135,11 +1254,11 @@ function BuilderWrapper({
1135
1254
  config,
1136
1255
  onBack
1137
1256
  }) {
1138
- const FormBuilderLazy = React10.lazy(
1139
- () => import('./FormBuilder-YHJHF6HZ.js').then((m) => ({ default: m.FormBuilder }))
1257
+ const FormBuilderLazy = React11.lazy(
1258
+ () => import('./FormBuilder-AO6V2YOT.js').then((m) => ({ default: m.FormBuilder }))
1140
1259
  );
1141
1260
  return /* @__PURE__ */ jsx(
1142
- React10.Suspense,
1261
+ React11.Suspense,
1143
1262
  {
1144
1263
  fallback: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "h-6 w-6 border-2 border-primary border-t-transparent animate-spin rounded-full" }) }),
1145
1264
  children: /* @__PURE__ */ jsx(
@@ -1155,71 +1274,274 @@ function BuilderWrapper({
1155
1274
  }
1156
1275
  );
1157
1276
  }
1158
- var STATUS_COLORS = {
1159
- draft: "bg-warning-soft text-warning-soft-foreground",
1160
- completed: "bg-success-soft text-success-soft-foreground",
1161
- signed: "bg-info-soft text-info-soft-foreground",
1162
- archived: "bg-muted text-muted-foreground"
1277
+ var MAX_FIELDS = 8;
1278
+ var MAX_CHARS = 280;
1279
+ function isEmpty(value) {
1280
+ if (value == null) return true;
1281
+ if (typeof value === "string") return value.trim() === "";
1282
+ if (Array.isArray(value)) return value.length === 0;
1283
+ return false;
1284
+ }
1285
+ function asText(value) {
1286
+ if (Array.isArray(value)) return value.map((v) => String(v)).join(", ");
1287
+ if (typeof value === "boolean") return value ? "Sim" : "N\xE3o";
1288
+ return htmlToPlainText(String(value)) ?? String(value);
1289
+ }
1290
+ function truncate(text) {
1291
+ return text.length > MAX_CHARS ? `${text.slice(0, MAX_CHARS).trimEnd()}\u2026` : text;
1292
+ }
1293
+ function DocumentPreview({
1294
+ document: doc,
1295
+ provider,
1296
+ onOpen
1297
+ }) {
1298
+ const t = useTranslation();
1299
+ const [template, setTemplate] = useState(null);
1300
+ const [loading, setLoading] = useState(true);
1301
+ useEffect(() => {
1302
+ let alive = true;
1303
+ if (!doc.templateId) {
1304
+ setLoading(false);
1305
+ return;
1306
+ }
1307
+ void provider.getTemplateById(doc.templateId).then((found) => {
1308
+ if (alive) {
1309
+ setTemplate(found);
1310
+ setLoading(false);
1311
+ }
1312
+ }).catch(() => {
1313
+ if (alive) setLoading(false);
1314
+ });
1315
+ return () => {
1316
+ alive = false;
1317
+ };
1318
+ }, [doc.templateId, provider]);
1319
+ if (doc.kind === "image" && doc.fileUrl) {
1320
+ return /* @__PURE__ */ jsx(
1321
+ "img",
1322
+ {
1323
+ src: doc.fileUrl,
1324
+ alt: doc.fileName ?? "",
1325
+ className: "max-h-64 rounded-lg object-contain"
1326
+ }
1327
+ );
1328
+ }
1329
+ if (doc.kind === "attachment") {
1330
+ return /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1331
+ doc.fileName ?? t("customForms.documentFallbackTitle"),
1332
+ doc.fileSize ? ` \xB7 ${(doc.fileSize / 1024).toFixed(1)} KB` : ""
1333
+ ] });
1334
+ }
1335
+ if (loading) {
1336
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1337
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-1/3 animate-pulse rounded bg-muted" }),
1338
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-2/3 animate-pulse rounded bg-muted" })
1339
+ ] });
1340
+ }
1341
+ const fields = template?.schema?.fields ?? [];
1342
+ const answered = fields.filter((f) => f.type !== "title" && !isEmpty(doc.data?.[f.id])).sort((a, b) => a.row - b.row || a.col - b.col);
1343
+ if (answered.length === 0) {
1344
+ return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("customForms.previewEmpty") });
1345
+ }
1346
+ const shown = answered.slice(0, MAX_FIELDS);
1347
+ const hidden = answered.length - shown.length;
1348
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1349
+ /* @__PURE__ */ jsx("dl", { className: "grid gap-x-6 gap-y-2 sm:grid-cols-2", children: shown.map((field) => {
1350
+ const text = truncate(asText(doc.data[field.id]));
1351
+ const wide = field.type === "memo" || field.type === "richtext" || text.length > 60;
1352
+ return /* @__PURE__ */ jsxs("div", { className: `min-w-0 ${wide ? "sm:col-span-2" : ""}`, children: [
1353
+ /* @__PURE__ */ jsx("dt", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: htmlToPlainText(field.label) ?? field.label }),
1354
+ /* @__PURE__ */ jsx("dd", { className: "whitespace-pre-wrap text-sm leading-6 text-foreground", children: text })
1355
+ ] }, field.id);
1356
+ }) }),
1357
+ hidden > 0 && /* @__PURE__ */ jsx(
1358
+ "button",
1359
+ {
1360
+ type: "button",
1361
+ onClick: onOpen,
1362
+ className: "text-xs font-medium text-primary hover:underline",
1363
+ children: t("customForms.previewMore", { count: String(hidden) })
1364
+ }
1365
+ )
1366
+ ] });
1367
+ }
1368
+ var CATEGORY_ORDER = [
1369
+ "anamnesis",
1370
+ "evolution",
1371
+ "report",
1372
+ "contract",
1373
+ "general"
1374
+ ];
1375
+ var CATEGORY_GROUP_ORDER = {
1376
+ anamnesis: 10,
1377
+ evolution: 20,
1378
+ report: 30,
1379
+ contract: 40,
1380
+ general: 50
1163
1381
  };
1164
- var KIND_ICONS = {
1165
- form: FileText,
1166
- image: Image,
1167
- attachment: Paperclip,
1168
- prescription: FileText,
1169
- contract: FileText
1382
+ var CATEGORY_ICONS = {
1383
+ anamnesis: ClipboardList,
1384
+ evolution: Activity,
1385
+ report: FileCheck2,
1386
+ contract: FileSignature,
1387
+ general: FileText
1170
1388
  };
1171
- function formatDateLabel(dateStr) {
1172
- const date = new Date(dateStr);
1173
- const now2 = /* @__PURE__ */ new Date();
1174
- const today = new Date(now2.getFullYear(), now2.getMonth(), now2.getDate());
1175
- const docDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
1176
- const diffDays = Math.round((today.getTime() - docDate.getTime()) / (1e3 * 60 * 60 * 24));
1177
- if (diffDays === 0) return "Hoje";
1178
- if (diffDays === 1) return "Ontem";
1179
- if (diffDays < 7) return `${diffDays} dias atr\xE1s`;
1180
- return date.toLocaleDateString(void 0, { day: "numeric", month: "long", year: "numeric" });
1181
- }
1182
- function groupByDate(docs) {
1183
- const groups = /* @__PURE__ */ new Map();
1184
- for (const doc of docs) {
1185
- const dateKey = doc.createdAt.slice(0, 10);
1186
- if (!groups.has(dateKey)) groups.set(dateKey, []);
1187
- groups.get(dateKey).push(doc);
1389
+ function categoryLabel(category) {
1390
+ switch (category) {
1391
+ case "anamnesis":
1392
+ return tl("Anamnesis", "Anamnese");
1393
+ case "evolution":
1394
+ return tl("Evolution", "Evolu\xE7\xE3o");
1395
+ case "report":
1396
+ return tl("Report", "Laudo");
1397
+ case "contract":
1398
+ return tl("Contract", "Contrato");
1399
+ default:
1400
+ return tl("General", "Geral");
1188
1401
  }
1189
- return Array.from(groups.entries()).sort(([a], [b]) => b.localeCompare(a)).map(([date, items]) => ({
1190
- date,
1191
- label: formatDateLabel(date),
1192
- items
1193
- }));
1194
1402
  }
1195
- function DocumentList({ personId, provider, store, onView, onFileDrop }) {
1403
+ function normalizeCategory(value) {
1404
+ return CATEGORY_ORDER.includes(value) ? value : "general";
1405
+ }
1406
+ var CATEGORY_TIMELINE_ICON = {
1407
+ anamnesis: "ClipboardList",
1408
+ evolution: "Activity",
1409
+ report: "FileCheck2",
1410
+ contract: "FileSignature",
1411
+ general: "FileText"
1412
+ };
1413
+ var CATEGORY_TONE = {
1414
+ anamnesis: "primary",
1415
+ evolution: "info",
1416
+ report: "success",
1417
+ contract: "warning",
1418
+ general: "neutral"
1419
+ };
1420
+ function bucketOf(doc) {
1421
+ if (doc.kind === "image" || doc.kind === "attachment") return "file";
1422
+ return normalizeCategory(doc.templateCategory);
1423
+ }
1424
+ function subtitleFor(doc) {
1425
+ const title = doc.title?.trim();
1426
+ if (!title) return void 0;
1427
+ const model = doc.templateName?.trim();
1428
+ if (!model) return title;
1429
+ if (title === model) return void 0;
1430
+ return title.toLowerCase().startsWith(model.toLowerCase()) ? void 0 : title;
1431
+ }
1432
+ function documentToTimelineItem(doc, fallbackTitle) {
1433
+ const b = bucketOf(doc);
1434
+ const secondary = subtitleFor(doc);
1435
+ return {
1436
+ id: doc.id,
1437
+ sourceId: b,
1438
+ occurredAt: doc.createdAt,
1439
+ title: doc.templateName ?? doc.title ?? fallbackTitle,
1440
+ description: secondary,
1441
+ badge: b === "file" ? void 0 : categoryLabel(b),
1442
+ icon: b === "file" ? doc.kind === "image" ? "Image" : "Paperclip" : CATEGORY_TIMELINE_ICON[b],
1443
+ tone: b === "file" ? "neutral" : CATEGORY_TONE[b]
1444
+ };
1445
+ }
1446
+ var ACTIVE_STATUSES = ["draft", "completed", "signed"];
1447
+ var EXPAND_KEY = "fayz.forms.documents.expandAll";
1448
+ function DocumentList({ personId, provider, store, onView, onFileDrop, kinds }) {
1449
+ const acceptsFiles = Boolean(onFileDrop) && (!kinds || kinds.includes("attachment"));
1196
1450
  const t = useTranslation();
1197
1451
  const [documents, setDocuments] = useState([]);
1452
+ const [total, setTotal] = useState(0);
1453
+ const [page, setPage] = useState(1);
1198
1454
  const [loading, setLoading] = useState(true);
1455
+ const [loadingMore, setLoadingMore] = useState(false);
1456
+ const [counts, setCounts] = useState({});
1457
+ const [bucket, setBucket] = useState(void 0);
1199
1458
  const [isDragOver, setIsDragOver] = useState(false);
1459
+ const [expandAll, setExpandAll] = useState(() => {
1460
+ try {
1461
+ return localStorage.getItem(EXPAND_KEY) === "1";
1462
+ } catch {
1463
+ return false;
1464
+ }
1465
+ });
1466
+ const toggleExpandAll = useCallback(() => {
1467
+ setExpandAll((prev) => {
1468
+ try {
1469
+ localStorage.setItem(EXPAND_KEY, prev ? "0" : "1");
1470
+ } catch {
1471
+ }
1472
+ return !prev;
1473
+ });
1474
+ }, []);
1475
+ const [openIds, setOpenIds] = useState(/* @__PURE__ */ new Set());
1200
1476
  const dragCounter = useRef(0);
1477
+ const PAGE_SIZE = 20;
1478
+ const queryFor = useCallback((nextPage) => ({
1479
+ personId,
1480
+ kinds,
1481
+ templateCategory: bucket && bucket !== "file" ? bucket : void 0,
1482
+ ...bucket === "file" ? { kinds: ["image", "attachment"] } : {},
1483
+ status: ACTIVE_STATUSES,
1484
+ page: nextPage,
1485
+ pageSize: PAGE_SIZE
1486
+ }), [personId, kinds, bucket]);
1201
1487
  const fetchDocs = useCallback(async () => {
1202
1488
  setLoading(true);
1203
1489
  try {
1204
- const result = await provider.getDocuments({ personId });
1205
- setDocuments(result.data.filter((d) => d.status !== "archived"));
1490
+ const result = await provider.getDocuments(queryFor(1));
1491
+ setDocuments(result.data);
1492
+ setTotal(result.total);
1493
+ setPage(1);
1206
1494
  } finally {
1207
1495
  setLoading(false);
1208
1496
  }
1209
- }, [personId, provider]);
1497
+ }, [provider, queryFor]);
1498
+ const fetchCounts = useCallback(async () => {
1499
+ try {
1500
+ setCounts(await provider.getDocumentCounts({ personId, kinds }));
1501
+ } catch {
1502
+ setCounts({});
1503
+ }
1504
+ }, [provider, personId, kinds]);
1505
+ const loadMore = useCallback(async () => {
1506
+ setLoadingMore(true);
1507
+ try {
1508
+ const next = page + 1;
1509
+ const result = await provider.getDocuments(queryFor(next));
1510
+ setDocuments((prev) => {
1511
+ const seen = new Set(prev.map((d) => d.id));
1512
+ return [...prev, ...result.data.filter((d) => !seen.has(d.id))];
1513
+ });
1514
+ setTotal(result.total);
1515
+ setPage(next);
1516
+ } finally {
1517
+ setLoadingMore(false);
1518
+ }
1519
+ }, [provider, queryFor, page]);
1210
1520
  useEffect(() => {
1211
1521
  const timer = setTimeout(() => fetchDocs(), 50);
1212
1522
  return () => clearTimeout(timer);
1213
1523
  }, [fetchDocs]);
1214
1524
  useEffect(() => {
1215
- const unsub = store.subscribe(() => fetchDocs());
1525
+ void fetchCounts();
1526
+ }, [fetchCounts]);
1527
+ useEffect(() => {
1528
+ const unsub = store.subscribe(() => {
1529
+ void fetchDocs();
1530
+ void fetchCounts();
1531
+ });
1216
1532
  return unsub;
1217
- }, [store, fetchDocs]);
1218
- const handleArchive = useCallback(async (doc) => {
1219
- if (!window.confirm(t("customForms.archiveDocumentConfirm"))) return;
1533
+ }, [store, fetchDocs, fetchCounts]);
1534
+ const [archiving, setArchiving] = useState(null);
1535
+ const handleArchive = useCallback(async () => {
1536
+ const doc = archiving;
1537
+ setArchiving(null);
1538
+ if (!doc) return;
1220
1539
  await provider.updateDocument(doc.id, { status: "archived" });
1221
1540
  setDocuments((prev) => prev.filter((d) => d.id !== doc.id));
1222
- }, [provider, t]);
1541
+ setTotal((n) => Math.max(0, n - 1));
1542
+ void fetchCounts();
1543
+ invalidateTabCounts();
1544
+ }, [provider, archiving, fetchCounts]);
1223
1545
  const handleDragEnter = useCallback((e) => {
1224
1546
  e.preventDefault();
1225
1547
  dragCounter.current++;
@@ -1242,7 +1564,44 @@ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
1242
1564
  onFileDrop(files);
1243
1565
  }
1244
1566
  }, [onFileDrop]);
1245
- const groups = groupByDate(documents);
1567
+ const buckets = useMemo(() => {
1568
+ const order = [...CATEGORY_ORDER, "file"];
1569
+ return order.filter((b) => (counts[b] ?? 0) > 0).map((b) => ({
1570
+ id: b,
1571
+ label: b === "file" ? t("customForms.bucket.file") : categoryLabel(b),
1572
+ count: counts[b] ?? 0
1573
+ }));
1574
+ }, [counts, t]);
1575
+ const totalAll = useMemo(
1576
+ () => Object.values(counts).reduce((sum, n) => sum + n, 0),
1577
+ [counts]
1578
+ );
1579
+ useEffect(() => {
1580
+ if (bucket && !buckets.some((b) => b.id === bucket)) setBucket(void 0);
1581
+ }, [buckets, bucket]);
1582
+ const visible = documents;
1583
+ const items = useMemo(
1584
+ () => visible.map((doc) => documentToTimelineItem(doc, t("customForms.documentFallbackTitle"))),
1585
+ [visible, t]
1586
+ );
1587
+ const byId = useMemo(() => new Map(visible.map((d) => [d.id, d])), [visible]);
1588
+ const expandedIds = useMemo(() => {
1589
+ if (!expandAll) return openIds;
1590
+ const open = new Set(visible.map((d) => d.id));
1591
+ for (const id of openIds) open.delete(id);
1592
+ return open;
1593
+ }, [expandAll, openIds, visible]);
1594
+ const toggleOne = useCallback((id) => {
1595
+ setOpenIds((prev) => {
1596
+ const next = new Set(prev);
1597
+ if (next.has(id)) next.delete(id);
1598
+ else next.add(id);
1599
+ return next;
1600
+ });
1601
+ }, []);
1602
+ useEffect(() => {
1603
+ setOpenIds(/* @__PURE__ */ new Set());
1604
+ }, [expandAll]);
1246
1605
  if (loading) {
1247
1606
  return /* @__PURE__ */ jsx("div", { className: "space-y-4 py-4", children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1248
1607
  /* @__PURE__ */ jsx("div", { className: "h-9 w-9 rounded-lg bg-muted animate-pulse shrink-0" }),
@@ -1252,86 +1611,133 @@ function DocumentList({ personId, provider, store, onView, onFileDrop }) {
1252
1611
  ] })
1253
1612
  ] }, i)) });
1254
1613
  }
1255
- return /* @__PURE__ */ jsxs(
1256
- "div",
1257
- {
1258
- className: `relative min-h-[200px] rounded-xl border-2 border-dashed transition-colors ${isDragOver ? "border-primary bg-primary/5" : documents.length === 0 ? "border-border" : "border-transparent"}`,
1259
- onDragEnter: handleDragEnter,
1260
- onDragLeave: handleDragLeave,
1261
- onDragOver: handleDragOver,
1262
- onDrop: handleDrop,
1263
- children: [
1264
- isDragOver && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-10 flex flex-col items-center justify-center rounded-xl bg-primary/5", children: [
1265
- /* @__PURE__ */ jsx(Upload, { className: "h-8 w-8 text-primary/50 mb-2" }),
1266
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-primary/70", children: "Solte os arquivos aqui" })
1267
- ] }),
1268
- documents.length === 0 && !isDragOver && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [
1269
- /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted mb-3", children: /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 text-muted-foreground" }) }),
1270
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("customForms.noDocuments") }),
1271
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 max-w-xs", children: t("customForms.noDocumentsDescription") }),
1272
- /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground mt-3 flex items-center gap-1", children: [
1273
- /* @__PURE__ */ jsx(Upload, { className: "h-3 w-3" }),
1274
- "Arraste arquivos para c\xE1"
1275
- ] })
1276
- ] }),
1277
- groups.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-5 py-2", children: groups.map((group) => /* @__PURE__ */ jsxs("div", { children: [
1278
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
1279
- /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" }),
1280
- /* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground px-1", children: group.label }),
1281
- /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" })
1614
+ const filters = (buckets.length > 1 || totalAll > 0) && /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-1.5", children: [
1615
+ buckets.length > 1 && [{ id: void 0, label: t("customForms.bucket.all"), count: totalAll }, ...buckets].map((chip) => {
1616
+ const active = chip.id === bucket;
1617
+ return /* @__PURE__ */ jsxs(
1618
+ "button",
1619
+ {
1620
+ type: "button",
1621
+ onClick: () => setBucket(chip.id),
1622
+ className: `rounded-full border px-2.5 py-1 text-xs transition-colors ${active ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"}`,
1623
+ children: [
1624
+ chip.label,
1625
+ /* @__PURE__ */ jsx("span", { className: "ml-1.5 tabular-nums opacity-60", children: chip.count })
1626
+ ]
1627
+ },
1628
+ chip.id ?? "__all__"
1629
+ );
1630
+ }),
1631
+ /* @__PURE__ */ jsxs(
1632
+ "button",
1633
+ {
1634
+ type: "button",
1635
+ onClick: toggleExpandAll,
1636
+ "aria-pressed": expandAll,
1637
+ title: t(expandAll ? "customForms.hideContent" : "customForms.showContent"),
1638
+ className: `ml-auto flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors ${expandAll ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"}`,
1639
+ children: [
1640
+ expandAll ? /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" }),
1641
+ t(expandAll ? "customForms.hideContent" : "customForms.showContent")
1642
+ ]
1643
+ }
1644
+ )
1645
+ ] });
1646
+ return /* @__PURE__ */ jsxs("div", { children: [
1647
+ filters,
1648
+ /* @__PURE__ */ jsxs(
1649
+ "div",
1650
+ {
1651
+ className: `relative min-h-[200px] rounded-xl border-2 transition-colors ${isDragOver ? "border-dashed border-primary bg-primary/5" : documents.length === 0 && acceptsFiles ? "border-dashed border-border" : "border-transparent"}`,
1652
+ ...acceptsFiles ? {
1653
+ onDragEnter: handleDragEnter,
1654
+ onDragLeave: handleDragLeave,
1655
+ onDragOver: handleDragOver,
1656
+ onDrop: handleDrop
1657
+ } : {},
1658
+ children: [
1659
+ isDragOver && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-10 flex flex-col items-center justify-center rounded-xl bg-primary/5", children: [
1660
+ /* @__PURE__ */ jsx(Upload, { className: "h-8 w-8 text-primary/50 mb-2" }),
1661
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-primary/70", children: t("customForms.dropFilesHere") })
1282
1662
  ] }),
1283
- /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: group.items.map((doc) => {
1284
- const Icon = KIND_ICONS[doc.kind ?? "form"] ?? FileText;
1285
- const time = new Date(doc.createdAt).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
1286
- return /* @__PURE__ */ jsxs(
1287
- "div",
1288
- {
1289
- className: "group flex items-center gap-3 rounded-lg p-2.5 hover:bg-muted/50 cursor-pointer transition-colors",
1290
- onClick: () => onView(doc),
1291
- children: [
1292
- /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 shrink-0", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-primary" }) }),
1293
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1294
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: doc.title ?? doc.templateName ?? "Documento" }),
1295
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mt-0.5", children: [
1296
- doc.templateName && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: doc.templateName }),
1297
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: time })
1298
- ] })
1299
- ] }),
1300
- /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] shrink-0 ${STATUS_COLORS[doc.status] ?? ""}`, children: t(`customForms.status.${doc.status}`) }),
1301
- /* @__PURE__ */ jsxs("div", { className: "flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity", children: [
1302
- /* @__PURE__ */ jsx(
1303
- "button",
1304
- {
1305
- onClick: (e) => {
1306
- e.stopPropagation();
1307
- onView(doc);
1308
- },
1309
- className: "p-1.5 rounded-md hover:bg-muted",
1310
- children: /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5 text-muted-foreground" })
1311
- }
1312
- ),
1313
- /* @__PURE__ */ jsx(
1314
- "button",
1315
- {
1316
- onClick: (e) => {
1317
- e.stopPropagation();
1318
- handleArchive(doc);
1319
- },
1320
- className: "p-1.5 rounded-md hover:bg-muted",
1321
- title: t("customForms.archive"),
1322
- children: /* @__PURE__ */ jsx(Archive, { className: "h-3.5 w-3.5 text-muted-foreground" })
1323
- }
1324
- )
1325
- ] })
1326
- ]
1663
+ documents.length > 0 && visible.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("customForms.noDocumentsInFilter") }) }),
1664
+ documents.length === 0 && !isDragOver && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [
1665
+ /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted mb-3", children: acceptsFiles ? /* @__PURE__ */ jsx(Paperclip, { className: "h-5 w-5 text-muted-foreground" }) : /* @__PURE__ */ jsx(ClipboardList, { className: "h-5 w-5 text-muted-foreground" }) }),
1666
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t(acceptsFiles ? "customForms.noFiles" : "customForms.noRecords") }),
1667
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 max-w-xs", children: t(acceptsFiles ? "customForms.noFilesDescription" : "customForms.noRecordsDescription") }),
1668
+ acceptsFiles && /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground mt-3 flex items-center gap-1", children: [
1669
+ /* @__PURE__ */ jsx(Upload, { className: "h-3 w-3" }),
1670
+ t("customForms.dragFilesHere")
1671
+ ] })
1672
+ ] }),
1673
+ visible.length > 0 && /* @__PURE__ */ jsx(
1674
+ TimelineList,
1675
+ {
1676
+ items,
1677
+ expandedIds,
1678
+ renderPreview: (it) => {
1679
+ const doc = byId.get(it.id);
1680
+ return doc ? /* @__PURE__ */ jsx(DocumentPreview, { document: doc, provider, onOpen: () => onView(doc) }) : null;
1327
1681
  },
1328
- doc.id
1329
- );
1330
- }) })
1331
- ] }, group.date)) })
1332
- ]
1333
- }
1334
- );
1682
+ onSelect: (it) => {
1683
+ const doc = byId.get(it.id);
1684
+ if (doc) onView(doc);
1685
+ },
1686
+ renderActions: (it) => {
1687
+ const doc = byId.get(it.id);
1688
+ if (!doc) return null;
1689
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1690
+ /* @__PURE__ */ jsx(
1691
+ "button",
1692
+ {
1693
+ type: "button",
1694
+ onClick: (e) => {
1695
+ e.stopPropagation();
1696
+ toggleOne(doc.id);
1697
+ },
1698
+ className: "rounded-md p-1.5 hover:bg-muted",
1699
+ "aria-label": t(expandedIds.has(doc.id) ? "customForms.hideContent" : "customForms.showContent"),
1700
+ "aria-expanded": expandedIds.has(doc.id),
1701
+ children: expandedIds.has(doc.id) ? /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5 text-muted-foreground" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5 text-muted-foreground" })
1702
+ }
1703
+ ),
1704
+ /* @__PURE__ */ jsx(
1705
+ "button",
1706
+ {
1707
+ type: "button",
1708
+ onClick: (e) => {
1709
+ e.stopPropagation();
1710
+ setArchiving(doc);
1711
+ },
1712
+ className: "rounded-md p-1.5 hover:bg-muted",
1713
+ "aria-label": t("customForms.archive"),
1714
+ title: t("customForms.archive"),
1715
+ children: /* @__PURE__ */ jsx(Archive, { className: "h-3.5 w-3.5 text-muted-foreground" })
1716
+ }
1717
+ )
1718
+ ] });
1719
+ }
1720
+ }
1721
+ ),
1722
+ visible.length < total && /* @__PURE__ */ jsx("div", { className: "flex justify-center pt-4", children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", disabled: loadingMore, onClick: () => void loadMore(), children: loadingMore ? t("customForms.loadingMore") : t("customForms.loadMore", { count: String(total - visible.length) }) }) })
1723
+ ]
1724
+ }
1725
+ ),
1726
+ /* @__PURE__ */ jsx(
1727
+ ConfirmDialog,
1728
+ {
1729
+ open: archiving !== null,
1730
+ title: t("customForms.archiveTitle"),
1731
+ description: t("customForms.archiveDescription", {
1732
+ name: archiving?.templateName ?? archiving?.title ?? t("customForms.documentFallbackTitle")
1733
+ }),
1734
+ confirmLabel: t("customForms.archive"),
1735
+ cancelLabel: t("common.cancel"),
1736
+ onConfirm: () => void handleArchive(),
1737
+ onCancel: () => setArchiving(null)
1738
+ }
1739
+ )
1740
+ ] });
1335
1741
  }
1336
1742
  function DocumentBodySkeleton({ rows = 5 }) {
1337
1743
  return /* @__PURE__ */ jsx("div", { className: "space-y-4 rounded-xl border p-4", "aria-hidden": true, children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
@@ -1375,7 +1781,7 @@ function DocumentFormDialog({
1375
1781
  provider.getTemplateById(existingDocument.templateId).then(setTemplate);
1376
1782
  }
1377
1783
  }, [existingDocument, template, provider]);
1378
- const prefilled = React10.useRef(!!initialTemplate || !!existingDocument);
1784
+ const prefilled = React11.useRef(!!initialTemplate || !!existingDocument);
1379
1785
  useEffect(() => {
1380
1786
  if (prefilled.current || !template || existingDocument) return;
1381
1787
  prefilled.current = true;
@@ -1407,7 +1813,7 @@ function DocumentFormDialog({
1407
1813
  },
1408
1814
  [template, existingDocument, data, personId, personName, store, onSaved, guardDocuments]
1409
1815
  );
1410
- const snapshot = React10.useRef(null);
1816
+ const snapshot = React11.useRef(null);
1411
1817
  useEffect(() => {
1412
1818
  if (template && snapshot.current === null) {
1413
1819
  snapshot.current = JSON.stringify(existingDocument?.data ?? {});
@@ -1478,7 +1884,7 @@ function DocumentFormDialog({
1478
1884
  ) })
1479
1885
  ] });
1480
1886
  }
1481
- var STATUS_COLORS2 = {
1887
+ var STATUS_COLORS = {
1482
1888
  draft: "bg-warning-soft text-warning-soft-foreground",
1483
1889
  completed: "bg-success-soft text-success-soft-foreground",
1484
1890
  signed: "bg-info-soft text-info-soft-foreground",
@@ -1508,7 +1914,7 @@ function FormViewer({ document: doc, provider, onBack, onEdit }) {
1508
1914
  /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm truncate", children: doc.title ?? template?.name ?? "" }),
1509
1915
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: new Date(doc.createdAt).toLocaleDateString() })
1510
1916
  ] }),
1511
- /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${STATUS_COLORS2[doc.status] ?? ""}`, children: t(`customForms.status.${doc.status}`) }),
1917
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: `text-[10px] ${STATUS_COLORS[doc.status] ?? ""}`, children: t(`customForms.status.${doc.status}`) }),
1512
1918
  /* @__PURE__ */ jsxs(
1513
1919
  Button,
1514
1920
  {
@@ -1561,7 +1967,7 @@ async function getAllDocumentTypes(personId) {
1561
1967
  }
1562
1968
  var SEARCH_THRESHOLD = 8;
1563
1969
  var normalize = (s) => s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
1564
- function AddDocumentDropdown({ personId, onSelect }) {
1970
+ function AddDocumentDropdown({ personId, onSelect, optionFilter, label }) {
1565
1971
  const t = useTranslation();
1566
1972
  const [open, setOpen] = useState(false);
1567
1973
  const [options, setOptions] = useState([]);
@@ -1579,11 +1985,11 @@ function AddDocumentDropdown({ personId, onSelect }) {
1579
1985
  if (loaded || loadingRef.current) return;
1580
1986
  loadingRef.current = true;
1581
1987
  getAllDocumentTypes(personId).then((opts) => {
1582
- setOptions(opts);
1988
+ setOptions(optionFilter ? opts.filter(optionFilter) : opts);
1583
1989
  setLoaded(true);
1584
1990
  loadingRef.current = false;
1585
1991
  });
1586
- }, [personId, loaded]);
1992
+ }, [personId, loaded, optionFilter]);
1587
1993
  const filtered = useMemo(() => {
1588
1994
  const q = normalize(query.trim());
1589
1995
  if (!q) return options;
@@ -1635,7 +2041,7 @@ function AddDocumentDropdown({ personId, onSelect }) {
1635
2041
  return /* @__PURE__ */ jsxs(Dropdown, { open, onOpenChange: handleOpenChange, children: [
1636
2042
  /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { size: "sm", className: "h-8", children: [
1637
2043
  /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 mr-1" }),
1638
- t("customForms.addDocument"),
2044
+ label ?? t("customForms.addDocument"),
1639
2045
  /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 ml-1 opacity-50" })
1640
2046
  ] }) }),
1641
2047
  /* @__PURE__ */ jsxs(DropdownContent, { align: "end", className: "w-72 p-0", children: [
@@ -1657,7 +2063,7 @@ function AddDocumentDropdown({ personId, onSelect }) {
1657
2063
  )
1658
2064
  ] }),
1659
2065
  /* @__PURE__ */ jsxs("div", { ref: listRef, className: "max-h-[min(60vh,20rem)] overflow-y-auto overscroll-contain p-1", children: [
1660
- groups.map((group, gi) => /* @__PURE__ */ jsxs(React10.Fragment, { children: [
2066
+ groups.map((group, gi) => /* @__PURE__ */ jsxs(React11.Fragment, { children: [
1661
2067
  gi > 0 && /* @__PURE__ */ jsx(DropdownSeparator, {}),
1662
2068
  /* @__PURE__ */ jsx(DropdownLabel, { className: "text-[10px] uppercase tracking-wider", children: group.label }),
1663
2069
  group.items.map((opt) => {
@@ -1691,8 +2097,17 @@ function AddDocumentDropdown({ personId, onSelect }) {
1691
2097
  ] })
1692
2098
  ] });
1693
2099
  }
1694
- function PersonDocumentsWidget({ item, config, provider, store }) {
2100
+ var SURFACE_KINDS = {
2101
+ records: ["form"],
2102
+ files: ["image", "attachment"]
2103
+ };
2104
+ function PersonDocumentsWidget({ item, config, provider, store, surface = "records" }) {
1695
2105
  const t = useTranslation();
2106
+ const kinds = SURFACE_KINDS[surface];
2107
+ const optionFilter = useCallback(
2108
+ (o) => surface === "records" ? o.id.startsWith("template:") : o.id === "core:image" || o.id === "core:attachment",
2109
+ [surface]
2110
+ );
1696
2111
  const guardDocuments = useLimitGuard("documents_month");
1697
2112
  const [view, setView] = useState({ type: "list" });
1698
2113
  const fileInputRef = useRef(null);
@@ -1799,8 +2214,16 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
1799
2214
  }
1800
2215
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1801
2216
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1802
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t("customForms.documents") }),
1803
- /* @__PURE__ */ jsx(AddDocumentDropdown, { personId: item.id, onSelect: handleDocTypeSelect })
2217
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t(surface === "files" ? "customForms.filesTitle" : "customForms.recordsTitle") }),
2218
+ /* @__PURE__ */ jsx(
2219
+ AddDocumentDropdown,
2220
+ {
2221
+ personId: item.id,
2222
+ onSelect: handleDocTypeSelect,
2223
+ optionFilter,
2224
+ label: t(surface === "files" ? "customForms.addFile" : "customForms.addRecord")
2225
+ }
2226
+ )
1804
2227
  ] }),
1805
2228
  /* @__PURE__ */ jsx(
1806
2229
  DocumentList,
@@ -1808,6 +2231,7 @@ function PersonDocumentsWidget({ item, config, provider, store }) {
1808
2231
  personId: item.id,
1809
2232
  provider,
1810
2233
  store,
2234
+ kinds,
1811
2235
  onView: handleViewDoc,
1812
2236
  onFileDrop: handleFiles
1813
2237
  }
@@ -1882,6 +2306,24 @@ function registerFormsTimelineSource() {
1882
2306
  }
1883
2307
  });
1884
2308
  }
2309
+ function registerFormsTabCounts() {
2310
+ registerTabCountProvider({
2311
+ id: "custom_forms:documents",
2312
+ async fetch({ tenantId, personId }) {
2313
+ const db = getSupabaseClientOptional();
2314
+ if (!db) return {};
2315
+ const { data, error } = await db.from("documents").select("kind").eq("tenant_id", tenantId).eq("person_id", personId).eq("is_active", true).neq("status", "archived");
2316
+ if (error) return {};
2317
+ let records = 0;
2318
+ let files = 0;
2319
+ for (const row of data ?? []) {
2320
+ if (row.kind === "image" || row.kind === "attachment") files += 1;
2321
+ else records += 1;
2322
+ }
2323
+ return { documents: records, files };
2324
+ }
2325
+ });
2326
+ }
1885
2327
  var SUBMISSIONS = "plg_forms_rep_submissions";
1886
2328
  var CAT = tl("Forms", "Formul\xE1rios");
1887
2329
  var STATUS_LABELS = {
@@ -1977,6 +2419,7 @@ async function getPersonFormRequirements(tenantId, personId) {
1977
2419
  templateName: r.template_name,
1978
2420
  category: r.category,
1979
2421
  validityDays: r.validity_days ?? null,
2422
+ alertWhenMissing: r.alert_when_missing ?? false,
1980
2423
  lastFilledAt: r.last_filled_at ?? null,
1981
2424
  validUntil: r.valid_until ?? null,
1982
2425
  state: r.state
@@ -2011,8 +2454,8 @@ function Linha({ item }) {
2011
2454
  }
2012
2455
  function PersonFormsAlert({ personId, onFill, version = 0, extras, onPending, className }) {
2013
2456
  const tenantId = useOrganizationStore((s) => s.currentOrg?.id);
2014
- const [rows, setRows] = React10.useState(null);
2015
- React10.useEffect(() => {
2457
+ const [rows, setRows] = React11.useState(null);
2458
+ React11.useEffect(() => {
2016
2459
  if (!personId || !tenantId) {
2017
2460
  setRows([]);
2018
2461
  return;
@@ -2028,8 +2471,17 @@ function PersonFormsAlert({ personId, onFill, version = 0, extras, onPending, cl
2028
2471
  cancelled = true;
2029
2472
  };
2030
2473
  }, [personId, tenantId, version]);
2031
- const itens = React10.useMemo(() => [
2032
- ...(rows ?? []).map((r) => {
2474
+ const itens = React11.useMemo(() => [
2475
+ // O QUE NÃO ALERTA NÃO INTERROMPE. Exigir um formulário põe a bola no
2476
+ // checklist do atendimento; ALERTAR é a segunda decisão do V1
2477
+ // (`alert_when_missing`, desligada por padrão) e é ela que autoriza a
2478
+ // faixa âmbar na frente de quem só queria marcar um horário. Sem este
2479
+ // recorte, uma anamnese importada com a segunda chave desligada — como a
2480
+ // casa já a tinha no V1 — abria "1 item pendente" em todo agendamento.
2481
+ //
2482
+ // Em dia continua aparecendo de qualquer forma: a linha verde é
2483
+ // confirmação, não interrupção, e é ela que faz a âmbar ter crédito.
2484
+ ...(rows ?? []).filter((r) => r.alertWhenMissing || !isOutstanding(r)).map((r) => {
2033
2485
  const pendente = isOutstanding(r);
2034
2486
  return {
2035
2487
  id: r.templateId,
@@ -2046,7 +2498,7 @@ function PersonFormsAlert({ personId, onFill, version = 0, extras, onPending, cl
2046
2498
  const pendentes = itens.filter((i) => i.state !== "done");
2047
2499
  const faltando = pendentes.length;
2048
2500
  const chave = pendentes.map((i) => i.label).join("|");
2049
- React10.useEffect(() => {
2501
+ React11.useEffect(() => {
2050
2502
  onPending?.(chave ? chave.split("|") : []);
2051
2503
  }, [chave]);
2052
2504
  if (rows === null) return /* @__PURE__ */ jsx(Skeleton, { className: `h-12 w-full ${className ?? ""}` });
@@ -2064,19 +2516,19 @@ function CustomFormsProvider({
2064
2516
  value,
2065
2517
  children
2066
2518
  }) {
2067
- return React10.createElement(Ctx.Provider, { value }, children);
2519
+ return React11.createElement(Ctx.Provider, { value }, children);
2068
2520
  }
2069
2521
  function FormFillDialog({ templateId, personId, personName, onSaved, onClose, frame = "modal" }) {
2070
- const provider = React10.useMemo(() => createSafeDataProvider(
2522
+ const provider = React11.useMemo(() => createSafeDataProvider(
2071
2523
  () => createSupabaseFormsProvider(),
2072
2524
  () => createMockFormsProvider()
2073
2525
  ), []);
2074
- const store = React10.useMemo(() => createCustomFormsStore(provider), [provider]);
2075
- const config = React10.useMemo(() => resolveConfig(), []);
2076
- const [template, setTemplate] = React10.useState(null);
2077
- const [erro, setErro] = React10.useState(null);
2078
- const [data, setData] = React10.useState({});
2079
- const [salvando, setSalvando] = React10.useState(false);
2526
+ const store = React11.useMemo(() => createCustomFormsStore(provider), [provider]);
2527
+ const config = React11.useMemo(() => resolveConfig(), []);
2528
+ const [template, setTemplate] = React11.useState(null);
2529
+ const [erro, setErro] = React11.useState(null);
2530
+ const [data, setData] = React11.useState({});
2531
+ const [salvando, setSalvando] = React11.useState(false);
2080
2532
  async function salvar() {
2081
2533
  if (!template) return;
2082
2534
  setSalvando(true);
@@ -2096,7 +2548,7 @@ function FormFillDialog({ templateId, personId, personName, onSaved, onClose, fr
2096
2548
  setSalvando(false);
2097
2549
  }
2098
2550
  }
2099
- React10.useEffect(() => {
2551
+ React11.useEffect(() => {
2100
2552
  let vivo = true;
2101
2553
  (async () => {
2102
2554
  try {
@@ -2156,16 +2608,19 @@ function registerBuiltInProviders(formsProvider) {
2156
2608
  async getTypes() {
2157
2609
  try {
2158
2610
  const result = await formsProvider.getTemplates({});
2159
- return result.data.map((tpl) => ({
2160
- id: `template:${tpl.id}`,
2161
- label: tpl.name,
2162
- icon: FileText,
2163
- group: "Formul\xE1rios",
2164
- groupOrder: 10,
2165
- order: 0,
2166
- description: tpl.description,
2167
- _templateId: tpl.id
2168
- }));
2611
+ return result.data.map((tpl) => {
2612
+ const category = normalizeCategory(tpl.category);
2613
+ return {
2614
+ id: `template:${tpl.id}`,
2615
+ label: tpl.name,
2616
+ icon: CATEGORY_ICONS[category] ?? FileText,
2617
+ group: categoryLabel(category),
2618
+ groupOrder: CATEGORY_GROUP_ORDER[category] ?? 50,
2619
+ order: 0,
2620
+ description: tpl.description,
2621
+ _templateId: tpl.id
2622
+ };
2623
+ });
2169
2624
  } catch {
2170
2625
  return [];
2171
2626
  }
@@ -2175,8 +2630,8 @@ function registerBuiltInProviders(formsProvider) {
2175
2630
  id: "core:files",
2176
2631
  getTypes() {
2177
2632
  return [
2178
- { id: "core:image", label: "Imagem", icon: Image, group: "Arquivos", groupOrder: 90, order: 0 },
2179
- { id: "core:attachment", label: "Anexo", icon: Paperclip, group: "Arquivos", groupOrder: 90, order: 1 }
2633
+ { id: "core:image", label: tl("Image", "Imagem"), icon: Image, group: tl("Files", "Arquivos"), groupOrder: 90, order: 0 },
2634
+ { id: "core:attachment", label: tl("Attachment", "Anexo"), icon: Paperclip, group: tl("Files", "Arquivos"), groupOrder: 90, order: 1 }
2180
2635
  ];
2181
2636
  }
2182
2637
  });
@@ -2191,7 +2646,85 @@ function createCustomFormsPlugin(options) {
2191
2646
  const store = createCustomFormsStore(provider);
2192
2647
  registerBuiltInProviders(provider);
2193
2648
  registerFormsTimelineSource();
2649
+ registerFormsTabCounts();
2650
+ const contractTemplateEntity = {
2651
+ name: "Modelo de contrato",
2652
+ namePlural: "Modelos de contrato",
2653
+ icon: "FileSignature",
2654
+ displayField: "name",
2655
+ defaultSort: "name",
2656
+ formColumns: 2,
2657
+ facets: [activeFacet({ active: "Ativos", inactive: "Inativos", all: "Todos" })],
2658
+ fieldGroups: [
2659
+ { id: "identity", label: "O contrato", columns: 1 },
2660
+ {
2661
+ id: "when",
2662
+ label: "Quando ele nasce",
2663
+ columns: 1,
2664
+ description: "As duas portas convivem: um or\xE7amento aprovado gera o contrato do pacote, e a execu\xE7\xE3o gera o termo daquele procedimento."
2665
+ },
2666
+ {
2667
+ id: "signature",
2668
+ label: "Assinatura",
2669
+ columns: 1,
2670
+ description: "Os dois primeiros dizem COMO se assina. O terceiro diz o que acontece se ningu\xE9m assinar \u2014 e \xE9 o \xFAnico que bloqueia."
2671
+ },
2672
+ { ...RECORD_GROUP }
2673
+ ],
2674
+ fields: [
2675
+ { key: "name", label: "Nome", type: "text", required: true, showInTable: true, searchable: true, group: "identity" },
2676
+ // Markdown e não editor de página: quem escreve contrato precisa de
2677
+ // parágrafo e negrito. As variáveis ({{cliente.nome}}) são substituídas
2678
+ // na geração.
2679
+ {
2680
+ key: "content",
2681
+ label: "Texto do contrato",
2682
+ type: "markdown",
2683
+ showInTable: false,
2684
+ group: "identity",
2685
+ hint: "Use {{cliente.nome}}, {{servico.nome}} e afins \u2014 a gera\xE7\xE3o substitui."
2686
+ },
2687
+ { key: "generateOnQuoteApproval", label: "Gerar quando um or\xE7amento for aprovado", type: "boolean", showInTable: false, defaultValue: false, group: "when" },
2688
+ { key: "generateOnExecution", label: "Gerar na execu\xE7\xE3o do servi\xE7o", type: "boolean", showInTable: false, defaultValue: false, group: "when" },
2689
+ // Um contrato por atendimento ou um por serviço: é o que decide se a
2690
+ // cliente assina uma folha ou seis.
2691
+ {
2692
+ key: "groupServices",
2693
+ label: "Um contrato para todos os servi\xE7os do atendimento",
2694
+ type: "boolean",
2695
+ showInTable: false,
2696
+ defaultValue: true,
2697
+ group: "when",
2698
+ hint: "Desligado, sai um contrato por servi\xE7o."
2699
+ },
2700
+ { key: "requireScreenSignature", label: "Assinar na tela", type: "boolean", showInTable: true, defaultValue: false, group: "signature" },
2701
+ { key: "requireSelfie", label: "Pedir selfie de quem assina", type: "boolean", showInTable: false, defaultValue: false, group: "signature" },
2702
+ {
2703
+ key: "requireSignatureForExecution",
2704
+ label: "Sem assinatura, n\xE3o executa",
2705
+ type: "boolean",
2706
+ showInTable: true,
2707
+ defaultValue: false,
2708
+ group: "signature",
2709
+ hint: "O \xFAnico que bloqueia: o servi\xE7o n\xE3o pode ser executado sem o contrato assinado."
2710
+ },
2711
+ ...recordRailFields({ tagsTable: "plg_forms_contract_templates", activeLabel: "Ativo" })
2712
+ ],
2713
+ data: {
2714
+ table: "plg_forms_contract_templates",
2715
+ schema: "public",
2716
+ tenantScoped: true,
2717
+ searchColumns: ["name"]
2718
+ }
2719
+ };
2194
2720
  const formRegistries = [
2721
+ {
2722
+ id: "contract-templates",
2723
+ entity: contractTemplateEntity,
2724
+ icon: "FileSignature",
2725
+ description: "Modelos de contrato que o cliente assina",
2726
+ agentHint: "O MODELO do contrato \u2014 n\xE3o \xE9 um contrato gerado nem uma assinatura."
2727
+ },
2195
2728
  {
2196
2729
  id: "form-categories",
2197
2730
  entity: {
@@ -2224,14 +2757,14 @@ function createCustomFormsPlugin(options) {
2224
2757
  }
2225
2758
  ];
2226
2759
  const registries = [...formRegistries, ...options?.settingsRegistries ?? []];
2227
- const SettingsComponent = () => React10.createElement(PluginSettingsPanel, {
2760
+ const SettingsComponent = () => React11.createElement(PluginSettingsPanel, {
2228
2761
  title: config.labels.settingsLabel,
2229
2762
  customTabs: [
2230
2763
  {
2231
2764
  id: "forms",
2232
2765
  label: config.labels.pageTitle,
2233
2766
  icon: "FileText",
2234
- content: React10.createElement(CustomFormsSettingsTab, { config, provider, store })
2767
+ content: React11.createElement(CustomFormsSettingsTab, { config, provider, store })
2235
2768
  }
2236
2769
  ],
2237
2770
  registries,
@@ -2239,8 +2772,10 @@ function createCustomFormsPlugin(options) {
2239
2772
  hostPluginId: "custom_forms"
2240
2773
  });
2241
2774
  SettingsComponent.displayName = "CustomFormsSettingsPanel";
2242
- const PersonDocumentsWidget2 = (props) => React10.createElement(PersonDocumentsWidget, { ...props, config, provider, store });
2775
+ const PersonDocumentsWidget2 = (props) => React11.createElement(PersonDocumentsWidget, { ...props, config, provider, store, surface: "records" });
2243
2776
  PersonDocumentsWidget2.displayName = "PersonDocumentsWidget";
2777
+ const PersonFilesWidget = (props) => React11.createElement(PersonDocumentsWidget, { ...props, config, provider, store, surface: "files" });
2778
+ PersonFilesWidget.displayName = "PersonFilesWidget";
2244
2779
  return {
2245
2780
  id: "custom_forms",
2246
2781
  defaultAgentRole: "setup",
@@ -2279,6 +2814,12 @@ function createCustomFormsPlugin(options) {
2279
2814
  zone: "person.detail.documents",
2280
2815
  component: PersonDocumentsWidget2,
2281
2816
  order: 0
2817
+ },
2818
+ {
2819
+ id: "person-files-tab",
2820
+ zone: "person.detail.files",
2821
+ component: PersonFilesWidget,
2822
+ order: 0
2282
2823
  }
2283
2824
  ],
2284
2825
  registries,