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 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';
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Brand-neutral React portal shell, account preferences and protected-profile UI for UHC and VetChain portals.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",