@fayz-ai/plugin-forms 0.14.8 → 0.15.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.
- package/dist/{FormBuilder-EI7ILXQW.js → FormBuilder-Q2Q42HAT.js} +44 -35
- package/dist/FormBuilder-Q2Q42HAT.js.map +1 -0
- package/dist/{chunk-ANOY2YYX.js → chunk-JRR7A5HN.js} +59 -42
- package/dist/chunk-JRR7A5HN.js.map +1 -0
- package/dist/components/BuilderCanvas.d.ts +1 -1
- package/dist/components/BuilderCanvas.d.ts.map +1 -1
- package/dist/components/BuilderField.d.ts +1 -1
- package/dist/components/BuilderField.d.ts.map +1 -1
- package/dist/components/DocumentFormDialog.d.ts +6 -5
- package/dist/components/DocumentFormDialog.d.ts.map +1 -1
- package/dist/components/DocumentList.d.ts +5 -5
- package/dist/components/DocumentList.d.ts.map +1 -1
- package/dist/components/DocumentPreview.d.ts +3 -3
- package/dist/components/DocumentPreview.d.ts.map +1 -1
- package/dist/components/FieldConfigDialog.d.ts +1 -1
- package/dist/components/FieldConfigDialog.d.ts.map +1 -1
- package/dist/components/FieldPalette.d.ts +1 -1
- package/dist/components/FieldPalette.d.ts.map +1 -1
- package/dist/components/FormBuilder.d.ts +4 -4
- package/dist/components/FormBuilder.d.ts.map +1 -1
- package/dist/components/FormFillDialog.d.ts +9 -32
- package/dist/components/FormFillDialog.d.ts.map +1 -1
- package/dist/components/FormRenderer.d.ts +1 -1
- package/dist/components/FormRenderer.d.ts.map +1 -1
- package/dist/components/FormViewer.d.ts +3 -3
- package/dist/components/FormViewer.d.ts.map +1 -1
- package/dist/components/PersonAnswerAlerts.d.ts +2 -2
- package/dist/components/PersonAnswerAlerts.d.ts.map +1 -1
- package/dist/components/PersonDocumentsWidget.d.ts +4 -4
- package/dist/components/PersonDocumentsWidget.d.ts.map +1 -1
- package/dist/components/PersonFormsAlert.d.ts +7 -41
- package/dist/components/PersonFormsAlert.d.ts.map +1 -1
- package/dist/components/TemplateSelector.d.ts +3 -3
- package/dist/components/TemplateSelector.d.ts.map +1 -1
- package/dist/components/fields/FieldTypeSettings.d.ts +1 -1
- package/dist/components/fields/FieldTypeSettings.d.ts.map +1 -1
- package/dist/components/fields/StencilCamera.d.ts.map +1 -1
- package/dist/components/fields/fields.d.ts +3 -7
- package/dist/components/fields/fields.d.ts.map +1 -1
- package/dist/components/fields/media.d.ts +1 -1
- package/dist/components/fields/media.d.ts.map +1 -1
- package/dist/components/formsZoneWidgets.d.ts +5 -0
- package/dist/components/formsZoneWidgets.d.ts.map +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/context.d.ts +4 -4
- package/dist/context.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +382 -1099
- package/dist/index.js.map +1 -1
- package/dist/lib/attendance-contracts.d.ts.map +1 -1
- package/dist/lib/categories.d.ts +2 -2
- package/dist/lib/categories.d.ts.map +1 -1
- package/dist/lib/contract-render.d.ts.map +1 -1
- package/dist/lib/person-fields.d.ts +1 -1
- package/dist/lib/person-fields.d.ts.map +1 -1
- package/dist/lib/print.d.ts +2 -1
- package/dist/lib/print.d.ts.map +1 -1
- package/dist/settings/v1-settings.d.ts +2 -0
- package/dist/settings/v1-settings.d.ts.map +1 -0
- package/dist/views/CustomFormsSettingsTab.d.ts +4 -4
- package/dist/views/CustomFormsSettingsTab.d.ts.map +1 -1
- package/dist/views/TemplateListView.d.ts +3 -3
- package/dist/views/TemplateListView.d.ts.map +1 -1
- package/package.json +5 -4
- package/dist/FormBuilder-EI7ILXQW.js.map +0 -1
- package/dist/chunk-ANOY2YYX.js.map +0 -1
- package/dist/data/index.d.ts +0 -4
- package/dist/data/index.d.ts.map +0 -1
- package/dist/data/mock.d.ts +0 -3
- package/dist/data/mock.d.ts.map +0 -1
- package/dist/data/supabase.d.ts +0 -3
- package/dist/data/supabase.d.ts.map +0 -1
- package/dist/data/tables.d.ts +0 -7
- package/dist/data/tables.d.ts.map +0 -1
- package/dist/data/types.d.ts +0 -57
- package/dist/data/types.d.ts.map +0 -1
- package/dist/lib/person-requirements.d.ts +0 -32
- package/dist/lib/person-requirements.d.ts.map +0 -1
- package/dist/lib/tenant.d.ts +0 -3
- package/dist/lib/tenant.d.ts.map +0 -1
- package/dist/store.d.ts +0 -40
- package/dist/store.d.ts.map +0 -1
- package/dist/types.d.ts +0 -248
- package/dist/types.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,772 +1,284 @@
|
|
|
1
|
-
import { isFieldEmpty, CustomFormsProvider, FormRenderer, sanitizeHtml, createOrUpdateFormTool, alertOf, answerAlerts, yesNoAnswer, prefillFromPerson, printDocument, htmlToPlainText, isMedia } from './chunk-
|
|
2
|
-
import
|
|
3
|
-
import { tl,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { isFieldEmpty, CustomFormsProvider, FormRenderer, sanitizeHtml, createOrUpdateFormTool, alertOf, answerAlerts, yesNoAnswer, prefillFromPerson, printDocument, htmlToPlainText, isMedia } from './chunk-JRR7A5HN.js';
|
|
2
|
+
import React13, { useState, useCallback, useRef, useMemo, useEffect } from 'react';
|
|
3
|
+
import { tl, describeError, useActiveTenantId, registerTranslations, recordRailFields, RECORD_GROUP, activeFacet, getSupabaseClientOptional, getActiveTenantId, useTranslation, formatCurrency, useDataVersion } from '@fayz-ai/core';
|
|
4
|
+
import { T } from '@fayz-ai/forms/data/tables';
|
|
5
|
+
import { createSafeFormsProvider, createFormsStore, FORM_TEMPLATE_EVENT } from '@fayz-ai/forms/store';
|
|
6
|
+
import { defineSettingsTab, PluginSettingsPanel, registerTimelineSource, registerTabCountProvider, useLimitGuard, invalidateLimit, askAI, PermissionGate, PageActions, invalidateTabCounts, TimelineList } from '@fayz-ai/admin';
|
|
7
|
+
import { FORMS_ZONES } from '@fayz-ai/forms/views/zones';
|
|
8
|
+
import { Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Skeleton, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, Input, Badge, Switch, RowActions, PageHeaderActions, Card, DataTable, useSaveBar, Dropdown, DropdownTrigger, DropdownContent, DropdownSeparator, DropdownLabel, ConfirmDialog } from '@fayz-ai/ui';
|
|
9
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
10
|
import { ClipboardList, QrCode, Printer, Check, CheckCircle2, Clock, Circle, X, Camera, FileText, FileSignature, FileCheck2, Activity, Stethoscope, Image, Paperclip, AlertTriangle, ChevronUp, ChevronDown, Plus, WandSparkles, Pencil, Archive, Search, ArrowLeft, EyeOff, Eye, Upload } from 'lucide-react';
|
|
7
|
-
import {
|
|
8
|
-
|
|
11
|
+
import { shownInChecklist, isOutstanding } from '@fayz-ai/forms/domain/requirement';
|
|
12
|
+
export { isOutstanding } from '@fayz-ai/forms/domain/requirement';
|
|
13
|
+
export { getPersonFormRequirements } from '@fayz-ai/forms/data/supabase';
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function
|
|
20
|
-
return crypto.randomUUID();
|
|
21
|
-
}
|
|
22
|
-
function now() {
|
|
23
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
24
|
-
}
|
|
25
|
-
function createMockFormsProvider() {
|
|
26
|
-
const templates = [];
|
|
27
|
-
const documents = [];
|
|
28
|
-
const files = [];
|
|
15
|
+
var defaultLabels = () => ({
|
|
16
|
+
pageTitle: tl("Forms", "Formul\xE1rios"),
|
|
17
|
+
settingsLabel: tl("Forms", "Formul\xE1rios"),
|
|
18
|
+
settingsSubtitle: tl("Create and manage custom forms for your business", "Crie e organize os formul\xE1rios da sua empresa"),
|
|
19
|
+
templates: tl("Templates", "Modelos"),
|
|
20
|
+
documents: tl("Documents", "Documentos"),
|
|
21
|
+
newTemplate: tl("New Template", "Novo modelo"),
|
|
22
|
+
addDocument: tl("Add Document", "Adicionar documento")
|
|
23
|
+
});
|
|
24
|
+
function resolveConfig(options) {
|
|
29
25
|
return {
|
|
30
|
-
|
|
31
|
-
async getTemplates(query) {
|
|
32
|
-
let filtered = templates.filter((t) => !t.isDeleted && t.isCurrent);
|
|
33
|
-
if (query.category) {
|
|
34
|
-
filtered = filtered.filter((t) => t.category === query.category);
|
|
35
|
-
}
|
|
36
|
-
if (query.search) {
|
|
37
|
-
const s = query.search.toLowerCase();
|
|
38
|
-
filtered = filtered.filter((t) => t.name.toLowerCase().includes(s));
|
|
39
|
-
}
|
|
40
|
-
const page = query.page ?? 1;
|
|
41
|
-
const pageSize = query.pageSize ?? 25;
|
|
42
|
-
const start = (page - 1) * pageSize;
|
|
43
|
-
return { data: filtered.slice(start, start + pageSize), total: filtered.length };
|
|
44
|
-
},
|
|
45
|
-
async getTemplateById(id) {
|
|
46
|
-
return templates.find((t) => t.id === id && !t.isDeleted) ?? null;
|
|
47
|
-
},
|
|
48
|
-
async createTemplate(input) {
|
|
49
|
-
const template = {
|
|
50
|
-
id: uuid(),
|
|
51
|
-
tenantId: "mock-tenant",
|
|
52
|
-
name: input.name,
|
|
53
|
-
description: input.description,
|
|
54
|
-
category: input.category,
|
|
55
|
-
version: 1,
|
|
56
|
-
isCurrent: true,
|
|
57
|
-
schema: input.schema,
|
|
58
|
-
specialty: input.specialty,
|
|
59
|
-
tags: input.tags ?? [],
|
|
60
|
-
metadata: input.metadata ?? {},
|
|
61
|
-
isActive: true,
|
|
62
|
-
isDeleted: false,
|
|
63
|
-
requiredOnAttendance: false,
|
|
64
|
-
alertWhenMissing: false,
|
|
65
|
-
validityDays: null,
|
|
66
|
-
createdAt: now(),
|
|
67
|
-
updatedAt: now()
|
|
68
|
-
};
|
|
69
|
-
templates.push(template);
|
|
70
|
-
return template;
|
|
71
|
-
},
|
|
72
|
-
async updateTemplate(id, input) {
|
|
73
|
-
const idx = templates.findIndex((t) => t.id === id);
|
|
74
|
-
if (idx === -1) throw new Error("Template not found");
|
|
75
|
-
const existing = templates[idx];
|
|
76
|
-
const updated = {
|
|
77
|
-
...existing,
|
|
78
|
-
...input,
|
|
79
|
-
tags: input.tags ?? existing.tags,
|
|
80
|
-
metadata: input.metadata ?? existing.metadata,
|
|
81
|
-
schema: input.schema ?? existing.schema,
|
|
82
|
-
updatedAt: now()
|
|
83
|
-
};
|
|
84
|
-
templates[idx] = updated;
|
|
85
|
-
return updated;
|
|
86
|
-
},
|
|
87
|
-
async deleteTemplate(id) {
|
|
88
|
-
const idx = templates.findIndex((t) => t.id === id);
|
|
89
|
-
if (idx !== -1) templates[idx] = { ...templates[idx], isDeleted: true };
|
|
90
|
-
},
|
|
91
|
-
async getTemplateUsage() {
|
|
92
|
-
const usage = {};
|
|
93
|
-
for (const doc of documents) {
|
|
94
|
-
if (doc.isDeleted || !doc.templateId) continue;
|
|
95
|
-
const current = usage[doc.templateId] ?? { documentCount: 0, lastDocumentAt: null };
|
|
96
|
-
current.documentCount += 1;
|
|
97
|
-
if (!current.lastDocumentAt || doc.createdAt > current.lastDocumentAt) {
|
|
98
|
-
current.lastDocumentAt = doc.createdAt;
|
|
99
|
-
}
|
|
100
|
-
usage[doc.templateId] = current;
|
|
101
|
-
}
|
|
102
|
-
return usage;
|
|
103
|
-
},
|
|
104
|
-
// ── Documents ──────────────────────────────────────────
|
|
105
|
-
async getDocuments(query) {
|
|
106
|
-
let filtered = documents.filter((d) => !d.isDeleted);
|
|
107
|
-
if (query.personId) {
|
|
108
|
-
filtered = filtered.filter((d) => d.personId === query.personId);
|
|
109
|
-
}
|
|
110
|
-
if (query.templateId) {
|
|
111
|
-
filtered = filtered.filter((d) => d.templateId === query.templateId);
|
|
112
|
-
}
|
|
113
|
-
if (query.status) {
|
|
114
|
-
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
115
|
-
filtered = filtered.filter((d) => statuses.includes(d.status));
|
|
116
|
-
}
|
|
117
|
-
if (query.search) {
|
|
118
|
-
const s = query.search.toLowerCase();
|
|
119
|
-
filtered = filtered.filter(
|
|
120
|
-
(d) => d.title?.toLowerCase().includes(s) || d.templateName?.toLowerCase().includes(s)
|
|
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
|
-
}
|
|
129
|
-
filtered.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
130
|
-
const page = query.page ?? 1;
|
|
131
|
-
const pageSize = query.pageSize ?? 25;
|
|
132
|
-
const start = (page - 1) * pageSize;
|
|
133
|
-
return { data: filtered.slice(start, start + pageSize), total: filtered.length };
|
|
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
|
-
},
|
|
146
|
-
async getDocumentById(id) {
|
|
147
|
-
return documents.find((d) => d.id === id && !d.isDeleted) ?? null;
|
|
148
|
-
},
|
|
149
|
-
async createDocument(input) {
|
|
150
|
-
const template = templates.find((t) => t.id === input.templateId);
|
|
151
|
-
const doc = {
|
|
152
|
-
id: uuid(),
|
|
153
|
-
tenantId: "mock-tenant",
|
|
154
|
-
templateId: input.templateId ?? "",
|
|
155
|
-
templateName: template?.name,
|
|
156
|
-
templateCategory: template?.category,
|
|
157
|
-
personId: input.personId,
|
|
158
|
-
title: input.title,
|
|
159
|
-
data: input.data ?? {},
|
|
160
|
-
kind: input.kind ?? (input.templateId ? "form" : "attachment"),
|
|
161
|
-
status: input.status ?? "draft",
|
|
162
|
-
metadata: {},
|
|
163
|
-
isDeleted: false,
|
|
164
|
-
createdAt: now(),
|
|
165
|
-
updatedAt: now()
|
|
166
|
-
};
|
|
167
|
-
documents.push(doc);
|
|
168
|
-
return doc;
|
|
169
|
-
},
|
|
170
|
-
async updateDocument(id, input) {
|
|
171
|
-
const idx = documents.findIndex((d) => d.id === id);
|
|
172
|
-
if (idx === -1) throw new Error("Document not found");
|
|
173
|
-
const existing = documents[idx];
|
|
174
|
-
const updated = {
|
|
175
|
-
...existing,
|
|
176
|
-
...input,
|
|
177
|
-
data: input.data ?? existing.data,
|
|
178
|
-
updatedAt: now()
|
|
179
|
-
};
|
|
180
|
-
documents[idx] = updated;
|
|
181
|
-
return updated;
|
|
182
|
-
},
|
|
183
|
-
async deleteDocument(id) {
|
|
184
|
-
const idx = documents.findIndex((d) => d.id === id);
|
|
185
|
-
if (idx !== -1) documents[idx] = { ...documents[idx], isDeleted: true };
|
|
186
|
-
},
|
|
187
|
-
// ── Files ──────────────────────────────────────────────
|
|
188
|
-
async getDocumentFiles(documentId) {
|
|
189
|
-
return files.filter((f) => f.documentId === documentId);
|
|
190
|
-
},
|
|
191
|
-
async uploadFile(documentId, fieldKey, file) {
|
|
192
|
-
const docFile = {
|
|
193
|
-
id: uuid(),
|
|
194
|
-
tenantId: "mock-tenant",
|
|
195
|
-
documentId,
|
|
196
|
-
fieldKey,
|
|
197
|
-
fileUrl: URL.createObjectURL(file),
|
|
198
|
-
fileName: file.name,
|
|
199
|
-
fileSize: file.size,
|
|
200
|
-
mimeType: file.type,
|
|
201
|
-
sortOrder: files.filter((f) => f.documentId === documentId && f.fieldKey === fieldKey).length,
|
|
202
|
-
metadata: {},
|
|
203
|
-
createdAt: now()
|
|
204
|
-
};
|
|
205
|
-
files.push(docFile);
|
|
206
|
-
return docFile;
|
|
207
|
-
},
|
|
208
|
-
async uploadMedia(blob) {
|
|
209
|
-
return { path: URL.createObjectURL(blob) };
|
|
210
|
-
},
|
|
211
|
-
async mediaUrl(path) {
|
|
212
|
-
return path;
|
|
213
|
-
},
|
|
214
|
-
async searchCatalog() {
|
|
215
|
-
return [];
|
|
216
|
-
},
|
|
217
|
-
async deleteFile(fileId) {
|
|
218
|
-
const idx = files.findIndex((f) => f.id === fileId);
|
|
219
|
-
if (idx !== -1) files.splice(idx, 1);
|
|
220
|
-
}
|
|
26
|
+
labels: { ...defaultLabels(), ...options?.labels }
|
|
221
27
|
};
|
|
222
28
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const supabase = getSupabaseClientOptional();
|
|
231
|
-
if (!supabase) throw new Error("Supabase not initialized");
|
|
232
|
-
return supabase;
|
|
233
|
-
}
|
|
234
|
-
var MEDIA_BUCKET = "attachments";
|
|
235
|
-
var SIGNED_TTL_S = 3600;
|
|
236
|
-
var signedCache = /* @__PURE__ */ new Map();
|
|
237
|
-
var USAGE_FALLBACK_LIMIT = 5e3;
|
|
238
|
-
async function countTemplateUsageClientSide(db2, tenantId2) {
|
|
239
|
-
try {
|
|
240
|
-
const { data, error, count } = await db2.from("v_documents").select("template_id, created_at", { count: "exact" }).eq("tenant_id", tenantId2).eq("is_active", true).not("template_id", "is", null).limit(USAGE_FALLBACK_LIMIT);
|
|
241
|
-
if (error) return {};
|
|
242
|
-
if (typeof count === "number" && count > USAGE_FALLBACK_LIMIT) return {};
|
|
243
|
-
const usage = {};
|
|
244
|
-
for (const row of data ?? []) {
|
|
245
|
-
const current = usage[row.template_id] ?? { documentCount: 0, lastDocumentAt: null };
|
|
246
|
-
current.documentCount += 1;
|
|
247
|
-
if (!current.lastDocumentAt || row.created_at > current.lastDocumentAt) {
|
|
248
|
-
current.lastDocumentAt = row.created_at;
|
|
249
|
-
}
|
|
250
|
-
usage[row.template_id] = current;
|
|
29
|
+
function recommendationsOf(template, data) {
|
|
30
|
+
const by = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const f of template.schema.fields) {
|
|
32
|
+
if (f.type !== "recommendation" || !Array.isArray(data[f.id])) continue;
|
|
33
|
+
for (const l of data[f.id]) {
|
|
34
|
+
const cur = by.get(l.serviceId);
|
|
35
|
+
by.set(l.serviceId, cur ? { ...cur, quantity: cur.quantity + l.quantity } : { ...l });
|
|
251
36
|
}
|
|
252
|
-
return usage;
|
|
253
|
-
} catch {
|
|
254
|
-
return {};
|
|
255
37
|
}
|
|
38
|
+
return [...by.values()];
|
|
256
39
|
}
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
notes: r.notes,
|
|
297
|
-
metadata: r.metadata ?? {},
|
|
298
|
-
isDeleted: false,
|
|
299
|
-
createdBy: r.created_by,
|
|
300
|
-
updatedBy: r.updated_by,
|
|
301
|
-
createdAt: r.created_at,
|
|
302
|
-
updatedAt: r.updated_at,
|
|
303
|
-
// Extra fields from core document
|
|
304
|
-
kind: r.kind,
|
|
305
|
-
fileUrl: r.file_url,
|
|
306
|
-
fileName: r.file_name,
|
|
307
|
-
fileSize: r.file_size,
|
|
308
|
-
mimeType: r.mime_type
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
function mapFile(r) {
|
|
312
|
-
return {
|
|
313
|
-
id: r.id,
|
|
314
|
-
tenantId: r.tenant_id,
|
|
315
|
-
documentId: r.document_id,
|
|
316
|
-
fieldKey: r.field_key,
|
|
317
|
-
fileUrl: r.file_url,
|
|
318
|
-
fileName: r.file_name,
|
|
319
|
-
fileSize: r.file_size,
|
|
320
|
-
mimeType: r.mime_type,
|
|
321
|
-
sortOrder: r.sort_order,
|
|
322
|
-
metadata: r.metadata ?? {},
|
|
323
|
-
createdAt: r.created_at
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function createSupabaseFormsProvider() {
|
|
327
|
-
return {
|
|
328
|
-
// ── Templates ──────────────────────────────────────────
|
|
329
|
-
async getTemplates(query) {
|
|
330
|
-
const db2 = getClient();
|
|
331
|
-
let qb = db2.from(T.templates).select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_current", true).eq("is_deleted", false).eq("is_active", true);
|
|
332
|
-
if (query.category) qb = qb.eq("category", query.category);
|
|
333
|
-
if (query.search) qb = qb.ilike("name", `%${query.search}%`);
|
|
334
|
-
const page = query.page ?? 1;
|
|
335
|
-
const pageSize = query.pageSize ?? 25;
|
|
336
|
-
qb = qb.order("updated_at", { ascending: false }).range((page - 1) * pageSize, page * pageSize - 1);
|
|
337
|
-
const { data, count, error } = await qb;
|
|
338
|
-
if (error) throw new Error(error.message);
|
|
339
|
-
return { data: (data ?? []).map(mapTemplate), total: count ?? 0 };
|
|
340
|
-
},
|
|
341
|
-
async getTemplateById(id) {
|
|
342
|
-
const db2 = getClient();
|
|
343
|
-
const { data, error } = await db2.from(T.templates).select("*").eq("id", id).eq("is_deleted", false).single();
|
|
344
|
-
if (error || !data) return null;
|
|
345
|
-
return mapTemplate(data);
|
|
346
|
-
},
|
|
347
|
-
async createTemplate(input) {
|
|
348
|
-
const db2 = getClient();
|
|
349
|
-
const tenantId2 = getTenantId();
|
|
350
|
-
const { data, error } = await db2.from(T.templates).insert({
|
|
351
|
-
tenant_id: tenantId2,
|
|
352
|
-
name: input.name,
|
|
353
|
-
description: input.description,
|
|
354
|
-
category: input.category,
|
|
355
|
-
schema: input.schema,
|
|
356
|
-
specialty: input.specialty,
|
|
357
|
-
tags: input.tags ?? [],
|
|
358
|
-
metadata: input.metadata ?? {}
|
|
359
|
-
}).select().single();
|
|
360
|
-
if (error) throw new Error(error.message);
|
|
361
|
-
return mapTemplate(data);
|
|
362
|
-
},
|
|
363
|
-
async updateTemplate(id, input) {
|
|
364
|
-
const db2 = getClient();
|
|
365
|
-
const tenantId2 = getTenantId();
|
|
366
|
-
const { count } = await db2.from(T.documents).select("document_id", { count: "exact", head: true }).eq("template_id", id);
|
|
367
|
-
const hasDocuments = (count ?? 0) > 0;
|
|
368
|
-
if (hasDocuments && input.schema) {
|
|
369
|
-
const existing = await this.getTemplateById(id);
|
|
370
|
-
if (!existing) throw new Error("Template not found");
|
|
371
|
-
const { data: newRow, error: insertError } = await db2.from(T.templates).insert({
|
|
372
|
-
tenant_id: tenantId2,
|
|
373
|
-
name: input.name ?? existing.name,
|
|
374
|
-
description: input.description ?? existing.description,
|
|
375
|
-
category: input.category ?? existing.category,
|
|
376
|
-
schema: input.schema,
|
|
377
|
-
specialty: input.specialty ?? existing.specialty,
|
|
378
|
-
tags: input.tags ?? existing.tags,
|
|
379
|
-
metadata: input.metadata ?? existing.metadata,
|
|
380
|
-
is_active: input.isActive ?? existing.isActive,
|
|
381
|
-
version: existing.version + 1,
|
|
382
|
-
is_current: true,
|
|
383
|
-
parent_id: existing.parentId ?? existing.id,
|
|
384
|
-
// A EXIGÊNCIA É DO MODELO, NÃO DA VERSÃO. Elas eram gravadas na
|
|
385
|
-
// linha que estava sendo ARQUIVADA: editar os campos de uma
|
|
386
|
-
// anamnese exigida devolvia uma versão nova que não exigia nada,
|
|
387
|
-
// e o aviso sumia sem ninguém ter desligado nada.
|
|
388
|
-
required_on_attendance: input.requiredOnAttendance ?? existing.requiredOnAttendance,
|
|
389
|
-
validity_days: input.validityDays !== void 0 ? input.validityDays : existing.validityDays,
|
|
390
|
-
alert_when_missing: input.alertWhenMissing ?? existing.alertWhenMissing
|
|
391
|
-
}).select().single();
|
|
392
|
-
if (insertError) throw new Error(insertError.message);
|
|
393
|
-
await db2.from(T.templates).update({ is_current: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
394
|
-
return mapTemplate(newRow);
|
|
395
|
-
}
|
|
396
|
-
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
397
|
-
if (input.name !== void 0) updates.name = input.name;
|
|
398
|
-
if (input.description !== void 0) updates.description = input.description;
|
|
399
|
-
if (input.category !== void 0) updates.category = input.category;
|
|
400
|
-
if (input.schema !== void 0) updates.schema = input.schema;
|
|
401
|
-
if (input.specialty !== void 0) updates.specialty = input.specialty;
|
|
402
|
-
if (input.tags !== void 0) updates.tags = input.tags;
|
|
403
|
-
if (input.metadata !== void 0) updates.metadata = input.metadata;
|
|
404
|
-
if (input.isActive !== void 0) updates.is_active = input.isActive;
|
|
405
|
-
if (input.requiredOnAttendance !== void 0) updates.required_on_attendance = input.requiredOnAttendance;
|
|
406
|
-
if (input.validityDays !== void 0) updates.validity_days = input.validityDays;
|
|
407
|
-
if (input.alertWhenMissing !== void 0) updates.alert_when_missing = input.alertWhenMissing;
|
|
408
|
-
const { data, error } = await db2.from(T.templates).update(updates).eq("id", id).select().single();
|
|
409
|
-
if (error) throw new Error(error.message);
|
|
410
|
-
return mapTemplate(data);
|
|
411
|
-
},
|
|
412
|
-
async deleteTemplate(id) {
|
|
413
|
-
const db2 = getClient();
|
|
414
|
-
const { error } = await db2.from(T.templates).update({ is_deleted: true, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
415
|
-
if (error) throw new Error(error.message);
|
|
416
|
-
},
|
|
417
|
-
// Uso por modelo. Caminho principal: `v_forms_template_usage`, que agrega no
|
|
418
|
-
// banco (004_template_usage.sql) e devolve uma linha por modelo.
|
|
419
|
-
//
|
|
420
|
-
// O fallback existe porque um pool provisionado antes daquela migração não
|
|
421
|
-
// tem a view, e a tela não pode ficar sem o número por causa disso: ele
|
|
422
|
-
// relê as colunas mínimas dos documentos e agrega aqui. É limitado de
|
|
423
|
-
// propósito — acima do teto o número seria mentira, e mentir sobre uma
|
|
424
|
-
// contagem é pior do que omiti-la, então ele desiste em silêncio.
|
|
425
|
-
async getTemplateUsage() {
|
|
426
|
-
const db2 = getClient();
|
|
427
|
-
const tenantId2 = getTenantId();
|
|
428
|
-
if (!tenantId2) return {};
|
|
429
|
-
const { data, error } = await db2.from("v_forms_template_usage").select("template_id, document_count, last_document_at").eq("tenant_id", tenantId2);
|
|
430
|
-
if (!error) {
|
|
431
|
-
const usage = {};
|
|
432
|
-
for (const row of data ?? []) {
|
|
433
|
-
usage[row.template_id] = {
|
|
434
|
-
documentCount: Number(row.document_count ?? 0),
|
|
435
|
-
lastDocumentAt: row.last_document_at ?? null
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
return usage;
|
|
439
|
-
}
|
|
440
|
-
return countTemplateUsageClientSide(db2, tenantId2);
|
|
441
|
-
},
|
|
442
|
-
// ── Documents (public.documents + plg_forms_documents extension) ──
|
|
443
|
-
async getDocuments(query) {
|
|
444
|
-
const db2 = getClient();
|
|
445
|
-
let qb = db2.from("v_documents").select("*", { count: "exact" }).eq("tenant_id", getTenantId()).eq("is_active", true);
|
|
446
|
-
if (query.personId) qb = qb.eq("person_id", query.personId);
|
|
447
|
-
if (query.templateId) qb = qb.eq("template_id", query.templateId);
|
|
448
|
-
if (query.status) {
|
|
449
|
-
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
450
|
-
qb = qb.in("status", statuses);
|
|
451
|
-
}
|
|
452
|
-
if (query.search) qb = qb.or(`title.ilike.%${query.search}%,template_name.ilike.%${query.search}%`);
|
|
453
|
-
if (query.kinds?.length) qb = qb.in("kind", query.kinds);
|
|
454
|
-
if (query.templateCategory) qb = qb.eq("template_category", query.templateCategory);
|
|
455
|
-
const page = query.page ?? 1;
|
|
456
|
-
const pageSize = query.pageSize ?? 50;
|
|
457
|
-
qb = qb.order("created_at", { ascending: false }).range((page - 1) * pageSize, page * pageSize - 1);
|
|
458
|
-
const { data, count, error } = await qb;
|
|
459
|
-
if (error) throw new Error(error.message);
|
|
460
|
-
return { data: (data ?? []).map(mapDocument), total: count ?? 0 };
|
|
461
|
-
},
|
|
462
|
-
/**
|
|
463
|
-
* Os totais por balde, sem trazer documento nenhum.
|
|
464
|
-
*
|
|
465
|
-
* Duas colunas e nada mais: o corpo do formulário (`form_data`) é o campo
|
|
466
|
-
* pesado da view, e contar não precisa dele. Agrupa no cliente porque
|
|
467
|
-
* PostgREST não faz `group by` — e a alternativa, um `count` por balde,
|
|
468
|
-
* seriam seis idas para responder uma pergunta.
|
|
469
|
-
*/
|
|
470
|
-
async getDocumentCounts(query) {
|
|
471
|
-
const db2 = getClient();
|
|
472
|
-
let qb = db2.from("v_documents").select("kind, template_category").eq("tenant_id", getTenantId()).eq("person_id", query.personId).eq("is_active", true).neq("status", "archived");
|
|
473
|
-
if (query.kinds?.length) qb = qb.in("kind", query.kinds);
|
|
474
|
-
const { data, error } = await qb;
|
|
475
|
-
if (error) return {};
|
|
476
|
-
const counts = {};
|
|
477
|
-
for (const row of data ?? []) {
|
|
478
|
-
const bucket = row.kind === "image" || row.kind === "attachment" ? "file" : row.template_category || "general";
|
|
479
|
-
counts[bucket] = (counts[bucket] ?? 0) + 1;
|
|
480
|
-
}
|
|
481
|
-
return counts;
|
|
482
|
-
},
|
|
483
|
-
async getDocumentById(id) {
|
|
484
|
-
const db2 = getClient();
|
|
485
|
-
const { data, error } = await db2.from("v_documents").select("*").eq("id", id).single();
|
|
486
|
-
if (error || !data) return null;
|
|
487
|
-
return mapDocument(data);
|
|
488
|
-
},
|
|
489
|
-
async createDocument(input) {
|
|
490
|
-
const tenantId2 = getTenantId();
|
|
491
|
-
const isForm = !!input.templateId;
|
|
492
|
-
const { data: coreDoc, error: coreError } = await getClient().from("documents").insert({
|
|
493
|
-
tenant_id: tenantId2,
|
|
494
|
-
kind: input.kind ?? (isForm ? "form" : "attachment"),
|
|
495
|
-
person_id: input.personId,
|
|
496
|
-
title: input.title,
|
|
497
|
-
status: input.status ?? "draft",
|
|
498
|
-
file_url: input.fileUrl,
|
|
499
|
-
file_name: input.fileName,
|
|
500
|
-
file_size: input.fileSize,
|
|
501
|
-
mime_type: input.mimeType,
|
|
502
|
-
subject_type: input.subjectType,
|
|
503
|
-
subject_id: input.subjectId,
|
|
504
|
-
metadata: {}
|
|
505
|
-
}).select().single();
|
|
506
|
-
if (coreError) throw new Error(coreError.message);
|
|
507
|
-
if (isForm && input.templateId) {
|
|
508
|
-
const db2 = getClient();
|
|
509
|
-
const { error: extError } = await db2.from(T.documents).insert({
|
|
510
|
-
document_id: coreDoc.id,
|
|
511
|
-
tenant_id: tenantId2,
|
|
512
|
-
template_id: input.templateId,
|
|
513
|
-
data: input.data ?? {},
|
|
514
|
-
product_id: input.productId ?? null,
|
|
515
|
-
professional_id: input.professionalId ?? null
|
|
516
|
-
});
|
|
517
|
-
if (extError) {
|
|
518
|
-
await getClient().from("documents").delete().eq("id", coreDoc.id);
|
|
519
|
-
throw new Error(extError.message);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
const doc = await this.getDocumentById(coreDoc.id);
|
|
523
|
-
return doc;
|
|
524
|
-
},
|
|
525
|
-
async updateDocument(id, input) {
|
|
526
|
-
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
527
|
-
const coreUpdates = { updated_at: now2 };
|
|
528
|
-
if (input.status !== void 0) coreUpdates.status = input.status;
|
|
529
|
-
if (input.title !== void 0) coreUpdates.title = input.title;
|
|
530
|
-
if (input.notes !== void 0) coreUpdates.notes = input.notes;
|
|
531
|
-
const { error: coreError } = await getClient().from("documents").update(coreUpdates).eq("id", id);
|
|
532
|
-
if (coreError) throw new Error(coreError.message);
|
|
533
|
-
if (input.data !== void 0) {
|
|
534
|
-
const db2 = getClient();
|
|
535
|
-
await db2.from(T.documents).update({ data: input.data, updated_at: now2 }).eq("document_id", id);
|
|
536
|
-
}
|
|
537
|
-
const doc = await this.getDocumentById(id);
|
|
538
|
-
return doc;
|
|
539
|
-
},
|
|
540
|
-
async deleteDocument(id) {
|
|
541
|
-
const { error } = await getClient().from("documents").update({ is_active: false, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
542
|
-
if (error) throw new Error(error.message);
|
|
543
|
-
},
|
|
544
|
-
// ── Files ──────────────────────────────────────────────
|
|
545
|
-
async getDocumentFiles(documentId) {
|
|
546
|
-
const db2 = getClient();
|
|
547
|
-
const { data, error } = await db2.from(T.documentFiles).select("*").eq("document_id", documentId).order("sort_order");
|
|
548
|
-
if (error) throw new Error(error.message);
|
|
549
|
-
return (data ?? []).map(mapFile);
|
|
550
|
-
},
|
|
551
|
-
async uploadFile(documentId, fieldKey, file) {
|
|
552
|
-
const db2 = getClient();
|
|
553
|
-
const tenantId2 = getTenantId();
|
|
554
|
-
const ext = file.name.split(".").pop() ?? "bin";
|
|
555
|
-
const storagePath = `${tenantId2}/forms/${documentId}/${fieldKey}/${crypto.randomUUID()}.${ext}`;
|
|
556
|
-
const { error: uploadError } = await db2.storage.from("documents").upload(storagePath, file, { contentType: file.type });
|
|
557
|
-
if (uploadError) throw new Error(uploadError.message);
|
|
558
|
-
const { data: urlData } = db2.storage.from("documents").getPublicUrl(storagePath);
|
|
559
|
-
const { data, error } = await db2.from(T.documentFiles).insert({
|
|
560
|
-
tenant_id: tenantId2,
|
|
561
|
-
document_id: documentId,
|
|
562
|
-
field_key: fieldKey,
|
|
563
|
-
file_url: urlData.publicUrl,
|
|
564
|
-
file_name: file.name,
|
|
565
|
-
file_size: file.size,
|
|
566
|
-
mime_type: file.type
|
|
567
|
-
}).select().single();
|
|
568
|
-
if (error) throw new Error(error.message);
|
|
569
|
-
return mapFile(data);
|
|
570
|
-
},
|
|
571
|
-
async uploadMedia(blob, folder, fileName) {
|
|
572
|
-
const tenantId2 = getTenantId();
|
|
573
|
-
const ext = (fileName.split(".").pop() || "bin").toLowerCase();
|
|
574
|
-
const path = `${tenantId2}/forms/${folder}/${crypto.randomUUID()}.${ext}`;
|
|
575
|
-
const { error } = await getClient().storage.from(MEDIA_BUCKET).upload(path, blob, { contentType: blob.type || void 0, upsert: false });
|
|
576
|
-
if (error) throw new Error(error.message);
|
|
577
|
-
return { path };
|
|
578
|
-
},
|
|
579
|
-
async mediaUrl(path) {
|
|
580
|
-
const cached = signedCache.get(path);
|
|
581
|
-
if (cached && cached.until > Date.now()) return cached.url;
|
|
582
|
-
const { data, error } = await getClient().storage.from(MEDIA_BUCKET).createSignedUrl(path, SIGNED_TTL_S);
|
|
583
|
-
if (error || !data?.signedUrl) return null;
|
|
584
|
-
signedCache.set(path, { url: data.signedUrl, until: Date.now() + (SIGNED_TTL_S - 60) * 1e3 });
|
|
585
|
-
return data.signedUrl;
|
|
586
|
-
},
|
|
587
|
-
async searchCatalog(term) {
|
|
588
|
-
const db2 = getClient();
|
|
589
|
-
const tenantId2 = getTenantId();
|
|
590
|
-
const like = term.trim() ? `%${term.trim()}%` : null;
|
|
591
|
-
let sq = db2.from("products").select("id, name, price, category:categories(name)").eq("tenant_id", tenantId2).eq("kind", "service").eq("is_active", true).order("name").limit(500);
|
|
592
|
-
if (like) sq = sq.ilike("name", like);
|
|
593
|
-
let pq = db2.from("service_packages").select("id, name, price").eq("tenant_id", tenantId2).eq("is_active", true).order("name").limit(100);
|
|
594
|
-
if (like) pq = pq.ilike("name", like);
|
|
595
|
-
const [sv, pk] = await Promise.all([sq, pq]);
|
|
596
|
-
if (sv.error) throw new Error(sv.error.message);
|
|
597
|
-
return [
|
|
598
|
-
...(sv.data ?? []).map((r) => ({
|
|
599
|
-
kind: "service",
|
|
600
|
-
id: r.id,
|
|
601
|
-
name: r.name,
|
|
602
|
-
price: Number(r.price) || 0,
|
|
603
|
-
group: r.category?.name ?? null
|
|
604
|
-
})),
|
|
605
|
-
...(pk.data ?? []).map((r) => ({
|
|
606
|
-
kind: "package",
|
|
607
|
-
id: r.id,
|
|
608
|
-
name: r.name,
|
|
609
|
-
price: Number(r.price) || 0,
|
|
610
|
-
group: null
|
|
611
|
-
}))
|
|
612
|
-
];
|
|
613
|
-
},
|
|
614
|
-
async getLatestAnswers(personId, templateIds) {
|
|
615
|
-
if (!templateIds.length) return [];
|
|
616
|
-
const { data, error } = await getClient().from(T.documents).select("template_id, data, document:documents!inner(person_id, created_at, updated_at)").in("template_id", templateIds).eq("document.person_id", personId).order("created_at", { ascending: false, referencedTable: "documents" }).limit(50);
|
|
617
|
-
if (error) throw new Error(error.message);
|
|
618
|
-
const latest = /* @__PURE__ */ new Map();
|
|
619
|
-
for (const r of data ?? []) {
|
|
620
|
-
const at = r.document?.updated_at ?? r.document?.created_at ?? "";
|
|
621
|
-
const cur = latest.get(r.template_id);
|
|
622
|
-
if (!cur || at > cur.at) latest.set(r.template_id, { templateId: r.template_id, data: r.data ?? {}, at });
|
|
623
|
-
}
|
|
624
|
-
return [...latest.values()];
|
|
625
|
-
},
|
|
626
|
-
async getPackageServices(packageId) {
|
|
627
|
-
const { data, error } = await getClient().from("service_package_items").select("product_id, quantity, unit_price, product:products(name, price, kind)").eq("package_id", packageId).order("sort_order");
|
|
628
|
-
if (error) throw new Error(error.message);
|
|
629
|
-
return (data ?? []).filter((r) => r.product_id && (r.product?.kind ?? "service") === "service").map((r) => ({
|
|
630
|
-
serviceId: r.product_id,
|
|
631
|
-
name: r.product?.name ?? "",
|
|
632
|
-
unitPrice: Number(r.unit_price ?? r.product?.price) || 0,
|
|
633
|
-
quantity: Math.max(1, Number(r.quantity) || 1)
|
|
634
|
-
}));
|
|
635
|
-
},
|
|
636
|
-
async deleteFile(fileId) {
|
|
637
|
-
const db2 = getClient();
|
|
638
|
-
const { error } = await db2.from(T.documentFiles).delete().eq("id", fileId);
|
|
639
|
-
if (error) throw new Error(error.message);
|
|
40
|
+
function FormFillDialog({
|
|
41
|
+
templateId,
|
|
42
|
+
personId,
|
|
43
|
+
personName,
|
|
44
|
+
onSaved,
|
|
45
|
+
onClose,
|
|
46
|
+
frame = "modal",
|
|
47
|
+
subject,
|
|
48
|
+
productId,
|
|
49
|
+
professionalId,
|
|
50
|
+
recommendationShortcuts,
|
|
51
|
+
provider: given
|
|
52
|
+
}) {
|
|
53
|
+
const provider = React13.useMemo(() => given ?? createSafeFormsProvider(), [given]);
|
|
54
|
+
const store = React13.useMemo(() => createFormsStore(provider), [provider]);
|
|
55
|
+
const config = React13.useMemo(() => resolveConfig(), []);
|
|
56
|
+
const [template, setTemplate] = React13.useState(null);
|
|
57
|
+
const [erro, setErro] = React13.useState(null);
|
|
58
|
+
const [data, setData] = React13.useState({});
|
|
59
|
+
const [salvando, setSalvando] = React13.useState(false);
|
|
60
|
+
const [missing, setMissing] = React13.useState(/* @__PURE__ */ new Set());
|
|
61
|
+
React13.useEffect(() => {
|
|
62
|
+
if (!template || missing.size === 0) return;
|
|
63
|
+
const still = new Set([...missing].filter((id) => {
|
|
64
|
+
const f = template.schema.fields.find((x) => x.id === id);
|
|
65
|
+
return f ? isFieldEmpty(f, data[id]) : false;
|
|
66
|
+
}));
|
|
67
|
+
if (still.size !== missing.size) setMissing(still);
|
|
68
|
+
if (still.size === 0) setErro(null);
|
|
69
|
+
}, [data, template, missing]);
|
|
70
|
+
async function salvar() {
|
|
71
|
+
if (!template) return;
|
|
72
|
+
const faltando = template.schema.fields.filter((f) => f.required && isFieldEmpty(f, data[f.id]));
|
|
73
|
+
if (faltando.length) {
|
|
74
|
+
setMissing(new Set(faltando.map((f) => f.id)));
|
|
75
|
+
const primeiro = [...faltando].sort((a, b) => a.row - b.row || a.col - b.col)[0];
|
|
76
|
+
requestAnimationFrame(() => document.getElementById(`ff-field-${primeiro.id}`)?.scrollIntoView({ block: "center" }));
|
|
77
|
+
setErro(faltando.length === 1 ? `Preencha o campo obrigat\xF3rio destacado: ${faltando[0].label}` : `Preencha os ${faltando.length} campos obrigat\xF3rios destacados.`);
|
|
78
|
+
return;
|
|
640
79
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
async fetchTemplates(query) {
|
|
667
|
-
set({ templatesLoading: true, templatesError: null });
|
|
668
|
-
try {
|
|
669
|
-
const result = await provider.getTemplates(query ?? {});
|
|
670
|
-
set({ templates: result.data, templatesTotal: result.total });
|
|
671
|
-
} catch (e) {
|
|
672
|
-
set({ templatesError: e.message ?? "Failed to fetch templates" });
|
|
673
|
-
} finally {
|
|
674
|
-
set({ templatesLoading: false });
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
// Separado de `fetchTemplates` porque é outra pergunta e outro custo: a
|
|
678
|
-
// lista precisa dos modelos para desenhar, e o uso é um enfeite que chega
|
|
679
|
-
// depois. Junto, a tela inteira esperaria a agregação; e uma falha na
|
|
680
|
-
// contagem levaria embora a lista, que não tem nada a ver com isso — daí
|
|
681
|
-
// ele engolir o erro em vez de escrevê-lo em `templatesError`.
|
|
682
|
-
async fetchTemplateUsage() {
|
|
683
|
-
try {
|
|
684
|
-
set({ templateUsage: await provider.getTemplateUsage() });
|
|
685
|
-
} catch {
|
|
686
|
-
set({ templateUsage: {} });
|
|
687
|
-
}
|
|
688
|
-
},
|
|
689
|
-
async fetchTemplateById(id) {
|
|
690
|
-
set({ activeTemplateLoading: true });
|
|
691
|
-
try {
|
|
692
|
-
const template = await provider.getTemplateById(id);
|
|
693
|
-
set({ activeTemplate: template });
|
|
694
|
-
return template;
|
|
695
|
-
} catch {
|
|
696
|
-
set({ activeTemplate: null });
|
|
697
|
-
return null;
|
|
698
|
-
} finally {
|
|
699
|
-
set({ activeTemplateLoading: false });
|
|
700
|
-
}
|
|
701
|
-
},
|
|
702
|
-
async createTemplate(input) {
|
|
703
|
-
const template = await provider.createTemplate(input);
|
|
704
|
-
set((s) => ({ templates: [template, ...s.templates], templatesTotal: s.templatesTotal + 1 }));
|
|
705
|
-
emitTemplateEvent("created", template.id, template.name);
|
|
706
|
-
return template;
|
|
707
|
-
},
|
|
708
|
-
async updateTemplate(id, input) {
|
|
709
|
-
const template = await provider.updateTemplate(id, input);
|
|
710
|
-
set((s) => ({
|
|
711
|
-
templates: s.templates.map((t) => t.id === id ? template : t),
|
|
712
|
-
activeTemplate: s.activeTemplate?.id === id ? template : s.activeTemplate
|
|
713
|
-
}));
|
|
714
|
-
emitTemplateEvent("updated", template.id, template.name);
|
|
715
|
-
return template;
|
|
716
|
-
},
|
|
717
|
-
async deleteTemplate(id) {
|
|
718
|
-
await provider.deleteTemplate(id);
|
|
719
|
-
emitTemplateEvent("deleted", id);
|
|
720
|
-
set((s) => ({
|
|
721
|
-
templates: s.templates.filter((t) => t.id !== id),
|
|
722
|
-
templatesTotal: s.templatesTotal - 1
|
|
723
|
-
}));
|
|
724
|
-
},
|
|
725
|
-
async fetchDocuments(query) {
|
|
726
|
-
set({ documentsLoading: true, documentsError: null });
|
|
80
|
+
setErro(null);
|
|
81
|
+
setSalvando(true);
|
|
82
|
+
try {
|
|
83
|
+
const doc = await store.getState().createDocument({
|
|
84
|
+
templateId: template.id,
|
|
85
|
+
personId,
|
|
86
|
+
title: personName ? `${template.name} \u2014 ${personName}` : template.name,
|
|
87
|
+
data,
|
|
88
|
+
status: "completed",
|
|
89
|
+
subjectType: subject?.type,
|
|
90
|
+
subjectId: subject?.id,
|
|
91
|
+
productId: productId ?? void 0,
|
|
92
|
+
professionalId: professionalId ?? void 0
|
|
93
|
+
});
|
|
94
|
+
await onSaved?.({ documentId: doc.id, template, data, recommendations: recommendationsOf(template, data) });
|
|
95
|
+
onClose();
|
|
96
|
+
} catch (e) {
|
|
97
|
+
setErro(describeError(e, "N\xE3o foi poss\xEDvel salvar o formul\xE1rio."));
|
|
98
|
+
} finally {
|
|
99
|
+
setSalvando(false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
React13.useEffect(() => {
|
|
103
|
+
let vivo = true;
|
|
104
|
+
(async () => {
|
|
727
105
|
try {
|
|
728
|
-
const
|
|
729
|
-
|
|
106
|
+
const t = await store.getState().fetchTemplateById(templateId);
|
|
107
|
+
if (vivo) setTemplate(t);
|
|
730
108
|
} catch (e) {
|
|
731
|
-
|
|
732
|
-
} finally {
|
|
733
|
-
set({ documentsLoading: false });
|
|
109
|
+
if (vivo) setErro(describeError(e, "N\xE3o foi poss\xEDvel abrir o formul\xE1rio."));
|
|
734
110
|
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
111
|
+
})();
|
|
112
|
+
return () => {
|
|
113
|
+
vivo = false;
|
|
114
|
+
};
|
|
115
|
+
}, [templateId, store]);
|
|
116
|
+
const corpo = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto px-5 py-4", children: [
|
|
118
|
+
erro && /* @__PURE__ */ jsx("p", { role: "alert", className: "mb-3 rounded-lg bg-destructive/10 px-3 py-2 text-xs text-destructive", children: erro }),
|
|
119
|
+
!template ? erro ? null : (
|
|
120
|
+
/* Esqueleto com a FORMA do formulário: três campos e um rótulo. */
|
|
121
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-4", "aria-busy": "true", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
122
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-muted/70" }),
|
|
123
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 w-full animate-pulse rounded-lg bg-muted" })
|
|
124
|
+
] }, i)) })
|
|
125
|
+
) : /* @__PURE__ */ jsx(CustomFormsProvider, { value: { config, provider, store }, children: /* @__PURE__ */ jsx(
|
|
126
|
+
FormRenderer,
|
|
127
|
+
{
|
|
128
|
+
schema: template.schema,
|
|
129
|
+
data,
|
|
130
|
+
onChange: setData,
|
|
131
|
+
missing,
|
|
132
|
+
recommendationShortcuts
|
|
133
|
+
}
|
|
134
|
+
) })
|
|
135
|
+
] }),
|
|
136
|
+
/* @__PURE__ */ jsx("div", { className: "border-t fayz-glass-divider px-5 py-2.5 pb-[calc(0.625rem+env(safe-area-inset-bottom))] sm:pb-2.5 shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
137
|
+
/* @__PURE__ */ jsx(
|
|
138
|
+
Button,
|
|
139
|
+
{
|
|
140
|
+
variant: "bare",
|
|
141
|
+
size: "bare",
|
|
142
|
+
type: "button",
|
|
143
|
+
onClick: onClose,
|
|
144
|
+
className: "rounded-button px-2 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
145
|
+
children: "Agora n\xE3o"
|
|
146
|
+
}
|
|
147
|
+
),
|
|
148
|
+
/* @__PURE__ */ jsx(
|
|
149
|
+
Button,
|
|
150
|
+
{
|
|
151
|
+
variant: "bare",
|
|
152
|
+
size: "bare",
|
|
153
|
+
type: "button",
|
|
154
|
+
onClick: salvar,
|
|
155
|
+
disabled: !template || salvando,
|
|
156
|
+
className: "rounded-button bg-foreground px-3 py-1.5 text-xs font-semibold text-background disabled:opacity-40",
|
|
157
|
+
children: salvando ? "Salvando\u2026" : "Concluir"
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
] }) })
|
|
161
|
+
] });
|
|
162
|
+
if (frame === "inline") return corpo;
|
|
163
|
+
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
164
|
+
if (!o) onClose();
|
|
165
|
+
}, children: /* @__PURE__ */ jsxs(ModalContent, { size: "2xl", noPadding: true, className: "gap-0", children: [
|
|
166
|
+
/* @__PURE__ */ jsx(ModalHeader, { className: "border-b px-5 py-3.5", children: /* @__PURE__ */ jsx(ModalTitle, { className: "truncate text-base", children: template?.name ?? "Formul\xE1rio" }) }),
|
|
167
|
+
/* @__PURE__ */ jsx(ModalBody, { className: "p-0", children: corpo })
|
|
168
|
+
] }) });
|
|
169
|
+
}
|
|
170
|
+
var DATE = (iso) => (/* @__PURE__ */ new Date(iso + "T00:00:00")).toLocaleDateString("pt-BR");
|
|
171
|
+
function Linha({ item }) {
|
|
172
|
+
const Icone = item.state === "done" ? CheckCircle2 : item.state === "late" ? Clock : Circle;
|
|
173
|
+
const cor = item.state === "done" ? "text-success" : item.state === "late" ? "text-warning" : "text-muted-foreground";
|
|
174
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 px-3 py-1.5 text-xs", children: [
|
|
175
|
+
/* @__PURE__ */ jsx(Icone, { className: `h-3.5 w-3.5 shrink-0 ${cor}`, "aria-hidden": true }),
|
|
176
|
+
/* @__PURE__ */ jsx("span", { className: item.state === "done" ? "text-foreground" : "font-medium text-foreground", children: item.label }),
|
|
177
|
+
item.detail && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
178
|
+
"\u2014 ",
|
|
179
|
+
item.detail
|
|
180
|
+
] }),
|
|
181
|
+
item.action && // O VERBO NÃO PODE PARECER DESLIGADO. Em `text-primary` sobre o âmbar
|
|
182
|
+
// da faixa ele saía lavado e, quando quebrava a linha, parecia um
|
|
183
|
+
// rótulo cinza no canto — a recepção lia "pendente" e não via que
|
|
184
|
+
// havia o que clicar.
|
|
185
|
+
/* @__PURE__ */ jsx(
|
|
186
|
+
Button,
|
|
187
|
+
{
|
|
188
|
+
variant: "bare",
|
|
189
|
+
size: "bare",
|
|
190
|
+
type: "button",
|
|
191
|
+
onClick: item.action.onClick,
|
|
192
|
+
className: "ml-auto shrink-0 rounded-button bg-foreground px-2 py-0.5 text-[11px] font-semibold text-background hover:opacity-90",
|
|
193
|
+
children: item.action.label
|
|
747
194
|
}
|
|
195
|
+
)
|
|
196
|
+
] });
|
|
197
|
+
}
|
|
198
|
+
function PersonFormsAlert({ personId, onFill, version = 0, extras, onPending, sendLink, className, provider: given }) {
|
|
199
|
+
const provider = React13.useMemo(() => given ?? createSafeFormsProvider(), [given]);
|
|
200
|
+
const tenantId2 = useActiveTenantId();
|
|
201
|
+
const [rows, setRows] = React13.useState(null);
|
|
202
|
+
React13.useEffect(() => {
|
|
203
|
+
if (!personId || !tenantId2) {
|
|
204
|
+
setRows([]);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
let cancelled = false;
|
|
208
|
+
setRows(null);
|
|
209
|
+
provider.getPersonRequirements(personId).then((r) => {
|
|
210
|
+
if (!cancelled) setRows(r);
|
|
211
|
+
}).catch(() => {
|
|
212
|
+
if (!cancelled) setRows([]);
|
|
213
|
+
});
|
|
214
|
+
return () => {
|
|
215
|
+
cancelled = true;
|
|
216
|
+
};
|
|
217
|
+
}, [personId, tenantId2, version, provider]);
|
|
218
|
+
const itens = React13.useMemo(() => [
|
|
219
|
+
...(rows ?? []).filter(shownInChecklist).map((r) => {
|
|
220
|
+
const pendente = isOutstanding(r);
|
|
221
|
+
return {
|
|
222
|
+
id: r.templateId,
|
|
223
|
+
label: r.templateName,
|
|
224
|
+
state: !pendente ? "done" : r.state === "expired" ? "late" : "todo",
|
|
225
|
+
detail: !pendente ? r.validUntil ? `em dia, v\xE1lido at\xE9 ${DATE(r.validUntil)}` : "em dia" : r.state === "expired" && r.validUntil ? `venceu em ${DATE(r.validUntil)}` : "nunca preenchido",
|
|
226
|
+
action: pendente && onFill ? { label: "Preencher agora", onClick: () => onFill(r.templateId) } : void 0
|
|
227
|
+
};
|
|
228
|
+
}),
|
|
229
|
+
...extras ?? []
|
|
230
|
+
], [rows, extras, onFill]);
|
|
231
|
+
const peso = (i) => i.state === "late" ? 0 : i.state === "todo" ? 1 : 2;
|
|
232
|
+
itens.sort((a, b) => peso(a) - peso(b));
|
|
233
|
+
const pendentes = itens.filter((i) => i.state !== "done");
|
|
234
|
+
const faltando = pendentes.length;
|
|
235
|
+
const chave = pendentes.map((i) => i.label).join("|");
|
|
236
|
+
React13.useEffect(() => {
|
|
237
|
+
onPending?.(chave ? chave.split("|") : []);
|
|
238
|
+
}, [chave]);
|
|
239
|
+
if (rows === null) return /* @__PURE__ */ jsx(Skeleton, { className: `h-12 w-full ${className ?? ""}` });
|
|
240
|
+
if (itens.length === 0) return null;
|
|
241
|
+
return /* @__PURE__ */ jsxs("div", { className: `overflow-hidden rounded-lg border ${faltando ? "border-warning/40 bg-warning/5" : "border-success/30 bg-success/5"} ${className ?? ""}`, children: [
|
|
242
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-inherit px-3 py-1.5 text-[11px] font-medium text-foreground", children: [
|
|
243
|
+
/* @__PURE__ */ jsx(ClipboardList, { className: `h-3.5 w-3.5 ${faltando ? "text-warning" : "text-success"}`, "aria-hidden": true }),
|
|
244
|
+
faltando === 0 ? "Checagem do paciente \u2014 tudo em dia" : faltando === 1 ? "1 item pendente com o paciente" : `${faltando} itens pendentes com o paciente`,
|
|
245
|
+
sendLink && /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
246
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
247
|
+
Button,
|
|
248
|
+
{
|
|
249
|
+
variant: "bare",
|
|
250
|
+
size: "bare",
|
|
251
|
+
type: "button",
|
|
252
|
+
"aria-label": "Enviar link ao paciente pelo WhatsApp",
|
|
253
|
+
"aria-disabled": sendLink === "soon",
|
|
254
|
+
onClick: sendLink === "soon" ? void 0 : sendLink,
|
|
255
|
+
className: `font-normal ${`ml-auto inline-flex h-6 w-6 items-center justify-center rounded-md transition-colors ${sendLink === "soon" ? "cursor-not-allowed text-muted-foreground/60" : "text-foreground hover:bg-muted"}`}`,
|
|
256
|
+
children: /* @__PURE__ */ jsx(QrCode, { className: "h-3.5 w-3.5", "aria-hidden": true })
|
|
257
|
+
}
|
|
258
|
+
) }),
|
|
259
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "left", children: sendLink === "soon" ? "Enviar pelo WhatsApp (QR Code) \u2014 em breve" : "Enviar pelo WhatsApp (QR Code)" })
|
|
260
|
+
] }) })
|
|
261
|
+
] }),
|
|
262
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border/60", children: itens.map((i) => /* @__PURE__ */ jsx(Linha, { item: i }, i.id)) })
|
|
263
|
+
] });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/components/formsZoneWidgets.ts
|
|
267
|
+
function formsZoneWidgets(provider) {
|
|
268
|
+
const PersonChecklist = (props) => React13.createElement(PersonFormsAlert, { ...props, provider });
|
|
269
|
+
const FormFill = (props) => React13.createElement(FormFillDialog, { ...props, provider });
|
|
270
|
+
return [
|
|
271
|
+
{
|
|
272
|
+
id: "forms-person-checklist",
|
|
273
|
+
zone: FORMS_ZONES.personChecklist,
|
|
274
|
+
component: PersonChecklist
|
|
748
275
|
},
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
},
|
|
754
|
-
async updateDocument(id, input) {
|
|
755
|
-
const doc = await provider.updateDocument(id, input);
|
|
756
|
-
set((s) => ({
|
|
757
|
-
documents: s.documents.map((d) => d.id === id ? doc : d),
|
|
758
|
-
activeDocument: s.activeDocument?.id === id ? doc : s.activeDocument
|
|
759
|
-
}));
|
|
760
|
-
return doc;
|
|
761
|
-
},
|
|
762
|
-
async deleteDocument(id) {
|
|
763
|
-
await provider.deleteDocument(id);
|
|
764
|
-
set((s) => ({
|
|
765
|
-
documents: s.documents.filter((d) => d.id !== id),
|
|
766
|
-
documentsTotal: s.documentsTotal - 1
|
|
767
|
-
}));
|
|
276
|
+
{
|
|
277
|
+
id: "forms-form-fill",
|
|
278
|
+
zone: FORMS_ZONES.formFill,
|
|
279
|
+
component: FormFill
|
|
768
280
|
}
|
|
769
|
-
|
|
281
|
+
];
|
|
770
282
|
}
|
|
771
283
|
|
|
772
284
|
// src/locales/en.ts
|
|
@@ -1053,20 +565,6 @@ var customFormsLocales = {
|
|
|
1053
565
|
en,
|
|
1054
566
|
"pt-BR": ptBR
|
|
1055
567
|
};
|
|
1056
|
-
var defaultLabels = () => ({
|
|
1057
|
-
pageTitle: tl("Forms", "Formul\xE1rios"),
|
|
1058
|
-
settingsLabel: tl("Forms & Documents", "Formul\xE1rios e documentos"),
|
|
1059
|
-
settingsSubtitle: tl("Create and manage custom forms for your business", "Crie e organize os formul\xE1rios da sua empresa"),
|
|
1060
|
-
templates: tl("Templates", "Modelos"),
|
|
1061
|
-
documents: tl("Documents", "Documentos"),
|
|
1062
|
-
newTemplate: tl("New Template", "Novo modelo"),
|
|
1063
|
-
addDocument: tl("Add Document", "Adicionar documento")
|
|
1064
|
-
});
|
|
1065
|
-
function resolveConfig(options) {
|
|
1066
|
-
return {
|
|
1067
|
-
labels: { ...defaultLabels(), ...options?.labels }
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
568
|
var CATEGORY_COLORS = {
|
|
1071
569
|
anamnesis: "bg-info-soft text-info-soft-foreground",
|
|
1072
570
|
evaluation: "bg-info-soft text-info-soft-foreground",
|
|
@@ -1082,7 +580,7 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
1082
580
|
const templates = store((s) => s.templates);
|
|
1083
581
|
const loading = store((s) => s.templatesLoading);
|
|
1084
582
|
const usage = store((s) => s.templateUsage);
|
|
1085
|
-
const orgId =
|
|
583
|
+
const orgId = getActiveTenantId();
|
|
1086
584
|
const createActions = useMemo(() => {
|
|
1087
585
|
const permission = { feature: "custom_forms", action: "create" };
|
|
1088
586
|
return [
|
|
@@ -1275,13 +773,12 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
1275
773
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs("div", { className: "relative min-w-[10rem] flex-1", children: [
|
|
1276
774
|
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
1277
775
|
/* @__PURE__ */ jsx(
|
|
1278
|
-
|
|
776
|
+
Input,
|
|
1279
777
|
{
|
|
1280
|
-
type: "text",
|
|
1281
778
|
value: search,
|
|
1282
779
|
onChange: (e) => setSearch(e.target.value),
|
|
1283
780
|
placeholder: t("common.search"),
|
|
1284
|
-
className: "
|
|
781
|
+
className: "h-auto rounded-none border-0 bg-transparent py-1.5 pl-8 pr-3 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
1285
782
|
}
|
|
1286
783
|
)
|
|
1287
784
|
] }) }),
|
|
@@ -1319,7 +816,7 @@ function CustomFormsSettingsTab({ config, provider, store }) {
|
|
|
1319
816
|
window.location.hash = `#${ROUTE_BASE}/forms`;
|
|
1320
817
|
store.getState().fetchTemplates();
|
|
1321
818
|
}, [store]);
|
|
1322
|
-
|
|
819
|
+
React13.useEffect(() => {
|
|
1323
820
|
const sync = () => setBuilderTemplateId(readBuilderIdFromHash());
|
|
1324
821
|
window.addEventListener("hashchange", sync);
|
|
1325
822
|
return () => window.removeEventListener("hashchange", sync);
|
|
@@ -1350,11 +847,11 @@ function BuilderWrapper({
|
|
|
1350
847
|
config,
|
|
1351
848
|
onBack
|
|
1352
849
|
}) {
|
|
1353
|
-
const FormBuilderLazy =
|
|
1354
|
-
() => import('./FormBuilder-
|
|
850
|
+
const FormBuilderLazy = React13.lazy(
|
|
851
|
+
() => import('./FormBuilder-Q2Q42HAT.js').then((m) => ({ default: m.FormBuilder }))
|
|
1355
852
|
);
|
|
1356
853
|
return /* @__PURE__ */ jsx(CustomFormsProvider, { value: { config, provider, store }, children: /* @__PURE__ */ jsx(
|
|
1357
|
-
|
|
854
|
+
React13.Suspense,
|
|
1358
855
|
{
|
|
1359
856
|
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" }) }),
|
|
1360
857
|
children: /* @__PURE__ */ jsx(
|
|
@@ -1481,8 +978,10 @@ function DocumentPreview({
|
|
|
1481
978
|
] }, field.id);
|
|
1482
979
|
}) }),
|
|
1483
980
|
hidden > 0 && /* @__PURE__ */ jsx(
|
|
1484
|
-
|
|
981
|
+
Button,
|
|
1485
982
|
{
|
|
983
|
+
variant: "bare",
|
|
984
|
+
size: "bare",
|
|
1486
985
|
type: "button",
|
|
1487
986
|
onClick: onOpen,
|
|
1488
987
|
className: "text-xs font-medium text-primary hover:underline",
|
|
@@ -1755,11 +1254,13 @@ function DocumentList({ personId, provider, store, onView, onFileDrop, kinds })
|
|
|
1755
1254
|
buckets.length > 1 && [{ id: void 0, label: t("customForms.bucket.all"), count: totalAll }, ...buckets].map((chip) => {
|
|
1756
1255
|
const active = chip.id === bucket;
|
|
1757
1256
|
return /* @__PURE__ */ jsxs(
|
|
1758
|
-
|
|
1257
|
+
Button,
|
|
1759
1258
|
{
|
|
1259
|
+
variant: "bare",
|
|
1260
|
+
size: "bare",
|
|
1760
1261
|
type: "button",
|
|
1761
1262
|
onClick: () => setBucket(chip.id),
|
|
1762
|
-
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"}`,
|
|
1263
|
+
className: `gap-0 font-normal 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"}`,
|
|
1763
1264
|
children: [
|
|
1764
1265
|
chip.label,
|
|
1765
1266
|
/* @__PURE__ */ jsx("span", { className: "ml-1.5 tabular-nums opacity-60", children: chip.count })
|
|
@@ -1769,13 +1270,15 @@ function DocumentList({ personId, provider, store, onView, onFileDrop, kinds })
|
|
|
1769
1270
|
);
|
|
1770
1271
|
}),
|
|
1771
1272
|
/* @__PURE__ */ jsxs(
|
|
1772
|
-
|
|
1273
|
+
Button,
|
|
1773
1274
|
{
|
|
1275
|
+
variant: "bare",
|
|
1276
|
+
size: "bare",
|
|
1774
1277
|
type: "button",
|
|
1775
1278
|
onClick: toggleExpandAll,
|
|
1776
1279
|
"aria-pressed": expandAll,
|
|
1777
1280
|
title: t(expandAll ? "customForms.hideContent" : "customForms.showContent"),
|
|
1778
|
-
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"}`,
|
|
1281
|
+
className: `font-normal 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"}`,
|
|
1779
1282
|
children: [
|
|
1780
1283
|
expandAll ? /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" }),
|
|
1781
1284
|
t(expandAll ? "customForms.hideContent" : "customForms.showContent")
|
|
@@ -1828,8 +1331,10 @@ function DocumentList({ personId, provider, store, onView, onFileDrop, kinds })
|
|
|
1828
1331
|
if (!doc) return null;
|
|
1829
1332
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1830
1333
|
/* @__PURE__ */ jsx(
|
|
1831
|
-
|
|
1334
|
+
Button,
|
|
1832
1335
|
{
|
|
1336
|
+
variant: "bare",
|
|
1337
|
+
size: "bare",
|
|
1833
1338
|
type: "button",
|
|
1834
1339
|
onClick: (e) => {
|
|
1835
1340
|
e.stopPropagation();
|
|
@@ -1842,8 +1347,10 @@ function DocumentList({ personId, provider, store, onView, onFileDrop, kinds })
|
|
|
1842
1347
|
}
|
|
1843
1348
|
),
|
|
1844
1349
|
/* @__PURE__ */ jsx(
|
|
1845
|
-
|
|
1350
|
+
Button,
|
|
1846
1351
|
{
|
|
1352
|
+
variant: "bare",
|
|
1353
|
+
size: "bare",
|
|
1847
1354
|
type: "button",
|
|
1848
1355
|
onClick: (e) => {
|
|
1849
1356
|
e.stopPropagation();
|
|
@@ -1921,7 +1428,7 @@ function DocumentFormDialog({
|
|
|
1921
1428
|
provider.getTemplateById(existingDocument.templateId).then(setTemplate);
|
|
1922
1429
|
}
|
|
1923
1430
|
}, [existingDocument, template, provider]);
|
|
1924
|
-
const prefilled =
|
|
1431
|
+
const prefilled = React13.useRef(!!initialTemplate || !!existingDocument);
|
|
1925
1432
|
useEffect(() => {
|
|
1926
1433
|
if (prefilled.current || !template || existingDocument) return;
|
|
1927
1434
|
prefilled.current = true;
|
|
@@ -1953,7 +1460,7 @@ function DocumentFormDialog({
|
|
|
1953
1460
|
},
|
|
1954
1461
|
[template, existingDocument, data, personId, personName, store, onSaved, guardDocuments]
|
|
1955
1462
|
);
|
|
1956
|
-
const snapshot =
|
|
1463
|
+
const snapshot = React13.useRef(null);
|
|
1957
1464
|
useEffect(() => {
|
|
1958
1465
|
if (template && snapshot.current === null) {
|
|
1959
1466
|
snapshot.current = JSON.stringify(existingDocument?.data ?? {});
|
|
@@ -1973,8 +1480,10 @@ function DocumentFormDialog({
|
|
|
1973
1480
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1974
1481
|
existingDocument?.title ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1975
1482
|
/* @__PURE__ */ jsx(
|
|
1976
|
-
|
|
1483
|
+
Button,
|
|
1977
1484
|
{
|
|
1485
|
+
variant: "bare",
|
|
1486
|
+
size: "bare",
|
|
1978
1487
|
onClick: onBack,
|
|
1979
1488
|
className: "flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground",
|
|
1980
1489
|
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
|
|
@@ -1996,8 +1505,10 @@ function DocumentFormDialog({
|
|
|
1996
1505
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1997
1506
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1998
1507
|
/* @__PURE__ */ jsx(
|
|
1999
|
-
|
|
1508
|
+
Button,
|
|
2000
1509
|
{
|
|
1510
|
+
variant: "bare",
|
|
1511
|
+
size: "bare",
|
|
2001
1512
|
onClick: onBack,
|
|
2002
1513
|
className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
|
|
2003
1514
|
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
|
|
@@ -2043,8 +1554,10 @@ function FormViewer({ document: doc, provider, onBack, onEdit }) {
|
|
|
2043
1554
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2044
1555
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2045
1556
|
/* @__PURE__ */ jsx(
|
|
2046
|
-
|
|
1557
|
+
Button,
|
|
2047
1558
|
{
|
|
1559
|
+
variant: "bare",
|
|
1560
|
+
size: "bare",
|
|
2048
1561
|
onClick: onBack,
|
|
2049
1562
|
className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
|
|
2050
1563
|
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
|
|
@@ -2188,7 +1701,7 @@ function AddDocumentDropdown({ personId, onSelect, optionFilter, label }) {
|
|
|
2188
1701
|
showSearch && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-2.5 py-2", children: [
|
|
2189
1702
|
/* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
2190
1703
|
/* @__PURE__ */ jsx(
|
|
2191
|
-
|
|
1704
|
+
Input,
|
|
2192
1705
|
{
|
|
2193
1706
|
ref: inputRef,
|
|
2194
1707
|
value: query,
|
|
@@ -2198,12 +1711,12 @@ function AddDocumentDropdown({ personId, onSelect, optionFilter, label }) {
|
|
|
2198
1711
|
},
|
|
2199
1712
|
onKeyDown: handleInputKeyDown,
|
|
2200
1713
|
placeholder: t("customForms.searchDocumentTypes"),
|
|
2201
|
-
className: "
|
|
1714
|
+
className: "h-auto rounded-none border-0 bg-transparent p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
2202
1715
|
}
|
|
2203
1716
|
)
|
|
2204
1717
|
] }),
|
|
2205
1718
|
/* @__PURE__ */ jsxs("div", { ref: listRef, className: "max-h-[min(60vh,20rem)] overflow-y-auto overscroll-contain p-1", children: [
|
|
2206
|
-
groups.map((group, gi) => /* @__PURE__ */ jsxs(
|
|
1719
|
+
groups.map((group, gi) => /* @__PURE__ */ jsxs(React13.Fragment, { children: [
|
|
2207
1720
|
gi > 0 && /* @__PURE__ */ jsx(DropdownSeparator, {}),
|
|
2208
1721
|
/* @__PURE__ */ jsx(DropdownLabel, { className: "text-[10px] uppercase tracking-wider", children: group.label }),
|
|
2209
1722
|
group.items.map((opt) => {
|
|
@@ -2211,13 +1724,15 @@ function AddDocumentDropdown({ personId, onSelect, optionFilter, label }) {
|
|
|
2211
1724
|
flatIndex += 1;
|
|
2212
1725
|
const index = flatIndex;
|
|
2213
1726
|
return /* @__PURE__ */ jsxs(
|
|
2214
|
-
|
|
1727
|
+
Button,
|
|
2215
1728
|
{
|
|
1729
|
+
variant: "bare",
|
|
1730
|
+
size: "bare",
|
|
2216
1731
|
type: "button",
|
|
2217
1732
|
"data-index": index,
|
|
2218
1733
|
onClick: () => choose(opt),
|
|
2219
1734
|
onMouseEnter: () => setActiveIndex(index),
|
|
2220
|
-
className: `relative flex w-full cursor-default select-none items-center gap-2 rounded-button px-2 py-1.5 text-left text-sm outline-none transition-colors ${index === activeIndex ? "bg-muted" : ""}`,
|
|
1735
|
+
className: `font-normal justify-start relative flex w-full cursor-default select-none items-center gap-2 rounded-button px-2 py-1.5 text-left text-sm outline-none transition-colors ${index === activeIndex ? "bg-muted" : ""}`,
|
|
2221
1736
|
children: [
|
|
2222
1737
|
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
2223
1738
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
@@ -2395,8 +1910,10 @@ function FileDocumentViewer({ document: doc, onBack }) {
|
|
|
2395
1910
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2396
1911
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2397
1912
|
/* @__PURE__ */ jsx(
|
|
2398
|
-
|
|
1913
|
+
Button,
|
|
2399
1914
|
{
|
|
1915
|
+
variant: "bare",
|
|
1916
|
+
size: "bare",
|
|
2400
1917
|
onClick: onBack,
|
|
2401
1918
|
className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
|
|
2402
1919
|
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" })
|
|
@@ -2425,9 +1942,9 @@ var TONE = {
|
|
|
2425
1942
|
info: { wrap: "border-info/40 bg-info-soft", chip: "bg-info text-white", dot: "bg-info" }
|
|
2426
1943
|
};
|
|
2427
1944
|
function PersonAnswerAlerts({ personId, provider }) {
|
|
2428
|
-
const [alerts, setAlerts] =
|
|
2429
|
-
const [open, setOpen] =
|
|
2430
|
-
|
|
1945
|
+
const [alerts, setAlerts] = React13.useState([]);
|
|
1946
|
+
const [open, setOpen] = React13.useState(false);
|
|
1947
|
+
React13.useEffect(() => {
|
|
2431
1948
|
if (!personId || !provider.getLatestAnswers) return;
|
|
2432
1949
|
let alive = true;
|
|
2433
1950
|
(async () => {
|
|
@@ -2465,12 +1982,14 @@ function PersonAnswerAlerts({ personId, provider }) {
|
|
|
2465
1982
|
const tone = TONE[alerts[0].severity];
|
|
2466
1983
|
return /* @__PURE__ */ jsxs("div", { className: `overflow-hidden rounded-lg border-2 ${tone.wrap}`, children: [
|
|
2467
1984
|
/* @__PURE__ */ jsxs(
|
|
2468
|
-
|
|
1985
|
+
Button,
|
|
2469
1986
|
{
|
|
1987
|
+
variant: "bare",
|
|
1988
|
+
size: "bare",
|
|
2470
1989
|
type: "button",
|
|
2471
1990
|
onClick: () => setOpen((o) => !o),
|
|
2472
1991
|
"aria-expanded": open,
|
|
2473
|
-
className: "flex w-full items-center gap-2 px-3 py-2 text-left",
|
|
1992
|
+
className: "font-normal whitespace-normal justify-start flex w-full items-center gap-2 px-3 py-2 text-left",
|
|
2474
1993
|
children: [
|
|
2475
1994
|
/* @__PURE__ */ jsx("span", { className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-full ${tone.chip}`, children: /* @__PURE__ */ jsx(AlertTriangle, { className: "h-4 w-4" }) }),
|
|
2476
1995
|
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
@@ -2632,267 +2151,8 @@ function buildFormsAnalyticsCards() {
|
|
|
2632
2151
|
}
|
|
2633
2152
|
];
|
|
2634
2153
|
}
|
|
2635
|
-
async function getPersonFormRequirements(tenantId2, personId) {
|
|
2636
|
-
const client = getFayzSupabaseClient();
|
|
2637
|
-
const { data, error } = await client.rpc("forms_person_requirements", {
|
|
2638
|
-
p_tenant_id: tenantId2,
|
|
2639
|
-
p_person_id: personId
|
|
2640
|
-
});
|
|
2641
|
-
if (error) throw error;
|
|
2642
|
-
return (data ?? []).map((r) => ({
|
|
2643
|
-
templateId: r.template_id,
|
|
2644
|
-
templateName: r.template_name,
|
|
2645
|
-
category: r.category,
|
|
2646
|
-
validityDays: r.validity_days ?? null,
|
|
2647
|
-
alertWhenMissing: r.alert_when_missing ?? false,
|
|
2648
|
-
lastFilledAt: r.last_filled_at ?? null,
|
|
2649
|
-
validUntil: r.valid_until ?? null,
|
|
2650
|
-
state: r.state
|
|
2651
|
-
}));
|
|
2652
|
-
}
|
|
2653
|
-
var isOutstanding = (r) => r.state !== "valid";
|
|
2654
|
-
var DATE = (iso) => (/* @__PURE__ */ new Date(iso + "T00:00:00")).toLocaleDateString("pt-BR");
|
|
2655
|
-
function Linha({ item }) {
|
|
2656
|
-
const Icone = item.state === "done" ? CheckCircle2 : item.state === "late" ? Clock : Circle;
|
|
2657
|
-
const cor = item.state === "done" ? "text-success" : item.state === "late" ? "text-warning" : "text-muted-foreground";
|
|
2658
|
-
return /* @__PURE__ */ jsxs("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 px-3 py-1.5 text-xs", children: [
|
|
2659
|
-
/* @__PURE__ */ jsx(Icone, { className: `h-3.5 w-3.5 shrink-0 ${cor}`, "aria-hidden": true }),
|
|
2660
|
-
/* @__PURE__ */ jsx("span", { className: item.state === "done" ? "text-foreground" : "font-medium text-foreground", children: item.label }),
|
|
2661
|
-
item.detail && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
2662
|
-
"\u2014 ",
|
|
2663
|
-
item.detail
|
|
2664
|
-
] }),
|
|
2665
|
-
item.action && // O VERBO NÃO PODE PARECER DESLIGADO. Em `text-primary` sobre o âmbar
|
|
2666
|
-
// da faixa ele saía lavado e, quando quebrava a linha, parecia um
|
|
2667
|
-
// rótulo cinza no canto — a recepção lia "pendente" e não via que
|
|
2668
|
-
// havia o que clicar.
|
|
2669
|
-
/* @__PURE__ */ jsx(
|
|
2670
|
-
"button",
|
|
2671
|
-
{
|
|
2672
|
-
type: "button",
|
|
2673
|
-
onClick: item.action.onClick,
|
|
2674
|
-
className: "ml-auto shrink-0 rounded-button bg-foreground px-2 py-0.5 text-[11px] font-semibold text-background hover:opacity-90",
|
|
2675
|
-
children: item.action.label
|
|
2676
|
-
}
|
|
2677
|
-
)
|
|
2678
|
-
] });
|
|
2679
|
-
}
|
|
2680
|
-
function PersonFormsAlert({ personId, onFill, version = 0, extras, onPending, sendLink, className }) {
|
|
2681
|
-
const tenantId2 = useOrganizationStore((s) => s.currentOrg?.id);
|
|
2682
|
-
const [rows, setRows] = React12.useState(null);
|
|
2683
|
-
React12.useEffect(() => {
|
|
2684
|
-
if (!personId || !tenantId2) {
|
|
2685
|
-
setRows([]);
|
|
2686
|
-
return;
|
|
2687
|
-
}
|
|
2688
|
-
let cancelled = false;
|
|
2689
|
-
setRows(null);
|
|
2690
|
-
getPersonFormRequirements(tenantId2, personId).then((r) => {
|
|
2691
|
-
if (!cancelled) setRows(r);
|
|
2692
|
-
}).catch(() => {
|
|
2693
|
-
if (!cancelled) setRows([]);
|
|
2694
|
-
});
|
|
2695
|
-
return () => {
|
|
2696
|
-
cancelled = true;
|
|
2697
|
-
};
|
|
2698
|
-
}, [personId, tenantId2, version]);
|
|
2699
|
-
const itens = React12.useMemo(() => [
|
|
2700
|
-
// O QUE NÃO ALERTA NÃO INTERROMPE. Exigir um formulário põe a bola no
|
|
2701
|
-
// checklist do atendimento; ALERTAR é a segunda decisão do V1
|
|
2702
|
-
// (`alert_when_missing`, desligada por padrão) e é ela que autoriza a
|
|
2703
|
-
// faixa âmbar na frente de quem só queria marcar um horário. Sem este
|
|
2704
|
-
// recorte, uma anamnese importada com a segunda chave desligada — como a
|
|
2705
|
-
// casa já a tinha no V1 — abria "1 item pendente" em todo agendamento.
|
|
2706
|
-
//
|
|
2707
|
-
// Em dia continua aparecendo de qualquer forma: a linha verde é
|
|
2708
|
-
// confirmação, não interrupção, e é ela que faz a âmbar ter crédito.
|
|
2709
|
-
...(rows ?? []).filter((r) => r.alertWhenMissing || !isOutstanding(r)).map((r) => {
|
|
2710
|
-
const pendente = isOutstanding(r);
|
|
2711
|
-
return {
|
|
2712
|
-
id: r.templateId,
|
|
2713
|
-
label: r.templateName,
|
|
2714
|
-
state: !pendente ? "done" : r.state === "expired" ? "late" : "todo",
|
|
2715
|
-
detail: !pendente ? r.validUntil ? `em dia, v\xE1lido at\xE9 ${DATE(r.validUntil)}` : "em dia" : r.state === "expired" && r.validUntil ? `venceu em ${DATE(r.validUntil)}` : "nunca preenchido",
|
|
2716
|
-
action: pendente && onFill ? { label: "Preencher agora", onClick: () => onFill(r.templateId) } : void 0
|
|
2717
|
-
};
|
|
2718
|
-
}),
|
|
2719
|
-
...extras ?? []
|
|
2720
|
-
], [rows, extras, onFill]);
|
|
2721
|
-
const peso = (i) => i.state === "late" ? 0 : i.state === "todo" ? 1 : 2;
|
|
2722
|
-
itens.sort((a, b) => peso(a) - peso(b));
|
|
2723
|
-
const pendentes = itens.filter((i) => i.state !== "done");
|
|
2724
|
-
const faltando = pendentes.length;
|
|
2725
|
-
const chave = pendentes.map((i) => i.label).join("|");
|
|
2726
|
-
React12.useEffect(() => {
|
|
2727
|
-
onPending?.(chave ? chave.split("|") : []);
|
|
2728
|
-
}, [chave]);
|
|
2729
|
-
if (rows === null) return /* @__PURE__ */ jsx(Skeleton, { className: `h-12 w-full ${className ?? ""}` });
|
|
2730
|
-
if (itens.length === 0) return null;
|
|
2731
|
-
return /* @__PURE__ */ jsxs("div", { className: `overflow-hidden rounded-lg border ${faltando ? "border-warning/40 bg-warning/5" : "border-success/30 bg-success/5"} ${className ?? ""}`, children: [
|
|
2732
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-inherit px-3 py-1.5 text-[11px] font-medium text-foreground", children: [
|
|
2733
|
-
/* @__PURE__ */ jsx(ClipboardList, { className: `h-3.5 w-3.5 ${faltando ? "text-warning" : "text-success"}`, "aria-hidden": true }),
|
|
2734
|
-
faltando === 0 ? "Checagem do paciente \u2014 tudo em dia" : faltando === 1 ? "1 item pendente com o paciente" : `${faltando} itens pendentes com o paciente`,
|
|
2735
|
-
sendLink && /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2736
|
-
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
2737
|
-
"button",
|
|
2738
|
-
{
|
|
2739
|
-
type: "button",
|
|
2740
|
-
"aria-label": "Enviar link ao paciente pelo WhatsApp",
|
|
2741
|
-
"aria-disabled": sendLink === "soon",
|
|
2742
|
-
onClick: sendLink === "soon" ? void 0 : sendLink,
|
|
2743
|
-
className: `ml-auto inline-flex h-6 w-6 items-center justify-center rounded-md transition-colors ${sendLink === "soon" ? "cursor-not-allowed text-muted-foreground/60" : "text-foreground hover:bg-muted"}`,
|
|
2744
|
-
children: /* @__PURE__ */ jsx(QrCode, { className: "h-3.5 w-3.5", "aria-hidden": true })
|
|
2745
|
-
}
|
|
2746
|
-
) }),
|
|
2747
|
-
/* @__PURE__ */ jsx(TooltipContent, { side: "left", children: sendLink === "soon" ? "Enviar pelo WhatsApp (QR Code) \u2014 em breve" : "Enviar pelo WhatsApp (QR Code)" })
|
|
2748
|
-
] }) })
|
|
2749
|
-
] }),
|
|
2750
|
-
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border/60", children: itens.map((i) => /* @__PURE__ */ jsx(Linha, { item: i }, i.id)) })
|
|
2751
|
-
] });
|
|
2752
|
-
}
|
|
2753
|
-
function recommendationsOf(template, data) {
|
|
2754
|
-
const by = /* @__PURE__ */ new Map();
|
|
2755
|
-
for (const f of template.schema.fields) {
|
|
2756
|
-
if (f.type !== "recommendation" || !Array.isArray(data[f.id])) continue;
|
|
2757
|
-
for (const l of data[f.id]) {
|
|
2758
|
-
const cur = by.get(l.serviceId);
|
|
2759
|
-
by.set(l.serviceId, cur ? { ...cur, quantity: cur.quantity + l.quantity } : { ...l });
|
|
2760
|
-
}
|
|
2761
|
-
}
|
|
2762
|
-
return [...by.values()];
|
|
2763
|
-
}
|
|
2764
|
-
function FormFillDialog({
|
|
2765
|
-
templateId,
|
|
2766
|
-
personId,
|
|
2767
|
-
personName,
|
|
2768
|
-
onSaved,
|
|
2769
|
-
onClose,
|
|
2770
|
-
frame = "modal",
|
|
2771
|
-
subject,
|
|
2772
|
-
productId,
|
|
2773
|
-
professionalId,
|
|
2774
|
-
recommendationShortcuts
|
|
2775
|
-
}) {
|
|
2776
|
-
const provider = React12.useMemo(() => createSafeDataProvider(
|
|
2777
|
-
() => createSupabaseFormsProvider(),
|
|
2778
|
-
() => createMockFormsProvider()
|
|
2779
|
-
), []);
|
|
2780
|
-
const store = React12.useMemo(() => createCustomFormsStore(provider), [provider]);
|
|
2781
|
-
const config = React12.useMemo(() => resolveConfig(), []);
|
|
2782
|
-
const [template, setTemplate] = React12.useState(null);
|
|
2783
|
-
const [erro, setErro] = React12.useState(null);
|
|
2784
|
-
const [data, setData] = React12.useState({});
|
|
2785
|
-
const [salvando, setSalvando] = React12.useState(false);
|
|
2786
|
-
const [missing, setMissing] = React12.useState(/* @__PURE__ */ new Set());
|
|
2787
|
-
React12.useEffect(() => {
|
|
2788
|
-
if (!template || missing.size === 0) return;
|
|
2789
|
-
const still = new Set([...missing].filter((id) => {
|
|
2790
|
-
const f = template.schema.fields.find((x) => x.id === id);
|
|
2791
|
-
return f ? isFieldEmpty(f, data[id]) : false;
|
|
2792
|
-
}));
|
|
2793
|
-
if (still.size !== missing.size) setMissing(still);
|
|
2794
|
-
if (still.size === 0) setErro(null);
|
|
2795
|
-
}, [data, template, missing]);
|
|
2796
|
-
async function salvar() {
|
|
2797
|
-
if (!template) return;
|
|
2798
|
-
const faltando = template.schema.fields.filter((f) => f.required && isFieldEmpty(f, data[f.id]));
|
|
2799
|
-
if (faltando.length) {
|
|
2800
|
-
setMissing(new Set(faltando.map((f) => f.id)));
|
|
2801
|
-
const primeiro = [...faltando].sort((a, b) => a.row - b.row || a.col - b.col)[0];
|
|
2802
|
-
requestAnimationFrame(() => document.getElementById(`ff-field-${primeiro.id}`)?.scrollIntoView({ block: "center" }));
|
|
2803
|
-
setErro(faltando.length === 1 ? `Preencha o campo obrigat\xF3rio destacado: ${faltando[0].label}` : `Preencha os ${faltando.length} campos obrigat\xF3rios destacados.`);
|
|
2804
|
-
return;
|
|
2805
|
-
}
|
|
2806
|
-
setErro(null);
|
|
2807
|
-
setSalvando(true);
|
|
2808
|
-
try {
|
|
2809
|
-
const doc = await store.getState().createDocument({
|
|
2810
|
-
templateId: template.id,
|
|
2811
|
-
personId,
|
|
2812
|
-
title: personName ? `${template.name} \u2014 ${personName}` : template.name,
|
|
2813
|
-
data,
|
|
2814
|
-
status: "completed",
|
|
2815
|
-
subjectType: subject?.type,
|
|
2816
|
-
subjectId: subject?.id,
|
|
2817
|
-
productId: productId ?? void 0,
|
|
2818
|
-
professionalId: professionalId ?? void 0
|
|
2819
|
-
});
|
|
2820
|
-
await onSaved?.({ documentId: doc.id, template, data, recommendations: recommendationsOf(template, data) });
|
|
2821
|
-
onClose();
|
|
2822
|
-
} catch (e) {
|
|
2823
|
-
setErro(describeError(e, "N\xE3o foi poss\xEDvel salvar o formul\xE1rio."));
|
|
2824
|
-
} finally {
|
|
2825
|
-
setSalvando(false);
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
React12.useEffect(() => {
|
|
2829
|
-
let vivo = true;
|
|
2830
|
-
(async () => {
|
|
2831
|
-
try {
|
|
2832
|
-
const t = await store.getState().fetchTemplateById(templateId);
|
|
2833
|
-
if (vivo) setTemplate(t);
|
|
2834
|
-
} catch (e) {
|
|
2835
|
-
if (vivo) setErro(describeError(e, "N\xE3o foi poss\xEDvel abrir o formul\xE1rio."));
|
|
2836
|
-
}
|
|
2837
|
-
})();
|
|
2838
|
-
return () => {
|
|
2839
|
-
vivo = false;
|
|
2840
|
-
};
|
|
2841
|
-
}, [templateId, store]);
|
|
2842
|
-
const corpo = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2843
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto px-5 py-4", children: [
|
|
2844
|
-
erro && /* @__PURE__ */ jsx("p", { role: "alert", className: "mb-3 rounded-lg bg-destructive/10 px-3 py-2 text-xs text-destructive", children: erro }),
|
|
2845
|
-
!template ? erro ? null : (
|
|
2846
|
-
/* Esqueleto com a FORMA do formulário: três campos e um rótulo. */
|
|
2847
|
-
/* @__PURE__ */ jsx("div", { className: "space-y-4", "aria-busy": "true", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2848
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-muted/70" }),
|
|
2849
|
-
/* @__PURE__ */ jsx("div", { className: "h-9 w-full animate-pulse rounded-lg bg-muted" })
|
|
2850
|
-
] }, i)) })
|
|
2851
|
-
) : /* @__PURE__ */ jsx(CustomFormsProvider, { value: { config, provider, store }, children: /* @__PURE__ */ jsx(
|
|
2852
|
-
FormRenderer,
|
|
2853
|
-
{
|
|
2854
|
-
schema: template.schema,
|
|
2855
|
-
data,
|
|
2856
|
-
onChange: setData,
|
|
2857
|
-
missing,
|
|
2858
|
-
recommendationShortcuts
|
|
2859
|
-
}
|
|
2860
|
-
) })
|
|
2861
|
-
] }),
|
|
2862
|
-
/* @__PURE__ */ jsx("div", { className: "border-t fayz-glass-divider px-5 py-2.5 pb-[calc(0.625rem+env(safe-area-inset-bottom))] sm:pb-2.5 shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
2863
|
-
/* @__PURE__ */ jsx(
|
|
2864
|
-
"button",
|
|
2865
|
-
{
|
|
2866
|
-
type: "button",
|
|
2867
|
-
onClick: onClose,
|
|
2868
|
-
className: "rounded-button px-2 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
2869
|
-
children: "Agora n\xE3o"
|
|
2870
|
-
}
|
|
2871
|
-
),
|
|
2872
|
-
/* @__PURE__ */ jsx(
|
|
2873
|
-
"button",
|
|
2874
|
-
{
|
|
2875
|
-
type: "button",
|
|
2876
|
-
onClick: salvar,
|
|
2877
|
-
disabled: !template || salvando,
|
|
2878
|
-
className: "rounded-button bg-foreground px-3 py-1.5 text-xs font-semibold text-background disabled:opacity-40",
|
|
2879
|
-
children: salvando ? "Salvando\u2026" : "Concluir"
|
|
2880
|
-
}
|
|
2881
|
-
)
|
|
2882
|
-
] }) })
|
|
2883
|
-
] });
|
|
2884
|
-
if (frame === "inline") return corpo;
|
|
2885
|
-
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
2886
|
-
if (!o) onClose();
|
|
2887
|
-
}, children: /* @__PURE__ */ jsxs(ModalContent, { size: "2xl", noPadding: true, className: "gap-0", children: [
|
|
2888
|
-
/* @__PURE__ */ jsx(ModalHeader, { className: "border-b px-5 py-3.5", children: /* @__PURE__ */ jsx(ModalTitle, { className: "truncate text-base", children: template?.name ?? "Formul\xE1rio" }) }),
|
|
2889
|
-
/* @__PURE__ */ jsx(ModalBody, { className: "p-0", children: corpo })
|
|
2890
|
-
] }) });
|
|
2891
|
-
}
|
|
2892
|
-
|
|
2893
|
-
// src/lib/contract-render.ts
|
|
2894
2154
|
var MONTHS = ["janeiro", "fevereiro", "mar\xE7o", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"];
|
|
2895
|
-
var brl = (n) =>
|
|
2155
|
+
var brl = (n) => formatCurrency(n);
|
|
2896
2156
|
var pad = (n) => String(n).padStart(2, "0");
|
|
2897
2157
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
2898
2158
|
function dateBR(value) {
|
|
@@ -2913,7 +2173,7 @@ function itemsTable(items, withDiscount) {
|
|
|
2913
2173
|
return `<table><thead><tr>${head.map((h) => `<th>${h}</th>`).join("")}</tr></thead><tbody>${rows.map((r) => `<tr>${r.map((c) => `<td>${c}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
|
|
2914
2174
|
}
|
|
2915
2175
|
function renderContractTags(templateHtml, ctx) {
|
|
2916
|
-
const
|
|
2176
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
2917
2177
|
const c = ctx.client ?? {};
|
|
2918
2178
|
const u = ctx.unit ?? {};
|
|
2919
2179
|
const services = ctx.services ?? [];
|
|
@@ -2957,9 +2217,9 @@ function renderContractTags(templateHtml, ctx) {
|
|
|
2957
2217
|
"Orcamento.ItensAprovados": itemsTable(ctx.approvedItems, true),
|
|
2958
2218
|
"Indicacoes.Itens": itemsTable(ctx.recommendedItems, false),
|
|
2959
2219
|
"Indicacoes.Lista": (ctx.recommendedItems ?? []).map((i) => esc(i.description)).join(", "),
|
|
2960
|
-
"Sistema.Data": `${pad(
|
|
2961
|
-
"Sistema.DataExtenso": `${pad(
|
|
2962
|
-
"Sistema.Hora": `${pad(
|
|
2220
|
+
"Sistema.Data": `${pad(now.getDate())}/${pad(now.getMonth() + 1)}/${now.getFullYear()}`,
|
|
2221
|
+
"Sistema.DataExtenso": `${pad(now.getDate())} de ${MONTHS[now.getMonth()]} de ${now.getFullYear()}`,
|
|
2222
|
+
"Sistema.Hora": `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
|
2963
2223
|
};
|
|
2964
2224
|
const built = /* @__PURE__ */ new Set([
|
|
2965
2225
|
"Orcamento.Itens",
|
|
@@ -2983,7 +2243,7 @@ var db = () => {
|
|
|
2983
2243
|
if (!c) throw new Error("Supabase not initialized");
|
|
2984
2244
|
return c;
|
|
2985
2245
|
};
|
|
2986
|
-
var tenantId = () =>
|
|
2246
|
+
var tenantId = () => getActiveTenantId() ?? getActiveTenantId() ?? "";
|
|
2987
2247
|
var BUCKET = "attachments";
|
|
2988
2248
|
function toLine(s) {
|
|
2989
2249
|
const total = s.unitPrice * s.quantity - (s.discount ?? 0);
|
|
@@ -3188,8 +2448,8 @@ var CONTRACT_TEXT_CLASS = [
|
|
|
3188
2448
|
"[&_th]:border [&_th]:border-border [&_th]:bg-muted/50 [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-medium"
|
|
3189
2449
|
].join(" ");
|
|
3190
2450
|
function useSigned(path) {
|
|
3191
|
-
const [url, setUrl] =
|
|
3192
|
-
|
|
2451
|
+
const [url, setUrl] = React13.useState(null);
|
|
2452
|
+
React13.useEffect(() => {
|
|
3193
2453
|
let alive = true;
|
|
3194
2454
|
contractMediaUrl(path).then((u) => {
|
|
3195
2455
|
if (alive) setUrl(u);
|
|
@@ -3202,9 +2462,9 @@ function useSigned(path) {
|
|
|
3202
2462
|
return url;
|
|
3203
2463
|
}
|
|
3204
2464
|
function SignaturePad({ onChange }) {
|
|
3205
|
-
const ref =
|
|
3206
|
-
const drawing =
|
|
3207
|
-
|
|
2465
|
+
const ref = React13.useRef(null);
|
|
2466
|
+
const drawing = React13.useRef(false);
|
|
2467
|
+
React13.useEffect(() => {
|
|
3208
2468
|
const c = ref.current;
|
|
3209
2469
|
if (!c) return;
|
|
3210
2470
|
const r = window.devicePixelRatio || 1;
|
|
@@ -3249,10 +2509,12 @@ function SignaturePad({ onChange }) {
|
|
|
3249
2509
|
}
|
|
3250
2510
|
),
|
|
3251
2511
|
/* @__PURE__ */ jsx(
|
|
3252
|
-
|
|
2512
|
+
Button,
|
|
3253
2513
|
{
|
|
2514
|
+
variant: "bare",
|
|
2515
|
+
size: "bare",
|
|
3254
2516
|
type: "button",
|
|
3255
|
-
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
2517
|
+
className: "font-normal text-xs text-muted-foreground hover:text-foreground",
|
|
3256
2518
|
onClick: () => {
|
|
3257
2519
|
const c = ref.current;
|
|
3258
2520
|
if (c) {
|
|
@@ -3266,17 +2528,17 @@ function SignaturePad({ onChange }) {
|
|
|
3266
2528
|
] });
|
|
3267
2529
|
}
|
|
3268
2530
|
function SelfieCapture({ value, onChange }) {
|
|
3269
|
-
const video =
|
|
3270
|
-
const stream =
|
|
3271
|
-
const [on, setOn] =
|
|
3272
|
-
const [erro, setErro] =
|
|
3273
|
-
const preview =
|
|
2531
|
+
const video = React13.useRef(null);
|
|
2532
|
+
const stream = React13.useRef(null);
|
|
2533
|
+
const [on, setOn] = React13.useState(false);
|
|
2534
|
+
const [erro, setErro] = React13.useState(null);
|
|
2535
|
+
const preview = React13.useMemo(() => value ? URL.createObjectURL(value) : null, [value]);
|
|
3274
2536
|
const stop = () => {
|
|
3275
2537
|
stream.current?.getTracks().forEach((t) => t.stop());
|
|
3276
2538
|
stream.current = null;
|
|
3277
2539
|
setOn(false);
|
|
3278
2540
|
};
|
|
3279
|
-
|
|
2541
|
+
React13.useEffect(() => () => stop(), []);
|
|
3280
2542
|
async function start() {
|
|
3281
2543
|
setErro(null);
|
|
3282
2544
|
try {
|
|
@@ -3310,12 +2572,14 @@ function SelfieCapture({ value, onChange }) {
|
|
|
3310
2572
|
preview ? /* @__PURE__ */ jsxs("div", { className: "relative w-40", children: [
|
|
3311
2573
|
/* @__PURE__ */ jsx("img", { src: preview, alt: "Selfie", className: "rounded-lg border" }),
|
|
3312
2574
|
/* @__PURE__ */ jsx(
|
|
3313
|
-
|
|
2575
|
+
Button,
|
|
3314
2576
|
{
|
|
2577
|
+
variant: "bare",
|
|
2578
|
+
size: "bare",
|
|
3315
2579
|
type: "button",
|
|
3316
2580
|
"aria-label": "Remover selfie",
|
|
3317
2581
|
onClick: () => onChange(null),
|
|
3318
|
-
className: "absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-background/90",
|
|
2582
|
+
className: "font-normal absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-background/90",
|
|
3319
2583
|
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
3320
2584
|
}
|
|
3321
2585
|
)
|
|
@@ -3349,16 +2613,16 @@ function printContract(contract, signatureUrl) {
|
|
|
3349
2613
|
setTimeout(() => w.print(), 300);
|
|
3350
2614
|
}
|
|
3351
2615
|
function ContractSignPane({ contract, defaultSignerName, onSigned, onClose }) {
|
|
3352
|
-
const [signer, setSigner] =
|
|
3353
|
-
const [signature, setSignature] =
|
|
3354
|
-
const [selfie, setSelfie] =
|
|
3355
|
-
const [saving, setSaving] =
|
|
3356
|
-
const [erro, setErro] =
|
|
2616
|
+
const [signer, setSigner] = React13.useState(defaultSignerName ?? "");
|
|
2617
|
+
const [signature, setSignature] = React13.useState(null);
|
|
2618
|
+
const [selfie, setSelfie] = React13.useState(null);
|
|
2619
|
+
const [saving, setSaving] = React13.useState(false);
|
|
2620
|
+
const [erro, setErro] = React13.useState(null);
|
|
3357
2621
|
const signatureUrl = useSigned(contract.signaturePath);
|
|
3358
2622
|
const selfieUrl = useSigned(contract.selfiePath);
|
|
3359
2623
|
const signed = contract.status === "signed";
|
|
3360
|
-
const html =
|
|
3361
|
-
|
|
2624
|
+
const html = React13.useMemo(() => sanitizeHtml(contract.html), [contract.html]);
|
|
2625
|
+
React13.useEffect(() => {
|
|
3362
2626
|
setErro(null);
|
|
3363
2627
|
}, [signature, selfie, signer]);
|
|
3364
2628
|
const missing = contract.requireSignature && !signature ? "Desenhe a assinatura para confirmar." : contract.requireSelfie && !selfie ? "Tire a selfie para confirmar." : !signer.trim() ? "Informe o nome de quem assina." : null;
|
|
@@ -3406,8 +2670,10 @@ function ContractSignPane({ contract, defaultSignerName, onSigned, onClose }) {
|
|
|
3406
2670
|
] }),
|
|
3407
2671
|
/* @__PURE__ */ jsx("div", { className: "shrink-0 border-t fayz-glass-divider px-5 py-2.5 pb-[calc(0.625rem+env(safe-area-inset-bottom))] sm:pb-2.5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
3408
2672
|
/* @__PURE__ */ jsx(
|
|
3409
|
-
|
|
2673
|
+
Button,
|
|
3410
2674
|
{
|
|
2675
|
+
variant: "bare",
|
|
2676
|
+
size: "bare",
|
|
3411
2677
|
type: "button",
|
|
3412
2678
|
onClick: onClose,
|
|
3413
2679
|
className: "rounded-button px-2 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
@@ -3415,8 +2681,10 @@ function ContractSignPane({ contract, defaultSignerName, onSigned, onClose }) {
|
|
|
3415
2681
|
}
|
|
3416
2682
|
),
|
|
3417
2683
|
!signed && /* @__PURE__ */ jsx(
|
|
3418
|
-
|
|
2684
|
+
Button,
|
|
3419
2685
|
{
|
|
2686
|
+
variant: "bare",
|
|
2687
|
+
size: "bare",
|
|
3420
2688
|
type: "button",
|
|
3421
2689
|
onClick: sign,
|
|
3422
2690
|
disabled: saving,
|
|
@@ -3427,6 +2695,22 @@ function ContractSignPane({ contract, defaultSignerName, onSigned, onClose }) {
|
|
|
3427
2695
|
] }) })
|
|
3428
2696
|
] });
|
|
3429
2697
|
}
|
|
2698
|
+
var formsV1SettingsTabs = [
|
|
2699
|
+
defineSettingsTab({
|
|
2700
|
+
id: "forms-contract-rules",
|
|
2701
|
+
label: tl("Contract rules", "Regras de contrato"),
|
|
2702
|
+
icon: "FileCheck2",
|
|
2703
|
+
section: "registry",
|
|
2704
|
+
pending: true,
|
|
2705
|
+
defaults: { "contracts.execution_requires_signed_contract": false, "contracts.prepaid_purchase_flow": false },
|
|
2706
|
+
sections: [{
|
|
2707
|
+
fields: [
|
|
2708
|
+
{ key: "contracts.execution_requires_signed_contract", type: "boolean", label: tl("A service runs only with the contract signed", "Executar servi\xE7o s\xF3 com contrato assinado") },
|
|
2709
|
+
{ key: "contracts.prepaid_purchase_flow", type: "boolean", label: tl("Prepaid purchase flow", "Fluxo de compra pr\xE9-paga") }
|
|
2710
|
+
]
|
|
2711
|
+
}]
|
|
2712
|
+
})
|
|
2713
|
+
];
|
|
3430
2714
|
|
|
3431
2715
|
// src/index.ts
|
|
3432
2716
|
function registerBuiltInProviders(formsProvider) {
|
|
@@ -3466,11 +2750,8 @@ function registerBuiltInProviders(formsProvider) {
|
|
|
3466
2750
|
function createCustomFormsPlugin(options) {
|
|
3467
2751
|
registerTranslations(customFormsLocales);
|
|
3468
2752
|
const config = resolveConfig(options);
|
|
3469
|
-
const provider = options?.dataProvider ??
|
|
3470
|
-
|
|
3471
|
-
() => createMockFormsProvider()
|
|
3472
|
-
);
|
|
3473
|
-
const store = createCustomFormsStore(provider);
|
|
2753
|
+
const provider = options?.dataProvider ?? createSafeFormsProvider();
|
|
2754
|
+
const store = createFormsStore(provider);
|
|
3474
2755
|
registerBuiltInProviders(provider);
|
|
3475
2756
|
registerFormsTimelineSource();
|
|
3476
2757
|
registerFormsTabCounts();
|
|
@@ -3584,14 +2865,14 @@ function createCustomFormsPlugin(options) {
|
|
|
3584
2865
|
}
|
|
3585
2866
|
];
|
|
3586
2867
|
const registries = [...formRegistries, ...options?.settingsRegistries ?? []];
|
|
3587
|
-
const SettingsComponent = () =>
|
|
2868
|
+
const SettingsComponent = () => React13.createElement(PluginSettingsPanel, {
|
|
3588
2869
|
title: config.labels.settingsLabel,
|
|
3589
2870
|
customTabs: [
|
|
3590
2871
|
{
|
|
3591
2872
|
id: "forms",
|
|
3592
2873
|
label: config.labels.pageTitle,
|
|
3593
2874
|
icon: "FileText",
|
|
3594
|
-
content:
|
|
2875
|
+
content: React13.createElement(CustomFormsSettingsTab, { config, provider, store })
|
|
3595
2876
|
}
|
|
3596
2877
|
],
|
|
3597
2878
|
registries,
|
|
@@ -3599,11 +2880,11 @@ function createCustomFormsPlugin(options) {
|
|
|
3599
2880
|
hostPluginId: "custom_forms"
|
|
3600
2881
|
});
|
|
3601
2882
|
SettingsComponent.displayName = "CustomFormsSettingsPanel";
|
|
3602
|
-
const PersonDocumentsWidget2 = (props) =>
|
|
2883
|
+
const PersonDocumentsWidget2 = (props) => React13.createElement(PersonDocumentsWidget, { ...props, config, provider, store, surface: "records" });
|
|
3603
2884
|
PersonDocumentsWidget2.displayName = "PersonDocumentsWidget";
|
|
3604
|
-
const PersonFilesWidget = (props) =>
|
|
2885
|
+
const PersonFilesWidget = (props) => React13.createElement(PersonDocumentsWidget, { ...props, config, provider, store, surface: "files" });
|
|
3605
2886
|
PersonFilesWidget.displayName = "PersonFilesWidget";
|
|
3606
|
-
const PersonAnswerAlertsWidget = (props) =>
|
|
2887
|
+
const PersonAnswerAlertsWidget = (props) => React13.createElement(PersonAnswerAlerts, { personId: props.item?.id, provider });
|
|
3607
2888
|
PersonAnswerAlertsWidget.displayName = "PersonAnswerAlertsWidget";
|
|
3608
2889
|
return {
|
|
3609
2890
|
id: "custom_forms",
|
|
@@ -3629,6 +2910,7 @@ function createCustomFormsPlugin(options) {
|
|
|
3629
2910
|
navigation: [],
|
|
3630
2911
|
routes: [],
|
|
3631
2912
|
settings: [
|
|
2913
|
+
...formsV1SettingsTabs,
|
|
3632
2914
|
{
|
|
3633
2915
|
id: "custom_forms",
|
|
3634
2916
|
label: config.labels.settingsLabel,
|
|
@@ -3650,6 +2932,7 @@ function createCustomFormsPlugin(options) {
|
|
|
3650
2932
|
component: PersonFilesWidget,
|
|
3651
2933
|
order: 0
|
|
3652
2934
|
},
|
|
2935
|
+
...formsZoneWidgets(provider),
|
|
3653
2936
|
{
|
|
3654
2937
|
// Renders nothing unless a form marks an answer as an alert (V1 alert_config).
|
|
3655
2938
|
id: "person-answer-alerts",
|
|
@@ -3722,6 +3005,6 @@ function createCustomFormsPlugin(options) {
|
|
|
3722
3005
|
};
|
|
3723
3006
|
}
|
|
3724
3007
|
|
|
3725
|
-
export { ContractSignPane, FormFillDialog, PersonFormsAlert, createCustomFormsPlugin, ensureAttendanceContracts,
|
|
3008
|
+
export { ContractSignPane, FormFillDialog, PersonFormsAlert, createCustomFormsPlugin, ensureAttendanceContracts, listAttendanceContracts, recommendationsOf, registerDocumentTypeProvider };
|
|
3726
3009
|
//# sourceMappingURL=index.js.map
|
|
3727
3010
|
//# sourceMappingURL=index.js.map
|