uhc-portal-components-react 0.1.18 → 0.1.20
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/CHANGELOG.md +23 -0
- package/README.md +14 -0
- package/dist/ClinicalArtifactEditor.d.ts +64 -0
- package/dist/ClinicalArtifactEditor.d.ts.map +1 -0
- package/dist/ClinicalArtifactEditor.js +157 -0
- package/dist/EncounterTransferConsole.d.ts +84 -0
- package/dist/EncounterTransferConsole.d.ts.map +1 -0
- package/dist/EncounterTransferConsole.js +51 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/styles.css +95 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.20
|
|
4
|
+
|
|
5
|
+
- Add one brand-neutral, schema-driven clinical artifact editor for UHC,
|
|
6
|
+
Vet/Pet and SOSChain Personal and Professional portals.
|
|
7
|
+
- Keep human-entered clinical text when a terminology candidate is explicitly
|
|
8
|
+
selected, and place its code and display in the schema-owned companion
|
|
9
|
+
claims without silently coding free text.
|
|
10
|
+
- Require host-owned provider persistence to return a fresh Bundle before the
|
|
11
|
+
editor publishes a successful readback. The shared component owns no auth,
|
|
12
|
+
provider URL, write capability or human-versus-animal policy.
|
|
13
|
+
|
|
14
|
+
## 0.1.19
|
|
15
|
+
|
|
16
|
+
- Add an accessible Encounter transfer console with patient lookup, a
|
|
17
|
+
horizontally scrollable care journey, explicit return/on-hold versus
|
|
18
|
+
definitive/completed choices, and an in-progress destination.
|
|
19
|
+
- Add emergency-contact call, video and message actions plus PDF, FHIR Bundle
|
|
20
|
+
and calendar attachment choices without deriving authorization in the UI.
|
|
21
|
+
- Keep plain-language help visible and place FHIR resource names in optional
|
|
22
|
+
technical details.
|
|
23
|
+
- Render a host-provided encounter business identifier without replacing the
|
|
24
|
+
FHIR resource identity or deriving identifiers in browser state.
|
|
25
|
+
|
|
3
26
|
## 0.1.18
|
|
4
27
|
|
|
5
28
|
- Add shared, read-only Health summary and Care journey areas to the SOSChain
|
package/README.md
CHANGED
|
@@ -53,6 +53,12 @@ The package deliberately exposes modules instead of a cloned application:
|
|
|
53
53
|
- `IpsBundleSummary`: receives one in-memory IPS document Bundle, obtains all
|
|
54
54
|
`Composition.section` entries through Common Utils and renders every
|
|
55
55
|
section's cards. It performs no ingestion or GW call.
|
|
56
|
+
- `ClinicalArtifactEditor`: renders the same schema-driven clinical form for
|
|
57
|
+
human and animal subjects. The host supplies its UHC/Vet schema,
|
|
58
|
+
terminology-search adapter and authorized persistence callback. Selecting a
|
|
59
|
+
terminology candidate preserves source text and adds only the confirmed
|
|
60
|
+
`system|code` and display. Success requires a fresh provider readback Bundle;
|
|
61
|
+
local form state is never presented as a persisted write.
|
|
56
62
|
- `InvitationAcceptancePanel`: controlled invitation-code UI for a related
|
|
57
63
|
individual member; the host refreshes the authorized-subject list after its
|
|
58
64
|
BFF accepts the invitation.
|
|
@@ -64,6 +70,14 @@ The package deliberately exposes modules instead of a cloned application:
|
|
|
64
70
|
- `SoschainWorkspace`: subject-card picker and four-area workspace for Places,
|
|
65
71
|
devices/vehicles and read-only emergency communications. The host supplies
|
|
66
72
|
already-authorized cards and capabilities.
|
|
73
|
+
- `EncounterTransferConsole`: visual patient-transfer workspace. The host
|
|
74
|
+
resolves the authorized subject, contacts and write capability; the component
|
|
75
|
+
only reports the chosen source outcome (`on-hold` when returning or
|
|
76
|
+
`completed` when leaving definitively) and never treats browser state as
|
|
77
|
+
clinical authority. `EncounterTransferStage.identifier` remains the FHIR
|
|
78
|
+
resource identity; an optional `displayIdentifier` is a provider-assigned
|
|
79
|
+
human-readable business identifier. The component never generates it or
|
|
80
|
+
resolves same-second concurrency.
|
|
67
81
|
- `OrganizationWorkforceAdministration`: controlled license-capacity,
|
|
68
82
|
employee lifecycle and invitation-status UI. Invitations remain host-owned
|
|
69
83
|
application records; the component does not reserve licenses or create GW
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ClinicalResourceBundleLike } from 'gdc-common-utils-ts/utils/clinical-resource-view';
|
|
2
|
+
export type ClinicalArtifactSchema = Readonly<{
|
|
3
|
+
resourceType: string;
|
|
4
|
+
requiredClaims: readonly string[];
|
|
5
|
+
optionalClaims: readonly string[];
|
|
6
|
+
}>;
|
|
7
|
+
export type ClinicalArtifactEditorRequest = Readonly<{
|
|
8
|
+
resourceType: string;
|
|
9
|
+
sectionCode: string;
|
|
10
|
+
mode: 'create' | 'update';
|
|
11
|
+
artifactId?: string;
|
|
12
|
+
initialClaims?: Readonly<Record<string, unknown>>;
|
|
13
|
+
}>;
|
|
14
|
+
export type ClinicalArtifactDraft = Readonly<{
|
|
15
|
+
resourceType: string;
|
|
16
|
+
id: string;
|
|
17
|
+
sectionCode: string;
|
|
18
|
+
mode: 'create' | 'update';
|
|
19
|
+
claims: Readonly<Record<string, string>>;
|
|
20
|
+
}>;
|
|
21
|
+
export type ClinicalTerminologyCandidate = Readonly<{
|
|
22
|
+
system: string;
|
|
23
|
+
code: string;
|
|
24
|
+
display: string;
|
|
25
|
+
text?: string;
|
|
26
|
+
}>;
|
|
27
|
+
export type ClinicalTerminologySearch = Readonly<{
|
|
28
|
+
resourceType: string;
|
|
29
|
+
claim: string;
|
|
30
|
+
language: string;
|
|
31
|
+
text: string;
|
|
32
|
+
taxonomyId?: string;
|
|
33
|
+
limit: number;
|
|
34
|
+
}>;
|
|
35
|
+
export type ClinicalArtifactEditorCopy = Readonly<{
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
save: string;
|
|
39
|
+
saving: string;
|
|
40
|
+
cancel: string;
|
|
41
|
+
close: string;
|
|
42
|
+
terminologyLoading: string;
|
|
43
|
+
terminologyUnavailable: string;
|
|
44
|
+
readbackMissing: string;
|
|
45
|
+
}>;
|
|
46
|
+
export type ClinicalArtifactEditorProps = Readonly<{
|
|
47
|
+
request: ClinicalArtifactEditorRequest;
|
|
48
|
+
schema: ClinicalArtifactSchema;
|
|
49
|
+
copy: ClinicalArtifactEditorCopy;
|
|
50
|
+
locale?: string;
|
|
51
|
+
taxonomyId?: string;
|
|
52
|
+
fieldLabel: (claim: string) => string;
|
|
53
|
+
searchTerminology?: (input: ClinicalTerminologySearch) => Promise<readonly ClinicalTerminologyCandidate[]>;
|
|
54
|
+
saveArtifact: (draft: ClinicalArtifactDraft) => Promise<ClinicalResourceBundleLike | undefined>;
|
|
55
|
+
onCancel: () => void;
|
|
56
|
+
onReadback: (bundle: ClinicalResourceBundleLike) => void;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Shared human/animal clinical editor. The host owns authorization, transport,
|
|
60
|
+
* persistence and terminology service access. A successful form submission is
|
|
61
|
+
* published only after the host returns a fresh provider readback Bundle.
|
|
62
|
+
*/
|
|
63
|
+
export declare function ClinicalArtifactEditor({ request, schema, copy, locale, taxonomyId, fieldLabel, searchTerminology, saveArtifact, onCancel, onReadback, }: ClinicalArtifactEditorProps): import("react").JSX.Element;
|
|
64
|
+
//# sourceMappingURL=ClinicalArtifactEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;AAElG,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClD,CAAC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACzC,CAAC,CAAA;AAEF,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,kBAAkB,EAAE,MAAM,CAAA;IAC1B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,eAAe,EAAE,MAAM,CAAA;CACxB,CAAC,CAAA;AAEF,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,OAAO,EAAE,6BAA6B,CAAA;IACtC,MAAM,EAAE,sBAAsB,CAAA;IAC9B,IAAI,EAAE,0BAA0B,CAAA;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,OAAO,CAAC,SAAS,4BAA4B,EAAE,CAAC,CAAA;IAC1G,YAAY,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAA;IAC/F,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAA;CACzD,CAAC,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,OAAO,EACP,MAAM,EACN,IAAI,EACJ,MAAa,EACb,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,UAAU,GACX,EAAE,2BAA2B,+BA2G7B"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useMemo, useState } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Shared human/animal clinical editor. The host owns authorization, transport,
|
|
6
|
+
* persistence and terminology service access. A successful form submission is
|
|
7
|
+
* published only after the host returns a fresh provider readback Bundle.
|
|
8
|
+
*/
|
|
9
|
+
export function ClinicalArtifactEditor({ request, schema, copy, locale = 'en', taxonomyId, fieldLabel, searchTerminology, saveArtifact, onCancel, onReadback, }) {
|
|
10
|
+
const titleId = useId();
|
|
11
|
+
const descriptionId = useId();
|
|
12
|
+
const acceptedClaims = useMemo(() => [...schema.requiredClaims, ...schema.optionalClaims], [schema.optionalClaims, schema.requiredClaims]);
|
|
13
|
+
const fields = useMemo(() => acceptedClaims
|
|
14
|
+
.filter((claim) => !claim.endsWith('.section') && !claim.endsWith('.user-selected'))
|
|
15
|
+
.filter((claim) => !isHiddenCodingCompanion(request.resourceType, claim, acceptedClaims)), [acceptedClaims, request.resourceType]);
|
|
16
|
+
const requiredClaims = useMemo(() => new Set(schema.requiredClaims), [schema.requiredClaims]);
|
|
17
|
+
const [claims, setClaims] = useState(() => ({
|
|
18
|
+
...defaultClaims(request.resourceType),
|
|
19
|
+
...Object.fromEntries(Object.entries(request.initialClaims ?? {}).map(([claim, value]) => [claim, String(value ?? '')])),
|
|
20
|
+
}));
|
|
21
|
+
const [activeTerminologyClaim, setActiveTerminologyClaim] = useState();
|
|
22
|
+
const [candidates, setCandidates] = useState([]);
|
|
23
|
+
const [terminologyBusy, setTerminologyBusy] = useState(false);
|
|
24
|
+
const [terminologyError, setTerminologyError] = useState(false);
|
|
25
|
+
const [saving, setSaving] = useState(false);
|
|
26
|
+
const [error, setError] = useState('');
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!activeTerminologyClaim || !searchTerminology)
|
|
29
|
+
return;
|
|
30
|
+
const sourceText = claims[activeTerminologyClaim]?.trim() ?? '';
|
|
31
|
+
if (sourceText.length < 2) {
|
|
32
|
+
setCandidates([]);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const codeClaim = terminologyCodeClaim(request.resourceType, activeTerminologyClaim);
|
|
36
|
+
let current = true;
|
|
37
|
+
setTerminologyBusy(true);
|
|
38
|
+
setTerminologyError(false);
|
|
39
|
+
const timer = window.setTimeout(() => {
|
|
40
|
+
void searchTerminology({
|
|
41
|
+
resourceType: request.resourceType,
|
|
42
|
+
claim: codeClaim,
|
|
43
|
+
language: locale,
|
|
44
|
+
text: sourceText,
|
|
45
|
+
taxonomyId,
|
|
46
|
+
limit: 20,
|
|
47
|
+
}).then((nextCandidates) => {
|
|
48
|
+
if (current)
|
|
49
|
+
setCandidates(nextCandidates);
|
|
50
|
+
}).catch(() => {
|
|
51
|
+
if (current) {
|
|
52
|
+
setCandidates([]);
|
|
53
|
+
setTerminologyError(true);
|
|
54
|
+
}
|
|
55
|
+
}).finally(() => {
|
|
56
|
+
if (current)
|
|
57
|
+
setTerminologyBusy(false);
|
|
58
|
+
});
|
|
59
|
+
}, 250);
|
|
60
|
+
return () => { current = false; window.clearTimeout(timer); };
|
|
61
|
+
}, [activeTerminologyClaim, claims, locale, request.resourceType, searchTerminology, taxonomyId]);
|
|
62
|
+
async function submit() {
|
|
63
|
+
setSaving(true);
|
|
64
|
+
setError('');
|
|
65
|
+
try {
|
|
66
|
+
const readback = await saveArtifact({
|
|
67
|
+
resourceType: request.resourceType,
|
|
68
|
+
id: request.artifactId ?? crypto.randomUUID(),
|
|
69
|
+
sectionCode: request.sectionCode,
|
|
70
|
+
mode: request.mode,
|
|
71
|
+
claims,
|
|
72
|
+
});
|
|
73
|
+
if (!readback)
|
|
74
|
+
throw new Error(copy.readbackMissing);
|
|
75
|
+
onReadback(readback);
|
|
76
|
+
}
|
|
77
|
+
catch (reason) {
|
|
78
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
setSaving(false);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return _jsxs("section", { className: "uhcp-clinical-editor", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, "aria-describedby": descriptionId, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h2", { id: titleId, children: copy.title }), _jsx("p", { id: descriptionId, children: copy.description })] }), _jsx("button", { type: "button", onClick: onCancel, "aria-label": copy.close, children: "\u00D7" })] }), _jsxs("form", { onSubmit: (event) => { event.preventDefault(); void submit(); }, children: [fields.map((claim) => {
|
|
85
|
+
const parameter = claim.slice(request.resourceType.length + 1);
|
|
86
|
+
const label = fieldLabel(claim);
|
|
87
|
+
const terminologyEnabled = Boolean(searchTerminology && terminologyCodeClaim(request.resourceType, claim));
|
|
88
|
+
const inputId = `${titleId}-${claim}`;
|
|
89
|
+
const value = claims[claim] ?? '';
|
|
90
|
+
const update = (event) => {
|
|
91
|
+
setClaims((current) => ({ ...current, [claim]: event.target.value }));
|
|
92
|
+
if (terminologyEnabled)
|
|
93
|
+
setActiveTerminologyClaim(claim);
|
|
94
|
+
};
|
|
95
|
+
return _jsxs("label", { className: isMultiline(parameter) ? 'uhcp-clinical-editor-wide' : undefined, htmlFor: inputId, children: [_jsxs("span", { children: [label, requiredClaims.has(claim) ? ' *' : ''] }), statusOptions(request.resourceType, parameter) ? _jsx("select", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), onChange: update, children: statusOptions(request.resourceType, parameter).map((option) => _jsx("option", { value: option, children: humanize(option) }, option)) }) : isMultiline(parameter) ? _jsx("textarea", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), rows: 3, onChange: update })
|
|
96
|
+
: _jsx("input", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), type: fieldInputType(parameter), onChange: update }), terminologyEnabled && activeTerminologyClaim === claim ? _jsxs("div", { className: "uhcp-terminology-results", children: [terminologyBusy ? _jsx("small", { role: "status", children: copy.terminologyLoading }) : null, terminologyError ? _jsx("small", { role: "alert", children: copy.terminologyUnavailable }) : null, candidates.length ? _jsx("ul", { children: candidates.map((candidate) => _jsx("li", { children: _jsxs("button", { type: "button", onClick: () => {
|
|
97
|
+
setClaims((current) => selectedCodingClaims(request.resourceType, claim, candidate, acceptedClaims, current));
|
|
98
|
+
setCandidates([]);
|
|
99
|
+
setActiveTerminologyClaim(undefined);
|
|
100
|
+
}, children: [candidate.display, _jsxs("small", { children: [candidate.system, " \u00B7 ", candidate.code] })] }) }, `${candidate.system}|${candidate.code}`)) }) : null] }) : null] }, claim);
|
|
101
|
+
}), error ? _jsx("p", { className: "uhcp-clinical-editor-error", role: "alert", children: error }) : null, _jsxs("footer", { children: [_jsx("button", { type: "button", onClick: onCancel, children: copy.cancel }), _jsx("button", { type: "submit", disabled: saving, children: saving ? copy.saving : copy.save })] })] })] });
|
|
102
|
+
}
|
|
103
|
+
function defaultClaims(resourceType) {
|
|
104
|
+
if (resourceType === 'Immunization')
|
|
105
|
+
return { 'Immunization.status': 'completed' };
|
|
106
|
+
if (resourceType === 'Observation' || resourceType === 'DiagnosticReport' || resourceType === 'Procedure')
|
|
107
|
+
return { [`${resourceType}.status`]: 'final' };
|
|
108
|
+
if (resourceType === 'Condition' || resourceType === 'AllergyIntolerance')
|
|
109
|
+
return { [`${resourceType}.clinical-status`]: 'active' };
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
function statusOptions(resourceType, parameter) {
|
|
113
|
+
if (parameter !== 'status' && !parameter.endsWith('-status'))
|
|
114
|
+
return undefined;
|
|
115
|
+
if (resourceType === 'Immunization')
|
|
116
|
+
return ['completed', 'not-done', 'entered-in-error'];
|
|
117
|
+
if (resourceType === 'Observation' || resourceType === 'DiagnosticReport')
|
|
118
|
+
return ['final', 'preliminary', 'amended', 'entered-in-error'];
|
|
119
|
+
return ['active', 'completed', 'inactive', 'entered-in-error'];
|
|
120
|
+
}
|
|
121
|
+
function terminologyCodeClaim(resourceType, inputClaim) {
|
|
122
|
+
const parameter = inputClaim.slice(resourceType.length + 1);
|
|
123
|
+
if (parameter.endsWith('-code-text'))
|
|
124
|
+
return `${resourceType}.${parameter.slice(0, -5)}`;
|
|
125
|
+
if (resourceType === 'MedicationStatement' && parameter === 'medication-text')
|
|
126
|
+
return 'MedicationStatement.code';
|
|
127
|
+
if (parameter === 'target-disease' || parameter.endsWith('-code') || parameter === 'code')
|
|
128
|
+
return inputClaim;
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
function isHiddenCodingCompanion(resourceType, claim, acceptedClaims) {
|
|
132
|
+
if (claim.endsWith('-display') && acceptedClaims.includes(`${claim.slice(0, -8)}-text`))
|
|
133
|
+
return true;
|
|
134
|
+
if (acceptedClaims.includes(`${claim}-text`))
|
|
135
|
+
return true;
|
|
136
|
+
return resourceType === 'MedicationStatement' && claim === 'MedicationStatement.code'
|
|
137
|
+
&& acceptedClaims.includes('MedicationStatement.medication-text');
|
|
138
|
+
}
|
|
139
|
+
function selectedCodingClaims(resourceType, inputClaim, candidate, acceptedClaims, current) {
|
|
140
|
+
const codeClaim = terminologyCodeClaim(resourceType, inputClaim);
|
|
141
|
+
const next = { ...current, [inputClaim]: current[inputClaim] || candidate.text || candidate.display };
|
|
142
|
+
if (acceptedClaims.includes(codeClaim))
|
|
143
|
+
next[codeClaim] = `${candidate.system}|${candidate.code}`;
|
|
144
|
+
const displayClaim = `${codeClaim}-display`;
|
|
145
|
+
if (acceptedClaims.includes(displayClaim))
|
|
146
|
+
next[displayClaim] = candidate.display;
|
|
147
|
+
return next;
|
|
148
|
+
}
|
|
149
|
+
function isMultiline(parameter) { return /note|description|summary|instruction|outcome|reference-range-text/.test(parameter); }
|
|
150
|
+
function humanize(value) { return value ? value[0].toUpperCase() + value.slice(1).replaceAll('_', ' ') : value; }
|
|
151
|
+
function fieldInputType(parameter) {
|
|
152
|
+
if (/(?:^|-)number$|frequency$|dose-sequence$/.test(parameter))
|
|
153
|
+
return 'number';
|
|
154
|
+
if (parameter === 'date' || parameter.endsWith('-date') || parameter.endsWith('-datetime'))
|
|
155
|
+
return 'date';
|
|
156
|
+
return 'text';
|
|
157
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export type EncounterTransferStatus = 'planned' | 'in-progress' | 'on-hold' | 'completed';
|
|
2
|
+
export type EncounterSourceOutcome = 'on-hold' | 'completed';
|
|
3
|
+
export type EncounterTransferStage = Readonly<{
|
|
4
|
+
identifier: string;
|
|
5
|
+
/** Human-readable provider business identifier; never replaces the FHIR resource id. */
|
|
6
|
+
displayIdentifier?: string;
|
|
7
|
+
serviceLabel: string;
|
|
8
|
+
locationLabel?: string;
|
|
9
|
+
arrivedAt: string;
|
|
10
|
+
status: EncounterTransferStatus;
|
|
11
|
+
}>;
|
|
12
|
+
export type EmergencyContactAction = Readonly<{
|
|
13
|
+
identifier: string;
|
|
14
|
+
name: string;
|
|
15
|
+
relationship: string;
|
|
16
|
+
phone?: string;
|
|
17
|
+
}>;
|
|
18
|
+
export type EncounterTransferConsoleCopy = Readonly<{
|
|
19
|
+
patientSearchTitle: string;
|
|
20
|
+
scanQr: string;
|
|
21
|
+
patientId: string;
|
|
22
|
+
search: string;
|
|
23
|
+
journeyTitle: string;
|
|
24
|
+
sourceTitle: string;
|
|
25
|
+
destinationTitle: string;
|
|
26
|
+
returning: string;
|
|
27
|
+
returningHint: string;
|
|
28
|
+
notReturning: string;
|
|
29
|
+
notReturningHint: string;
|
|
30
|
+
transferTo: string;
|
|
31
|
+
reviewChanges: string;
|
|
32
|
+
applyTransfer: string;
|
|
33
|
+
emergencyActions: string;
|
|
34
|
+
call: string;
|
|
35
|
+
videoCall: string;
|
|
36
|
+
sendMessage: string;
|
|
37
|
+
text: string;
|
|
38
|
+
audio: string;
|
|
39
|
+
messageLabel: string;
|
|
40
|
+
attach: string;
|
|
41
|
+
pdf: string;
|
|
42
|
+
fhirBundle: string;
|
|
43
|
+
calendar: string;
|
|
44
|
+
send: string;
|
|
45
|
+
resourceSummary: string;
|
|
46
|
+
sourceUpdate: string;
|
|
47
|
+
sourceHistory: string;
|
|
48
|
+
destinationUpdate: string;
|
|
49
|
+
destinationHistory: string;
|
|
50
|
+
indexProjection: string;
|
|
51
|
+
demoWarning: string;
|
|
52
|
+
helpTitle: string;
|
|
53
|
+
helpBody: string;
|
|
54
|
+
transferReady: string;
|
|
55
|
+
messageReady: string;
|
|
56
|
+
callReady: string;
|
|
57
|
+
videoReady: string;
|
|
58
|
+
patientReady: string;
|
|
59
|
+
reviewReady: string;
|
|
60
|
+
attachmentReady: string;
|
|
61
|
+
technicalDetails: string;
|
|
62
|
+
encounterIdentifier: string;
|
|
63
|
+
}>;
|
|
64
|
+
export type EncounterTransferConsoleProps = Readonly<{
|
|
65
|
+
copy: EncounterTransferConsoleCopy;
|
|
66
|
+
patientDisplay: string;
|
|
67
|
+
encounters: readonly EncounterTransferStage[];
|
|
68
|
+
sourceEncounterId: string;
|
|
69
|
+
destinationEncounterId: string;
|
|
70
|
+
contacts: readonly EmergencyContactAction[];
|
|
71
|
+
demo?: boolean;
|
|
72
|
+
onApplyTransfer?: (sourceOutcome: EncounterSourceOutcome) => void;
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Shared visual editor for one already-authorized subject transfer.
|
|
76
|
+
*
|
|
77
|
+
* Selection and demo state never grant access. The host must resolve the
|
|
78
|
+
* subject, contacts, department sender, HealthcareService and write
|
|
79
|
+
* capability in its BFF. `onApplyTransfer` reports only the requested source
|
|
80
|
+
* outcome; the provider commits authoritative Encounter changes before any
|
|
81
|
+
* completed Communication projects the result to the subject index.
|
|
82
|
+
*/
|
|
83
|
+
export declare function EncounterTransferConsole({ copy, patientDisplay, encounters, sourceEncounterId, destinationEncounterId, contacts, demo, onApplyTransfer }: EncounterTransferConsoleProps): import("react").JSX.Element;
|
|
84
|
+
//# sourceMappingURL=EncounterTransferConsole.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EncounterTransferConsole.d.ts","sourceRoot":"","sources":["../src/EncounterTransferConsole.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useMemo, useRef, useState } from 'react';
|
|
3
|
+
const icons = {
|
|
4
|
+
scan: _jsxs(_Fragment, { children: [_jsx("path", { d: "M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3" }), _jsx("path", { d: "M8 8h3v3H8zM14 8h2v2M8 14h2v2M13 13h3v3h-3z" })] }),
|
|
5
|
+
phone: _jsx("path", { d: "M7.2 3.8 9.4 7 8 9.1a14 14 0 0 0 6.9 6.9l2.1-1.4 3.2 2.2-.8 3a2 2 0 0 1-2.1 1.4C9.8 20.2 3.8 14.2 2.8 6.7A2 2 0 0 1 4.2 4.6z" }),
|
|
6
|
+
video: _jsxs(_Fragment, { children: [_jsx("rect", { x: "3", y: "6", width: "12", height: "12", rx: "2" }), _jsx("path", { d: "m15 10 6-3v10l-6-3z" })] }),
|
|
7
|
+
message: _jsx("path", { d: "M4 4h16v12H9l-5 4z" }),
|
|
8
|
+
chevron: _jsx("path", { d: "m9 5 7 7-7 7" }),
|
|
9
|
+
};
|
|
10
|
+
function Icon({ name }) {
|
|
11
|
+
return _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: icons[name] });
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Shared visual editor for one already-authorized subject transfer.
|
|
15
|
+
*
|
|
16
|
+
* Selection and demo state never grant access. The host must resolve the
|
|
17
|
+
* subject, contacts, department sender, HealthcareService and write
|
|
18
|
+
* capability in its BFF. `onApplyTransfer` reports only the requested source
|
|
19
|
+
* outcome; the provider commits authoritative Encounter changes before any
|
|
20
|
+
* completed Communication projects the result to the subject index.
|
|
21
|
+
*/
|
|
22
|
+
export function EncounterTransferConsole({ copy, patientDisplay, encounters, sourceEncounterId, destinationEncounterId, contacts, demo = false, onApplyTransfer }) {
|
|
23
|
+
const [sourceOutcome, setSourceOutcome] = useState('on-hold');
|
|
24
|
+
const [selectedContact, setSelectedContact] = useState(contacts[0]?.identifier);
|
|
25
|
+
const [messageOpen, setMessageOpen] = useState(false);
|
|
26
|
+
const [messageMode, setMessageMode] = useState('text');
|
|
27
|
+
const [attachments, setAttachments] = useState([]);
|
|
28
|
+
const [showTechnical, setShowTechnical] = useState(false);
|
|
29
|
+
const [status, setStatus] = useState('');
|
|
30
|
+
const railRef = useRef(null);
|
|
31
|
+
const source = useMemo(() => encounters.find((item) => item.identifier === sourceEncounterId), [encounters, sourceEncounterId]);
|
|
32
|
+
const destination = useMemo(() => encounters.find((item) => item.identifier === destinationEncounterId), [encounters, destinationEncounterId]);
|
|
33
|
+
const history = useMemo(() => encounters.filter((item) => item.identifier !== sourceEncounterId && item.identifier !== destinationEncounterId), [encounters, sourceEncounterId, destinationEncounterId]);
|
|
34
|
+
const contact = contacts.find((item) => item.identifier === selectedContact) ?? contacts[0];
|
|
35
|
+
const helpId = useId();
|
|
36
|
+
function toggleAttachment(kind) {
|
|
37
|
+
setAttachments(current => current.includes(kind) ? current.filter(item => item !== kind) : [...current, kind]);
|
|
38
|
+
setStatus(copy.attachmentReady);
|
|
39
|
+
}
|
|
40
|
+
function applyTransfer() {
|
|
41
|
+
onApplyTransfer?.(sourceOutcome);
|
|
42
|
+
setStatus(copy.transferReady);
|
|
43
|
+
}
|
|
44
|
+
return _jsxs("section", { className: "uhcp-transfer-console", children: [_jsx("aside", { className: "uhcp-transfer-contacts", children: _jsxs("details", { open: true, children: [_jsx("summary", { children: copy.emergencyActions }), _jsx("div", { className: "uhcp-transfer-contact-list", role: "list", children: contacts.map((item) => _jsxs("button", { type: "button", role: "listitem", "aria-pressed": item.identifier === contact?.identifier, onClick: () => setSelectedContact(item.identifier), children: [_jsx("span", { "aria-hidden": "true", children: item.name.split(/\s+/).map(part => part[0]).slice(0, 2).join('') }), _jsx("strong", { children: item.name }), _jsxs("small", { children: [item.relationship, item.phone ? ` · ${item.phone}` : ''] })] }, item.identifier)) }), _jsxs("div", { className: "uhcp-transfer-contact-actions", children: [_jsxs("button", { type: "button", onClick: () => setStatus(copy.callReady), children: [_jsx(Icon, { name: "phone" }), copy.call] }), _jsxs("button", { type: "button", onClick: () => setStatus(copy.videoReady), children: [_jsx(Icon, { name: "video" }), copy.videoCall] }), _jsxs("button", { type: "button", "aria-pressed": messageOpen, onClick: () => setMessageOpen(value => !value), children: [_jsx(Icon, { name: "message" }), copy.sendMessage] })] }), messageOpen && contact ? _jsxs("form", { className: "uhcp-transfer-composer", onSubmit: (event) => { event.preventDefault(); setStatus(copy.messageReady); }, children: [_jsxs("h3", { children: [copy.sendMessage, ": ", contact.name] }), _jsxs("div", { className: "uhcp-transfer-mode", role: "group", "aria-label": copy.sendMessage, children: [_jsx("button", { type: "button", "aria-pressed": messageMode === 'text', onClick: () => setMessageMode('text'), children: copy.text }), _jsx("button", { type: "button", "aria-pressed": messageMode === 'audio', onClick: () => setMessageMode('audio'), children: copy.audio })] }), messageMode === 'text' ? _jsxs("label", { children: [copy.messageLabel, _jsx("textarea", { rows: 5 })] }) : _jsxs("button", { className: "uhcp-transfer-record", type: "button", children: ["\u25CF ", copy.audio] }), _jsxs("fieldset", { children: [_jsx("legend", { children: copy.attach }), _jsxs("div", { children: [_jsx("button", { type: "button", "aria-pressed": attachments.includes('pdf'), onClick: () => toggleAttachment('pdf'), children: copy.pdf }), _jsx("button", { type: "button", "aria-pressed": attachments.includes('fhir'), onClick: () => toggleAttachment('fhir'), children: copy.fhirBundle }), _jsx("button", { type: "button", "aria-pressed": attachments.includes('calendar'), onClick: () => toggleAttachment('calendar'), children: copy.calendar })] })] }), _jsx("button", { className: "uhcp-transfer-primary", type: "submit", children: copy.send })] }) : null] }) }), _jsxs("div", { className: "uhcp-transfer-main", children: [_jsxs("header", { className: "uhcp-transfer-patient", children: [_jsx("h2", { children: copy.patientSearchTitle }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setStatus(copy.patientReady), children: [_jsx(Icon, { name: "scan" }), copy.scanQr] }), _jsx("span", { "aria-hidden": "true" }), _jsxs("label", { children: [copy.patientId, _jsx("input", { defaultValue: patientDisplay })] }), _jsx("button", { type: "button", onClick: () => setStatus(copy.patientReady), children: copy.search })] })] }), _jsxs("section", { className: "uhcp-transfer-journey", role: "region", "aria-label": copy.journeyTitle, "aria-describedby": helpId, children: [_jsxs("header", { children: [_jsx("h2", { children: copy.journeyTitle }), _jsxs("div", { children: [_jsx("button", { type: "button", "aria-label": "Anterior", onClick: () => railRef.current?.scrollBy({ left: -260, behavior: 'smooth' }), children: _jsx(Icon, { name: "chevron" }) }), _jsx("button", { type: "button", "aria-label": "Siguiente", onClick: () => railRef.current?.scrollBy({ left: 260, behavior: 'smooth' }), children: _jsx(Icon, { name: "chevron" }) })] })] }), _jsxs("div", { className: "uhcp-transfer-rail", ref: railRef, children: [history.map((item) => _jsx(EncounterCard, { encounter: item }, item.identifier)), source ? _jsxs("article", { className: "uhcp-transfer-card uhcp-transfer-source", children: [_jsx("h3", { children: copy.sourceTitle }), _jsx(EncounterCardBody, { encounter: source, identifierLabel: copy.encounterIdentifier }), _jsxs("fieldset", { children: [_jsx("legend", { children: copy.helpTitle }), _jsxs("label", { children: [_jsx("input", { type: "radio", name: "source-outcome", checked: sourceOutcome === 'on-hold', onChange: () => setSourceOutcome('on-hold') }), _jsxs("span", { children: [_jsx("strong", { children: copy.returning }), _jsx("small", { children: copy.returningHint })] })] }), _jsxs("label", { children: [_jsx("input", { type: "radio", name: "source-outcome", checked: sourceOutcome === 'completed', onChange: () => setSourceOutcome('completed') }), _jsxs("span", { children: [_jsx("strong", { children: copy.notReturning }), _jsx("small", { children: copy.notReturningHint })] })] })] })] }) : null, _jsxs("div", { className: "uhcp-transfer-arrow", children: [_jsx("span", { children: copy.transferTo }), _jsx("i", {}), _jsx(Icon, { name: "chevron" })] }), destination ? _jsxs("article", { className: "uhcp-transfer-card uhcp-transfer-destination", children: [_jsx("h3", { children: copy.destinationTitle }), _jsx(EncounterCardBody, { encounter: { ...destination, status: 'in-progress' }, identifierLabel: copy.encounterIdentifier })] }) : null] }), _jsxs("details", { className: "uhcp-transfer-help", open: true, children: [_jsx("summary", { id: helpId, children: copy.helpTitle }), _jsx("p", { children: copy.helpBody })] }), _jsxs("div", { className: "uhcp-transfer-controls", children: [_jsx("button", { type: "button", onClick: () => { setShowTechnical(true); setStatus(copy.reviewReady); }, children: copy.reviewChanges }), _jsx("button", { className: "uhcp-transfer-primary", type: "button", onClick: applyTransfer, children: copy.applyTransfer })] })] }), _jsxs("section", { className: "uhcp-transfer-review", children: [_jsxs("header", { children: [_jsx("h2", { children: copy.resourceSummary }), demo ? _jsx("p", { children: copy.demoWarning }) : null] }), _jsx("ol", { "aria-label": copy.resourceSummary, children: [[copy.sourceUpdate, 'Encounter'], [copy.sourceHistory, 'EncounterHistory'], [copy.destinationUpdate, 'Encounter'], [copy.destinationHistory, 'EncounterHistory'], [copy.indexProjection, 'Communication']].map(([label, resource], index) => _jsxs("li", { children: [_jsx("span", { children: index + 1 }), _jsx("strong", { children: label }), _jsxs("details", { open: showTechnical, children: [_jsx("summary", { children: copy.technicalDetails }), _jsx("code", { children: resource })] })] }, `${resource}-${index}`)) })] }), status ? _jsx("p", { className: "uhcp-transfer-status", role: "status", children: status }) : null] })] });
|
|
45
|
+
}
|
|
46
|
+
function EncounterCard({ encounter }) {
|
|
47
|
+
return _jsx("article", { className: "uhcp-transfer-card", children: _jsx(EncounterCardBody, { encounter: encounter }) });
|
|
48
|
+
}
|
|
49
|
+
function EncounterCardBody({ encounter, identifierLabel }) {
|
|
50
|
+
return _jsxs("div", { className: "uhcp-transfer-card-body", children: [_jsx("time", { children: encounter.arrivedAt }), _jsx("strong", { children: encounter.serviceLabel }), encounter.locationLabel ? _jsx("span", { children: encounter.locationLabel }) : null, _jsx("small", { "data-status": encounter.status, children: encounter.status }), _jsx("code", { "aria-label": identifierLabel, children: encounter.displayIdentifier ?? encounter.identifier })] });
|
|
51
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,9 +8,11 @@ export * from './ProfileProtectionGate.js';
|
|
|
8
8
|
export * from './PortalLoadingSkeleton.js';
|
|
9
9
|
export * from './ClinicalSectionSummary.js';
|
|
10
10
|
export * from './IpsBundleSummary.js';
|
|
11
|
+
export * from './ClinicalArtifactEditor.js';
|
|
11
12
|
export * from './InvitationAcceptancePanel.js';
|
|
12
13
|
export * from './SubjectAccessRequestPanel.js';
|
|
13
14
|
export * from './SoschainWorkspace.js';
|
|
15
|
+
export * from './EncounterTransferConsole.js';
|
|
14
16
|
export * from './OrganizationWorkforceAdministration.js';
|
|
15
17
|
export * from './ProtectedIndexEmergencyData.js';
|
|
16
18
|
export * from './PortalSettingsPanel.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,0CAA0C,CAAA;AACxD,cAAc,kCAAkC,CAAA;AAChD,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,0CAA0C,CAAA;AACxD,cAAc,kCAAkC,CAAA;AAChD,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,11 @@ export * from './ProfileProtectionGate.js';
|
|
|
8
8
|
export * from './PortalLoadingSkeleton.js';
|
|
9
9
|
export * from './ClinicalSectionSummary.js';
|
|
10
10
|
export * from './IpsBundleSummary.js';
|
|
11
|
+
export * from './ClinicalArtifactEditor.js';
|
|
11
12
|
export * from './InvitationAcceptancePanel.js';
|
|
12
13
|
export * from './SubjectAccessRequestPanel.js';
|
|
13
14
|
export * from './SoschainWorkspace.js';
|
|
15
|
+
export * from './EncounterTransferConsole.js';
|
|
14
16
|
export * from './OrganizationWorkforceAdministration.js';
|
|
15
17
|
export * from './ProtectedIndexEmergencyData.js';
|
|
16
18
|
export * from './PortalSettingsPanel.js';
|
package/dist/styles.css
CHANGED
|
@@ -171,6 +171,101 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
|
|
|
171
171
|
color: var(--uhcp-text);
|
|
172
172
|
padding: 1.25rem;
|
|
173
173
|
}
|
|
174
|
+
.uhcp-clinical-editor { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:1.5rem; box-shadow:0 20px 60px rgb(15 23 42 / 22%); color:var(--uhcp-text); inset:50% auto auto 50%; max-height:calc(100dvh - 2rem); max-width:48rem; overflow:auto; padding:1.5rem; position:fixed; transform:translate(-50%,-50%); width:calc(100% - 2rem); z-index:60; }
|
|
175
|
+
.uhcp-clinical-editor>header { align-items:flex-start; display:flex; gap:1rem; justify-content:space-between; }
|
|
176
|
+
.uhcp-clinical-editor h2,.uhcp-clinical-editor p { margin:.2rem 0; }
|
|
177
|
+
.uhcp-clinical-editor>header>button { background:transparent; border:1px solid var(--uhcp-border); border-radius:50%; color:inherit; cursor:pointer; font-size:1.4rem; height:2.5rem; width:2.5rem; }
|
|
178
|
+
.uhcp-clinical-editor form { display:grid; gap:1rem; grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:1.25rem; }
|
|
179
|
+
.uhcp-clinical-editor label { color:var(--uhcp-text); display:grid; font-weight:700; gap:.35rem; }
|
|
180
|
+
.uhcp-clinical-editor input,.uhcp-clinical-editor select,.uhcp-clinical-editor textarea { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:.7rem; color:inherit; font:inherit; padding:.75rem; }
|
|
181
|
+
.uhcp-clinical-editor-wide,.uhcp-clinical-editor-error,.uhcp-clinical-editor footer { grid-column:1/-1; }
|
|
182
|
+
.uhcp-clinical-editor footer { display:flex; gap:.75rem; justify-content:flex-end; }
|
|
183
|
+
.uhcp-clinical-editor footer button,.uhcp-terminology-results button { border:1px solid var(--uhcp-border); border-radius:.7rem; cursor:pointer; font:inherit; padding:.7rem 1rem; }
|
|
184
|
+
.uhcp-clinical-editor footer button[type=submit] { background:linear-gradient(135deg,var(--uhcp-action-start),var(--uhcp-action-end)); color:#fff; font-weight:800; }
|
|
185
|
+
.uhcp-clinical-editor-error { color:#b42318; font-weight:700; }
|
|
186
|
+
.uhcp-terminology-results ul { display:grid; gap:.35rem; list-style:none; margin:.25rem 0 0; padding:0; }
|
|
187
|
+
.uhcp-terminology-results button { background:var(--uhcp-surface-muted); color:inherit; display:grid; text-align:start; width:100%; }
|
|
188
|
+
.uhcp-terminology-results small { color:var(--uhcp-muted); display:block; font-weight:400; }
|
|
189
|
+
@media (max-width:44rem) { .uhcp-clinical-editor form { grid-template-columns:1fr; } }
|
|
190
|
+
|
|
191
|
+
.uhcp-transfer-console { background:#fff; color:#102754; display:grid; font-family:Inter,ui-sans-serif,system-ui,sans-serif; grid-template-columns:minmax(16rem,23rem) minmax(0,1fr); min-height:calc(100vh - 4.5rem); }
|
|
192
|
+
.uhcp-transfer-console button,.uhcp-transfer-console input,.uhcp-transfer-console textarea { font:inherit; }
|
|
193
|
+
.uhcp-transfer-console svg { fill:none; height:1.25rem; stroke:currentColor; stroke-linecap:round; stroke-linejoin:round; stroke-width:1.8; width:1.25rem; }
|
|
194
|
+
.uhcp-transfer-contacts { border-inline-end:1px solid #dce5ec; padding:1.2rem; }
|
|
195
|
+
.uhcp-transfer-contacts summary { cursor:pointer; font-size:1.05rem; font-weight:850; list-style-position:inside; padding:.35rem 0 1rem; }
|
|
196
|
+
.uhcp-transfer-contact-list { border-block:1px solid #e2e9ef; display:grid; padding:.45rem 0; }
|
|
197
|
+
.uhcp-transfer-contact-list button { background:transparent; border:0; border-inline-start:3px solid transparent; color:inherit; cursor:pointer; display:grid; gap:.15rem; grid-template-columns:2.7rem 1fr; padding:.75rem; text-align:start; }
|
|
198
|
+
.uhcp-transfer-contact-list button[aria-pressed=true] { background:#eaf8fd; border-inline-start-color:#09a9dc; }
|
|
199
|
+
.uhcp-transfer-contact-list button>span { align-items:center; background:#d7e7f1; border-radius:50%; display:flex; font-weight:800; grid-row:span 2; height:2.4rem; justify-content:center; width:2.4rem; }
|
|
200
|
+
.uhcp-transfer-contact-list small { color:#58708e; }
|
|
201
|
+
.uhcp-transfer-contact-actions { display:grid; gap:.55rem; padding:1rem 0; }
|
|
202
|
+
.uhcp-transfer-contact-actions button,.uhcp-transfer-mode button,.uhcp-transfer-composer fieldset button { align-items:center; background:#fff; border:1px solid #cad7e2; border-radius:.55rem; color:inherit; cursor:pointer; display:flex; gap:.7rem; padding:.7rem .85rem; }
|
|
203
|
+
.uhcp-transfer-contact-actions button[aria-pressed=true],.uhcp-transfer-mode button[aria-pressed=true] { background:#eaf8fd; border-color:#09a9dc; color:#006f96; font-weight:800; }
|
|
204
|
+
.uhcp-transfer-composer fieldset button[aria-pressed=true] { background:#eaf8fd; border-color:#09a9dc; color:#006f96; font-weight:800; }
|
|
205
|
+
.uhcp-transfer-composer { display:grid; gap:.8rem; }
|
|
206
|
+
.uhcp-transfer-composer h3 { font-size:.95rem; margin:.2rem 0 0; }
|
|
207
|
+
.uhcp-transfer-mode { display:grid; grid-template-columns:1fr 1fr; }
|
|
208
|
+
.uhcp-transfer-mode button { justify-content:center; }
|
|
209
|
+
.uhcp-transfer-composer label { display:grid; font-size:.82rem; font-weight:750; gap:.35rem; }
|
|
210
|
+
.uhcp-transfer-composer textarea { border:1px solid #cad7e2; border-radius:.55rem; color:inherit; resize:vertical; padding:.75rem; }
|
|
211
|
+
.uhcp-transfer-composer fieldset { border:0; margin:0; padding:0; }
|
|
212
|
+
.uhcp-transfer-composer legend { font-size:.82rem; font-weight:750; margin-bottom:.4rem; }
|
|
213
|
+
.uhcp-transfer-composer fieldset>div { display:flex; flex-wrap:wrap; gap:.4rem; }
|
|
214
|
+
.uhcp-transfer-record { background:#fff; border:1px dashed #09a9dc; border-radius:.55rem; color:#006f96; padding:1rem; }
|
|
215
|
+
.uhcp-transfer-primary { background:#0796a8!important; border-color:#0796a8!important; color:#fff!important; font-weight:850!important; }
|
|
216
|
+
.uhcp-transfer-main { min-width:0; padding:1rem; }
|
|
217
|
+
.uhcp-transfer-patient,.uhcp-transfer-journey,.uhcp-transfer-review { border:1px solid #dce5ec; border-radius:.7rem; box-shadow:0 4px 16px rgb(16 39 84 / 5%); }
|
|
218
|
+
.uhcp-transfer-patient { padding:1rem; }
|
|
219
|
+
.uhcp-transfer-patient h2,.uhcp-transfer-journey h2,.uhcp-transfer-review h2 { font-size:1.18rem; margin:0; }
|
|
220
|
+
.uhcp-transfer-patient>div { align-items:center; display:grid; gap:.75rem; grid-template-columns:minmax(11rem,.85fr) 1px minmax(13rem,1fr) auto; margin-top:.75rem; }
|
|
221
|
+
.uhcp-transfer-patient button,.uhcp-transfer-patient label { align-items:center; background:#fff; border:1px solid #9fb3c8; border-radius:.45rem; display:flex; gap:.65rem; min-height:3rem; padding:.7rem .9rem; }
|
|
222
|
+
.uhcp-transfer-patient button { color:inherit; cursor:pointer; font-weight:800; justify-content:center; }
|
|
223
|
+
.uhcp-transfer-patient button:last-child { background:#0796a8; border-color:#0796a8; color:#fff; }
|
|
224
|
+
.uhcp-transfer-patient>div>span { background:#cbd7e1; height:2rem; }
|
|
225
|
+
.uhcp-transfer-patient label { color:#64748b; }
|
|
226
|
+
.uhcp-transfer-patient input { border:0; color:#102754; min-width:0; outline:0; width:100%; }
|
|
227
|
+
.uhcp-transfer-journey { margin-top:.8rem; overflow:hidden; padding:1rem; }
|
|
228
|
+
.uhcp-transfer-journey>header,.uhcp-transfer-review>header { align-items:center; display:flex; justify-content:space-between; }
|
|
229
|
+
.uhcp-transfer-journey>header>div { display:flex; gap:.4rem; }
|
|
230
|
+
.uhcp-transfer-journey>header button { align-items:center; background:#fff; border:1px solid #cad7e2; border-radius:.45rem; color:inherit; display:flex; padding:.45rem; }
|
|
231
|
+
.uhcp-transfer-journey>header button:first-child svg { transform:rotate(180deg); }
|
|
232
|
+
.uhcp-transfer-rail { align-items:center; display:flex; gap:.8rem; min-height:18rem; overflow-x:auto; padding:1rem .25rem; scrollbar-color:#aebfce #edf2f5; }
|
|
233
|
+
.uhcp-transfer-card { border:1px solid #cad7e2; border-radius:.5rem; flex:0 0 10rem; min-height:12rem; overflow:hidden; }
|
|
234
|
+
.uhcp-transfer-card h3 { background:#087db8; color:#fff; font-size:.82rem; margin:0; padding:.65rem; }
|
|
235
|
+
.uhcp-transfer-card-body { display:grid; gap:.45rem; padding:.85rem; }
|
|
236
|
+
.uhcp-transfer-card-body time,.uhcp-transfer-card-body span,.uhcp-transfer-card-body code { color:#60738c; font-size:.72rem; }
|
|
237
|
+
.uhcp-transfer-card-body small { background:#edf2f5; border-radius:999px; color:#466079; font-size:.7rem; justify-self:start; padding:.25rem .55rem; }
|
|
238
|
+
.uhcp-transfer-card-body small[data-status=in-progress] { background:#d7f4e5; color:#087a47; }
|
|
239
|
+
.uhcp-transfer-card-body small[data-status=completed] { background:#dff6e8; color:#087a47; }
|
|
240
|
+
.uhcp-transfer-card-body small[data-status=on-hold] { background:#fff1d2; color:#9a5a00; }
|
|
241
|
+
.uhcp-transfer-source { border-color:#078dcc; flex-basis:13.5rem; }
|
|
242
|
+
.uhcp-transfer-destination { border-color:#069f69; flex-basis:13rem; }
|
|
243
|
+
.uhcp-transfer-destination h3 { background:#069f69; }
|
|
244
|
+
.uhcp-transfer-source fieldset { border:0; border-top:1px solid #dce5ec; margin:.2rem .7rem .7rem; padding:.7rem 0 0; }
|
|
245
|
+
.uhcp-transfer-source legend { font-size:.72rem; font-weight:800; }
|
|
246
|
+
.uhcp-transfer-source label { align-items:start; display:flex; gap:.45rem; padding:.35rem 0; }
|
|
247
|
+
.uhcp-transfer-source label span { display:grid; }
|
|
248
|
+
.uhcp-transfer-source label small { color:#60738c; font-size:.68rem; }
|
|
249
|
+
.uhcp-transfer-arrow { align-items:center; color:#0796a8; display:grid; flex:0 0 5rem; grid-template-columns:1fr auto; text-align:center; }
|
|
250
|
+
.uhcp-transfer-arrow span { font-size:.7rem; font-weight:800; grid-column:1/-1; }
|
|
251
|
+
.uhcp-transfer-arrow i { background:#0796a8; height:2px; }
|
|
252
|
+
.uhcp-transfer-arrow svg { margin-left:-.45rem; }
|
|
253
|
+
.uhcp-transfer-help { background:#f4f9fc; border-radius:.5rem; padding:.65rem .8rem; }
|
|
254
|
+
.uhcp-transfer-help summary { cursor:pointer; font-size:.78rem; font-weight:800; }
|
|
255
|
+
.uhcp-transfer-help p { color:#51677e; font-size:.78rem; margin:.4rem 0 0; }
|
|
256
|
+
.uhcp-transfer-controls { display:flex; gap:.55rem; justify-content:flex-end; padding-top:.75rem; }
|
|
257
|
+
.uhcp-transfer-controls button { background:#fff; border:1px solid #0796a8; border-radius:.45rem; color:#08738a; padding:.7rem 1rem; }
|
|
258
|
+
.uhcp-transfer-review { margin-top:.8rem; padding:1rem; }
|
|
259
|
+
.uhcp-transfer-review header p { background:#fff5dd; border:1px solid #f3ce79; border-radius:.4rem; color:#925800; font-size:.72rem; margin:0; padding:.5rem .7rem; }
|
|
260
|
+
.uhcp-transfer-review ol { list-style:none; margin:.7rem 0 0; padding:0; }
|
|
261
|
+
.uhcp-transfer-review li { align-items:center; border-top:1px solid #e1e8ee; display:grid; gap:.7rem; grid-template-columns:1.8rem 1fr auto; padding:.55rem; }
|
|
262
|
+
.uhcp-transfer-review li>span { align-items:center; background:#e8f6fc; border-radius:50%; color:#087db8; display:flex; font-size:.72rem; font-weight:900; height:1.7rem; justify-content:center; width:1.7rem; }
|
|
263
|
+
.uhcp-transfer-review li strong { font-size:.8rem; }
|
|
264
|
+
.uhcp-transfer-review details { font-size:.68rem; }
|
|
265
|
+
.uhcp-transfer-review summary { color:#60738c; cursor:pointer; }
|
|
266
|
+
.uhcp-transfer-status { background:#dff6e8; border-radius:.45rem; color:#087a47; font-weight:750; padding:.75rem; }
|
|
267
|
+
@media(max-width:900px){.uhcp-transfer-console{grid-template-columns:1fr}.uhcp-transfer-contacts{border-bottom:1px solid #dce5ec;border-inline-end:0}.uhcp-transfer-contact-actions{grid-template-columns:repeat(3,1fr)}.uhcp-transfer-patient>div{grid-template-columns:1fr}.uhcp-transfer-patient>div>span{display:none}.uhcp-transfer-review>header{align-items:flex-start;gap:.7rem;flex-direction:column}}
|
|
268
|
+
@media(max-width:560px){.uhcp-transfer-main{padding:.55rem}.uhcp-transfer-contact-actions{grid-template-columns:1fr}.uhcp-transfer-rail{align-items:stretch}.uhcp-transfer-review li{grid-template-columns:1.8rem 1fr}.uhcp-transfer-review li details{grid-column:2}.uhcp-transfer-controls button{flex:1}.uhcp-transfer-review header p{font-size:.65rem}}
|
|
174
269
|
|
|
175
270
|
.uhcp-identity-workspace {
|
|
176
271
|
background: var(--uhcp-panel);
|
package/package.json
CHANGED