@fayz-ai/plugin-forms 0.10.14 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/FormFillDialog.d.ts +17 -0
- package/dist/components/FormFillDialog.d.ts.map +1 -0
- package/dist/components/PersonFormsAlert.d.ts +39 -0
- package/dist/components/PersonFormsAlert.d.ts.map +1 -0
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +243 -17
- package/dist/index.js.map +1 -1
- package/dist/lib/person-requirements.d.ts +23 -0
- package/dist/lib/person-requirements.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/TemplateListView.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface FormFillDialogProps {
|
|
3
|
+
templateId: string;
|
|
4
|
+
personId: string;
|
|
5
|
+
personName?: string | null;
|
|
6
|
+
/** Depois de gravar — a régua reapura e a bola fecha sozinha. */
|
|
7
|
+
onSaved?: () => void;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
/**
|
|
10
|
+
* Onde o formulário mora. `inline` entrega só o miolo, para o anfitrião
|
|
11
|
+
* (agendamento, check-in, régua) desenhá-lo DENTRO da própria moldura em vez
|
|
12
|
+
* de abrir uma segunda janela por cima da primeira.
|
|
13
|
+
*/
|
|
14
|
+
frame?: 'modal' | 'inline';
|
|
15
|
+
}
|
|
16
|
+
export declare function FormFillDialog({ templateId, personId, personName, onSaved, onClose, frame }: FormFillDialogProps): React.JSX.Element;
|
|
17
|
+
//# sourceMappingURL=FormFillDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormFillDialog.d.ts","sourceRoot":"","sources":["../../src/components/FormFillDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAyBzB,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC3B;AAED,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,KAAe,EAAE,EAAE,mBAAmB,qBAwG1H"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface PersonChecklistItem {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
/** `done` risca e marca; `todo` deixa em aberto; `late` é o que venceu. */
|
|
6
|
+
state: 'done' | 'todo' | 'late';
|
|
7
|
+
/** A linha de contexto: "válido até …", "venceu em …", "nunca preenchido". */
|
|
8
|
+
detail?: string;
|
|
9
|
+
/** O verbo da linha, quando há o que fazer. */
|
|
10
|
+
action?: {
|
|
11
|
+
label: string;
|
|
12
|
+
onClick: () => void;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface PersonFormsAlertProps {
|
|
16
|
+
personId: string | null | undefined;
|
|
17
|
+
/** Abrir o formulário para preencher agora. Sem isto a faixa só informa. */
|
|
18
|
+
onFill?: (templateId: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Sobe quando algo foi gravado: a lista relê. Sem isto ela continua dizendo
|
|
21
|
+
* "pendente" sobre o formulário que a pessoa ACABOU de preencher.
|
|
22
|
+
*/
|
|
23
|
+
version?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Itens de fora dos formulários — o que mais a casa quer conferir com este
|
|
26
|
+
* paciente antes de atender. Entram na mesma lista, com o mesmo desenho:
|
|
27
|
+
* uma checagem é uma checagem, venha de onde vier.
|
|
28
|
+
*/
|
|
29
|
+
extras?: PersonChecklistItem[];
|
|
30
|
+
/**
|
|
31
|
+
* O que ainda falta, para quem hospeda a lista decidir o que fazer com isso
|
|
32
|
+
* — travar o botão de concluir, por exemplo. Vem com os rótulos, porque um
|
|
33
|
+
* botão travado sem dizer o motivo é pior do que um botão solto.
|
|
34
|
+
*/
|
|
35
|
+
onPending?: (labels: string[]) => void;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function PersonFormsAlert({ personId, onFill, version, extras, onPending, className }: PersonFormsAlertProps): React.JSX.Element | null;
|
|
39
|
+
//# sourceMappingURL=PersonFormsAlert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PersonFormsAlert.d.ts","sourceRoot":"","sources":["../../src/components/PersonFormsAlert.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AA4BzB,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,2EAA2E;IAC3E,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IAC/B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+CAA+C;IAC/C,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;CAChD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACnC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAA;IAC9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA4BD,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAW,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,qBAAqB,4BA0EtH"}
|
package/dist/data/mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/data/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAuBtD,wBAAgB,uBAAuB,IAAI,uBAAuB,
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/data/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAuBtD,wBAAgB,uBAAuB,IAAI,uBAAuB,CA0LjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAuItD,wBAAgB,2BAA2B,IAAI,uBAAuB,CAuVrE"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,8 @@ import type { PluginManifest } from '@fayz-ai/core';
|
|
|
2
2
|
import type { CustomFormsPluginOptions } from './config';
|
|
3
3
|
export type { CustomFormsPluginOptions };
|
|
4
4
|
export { registerDocumentTypeProvider, type DocumentTypeOption, type DocumentTypeProvider } from './lib/document-types';
|
|
5
|
+
export { PersonFormsAlert, type PersonFormsAlertProps } from './components/PersonFormsAlert';
|
|
6
|
+
export { FormFillDialog, type FormFillDialogProps } from './components/FormFillDialog';
|
|
7
|
+
export { getPersonFormRequirements, isOutstanding, type PersonFormRequirement, type RequirementState, } from './lib/person-requirements';
|
|
5
8
|
export declare function createCustomFormsPlugin(options?: CustomFormsPluginOptions): PluginManifest;
|
|
6
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAUnD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AA4CxD,YAAY,EAAE,wBAAwB,EAAE,CAAA;AACxC,OAAO,EAAE,4BAA4B,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAUnD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AA4CxD,YAAY,EAAE,wBAAwB,EAAE,CAAA;AACxC,OAAO,EAAE,4BAA4B,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAGvH,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AAG5F,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACtF,OAAO,EACL,yBAAyB,EAAE,aAAa,EACxC,KAAK,qBAAqB,EAAE,KAAK,gBAAgB,GAClD,MAAM,2BAA2B,CAAA;AAElC,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,cAAc,CAwL1F"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { createOrUpdateFormTool, prefillFromPerson,
|
|
2
|
-
import
|
|
3
|
-
import { tl,
|
|
4
|
-
import { PluginSettingsPanel, registerTimelineSource, useLimitGuard, invalidateLimit, askAI, PermissionGate, PageActions } from '@fayz-ai/admin';
|
|
1
|
+
import { FormRenderer, createOrUpdateFormTool, prefillFromPerson, printDocument } from './chunk-RBLXIKWB.js';
|
|
2
|
+
import React10, { createContext, useState, useCallback, useRef, useMemo, useEffect } from 'react';
|
|
3
|
+
import { tl, createSafeDataProvider, registerTranslations, getSupabaseClientOptional, useTranslation, getActiveTenantId, emitDomainEvent, useDataVersion } from '@fayz-ai/core';
|
|
4
|
+
import { getFayzSupabaseClient, useOrganizationStore, PluginSettingsPanel, registerTimelineSource, useLimitGuard, invalidateLimit, askAI, PermissionGate, PageActions } from '@fayz-ai/admin';
|
|
5
5
|
import { create } from 'zustand';
|
|
6
|
-
import { FileText, Image, Paperclip, Plus, WandSparkles, Pencil, Archive, Search, ArrowLeft, Printer, ChevronDown, Upload, Eye } from 'lucide-react';
|
|
7
|
-
import { Badge, RowActions, Button, PageHeaderActions, Card, DataTable, useSaveBar, Dropdown, DropdownTrigger, DropdownContent, DropdownSeparator, DropdownLabel } from '@fayz-ai/ui';
|
|
6
|
+
import { ClipboardList, CheckCircle2, Clock, Circle, FileText, Image, Paperclip, Plus, WandSparkles, Pencil, Archive, Search, ArrowLeft, Printer, ChevronDown, Upload, Eye } from 'lucide-react';
|
|
7
|
+
import { Skeleton, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Badge, Switch, RowActions, Button, PageHeaderActions, Card, DataTable, useSaveBar, Dropdown, DropdownTrigger, DropdownContent, DropdownSeparator, DropdownLabel } from '@fayz-ai/ui';
|
|
8
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
10
|
// src/data/tables.ts
|
|
@@ -60,6 +60,8 @@ function createMockFormsProvider() {
|
|
|
60
60
|
metadata: input.metadata ?? {},
|
|
61
61
|
isActive: true,
|
|
62
62
|
isDeleted: false,
|
|
63
|
+
requiredOnAttendance: false,
|
|
64
|
+
validityDays: null,
|
|
63
65
|
createdAt: now(),
|
|
64
66
|
updatedAt: now()
|
|
65
67
|
};
|
|
@@ -224,6 +226,8 @@ async function countTemplateUsageClientSide(db, tenantId) {
|
|
|
224
226
|
}
|
|
225
227
|
function mapTemplate(r) {
|
|
226
228
|
return {
|
|
229
|
+
requiredOnAttendance: r.required_on_attendance ?? false,
|
|
230
|
+
validityDays: r.validity_days ?? null,
|
|
227
231
|
id: r.id,
|
|
228
232
|
tenantId: r.tenant_id,
|
|
229
233
|
name: r.name,
|
|
@@ -348,7 +352,12 @@ function createSupabaseFormsProvider() {
|
|
|
348
352
|
parent_id: existing.parentId ?? existing.id
|
|
349
353
|
}).select().single();
|
|
350
354
|
if (insertError) throw new Error(insertError.message);
|
|
351
|
-
await db.from(T.templates).update({
|
|
355
|
+
await db.from(T.templates).update({
|
|
356
|
+
...input.requiredOnAttendance !== void 0 ? { required_on_attendance: input.requiredOnAttendance } : {},
|
|
357
|
+
...input.validityDays !== void 0 ? { validity_days: input.validityDays } : {},
|
|
358
|
+
is_current: false,
|
|
359
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
360
|
+
}).eq("id", id);
|
|
352
361
|
return mapTemplate(newRow);
|
|
353
362
|
}
|
|
354
363
|
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -958,6 +967,38 @@ function TemplateListView({ store, config, onEdit, onNew }) {
|
|
|
958
967
|
] })
|
|
959
968
|
] })
|
|
960
969
|
},
|
|
970
|
+
{
|
|
971
|
+
id: "required",
|
|
972
|
+
header: "Exigido",
|
|
973
|
+
// A PORTA da migration 001. Sem ela as duas colunas novas ficam no banco
|
|
974
|
+
// e o aviso de formulário pendente nunca acende para ninguém — código
|
|
975
|
+
// pronto sem consumidor é o mesmo que código ausente.
|
|
976
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
977
|
+
/* @__PURE__ */ jsx(
|
|
978
|
+
Switch,
|
|
979
|
+
{
|
|
980
|
+
size: "sm",
|
|
981
|
+
checked: row.original.requiredOnAttendance,
|
|
982
|
+
onCheckedChange: (on) => {
|
|
983
|
+
void store.getState().updateTemplate(row.original.id, {
|
|
984
|
+
requiredOnAttendance: on,
|
|
985
|
+
// Seis meses é o que o V1 usa na anamnese, e é o padrão que faz
|
|
986
|
+
// a validade existir sem pedir mais uma decisão na hora de
|
|
987
|
+
// ligar. Zero validade é o mesmo que "vale para sempre", que
|
|
988
|
+
// deixa uma anamnese de 2019 contando como preenchida.
|
|
989
|
+
validityDays: on ? row.original.validityDays ?? 180 : null
|
|
990
|
+
});
|
|
991
|
+
},
|
|
992
|
+
"aria-label": `Exigir ${row.original.name} de todo cliente`
|
|
993
|
+
}
|
|
994
|
+
),
|
|
995
|
+
row.original.requiredOnAttendance && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground", children: [
|
|
996
|
+
"vale ",
|
|
997
|
+
row.original.validityDays ?? "\u221E",
|
|
998
|
+
" dias"
|
|
999
|
+
] })
|
|
1000
|
+
] })
|
|
1001
|
+
},
|
|
961
1002
|
{
|
|
962
1003
|
id: "usage",
|
|
963
1004
|
header: t("customForms.usageColumn"),
|
|
@@ -1063,7 +1104,7 @@ function CustomFormsSettingsTab({ config, provider, store }) {
|
|
|
1063
1104
|
window.location.hash = `#${ROUTE_BASE}/forms`;
|
|
1064
1105
|
store.getState().fetchTemplates();
|
|
1065
1106
|
}, [store]);
|
|
1066
|
-
|
|
1107
|
+
React10.useEffect(() => {
|
|
1067
1108
|
const sync = () => setBuilderTemplateId(readBuilderIdFromHash());
|
|
1068
1109
|
window.addEventListener("hashchange", sync);
|
|
1069
1110
|
return () => window.removeEventListener("hashchange", sync);
|
|
@@ -1094,11 +1135,11 @@ function BuilderWrapper({
|
|
|
1094
1135
|
config,
|
|
1095
1136
|
onBack
|
|
1096
1137
|
}) {
|
|
1097
|
-
const FormBuilderLazy =
|
|
1138
|
+
const FormBuilderLazy = React10.lazy(
|
|
1098
1139
|
() => import('./FormBuilder-YHJHF6HZ.js').then((m) => ({ default: m.FormBuilder }))
|
|
1099
1140
|
);
|
|
1100
1141
|
return /* @__PURE__ */ jsx(
|
|
1101
|
-
|
|
1142
|
+
React10.Suspense,
|
|
1102
1143
|
{
|
|
1103
1144
|
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" }) }),
|
|
1104
1145
|
children: /* @__PURE__ */ jsx(
|
|
@@ -1334,7 +1375,7 @@ function DocumentFormDialog({
|
|
|
1334
1375
|
provider.getTemplateById(existingDocument.templateId).then(setTemplate);
|
|
1335
1376
|
}
|
|
1336
1377
|
}, [existingDocument, template, provider]);
|
|
1337
|
-
const prefilled =
|
|
1378
|
+
const prefilled = React10.useRef(!!initialTemplate || !!existingDocument);
|
|
1338
1379
|
useEffect(() => {
|
|
1339
1380
|
if (prefilled.current || !template || existingDocument) return;
|
|
1340
1381
|
prefilled.current = true;
|
|
@@ -1366,7 +1407,7 @@ function DocumentFormDialog({
|
|
|
1366
1407
|
},
|
|
1367
1408
|
[template, existingDocument, data, personId, personName, store, onSaved, guardDocuments]
|
|
1368
1409
|
);
|
|
1369
|
-
const snapshot =
|
|
1410
|
+
const snapshot = React10.useRef(null);
|
|
1370
1411
|
useEffect(() => {
|
|
1371
1412
|
if (template && snapshot.current === null) {
|
|
1372
1413
|
snapshot.current = JSON.stringify(existingDocument?.data ?? {});
|
|
@@ -1616,7 +1657,7 @@ function AddDocumentDropdown({ personId, onSelect }) {
|
|
|
1616
1657
|
)
|
|
1617
1658
|
] }),
|
|
1618
1659
|
/* @__PURE__ */ jsxs("div", { ref: listRef, className: "max-h-[min(60vh,20rem)] overflow-y-auto overscroll-contain p-1", children: [
|
|
1619
|
-
groups.map((group, gi) => /* @__PURE__ */ jsxs(
|
|
1660
|
+
groups.map((group, gi) => /* @__PURE__ */ jsxs(React10.Fragment, { children: [
|
|
1620
1661
|
gi > 0 && /* @__PURE__ */ jsx(DropdownSeparator, {}),
|
|
1621
1662
|
/* @__PURE__ */ jsx(DropdownLabel, { className: "text-[10px] uppercase tracking-wider", children: group.label }),
|
|
1622
1663
|
group.items.map((opt) => {
|
|
@@ -1924,6 +1965,191 @@ function buildFormsAnalyticsCards() {
|
|
|
1924
1965
|
}
|
|
1925
1966
|
];
|
|
1926
1967
|
}
|
|
1968
|
+
async function getPersonFormRequirements(tenantId, personId) {
|
|
1969
|
+
const client = getFayzSupabaseClient();
|
|
1970
|
+
const { data, error } = await client.rpc("forms_person_requirements", {
|
|
1971
|
+
p_tenant_id: tenantId,
|
|
1972
|
+
p_person_id: personId
|
|
1973
|
+
});
|
|
1974
|
+
if (error) throw error;
|
|
1975
|
+
return (data ?? []).map((r) => ({
|
|
1976
|
+
templateId: r.template_id,
|
|
1977
|
+
templateName: r.template_name,
|
|
1978
|
+
category: r.category,
|
|
1979
|
+
validityDays: r.validity_days ?? null,
|
|
1980
|
+
lastFilledAt: r.last_filled_at ?? null,
|
|
1981
|
+
validUntil: r.valid_until ?? null,
|
|
1982
|
+
state: r.state
|
|
1983
|
+
}));
|
|
1984
|
+
}
|
|
1985
|
+
var isOutstanding = (r) => r.state !== "valid";
|
|
1986
|
+
var DATE = (iso) => (/* @__PURE__ */ new Date(iso + "T00:00:00")).toLocaleDateString("pt-BR");
|
|
1987
|
+
function Linha({ item }) {
|
|
1988
|
+
const Icone = item.state === "done" ? CheckCircle2 : item.state === "late" ? Clock : Circle;
|
|
1989
|
+
const cor = item.state === "done" ? "text-success" : item.state === "late" ? "text-warning" : "text-muted-foreground";
|
|
1990
|
+
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: [
|
|
1991
|
+
/* @__PURE__ */ jsx(Icone, { className: `h-3.5 w-3.5 shrink-0 ${cor}`, "aria-hidden": true }),
|
|
1992
|
+
/* @__PURE__ */ jsx("span", { className: item.state === "done" ? "text-foreground" : "font-medium text-foreground", children: item.label }),
|
|
1993
|
+
item.detail && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
1994
|
+
"\u2014 ",
|
|
1995
|
+
item.detail
|
|
1996
|
+
] }),
|
|
1997
|
+
item.action && // O VERBO NÃO PODE PARECER DESLIGADO. Em `text-primary` sobre o âmbar
|
|
1998
|
+
// da faixa ele saía lavado e, quando quebrava a linha, parecia um
|
|
1999
|
+
// rótulo cinza no canto — a recepção lia "pendente" e não via que
|
|
2000
|
+
// havia o que clicar.
|
|
2001
|
+
/* @__PURE__ */ jsx(
|
|
2002
|
+
"button",
|
|
2003
|
+
{
|
|
2004
|
+
type: "button",
|
|
2005
|
+
onClick: item.action.onClick,
|
|
2006
|
+
className: "ml-auto shrink-0 rounded-button bg-foreground px-2 py-0.5 text-[11px] font-semibold text-background hover:opacity-90",
|
|
2007
|
+
children: item.action.label
|
|
2008
|
+
}
|
|
2009
|
+
)
|
|
2010
|
+
] });
|
|
2011
|
+
}
|
|
2012
|
+
function PersonFormsAlert({ personId, onFill, version = 0, extras, onPending, className }) {
|
|
2013
|
+
const tenantId = useOrganizationStore((s) => s.currentOrg?.id);
|
|
2014
|
+
const [rows, setRows] = React10.useState(null);
|
|
2015
|
+
React10.useEffect(() => {
|
|
2016
|
+
if (!personId || !tenantId) {
|
|
2017
|
+
setRows([]);
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
let cancelled = false;
|
|
2021
|
+
setRows(null);
|
|
2022
|
+
getPersonFormRequirements(tenantId, personId).then((r) => {
|
|
2023
|
+
if (!cancelled) setRows(r);
|
|
2024
|
+
}).catch(() => {
|
|
2025
|
+
if (!cancelled) setRows([]);
|
|
2026
|
+
});
|
|
2027
|
+
return () => {
|
|
2028
|
+
cancelled = true;
|
|
2029
|
+
};
|
|
2030
|
+
}, [personId, tenantId, version]);
|
|
2031
|
+
const itens = React10.useMemo(() => [
|
|
2032
|
+
...(rows ?? []).map((r) => {
|
|
2033
|
+
const pendente = isOutstanding(r);
|
|
2034
|
+
return {
|
|
2035
|
+
id: r.templateId,
|
|
2036
|
+
label: r.templateName,
|
|
2037
|
+
state: !pendente ? "done" : r.state === "expired" ? "late" : "todo",
|
|
2038
|
+
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",
|
|
2039
|
+
action: pendente && onFill ? { label: "Preencher agora", onClick: () => onFill(r.templateId) } : void 0
|
|
2040
|
+
};
|
|
2041
|
+
}),
|
|
2042
|
+
...extras ?? []
|
|
2043
|
+
], [rows, extras, onFill]);
|
|
2044
|
+
const peso = (i) => i.state === "late" ? 0 : i.state === "todo" ? 1 : 2;
|
|
2045
|
+
itens.sort((a, b) => peso(a) - peso(b));
|
|
2046
|
+
const pendentes = itens.filter((i) => i.state !== "done");
|
|
2047
|
+
const faltando = pendentes.length;
|
|
2048
|
+
const chave = pendentes.map((i) => i.label).join("|");
|
|
2049
|
+
React10.useEffect(() => {
|
|
2050
|
+
onPending?.(chave ? chave.split("|") : []);
|
|
2051
|
+
}, [chave]);
|
|
2052
|
+
if (rows === null) return /* @__PURE__ */ jsx(Skeleton, { className: `h-12 w-full ${className ?? ""}` });
|
|
2053
|
+
if (itens.length === 0) return null;
|
|
2054
|
+
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: [
|
|
2055
|
+
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 border-b border-inherit px-3 py-1.5 text-[11px] font-medium text-foreground", children: [
|
|
2056
|
+
/* @__PURE__ */ jsx(ClipboardList, { className: `h-3.5 w-3.5 ${faltando ? "text-warning" : "text-success"}`, "aria-hidden": true }),
|
|
2057
|
+
faltando === 0 ? "Checagem do paciente \u2014 tudo em dia" : faltando === 1 ? "1 item pendente com o paciente" : `${faltando} itens pendentes com o paciente`
|
|
2058
|
+
] }),
|
|
2059
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border/60", children: itens.map((i) => /* @__PURE__ */ jsx(Linha, { item: i }, i.id)) })
|
|
2060
|
+
] });
|
|
2061
|
+
}
|
|
2062
|
+
var Ctx = createContext(null);
|
|
2063
|
+
function CustomFormsProvider({
|
|
2064
|
+
value,
|
|
2065
|
+
children
|
|
2066
|
+
}) {
|
|
2067
|
+
return React10.createElement(Ctx.Provider, { value }, children);
|
|
2068
|
+
}
|
|
2069
|
+
function FormFillDialog({ templateId, personId, personName, onSaved, onClose, frame = "modal" }) {
|
|
2070
|
+
const provider = React10.useMemo(() => createSafeDataProvider(
|
|
2071
|
+
() => createSupabaseFormsProvider(),
|
|
2072
|
+
() => createMockFormsProvider()
|
|
2073
|
+
), []);
|
|
2074
|
+
const store = React10.useMemo(() => createCustomFormsStore(provider), [provider]);
|
|
2075
|
+
const config = React10.useMemo(() => resolveConfig(), []);
|
|
2076
|
+
const [template, setTemplate] = React10.useState(null);
|
|
2077
|
+
const [erro, setErro] = React10.useState(null);
|
|
2078
|
+
const [data, setData] = React10.useState({});
|
|
2079
|
+
const [salvando, setSalvando] = React10.useState(false);
|
|
2080
|
+
async function salvar() {
|
|
2081
|
+
if (!template) return;
|
|
2082
|
+
setSalvando(true);
|
|
2083
|
+
try {
|
|
2084
|
+
await store.getState().createDocument({
|
|
2085
|
+
templateId: template.id,
|
|
2086
|
+
personId,
|
|
2087
|
+
title: personName ? `${template.name} \u2014 ${personName}` : template.name,
|
|
2088
|
+
data,
|
|
2089
|
+
status: "completed"
|
|
2090
|
+
});
|
|
2091
|
+
onSaved?.();
|
|
2092
|
+
onClose();
|
|
2093
|
+
} catch (e) {
|
|
2094
|
+
setErro(e instanceof Error ? e.message : "N\xE3o foi poss\xEDvel salvar o formul\xE1rio.");
|
|
2095
|
+
} finally {
|
|
2096
|
+
setSalvando(false);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
React10.useEffect(() => {
|
|
2100
|
+
let vivo = true;
|
|
2101
|
+
(async () => {
|
|
2102
|
+
try {
|
|
2103
|
+
const t = await store.getState().fetchTemplateById(templateId);
|
|
2104
|
+
if (vivo) setTemplate(t);
|
|
2105
|
+
} catch (e) {
|
|
2106
|
+
if (vivo) setErro(e instanceof Error ? e.message : "N\xE3o foi poss\xEDvel abrir o formul\xE1rio.");
|
|
2107
|
+
}
|
|
2108
|
+
})();
|
|
2109
|
+
return () => {
|
|
2110
|
+
vivo = false;
|
|
2111
|
+
};
|
|
2112
|
+
}, [templateId, store]);
|
|
2113
|
+
const corpo = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2114
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-5 py-4", children: erro ? /* @__PURE__ */ jsx("p", { className: "rounded-lg bg-destructive/10 px-3 py-2 text-xs text-destructive", children: erro }) : !template ? (
|
|
2115
|
+
/* Esqueleto com a FORMA do formulário: três campos e um rótulo. */
|
|
2116
|
+
/* @__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: [
|
|
2117
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-muted/70" }),
|
|
2118
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 w-full animate-pulse rounded-lg bg-muted" })
|
|
2119
|
+
] }, i)) })
|
|
2120
|
+
) : /* @__PURE__ */ jsx(CustomFormsProvider, { value: { config, provider, store }, children: /* @__PURE__ */ jsx(FormRenderer, { schema: template.schema, data, onChange: setData }) }) }),
|
|
2121
|
+
/* @__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: [
|
|
2122
|
+
/* @__PURE__ */ jsx(
|
|
2123
|
+
"button",
|
|
2124
|
+
{
|
|
2125
|
+
type: "button",
|
|
2126
|
+
onClick: onClose,
|
|
2127
|
+
className: "rounded-button px-2 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
2128
|
+
children: "Agora n\xE3o"
|
|
2129
|
+
}
|
|
2130
|
+
),
|
|
2131
|
+
/* @__PURE__ */ jsx(
|
|
2132
|
+
"button",
|
|
2133
|
+
{
|
|
2134
|
+
type: "button",
|
|
2135
|
+
onClick: salvar,
|
|
2136
|
+
disabled: !template || salvando,
|
|
2137
|
+
className: "rounded-button bg-foreground px-3 py-1.5 text-xs font-semibold text-background disabled:opacity-40",
|
|
2138
|
+
children: salvando ? "Salvando\u2026" : "Concluir"
|
|
2139
|
+
}
|
|
2140
|
+
)
|
|
2141
|
+
] }) })
|
|
2142
|
+
] });
|
|
2143
|
+
if (frame === "inline") return corpo;
|
|
2144
|
+
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
2145
|
+
if (!o) onClose();
|
|
2146
|
+
}, children: /* @__PURE__ */ jsxs(ModalContent, { size: "2xl", noPadding: true, className: "gap-0", children: [
|
|
2147
|
+
/* @__PURE__ */ jsx(ModalHeader, { className: "border-b px-5 py-3.5", children: /* @__PURE__ */ jsx(ModalTitle, { className: "truncate text-base", children: template?.name ?? "Formul\xE1rio" }) }),
|
|
2148
|
+
/* @__PURE__ */ jsx(ModalBody, { className: "p-0", children: corpo })
|
|
2149
|
+
] }) });
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
// src/index.ts
|
|
1927
2153
|
function registerBuiltInProviders(formsProvider) {
|
|
1928
2154
|
registerDocumentTypeProvider({
|
|
1929
2155
|
id: "custom_forms:templates",
|
|
@@ -1998,14 +2224,14 @@ function createCustomFormsPlugin(options) {
|
|
|
1998
2224
|
}
|
|
1999
2225
|
];
|
|
2000
2226
|
const registries = [...formRegistries, ...options?.settingsRegistries ?? []];
|
|
2001
|
-
const SettingsComponent = () =>
|
|
2227
|
+
const SettingsComponent = () => React10.createElement(PluginSettingsPanel, {
|
|
2002
2228
|
title: config.labels.settingsLabel,
|
|
2003
2229
|
customTabs: [
|
|
2004
2230
|
{
|
|
2005
2231
|
id: "forms",
|
|
2006
2232
|
label: config.labels.pageTitle,
|
|
2007
2233
|
icon: "FileText",
|
|
2008
|
-
content:
|
|
2234
|
+
content: React10.createElement(CustomFormsSettingsTab, { config, provider, store })
|
|
2009
2235
|
}
|
|
2010
2236
|
],
|
|
2011
2237
|
registries,
|
|
@@ -2013,7 +2239,7 @@ function createCustomFormsPlugin(options) {
|
|
|
2013
2239
|
hostPluginId: "custom_forms"
|
|
2014
2240
|
});
|
|
2015
2241
|
SettingsComponent.displayName = "CustomFormsSettingsPanel";
|
|
2016
|
-
const PersonDocumentsWidget2 = (props) =>
|
|
2242
|
+
const PersonDocumentsWidget2 = (props) => React10.createElement(PersonDocumentsWidget, { ...props, config, provider, store });
|
|
2017
2243
|
PersonDocumentsWidget2.displayName = "PersonDocumentsWidget";
|
|
2018
2244
|
return {
|
|
2019
2245
|
id: "custom_forms",
|
|
@@ -2119,6 +2345,6 @@ function createCustomFormsPlugin(options) {
|
|
|
2119
2345
|
};
|
|
2120
2346
|
}
|
|
2121
2347
|
|
|
2122
|
-
export { createCustomFormsPlugin, registerDocumentTypeProvider };
|
|
2348
|
+
export { FormFillDialog, PersonFormsAlert, createCustomFormsPlugin, getPersonFormRequirements, isOutstanding, registerDocumentTypeProvider };
|
|
2123
2349
|
//# sourceMappingURL=index.js.map
|
|
2124
2350
|
//# sourceMappingURL=index.js.map
|