uhc-portal-components-react 0.1.27 → 0.1.30
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 +33 -0
- package/README.md +16 -2
- package/dist/AllClinicalSections.js +15 -1
- package/dist/ClinicalArtifactEditor.d.ts +49 -2
- package/dist/ClinicalArtifactEditor.d.ts.map +1 -1
- package/dist/ClinicalArtifactEditor.js +98 -16
- package/dist/ClinicalSectionSummary.d.ts +3 -1
- package/dist/ClinicalSectionSummary.d.ts.map +1 -1
- package/dist/ClinicalSectionSummary.js +11 -2
- package/dist/ClinicalTimeline.d.ts +52 -0
- package/dist/ClinicalTimeline.d.ts.map +1 -0
- package/dist/ClinicalTimeline.js +46 -0
- package/dist/EmergencyContactDirectory.d.ts +30 -0
- package/dist/EmergencyContactDirectory.d.ts.map +1 -0
- package/dist/EmergencyContactDirectory.js +21 -0
- package/dist/EncounterTransferConsole.d.ts +8 -1
- package/dist/EncounterTransferConsole.d.ts.map +1 -1
- package/dist/EncounterTransferConsole.js +23 -4
- package/dist/IpsBundleSummary.d.ts +8 -1
- package/dist/IpsBundleSummary.d.ts.map +1 -1
- package/dist/IpsBundleSummary.js +38 -9
- package/dist/VitalSignArtifactEditor.d.ts +5 -1
- package/dist/VitalSignArtifactEditor.d.ts.map +1 -1
- package/dist/VitalSignArtifactEditor.js +9 -5
- 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 +35 -4
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.30
|
|
4
|
+
|
|
5
|
+
- Pin the corrected shared clinical reader so IPS cards prefer each resource's
|
|
6
|
+
clinical event date over the enclosing Composition authoring timestamp.
|
|
7
|
+
|
|
8
|
+
## 0.1.29
|
|
9
|
+
|
|
10
|
+
- Bind `ClinicalArtifactEditor` to the shared multichannel JSON Schema. The
|
|
11
|
+
component now creates the claims-first changes Bundle through the reusable
|
|
12
|
+
`BundleEditor` helper and can hydrate the same form from authoritative
|
|
13
|
+
`BundleReader` readback or a native FHIR import.
|
|
14
|
+
- Keep one stable draft identifier across retries, expose pending/retryable
|
|
15
|
+
synchronization without reporting local state as saved, and add the same
|
|
16
|
+
retry contract to encounter transfers.
|
|
17
|
+
- Derive form input types from that JSON Schema, so textual FHIR fields such
|
|
18
|
+
as a vaccine lot number are not misclassified by their claim name.
|
|
19
|
+
|
|
20
|
+
## 0.1.28
|
|
21
|
+
|
|
22
|
+
- Add a reusable emergency-contact directory with provider-owned create,
|
|
23
|
+
update and delete callbacks plus accessible telephone and email actions.
|
|
24
|
+
- Keep Goals and Preferences and Clinical Tests inside the unified IPS
|
|
25
|
+
presentation instead of duplicating them as top-level sections.
|
|
26
|
+
- Add a locale-aware clinical timeline with inclusive from/to date and time
|
|
27
|
+
filters, OR-within/AND-across multi-select facets, latest-first cards,
|
|
28
|
+
accessible previous/next navigation and an expanded-list alternative.
|
|
29
|
+
- Replace free-text vital-sign timestamps with separate calendar date and time
|
|
30
|
+
controls, optional seconds defaulted to `00`, explicit current-date/current-
|
|
31
|
+
time actions and canonical FHIR instant composition in the selected IANA
|
|
32
|
+
time zone.
|
|
33
|
+
- Keep long clinical editors scrollable on mobile while their save/cancel
|
|
34
|
+
actions remain visible and clickable.
|
|
35
|
+
|
|
3
36
|
## 0.1.27
|
|
4
37
|
|
|
5
38
|
- Add generic `AllClinicalSections<Person|Animal>` organization with stack or
|
package/README.md
CHANGED
|
@@ -63,9 +63,23 @@ The package deliberately exposes modules instead of a cloned application:
|
|
|
63
63
|
exclude inapplicable areas. Family history, care-team members and emergency
|
|
64
64
|
contacts remain independent; the organizer never follows another subject's
|
|
65
65
|
card or documents automatically.
|
|
66
|
+
- `ClinicalTimeline` and `ClinicalDateTimeFields`: render longitudinal entries
|
|
67
|
+
with localized dates, inclusive date/time ranges, multi-select clinical
|
|
68
|
+
facets and accessible latest-first navigation. Editors capture calendar date
|
|
69
|
+
and time separately; ISO/FHIR instants remain wire values, not UI labels.
|
|
70
|
+
- `EmergencyContactDirectory`: renders already-authorized `RelatedPerson`
|
|
71
|
+
contacts and delegates create, update, delete and fresh provider readback to
|
|
72
|
+
the host portal. Browser editing never grants contact access.
|
|
66
73
|
- `ClinicalArtifactEditor`: renders the same schema-driven clinical form for
|
|
67
|
-
human and animal subjects.
|
|
68
|
-
|
|
74
|
+
human and animal subjects. For writes, the host supplies the shared
|
|
75
|
+
`MULTICHANNEL_OPERATION_SCHEMAS` contract and provenance context. The
|
|
76
|
+
component turns validated form values into the canonical claims-first batch
|
|
77
|
+
with `buildMultichannelOperationBundle`; it does not ask each portal to map
|
|
78
|
+
its own claims. Existing claims-first records can be reopened from an
|
|
79
|
+
authoritative Bundle with `BundleReader`, while native FHIR imports use the
|
|
80
|
+
shared FHIR converter before selecting the same schema-owned fields. The host
|
|
81
|
+
also supplies its IPS section descriptor, terminology-search adapter and
|
|
82
|
+
authorized persistence callback. Selecting a
|
|
69
83
|
terminology candidate preserves source text and adds only the confirmed
|
|
70
84
|
`system|code` and display. Success requires a fresh provider readback Bundle;
|
|
71
85
|
local form state is never presented as a persisted write.
|
|
@@ -21,11 +21,25 @@ export const ClinicalSectionNavigationIds = Object.freeze({
|
|
|
21
21
|
* never follows another subject's card or document links automatically.
|
|
22
22
|
*/
|
|
23
23
|
export function AllClinicalSections({ subjectKind, title, sections, excludedSectionIds = [], presentation = 'sections', }) {
|
|
24
|
-
const visibleSections = sections.filter(({ id }) => !excludedSectionIds.includes(id));
|
|
24
|
+
const visibleSections = organizeClinicalSections(sections.filter(({ id }) => !excludedSectionIds.includes(id)));
|
|
25
25
|
const [selectedId, setSelectedId] = useState(visibleSections[0]?.id ?? '');
|
|
26
26
|
const selectedSection = visibleSections.find(({ id }) => id === selectedId) ?? visibleSections[0];
|
|
27
27
|
return (_jsxs("section", { className: "uhcp-all-clinical-sections", "data-subject-kind": subjectKind, children: [_jsx("h2", { children: title }), presentation === 'tabs' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "uhcp-all-clinical-tabs", role: "tablist", "aria-label": title, children: visibleSections.map((section) => (_jsx("button", { type: "button", role: "tab", "aria-selected": section.id === selectedSection?.id, "aria-controls": `uhcp-all-section-${section.id}`, onClick: () => setSelectedId(section.id), children: section.title }, section.id))) }), selectedSection ? (_jsx("div", { id: `uhcp-all-section-${selectedSection.id}`, role: "tabpanel", children: _jsx(ClinicalSection, { definition: selectedSection, excludedSectionIds: excludedSectionIds }) })) : null] })) : (_jsx("div", { className: "uhcp-all-clinical-stack", children: visibleSections.map((section) => (_jsx(ClinicalSection, { definition: section, excludedSectionIds: excludedSectionIds }, section.id))) }))] }));
|
|
28
28
|
}
|
|
29
|
+
function organizeClinicalSections(sections) {
|
|
30
|
+
const ipsChildrenIds = new Set([
|
|
31
|
+
ClinicalSectionNavigationIds.GoalsAndPreferences,
|
|
32
|
+
ClinicalSectionNavigationIds.ClinicalTests,
|
|
33
|
+
]);
|
|
34
|
+
const ipsChildren = sections.filter(({ id }) => ipsChildrenIds.has(id));
|
|
35
|
+
if (ipsChildren.length === 0)
|
|
36
|
+
return sections;
|
|
37
|
+
return sections
|
|
38
|
+
.filter(({ id }) => !ipsChildrenIds.has(id))
|
|
39
|
+
.map((section) => section.id === ClinicalSectionNavigationIds.IpsSummary
|
|
40
|
+
? { ...section, children: [...(section.children ?? []), ...ipsChildren] }
|
|
41
|
+
: section);
|
|
42
|
+
}
|
|
29
43
|
function ClinicalSection({ definition, excludedSectionIds, }) {
|
|
30
44
|
const body = (_jsxs("div", { className: "uhcp-all-clinical-content", children: [definition.description ? _jsx("p", { children: definition.description }) : null, definition.content, definition.children?.filter(({ id }) => !excludedSectionIds.includes(id)).map((child) => (_jsx(ClinicalSection, { definition: child, excludedSectionIds: excludedSectionIds }, child.id)))] }));
|
|
31
45
|
if (definition.collapsible !== false) {
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import type { ClinicalResourceBundleLike } from 'gdc-common-utils-ts/utils/clinical-resource-view';
|
|
2
|
+
import { type MultichannelOperationSchema, type MultichannelOperationValues } from 'sos-data-utils-ts/multichannel-operation-schemas';
|
|
2
3
|
export type ClinicalArtifactSchema = Readonly<{
|
|
3
4
|
resourceType: string;
|
|
5
|
+
sectionCode?: string;
|
|
4
6
|
requiredClaims: readonly string[];
|
|
5
7
|
optionalClaims: readonly string[];
|
|
8
|
+
terminologyTextClaims?: readonly string[];
|
|
9
|
+
initialClaims?: Readonly<Record<string, string>>;
|
|
6
10
|
}>;
|
|
7
11
|
export type ClinicalArtifactEditorRequest = Readonly<{
|
|
8
12
|
resourceType: string;
|
|
9
13
|
sectionCode: string;
|
|
10
14
|
mode: 'create' | 'update';
|
|
11
15
|
artifactId?: string;
|
|
16
|
+
ifMatch?: string;
|
|
12
17
|
initialClaims?: Readonly<Record<string, unknown>>;
|
|
18
|
+
/** Native FHIR R4 resource hydrated through the selected shared schema. */
|
|
19
|
+
initialResource?: Readonly<Record<string, unknown> & {
|
|
20
|
+
resourceType: string;
|
|
21
|
+
}>;
|
|
22
|
+
/** Authoritative claims-first provider Bundle hydrated through BundleReader. */
|
|
23
|
+
initialBundle?: Readonly<Record<string, unknown>>;
|
|
13
24
|
}>;
|
|
14
25
|
export type ClinicalArtifactDraft = Readonly<{
|
|
15
26
|
resourceType: string;
|
|
@@ -17,6 +28,26 @@ export type ClinicalArtifactDraft = Readonly<{
|
|
|
17
28
|
sectionCode: string;
|
|
18
29
|
mode: 'create' | 'update';
|
|
19
30
|
claims: Readonly<Record<string, string>>;
|
|
31
|
+
/** Complete schema-validated operation values when a multichannel schema is supplied. */
|
|
32
|
+
operationValues?: MultichannelOperationValues;
|
|
33
|
+
/** Claims-first batch produced by the shared BundleEditor. */
|
|
34
|
+
changesBundle?: ClinicalArtifactChangesBundle;
|
|
35
|
+
}>;
|
|
36
|
+
export type ClinicalArtifactChangesBundle = Readonly<{
|
|
37
|
+
resourceType: string;
|
|
38
|
+
type: string;
|
|
39
|
+
data?: readonly unknown[];
|
|
40
|
+
entry?: readonly unknown[];
|
|
41
|
+
}>;
|
|
42
|
+
export type ClinicalArtifactAuthoringContext = Readonly<{
|
|
43
|
+
subjectReference: string;
|
|
44
|
+
language: string;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
dataAuthorReference: string;
|
|
47
|
+
attestationState: 'draft' | 'confirmed';
|
|
48
|
+
attesterReference?: string;
|
|
49
|
+
attesterMode?: string;
|
|
50
|
+
attestedAt?: string;
|
|
20
51
|
}>;
|
|
21
52
|
export type ClinicalTerminologyCandidate = Readonly<{
|
|
22
53
|
system: string;
|
|
@@ -42,16 +73,32 @@ export type ClinicalArtifactEditorCopy = Readonly<{
|
|
|
42
73
|
terminologyLoading: string;
|
|
43
74
|
terminologyUnavailable: string;
|
|
44
75
|
readbackMissing: string;
|
|
76
|
+
pending?: string;
|
|
77
|
+
retry?: string;
|
|
78
|
+
}>;
|
|
79
|
+
export type ClinicalArtifactSaveOutcome = Readonly<{
|
|
80
|
+
state: 'confirmed';
|
|
81
|
+
bundle: ClinicalResourceBundleLike;
|
|
82
|
+
} | {
|
|
83
|
+
state: 'pending' | 'retryable';
|
|
84
|
+
message?: string;
|
|
85
|
+
} | {
|
|
86
|
+
state: 'rejected';
|
|
87
|
+
message: string;
|
|
45
88
|
}>;
|
|
46
89
|
export type ClinicalArtifactEditorProps = Readonly<{
|
|
47
90
|
request: ClinicalArtifactEditorRequest;
|
|
48
91
|
schema: ClinicalArtifactSchema;
|
|
92
|
+
/** Shared JSON Schema 2020-12 contract used by portal, voice, chat and WhatsApp. */
|
|
93
|
+
operationSchema?: MultichannelOperationSchema;
|
|
94
|
+
/** Host-owned subject and provenance; never inferred from the transport sender. */
|
|
95
|
+
authoringContext?: ClinicalArtifactAuthoringContext;
|
|
49
96
|
copy: ClinicalArtifactEditorCopy;
|
|
50
97
|
locale?: string;
|
|
51
98
|
taxonomyId?: string;
|
|
52
99
|
fieldLabel: (claim: string) => string;
|
|
53
100
|
searchTerminology?: (input: ClinicalTerminologySearch) => Promise<readonly ClinicalTerminologyCandidate[]>;
|
|
54
|
-
saveArtifact: (draft: ClinicalArtifactDraft) => Promise<ClinicalResourceBundleLike | undefined>;
|
|
101
|
+
saveArtifact: (draft: ClinicalArtifactDraft) => Promise<ClinicalResourceBundleLike | ClinicalArtifactSaveOutcome | undefined>;
|
|
55
102
|
onCancel: () => void;
|
|
56
103
|
onReadback: (bundle: ClinicalResourceBundleLike) => void;
|
|
57
104
|
}>;
|
|
@@ -60,5 +107,5 @@ export type ClinicalArtifactEditorProps = Readonly<{
|
|
|
60
107
|
* persistence and terminology service access. A successful form submission is
|
|
61
108
|
* published only after the host returns a fresh provider readback Bundle.
|
|
62
109
|
*/
|
|
63
|
-
export declare function ClinicalArtifactEditor({ request, schema, copy, locale, taxonomyId, fieldLabel, searchTerminology, saveArtifact, onCancel, onReadback, }: ClinicalArtifactEditorProps): import("react").JSX.Element;
|
|
110
|
+
export declare function ClinicalArtifactEditor({ request, schema, operationSchema, authoringContext, copy, locale, taxonomyId, fieldLabel, searchTerminology, saveArtifact, onCancel, onReadback, }: ClinicalArtifactEditorProps): import("react").JSX.Element;
|
|
64
111
|
//# sourceMappingURL=ClinicalArtifactEditor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;
|
|
1
|
+
{"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;AAOlG,OAAO,EAML,KAAK,2BAA2B,EAEhC,KAAK,2BAA2B,EACjC,MAAM,kDAAkD,CAAA;AAEzD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACjD,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,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACjD,2EAA2E;IAC3E,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9E,gFAAgF;IAChF,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;IACxC,yFAAyF;IACzF,eAAe,CAAC,EAAE,2BAA2B,CAAA;IAC7C,8DAA8D;IAC9D,aAAa,CAAC,EAAE,6BAA6B,CAAA;CAC9C,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;IACzB,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAC3B,CAAC,CAAA;AAEF,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gBAAgB,EAAE,OAAO,GAAG,WAAW,CAAA;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,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;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAAA;AAEF,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAC9C;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,0BAA0B,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,SAAS,GAAG,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CACzC,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,OAAO,EAAE,6BAA6B,CAAA;IACtC,MAAM,EAAE,sBAAsB,CAAA;IAC9B,oFAAoF;IACpF,eAAe,CAAC,EAAE,2BAA2B,CAAA;IAC7C,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,gCAAgC,CAAA;IACnD,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,2BAA2B,GAAG,SAAS,CAAC,CAAA;IAC7H,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,eAAe,EACf,gBAAgB,EAChB,IAAI,EACJ,MAAa,EACb,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,UAAU,GACX,EAAE,2BAA2B,+BAmK7B"}
|
|
@@ -1,29 +1,60 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useId, useMemo, useState } from 'react';
|
|
3
|
+
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { clinicalArtifactEditorClaimsFromFhirR4, clinicalArtifactFhirR4ToFlat, } from 'fhir-data-utils-ts';
|
|
5
|
+
import { buildMultichannelOperationBundle, MultichannelOperationMode, readMultichannelOperationFromBundle, selectMultichannelOperationClaimValues, } from 'sos-data-utils-ts/multichannel-operation-schemas';
|
|
4
6
|
/**
|
|
5
7
|
* Shared human/animal clinical editor. The host owns authorization, transport,
|
|
6
8
|
* persistence and terminology service access. A successful form submission is
|
|
7
9
|
* published only after the host returns a fresh provider readback Bundle.
|
|
8
10
|
*/
|
|
9
|
-
export function ClinicalArtifactEditor({ request, schema, copy, locale = 'en', taxonomyId, fieldLabel, searchTerminology, saveArtifact, onCancel, onReadback, }) {
|
|
11
|
+
export function ClinicalArtifactEditor({ request, schema, operationSchema, authoringContext, copy, locale = 'en', taxonomyId, fieldLabel, searchTerminology, saveArtifact, onCancel, onReadback, }) {
|
|
10
12
|
const titleId = useId();
|
|
11
13
|
const descriptionId = useId();
|
|
12
|
-
const acceptedClaims = useMemo(() =>
|
|
14
|
+
const acceptedClaims = useMemo(() => operationSchema
|
|
15
|
+
? Object.keys(operationSchema.properties.claims?.properties ?? {})
|
|
16
|
+
: [...schema.requiredClaims, ...schema.optionalClaims], [operationSchema, schema.optionalClaims, schema.requiredClaims]);
|
|
13
17
|
const fields = useMemo(() => acceptedClaims
|
|
14
18
|
.filter((claim) => !claim.endsWith('.section') && !claim.endsWith('.user-selected'))
|
|
15
19
|
.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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const requiredClaims = useMemo(() => new Set(operationSchema?.properties.claims?.required ?? schema.requiredClaims), [operationSchema, schema.requiredClaims]);
|
|
21
|
+
const [claims, setClaims] = useState(() => {
|
|
22
|
+
const operationId = operationSchema?.['x-operation-id'];
|
|
23
|
+
const initialBundleClaims = request.initialBundle && operationId
|
|
24
|
+
? readMultichannelOperationFromBundle(operationId, request.initialBundle, {
|
|
25
|
+
operation: request.mode === 'update' ? MultichannelOperationMode.Update : MultichannelOperationMode.Create,
|
|
26
|
+
resourceId: request.artifactId,
|
|
27
|
+
ifMatch: request.ifMatch,
|
|
28
|
+
}).claims
|
|
29
|
+
: {};
|
|
30
|
+
const initialResourceClaims = request.initialResource
|
|
31
|
+
? operationId
|
|
32
|
+
? selectMultichannelOperationClaimValues(operationId, clinicalArtifactFhirR4ToFlat(request.initialResource))
|
|
33
|
+
: clinicalArtifactEditorClaimsFromFhirR4(request.initialResource, {
|
|
34
|
+
resourceType: request.resourceType,
|
|
35
|
+
sectionCode: schema.sectionCode ?? request.sectionCode,
|
|
36
|
+
requiredClaims: schema.requiredClaims,
|
|
37
|
+
optionalClaims: schema.optionalClaims,
|
|
38
|
+
terminologyTextClaims: schema.terminologyTextClaims ?? [],
|
|
39
|
+
initialClaims: schema.initialClaims ?? defaultClaims(request.resourceType),
|
|
40
|
+
})
|
|
41
|
+
: {};
|
|
42
|
+
return {
|
|
43
|
+
...(operationSchema ? jsonSchemaDefaultClaims(operationSchema) : defaultClaims(request.resourceType)),
|
|
44
|
+
...schema.initialClaims,
|
|
45
|
+
...stringClaims(initialBundleClaims),
|
|
46
|
+
...stringClaims(initialResourceClaims),
|
|
47
|
+
...Object.fromEntries(Object.entries(request.initialClaims ?? {}).map(([claim, value]) => [claim, String(value ?? '')])),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
21
50
|
const [activeTerminologyClaim, setActiveTerminologyClaim] = useState();
|
|
22
51
|
const [candidates, setCandidates] = useState([]);
|
|
23
52
|
const [terminologyBusy, setTerminologyBusy] = useState(false);
|
|
24
53
|
const [terminologyError, setTerminologyError] = useState(false);
|
|
25
54
|
const [saving, setSaving] = useState(false);
|
|
26
55
|
const [error, setError] = useState('');
|
|
56
|
+
const [pending, setPending] = useState('');
|
|
57
|
+
const draftId = useRef(request.artifactId ?? crypto.randomUUID());
|
|
27
58
|
useEffect(() => {
|
|
28
59
|
if (!activeTerminologyClaim || !searchTerminology)
|
|
29
60
|
return;
|
|
@@ -63,16 +94,29 @@ export function ClinicalArtifactEditor({ request, schema, copy, locale = 'en', t
|
|
|
63
94
|
setSaving(true);
|
|
64
95
|
setError('');
|
|
65
96
|
try {
|
|
66
|
-
const
|
|
97
|
+
const result = await saveArtifact({
|
|
67
98
|
resourceType: request.resourceType,
|
|
68
|
-
id:
|
|
99
|
+
id: draftId.current,
|
|
69
100
|
sectionCode: request.sectionCode,
|
|
70
101
|
mode: request.mode,
|
|
71
102
|
claims,
|
|
103
|
+
...(operationSchema ? buildOperationDraft(operationSchema, authoringContext, request, draftId.current, claims) : {}),
|
|
72
104
|
});
|
|
73
|
-
if (!
|
|
105
|
+
if (!result)
|
|
74
106
|
throw new Error(copy.readbackMissing);
|
|
75
|
-
|
|
107
|
+
if (isSaveOutcome(result)) {
|
|
108
|
+
if (result.state === 'confirmed') {
|
|
109
|
+
setPending('');
|
|
110
|
+
onReadback(result.bundle);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (result.state === 'rejected')
|
|
114
|
+
throw new Error(result.message);
|
|
115
|
+
setPending(result.message || copy.pending || copy.readbackMissing);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
setPending('');
|
|
119
|
+
onReadback(result);
|
|
76
120
|
}
|
|
77
121
|
catch (reason) {
|
|
78
122
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
@@ -93,18 +137,54 @@ export function ClinicalArtifactEditor({ request, schema, copy, locale = 'en', t
|
|
|
93
137
|
setActiveTerminologyClaim(claim);
|
|
94
138
|
};
|
|
95
139
|
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: () => {
|
|
140
|
+
: _jsx("input", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), type: fieldInputType(parameter, operationSchema?.properties.claims?.properties?.[claim]), 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
141
|
setClaims((current) => selectedCodingClaims(request.resourceType, claim, candidate, acceptedClaims, current));
|
|
98
142
|
setCandidates([]);
|
|
99
143
|
setActiveTerminologyClaim(undefined);
|
|
100
144
|
}, 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 })] })] })] });
|
|
145
|
+
}), error ? _jsx("p", { className: "uhcp-clinical-editor-error", role: "alert", children: error }) : null, pending ? _jsx("p", { className: "uhcp-clinical-editor-pending", role: "status", children: copy.pending || pending }) : null, _jsxs("footer", { children: [_jsx("button", { type: "button", onClick: onCancel, children: copy.cancel }), _jsx("button", { type: "submit", disabled: saving, children: saving ? copy.saving : pending ? (copy.retry || copy.save) : copy.save })] })] })] });
|
|
146
|
+
}
|
|
147
|
+
function isSaveOutcome(value) {
|
|
148
|
+
return typeof value === 'object' && value !== null && 'state' in value;
|
|
149
|
+
}
|
|
150
|
+
function buildOperationDraft(operationSchema, authoringContext, request, resourceId, claims) {
|
|
151
|
+
if (!authoringContext)
|
|
152
|
+
throw new TypeError('clinical_artifact_authoring_context_required');
|
|
153
|
+
const operationId = operationSchema['x-operation-id'];
|
|
154
|
+
const operation = request.mode === 'update' ? MultichannelOperationMode.Update : MultichannelOperationMode.Create;
|
|
155
|
+
const operationValues = {
|
|
156
|
+
operation,
|
|
157
|
+
resourceId,
|
|
158
|
+
...authoringContext,
|
|
159
|
+
...(request.ifMatch ? { ifMatch: request.ifMatch } : {}),
|
|
160
|
+
claims: coerceClaimsFromJsonSchema(operationSchema, claims),
|
|
161
|
+
};
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
operationValues,
|
|
164
|
+
changesBundle: buildMultichannelOperationBundle(operationId, operationValues),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function coerceClaimsFromJsonSchema(schema, claims) {
|
|
168
|
+
const properties = schema.properties.claims?.properties ?? {};
|
|
169
|
+
return Object.freeze(Object.fromEntries(Object.entries(claims)
|
|
170
|
+
.filter(([claim, value]) => properties[claim] && value !== '')
|
|
171
|
+
.map(([claim, value]) => [claim, properties[claim]?.type === 'number' ? Number(value) : value])));
|
|
172
|
+
}
|
|
173
|
+
function stringClaims(claims) {
|
|
174
|
+
return Object.fromEntries(Object.entries(claims).map(([claim, value]) => [claim, String(value ?? '')]));
|
|
175
|
+
}
|
|
176
|
+
function jsonSchemaDefaultClaims(schema) {
|
|
177
|
+
return Object.fromEntries(Object.entries(schema.properties.claims?.properties ?? {})
|
|
178
|
+
.filter(([, node]) => node.default !== undefined)
|
|
179
|
+
.map(([claim, node]) => [claim, String(node.default)]));
|
|
102
180
|
}
|
|
103
181
|
function defaultClaims(resourceType) {
|
|
104
182
|
if (resourceType === 'Immunization')
|
|
105
183
|
return { 'Immunization.status': 'completed' };
|
|
106
|
-
if (resourceType === 'Observation' || resourceType === 'DiagnosticReport'
|
|
184
|
+
if (resourceType === 'Observation' || resourceType === 'DiagnosticReport')
|
|
107
185
|
return { [`${resourceType}.status`]: 'final' };
|
|
186
|
+
if (resourceType === 'Procedure')
|
|
187
|
+
return { [`${resourceType}.status`]: 'completed' };
|
|
108
188
|
if (resourceType === 'Condition' || resourceType === 'AllergyIntolerance')
|
|
109
189
|
return { [`${resourceType}.clinical-status`]: 'active' };
|
|
110
190
|
return {};
|
|
@@ -148,7 +228,9 @@ function selectedCodingClaims(resourceType, inputClaim, candidate, acceptedClaim
|
|
|
148
228
|
}
|
|
149
229
|
function isMultiline(parameter) { return /note|description|summary|instruction|outcome|reference-range-text/.test(parameter); }
|
|
150
230
|
function humanize(value) { return value ? value[0].toUpperCase() + value.slice(1).replaceAll('_', ' ') : value; }
|
|
151
|
-
function fieldInputType(parameter) {
|
|
231
|
+
function fieldInputType(parameter, schemaNode) {
|
|
232
|
+
if (schemaNode)
|
|
233
|
+
return schemaNode.type === 'number' || schemaNode.type === 'integer' ? 'number' : 'text';
|
|
152
234
|
if (/(?:^|-)number$|frequency$|dose-sequence$/.test(parameter))
|
|
153
235
|
return 'number';
|
|
154
236
|
// FHIR date/search fields may carry a partial date or a complete dateTime
|
|
@@ -17,6 +17,8 @@ export type ClinicalSectionSummaryProps = Readonly<{
|
|
|
17
17
|
emptyLabel: string;
|
|
18
18
|
resourceTypeLabel?: (resourceType: string) => string;
|
|
19
19
|
actions?: ReactNode;
|
|
20
|
+
locale?: string;
|
|
21
|
+
timeZone?: string;
|
|
20
22
|
/** Hide only the heading when a host already owns the expandable section header. */
|
|
21
23
|
showHeader?: boolean;
|
|
22
24
|
}>;
|
|
@@ -28,5 +30,5 @@ export type ClinicalSectionSummaryProps = Readonly<{
|
|
|
28
30
|
* terminology identity/translation key and must never be supplied as a
|
|
29
31
|
* replacement for manually authored text.
|
|
30
32
|
*/
|
|
31
|
-
export declare function ClinicalSectionSummary({ code, title, resources, emptyLabel, resourceTypeLabel, actions, showHeader, }: ClinicalSectionSummaryProps): import("react").JSX.Element;
|
|
33
|
+
export declare function ClinicalSectionSummary({ code, title, resources, emptyLabel, resourceTypeLabel, actions, locale, timeZone, showHeader, }: ClinicalSectionSummaryProps): import("react").JSX.Element;
|
|
32
34
|
//# sourceMappingURL=ClinicalSectionSummary.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClinicalSectionSummary.d.ts","sourceRoot":"","sources":["../src/ClinicalSectionSummary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"ClinicalSectionSummary.d.ts","sourceRoot":"","sources":["../src/ClinicalSectionSummary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,SAAS,QAAQ,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAA;CAC/E,CAAC,CAAA;AAEF,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAA;IACzC,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACpD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oFAAoF;IACpF,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,KAAK,EACL,SAAS,EACT,UAAU,EACV,iBAAkD,EAClD,OAAO,EACP,MAAa,EACb,QAAQ,EACR,UAAiB,GAClB,EAAE,2BAA2B,+BAiC7B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { formatFhirTemporalForLocale } from 'fhir-data-utils-ts/fhir-temporal-presentation';
|
|
2
3
|
/**
|
|
3
4
|
* Brand-neutral renderer for one already-resolved IPS section.
|
|
4
5
|
*
|
|
@@ -7,8 +8,16 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
7
8
|
* terminology identity/translation key and must never be supplied as a
|
|
8
9
|
* replacement for manually authored text.
|
|
9
10
|
*/
|
|
10
|
-
export function ClinicalSectionSummary({ code, title, resources, emptyLabel, resourceTypeLabel = (resourceType) => resourceType, actions, showHeader = true, }) {
|
|
11
|
+
export function ClinicalSectionSummary({ code, title, resources, emptyLabel, resourceTypeLabel = (resourceType) => resourceType, actions, locale = 'en', timeZone, showHeader = true, }) {
|
|
11
12
|
return (_jsxs("section", { className: "uhcp-clinical-section", "data-ips-section": code, ...(showHeader ? { 'aria-labelledby': `uhcp-section-${code}` } : { 'aria-label': title }), children: [showHeader && (_jsxs("header", { children: [_jsxs("h3", { id: `uhcp-section-${code}`, children: [title, " ", _jsxs("span", { children: ["(", resources.length, ")"] })] }), actions] })), resources.length === 0
|
|
12
13
|
? _jsx("p", { className: "uhcp-clinical-empty", children: emptyLabel })
|
|
13
|
-
: (_jsx("div", { className: "uhcp-clinical-grid", children: resources.map((resource) => (_jsxs("article", { className: "uhcp-clinical-card", "data-resource-type": resource.resourceType, children: [_jsx("p", { className: "uhcp-clinical-title", dir: "auto", children: _jsx("bdi", { children: resource.title }) }), _jsx("p", { className: "uhcp-clinical-type", children: resourceTypeLabel(resource.resourceType) }), resource.date && _jsx("p", { className: "uhcp-clinical-date", children: resource.date }), resource.details?.length ? _jsx("dl", { className: "uhcp-clinical-details", children: resource.details.map((detail) => _jsxs("div", { children: [_jsx("dt", { children: detail.label }), _jsxs("dd", { children: [detail.value, detail.unit ? ` ${detail.unit}` : ''] })] }, `${detail.label}:${detail.value}:${detail.unit ?? ''}`)) }) : null] }, resource.key))) }))] }));
|
|
14
|
+
: (_jsx("div", { className: "uhcp-clinical-grid", children: resources.map((resource) => (_jsxs("article", { className: "uhcp-clinical-card", "data-resource-type": resource.resourceType, children: [_jsx("p", { className: "uhcp-clinical-title", dir: "auto", children: _jsx("bdi", { children: resource.title }) }), _jsx("p", { className: "uhcp-clinical-type", children: resourceTypeLabel(resource.resourceType) }), resource.date && _jsx("p", { className: "uhcp-clinical-date", children: _jsx("time", { dateTime: resource.date, children: localizedTemporal(resource.date, locale, timeZone) }) }), resource.details?.length ? _jsx("dl", { className: "uhcp-clinical-details", children: resource.details.map((detail) => _jsxs("div", { children: [_jsx("dt", { children: detail.label }), _jsxs("dd", { children: [detail.value, detail.unit ? ` ${detail.unit}` : ''] })] }, `${detail.label}:${detail.value}:${detail.unit ?? ''}`)) }) : null] }, resource.key))) }))] }));
|
|
15
|
+
}
|
|
16
|
+
function localizedTemporal(value, locale, timeZone) {
|
|
17
|
+
try {
|
|
18
|
+
return formatFhirTemporalForLocale(value, locale, { timeZone, includeTime: value.includes('T') });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
14
23
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type LocalDateTimeParts } from 'fhir-data-utils-ts/fhir-temporal-presentation';
|
|
3
|
+
export type ClinicalTimelineEntry = Readonly<{
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
dateTime: string;
|
|
7
|
+
facets?: Readonly<Record<string, readonly string[]>>;
|
|
8
|
+
content?: ReactNode;
|
|
9
|
+
}>;
|
|
10
|
+
export type ClinicalTimelineFacet = Readonly<{
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
options: readonly Readonly<{
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}>[];
|
|
17
|
+
}>;
|
|
18
|
+
export type ClinicalTimelineLabels = Readonly<{
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
date: string;
|
|
22
|
+
time: string;
|
|
23
|
+
useToday: string;
|
|
24
|
+
useCurrentTime: string;
|
|
25
|
+
previous: string;
|
|
26
|
+
next: string;
|
|
27
|
+
showAll: string;
|
|
28
|
+
empty: string;
|
|
29
|
+
}>;
|
|
30
|
+
export type ClinicalDateTimeFieldLabels = Readonly<{
|
|
31
|
+
date: string;
|
|
32
|
+
time: string;
|
|
33
|
+
useToday: string;
|
|
34
|
+
useCurrentTime: string;
|
|
35
|
+
}>;
|
|
36
|
+
export declare function ClinicalDateTimeFields({ value, onChange, labels, timeZone, now, includeSeconds, }: Readonly<{
|
|
37
|
+
value: LocalDateTimeParts;
|
|
38
|
+
onChange: (value: LocalDateTimeParts) => void;
|
|
39
|
+
labels: ClinicalDateTimeFieldLabels;
|
|
40
|
+
timeZone: string;
|
|
41
|
+
now?: Date;
|
|
42
|
+
includeSeconds?: boolean;
|
|
43
|
+
}>): import("react").JSX.Element;
|
|
44
|
+
export declare function ClinicalTimeline({ entries, facets, locale, timeZone, labels, now }: Readonly<{
|
|
45
|
+
entries: readonly ClinicalTimelineEntry[];
|
|
46
|
+
facets?: readonly ClinicalTimelineFacet[];
|
|
47
|
+
locale: string;
|
|
48
|
+
timeZone: string;
|
|
49
|
+
labels: ClinicalTimelineLabels;
|
|
50
|
+
now?: Date;
|
|
51
|
+
}>): import("react").JSX.Element;
|
|
52
|
+
//# sourceMappingURL=ClinicalTimeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClinicalTimeline.d.ts","sourceRoot":"","sources":["../src/ClinicalTimeline.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACzD,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,+CAA+C,CAAA;AAEtD,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;IACpD,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,CAAC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,SAAS,QAAQ,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAA;CAC5D,CAAC,CAAA;AAEF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;CACvB,CAAC,CAAA;AAEF,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,GAAgB,EAChB,cAAqB,GACtB,EAAE,QAAQ,CAAC;IACV,KAAK,EAAE,kBAAkB,CAAA;IACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC7C,MAAM,EAAE,2BAA2B,CAAA;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,IAAI,CAAA;IACV,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAC,+BAWD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,MAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAgB,EAAE,EAAE,QAAQ,CAAC;IAC9G,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAA;IACzC,MAAM,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAA;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,sBAAsB,CAAA;IAC9B,GAAG,CAAC,EAAE,IAAI,CAAA;CACX,CAAC,+BAuDD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { composeFhirDateTimeFromLocalParts, formatFhirTemporalForLocale, isFhirTemporalWithinRange, splitFhirDateTimeForInput, } from 'fhir-data-utils-ts/fhir-temporal-presentation';
|
|
4
|
+
export function ClinicalDateTimeFields({ value, onChange, labels, timeZone, now = new Date(), includeSeconds = true, }) {
|
|
5
|
+
const current = splitFhirDateTimeForInput(now, timeZone);
|
|
6
|
+
const time = includeSeconds ? current.time : current.time.slice(0, 5);
|
|
7
|
+
return (_jsxs("div", { className: "uhcp-clinical-date-time-fields", children: [_jsxs("label", { children: [labels.date, _jsx("input", { type: "date", value: value.date, onChange: (event) => onChange({ ...value, date: event.target.value }) })] }), _jsx("button", { type: "button", title: labels.useToday, "aria-label": labels.useToday, onClick: () => onChange({ ...value, date: current.date }), children: "\u25C9" }), _jsxs("label", { children: [labels.time, _jsx("input", { type: "time", step: includeSeconds ? 1 : 60, value: value.time, onChange: (event) => onChange({ ...value, time: event.target.value }) })] }), _jsx("button", { type: "button", title: labels.useCurrentTime, "aria-label": labels.useCurrentTime, onClick: () => onChange({ ...value, time }), children: "\u25F7" })] }));
|
|
8
|
+
}
|
|
9
|
+
export function ClinicalTimeline({ entries, facets = [], locale, timeZone, labels, now = new Date() }) {
|
|
10
|
+
const [selectedFacets, setSelectedFacets] = useState({});
|
|
11
|
+
const [from, setFrom] = useState({ date: '', time: '' });
|
|
12
|
+
const [to, setTo] = useState({ date: '', time: '' });
|
|
13
|
+
const [requestedIndex, setRequestedIndex] = useState(0);
|
|
14
|
+
const [showAll, setShowAll] = useState(false);
|
|
15
|
+
const current = splitFhirDateTimeForInput(now, timeZone);
|
|
16
|
+
const filtered = useMemo(() => [...entries]
|
|
17
|
+
.filter((entry) => matchesEverySelectedFacet(entry, selectedFacets))
|
|
18
|
+
.filter((entry) => isFhirTemporalWithinRange(entry.dateTime, {
|
|
19
|
+
from: composeOptionalBoundary(from, timeZone, 'from'),
|
|
20
|
+
to: composeOptionalBoundary(to, timeZone, 'to'),
|
|
21
|
+
}, now))
|
|
22
|
+
.sort((left, right) => new Date(right.dateTime).getTime() - new Date(left.dateTime).getTime()), [entries, from, now, selectedFacets, timeZone, to]);
|
|
23
|
+
const index = Math.min(requestedIndex, Math.max(filtered.length - 1, 0));
|
|
24
|
+
const selected = filtered[index];
|
|
25
|
+
return (_jsxs("section", { className: "uhcp-clinical-timeline", children: [_jsxs("div", { className: "uhcp-clinical-filters", children: [_jsxs("div", { className: "uhcp-clinical-range", children: [_jsx(DateTimeBoundary, { label: labels.from, labels: labels, current: current, value: from, onChange: (value) => { setFrom(value); setRequestedIndex(0); } }), _jsx(DateTimeBoundary, { label: labels.to, labels: labels, current: current, value: to, onChange: (value) => { setTo(value); setRequestedIndex(0); } })] }), facets.map((facet) => _jsxs("fieldset", { children: [_jsx("legend", { children: facet.label }), facet.options.map((option) => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: selectedFacets[facet.id]?.includes(option.id) ?? false, onChange: () => {
|
|
26
|
+
setSelectedFacets((current) => toggleFacet(current, facet.id, option.id));
|
|
27
|
+
setRequestedIndex(0);
|
|
28
|
+
} }), option.label] }, option.id))] }, facet.id))] }), selected ? _jsxs(_Fragment, { children: [_jsxs("article", { className: "uhcp-clinical-timeline-card", children: [_jsx("h4", { children: selected.title }), _jsx("time", { dateTime: selected.dateTime, children: formatFhirTemporalForLocale(selected.dateTime, locale, { timeZone, includeTime: true }) }), selected.content] }), _jsxs("nav", { className: "uhcp-clinical-timeline-navigation", "aria-label": `${selected.title} history`, children: [_jsx("button", { type: "button", disabled: index === 0, onClick: () => setRequestedIndex((value) => Math.max(0, value - 1)), children: labels.previous }), _jsxs("output", { "aria-live": "polite", children: [index + 1, " / ", filtered.length] }), _jsx("button", { type: "button", disabled: index >= filtered.length - 1, onClick: () => setRequestedIndex((value) => value + 1), children: labels.next }), _jsx("button", { type: "button", "aria-expanded": showAll, onClick: () => setShowAll((value) => !value), children: labels.showAll })] }), showAll ? _jsx("ol", { className: "uhcp-clinical-timeline-list", children: filtered.map((entry) => _jsxs("li", { children: [_jsx("strong", { children: entry.title }), _jsx("time", { dateTime: entry.dateTime, children: formatFhirTemporalForLocale(entry.dateTime, locale, { timeZone, includeTime: true }) })] }, entry.id)) }) : null] }) : _jsx("p", { className: "uhcp-clinical-empty", children: labels.empty })] }));
|
|
29
|
+
}
|
|
30
|
+
function DateTimeBoundary({ label, labels, current, value, onChange }) {
|
|
31
|
+
return _jsxs("fieldset", { children: [_jsx("legend", { children: label }), _jsxs("label", { children: [labels.date, _jsx("input", { "aria-label": `${label} ${labels.date}`, type: "date", value: value.date, onChange: (event) => onChange({ ...value, date: event.target.value }) })] }), _jsx("button", { type: "button", title: `${labels.useToday} (${label})`, "aria-label": `${labels.useToday} (${label})`, onClick: () => onChange({ ...value, date: current.date }), children: "\u25C9" }), _jsxs("label", { children: [labels.time, _jsx("input", { "aria-label": `${label} ${labels.time}`, type: "time", step: "1", value: value.time, onChange: (event) => onChange({ ...value, time: event.target.value }) })] }), _jsx("button", { type: "button", title: `${labels.useCurrentTime} (${label})`, "aria-label": `${labels.useCurrentTime} (${label})`, onClick: () => onChange({ ...value, time: current.time }), children: "\u25F7" })] });
|
|
32
|
+
}
|
|
33
|
+
function composeOptionalBoundary(parts, timeZone, edge) {
|
|
34
|
+
if (!parts.date)
|
|
35
|
+
return undefined;
|
|
36
|
+
const defaultTime = edge === 'from' ? '00:00:00' : '23:59:59';
|
|
37
|
+
return composeFhirDateTimeFromLocalParts({ date: parts.date, time: parts.time || defaultTime, timeZone });
|
|
38
|
+
}
|
|
39
|
+
function matchesEverySelectedFacet(entry, selected) {
|
|
40
|
+
return Object.entries(selected).every(([facetId, selectedOptions]) => selectedOptions.length === 0 || selectedOptions.some((option) => entry.facets?.[facetId]?.includes(option)));
|
|
41
|
+
}
|
|
42
|
+
function toggleFacet(current, facetId, optionId) {
|
|
43
|
+
const existing = current[facetId] ?? [];
|
|
44
|
+
const next = existing.includes(optionId) ? existing.filter((id) => id !== optionId) : [...existing, optionId];
|
|
45
|
+
return { ...current, [facetId]: next };
|
|
46
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type EmergencyContactRecord = Readonly<{
|
|
2
|
+
id?: string;
|
|
3
|
+
name: string;
|
|
4
|
+
relationship: string;
|
|
5
|
+
phone?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
}>;
|
|
8
|
+
export type EmergencyContactDirectoryCopy = Readonly<{
|
|
9
|
+
title: string;
|
|
10
|
+
empty: string;
|
|
11
|
+
add: string;
|
|
12
|
+
edit: string;
|
|
13
|
+
remove: string;
|
|
14
|
+
save: string;
|
|
15
|
+
cancel: string;
|
|
16
|
+
name: string;
|
|
17
|
+
relationship: string;
|
|
18
|
+
phone: string;
|
|
19
|
+
email: string;
|
|
20
|
+
call: string;
|
|
21
|
+
message: string;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function EmergencyContactDirectory({ contacts, copy, editable, onSave, onDelete }: Readonly<{
|
|
24
|
+
contacts: readonly EmergencyContactRecord[];
|
|
25
|
+
copy: EmergencyContactDirectoryCopy;
|
|
26
|
+
editable?: boolean;
|
|
27
|
+
onSave?: (contact: EmergencyContactRecord) => Promise<void>;
|
|
28
|
+
onDelete?: (id: string) => Promise<void>;
|
|
29
|
+
}>): import("react").JSX.Element;
|
|
30
|
+
//# sourceMappingURL=EmergencyContactDirectory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmergencyContactDirectory.d.ts","sourceRoot":"","sources":["../src/EmergencyContactDirectory.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAC,CAAA;AAIF,wBAAgB,yBAAyB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC;IACzG,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAA;IAC3C,IAAI,EAAE,6BAA6B,CAAA;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACzC,CAAC,+BAkCD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
const EMPTY_CONTACT = Object.freeze({ name: '', relationship: '', phone: '', email: '' });
|
|
5
|
+
export function EmergencyContactDirectory({ contacts, copy, editable = false, onSave, onDelete }) {
|
|
6
|
+
const [draft, setDraft] = useState();
|
|
7
|
+
const [saving, setSaving] = useState(false);
|
|
8
|
+
async function save() {
|
|
9
|
+
if (!draft || !onSave)
|
|
10
|
+
return;
|
|
11
|
+
setSaving(true);
|
|
12
|
+
try {
|
|
13
|
+
await onSave(draft);
|
|
14
|
+
setDraft(undefined);
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
setSaving(false);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return _jsxs("section", { className: "uhcp-emergency-contact-directory", children: [_jsxs("header", { children: [_jsx("h3", { children: copy.title }), editable ? _jsx("button", { type: "button", onClick: () => setDraft(EMPTY_CONTACT), children: copy.add }) : null] }), contacts.length ? _jsx("ul", { children: contacts.map((contact) => _jsxs("li", { children: [_jsxs("div", { children: [_jsx("strong", { children: contact.name }), _jsx("span", { children: contact.relationship })] }), _jsxs("nav", { "aria-label": contact.name, children: [contact.phone ? _jsx("a", { href: `tel:${contact.phone}`, children: copy.call }) : null, contact.email ? _jsx("a", { href: `mailto:${contact.email}`, children: copy.message }) : null, editable ? _jsxs("button", { type: "button", onClick: () => setDraft(contact), children: [copy.edit, " ", contact.name] }) : null, editable && contact.id && onDelete ? _jsxs("button", { type: "button", onClick: () => void onDelete(contact.id), children: [copy.remove, " ", contact.name] }) : null] })] }, contact.id ?? `${contact.name}|${contact.relationship}`)) }) : _jsx("p", { children: copy.empty }), draft ? _jsxs("form", { className: "uhcp-emergency-contact-editor", onSubmit: (event) => { event.preventDefault(); void save(); }, children: [_jsxs("label", { children: [copy.name, _jsx("input", { required: true, value: draft.name, onChange: (event) => setDraft((current) => ({ ...current, name: event.target.value })) })] }), _jsxs("label", { children: [copy.relationship, _jsx("input", { required: true, value: draft.relationship, onChange: (event) => setDraft((current) => ({ ...current, relationship: event.target.value })) })] }), _jsxs("label", { children: [copy.phone, _jsx("input", { type: "tel", value: draft.phone ?? '', onChange: (event) => setDraft((current) => ({ ...current, phone: event.target.value })) })] }), _jsxs("label", { children: [copy.email, _jsx("input", { type: "email", value: draft.email ?? '', onChange: (event) => setDraft((current) => ({ ...current, email: event.target.value })) })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", onClick: () => setDraft(undefined), children: copy.cancel }), _jsx("button", { type: "submit", disabled: saving, children: copy.save })] })] }) : null] });
|
|
21
|
+
}
|
|
@@ -60,6 +60,13 @@ export type EncounterTransferConsoleCopy = Readonly<{
|
|
|
60
60
|
attachmentReady: string;
|
|
61
61
|
technicalDetails: string;
|
|
62
62
|
encounterIdentifier: string;
|
|
63
|
+
transferPending: string;
|
|
64
|
+
retryTransfer: string;
|
|
65
|
+
transferRejected: string;
|
|
66
|
+
}>;
|
|
67
|
+
export type EncounterTransferApplyOutcome = Readonly<{
|
|
68
|
+
state: 'confirmed' | 'pending' | 'retryable' | 'rejected';
|
|
69
|
+
message?: string;
|
|
63
70
|
}>;
|
|
64
71
|
export type EncounterTransferConsoleProps = Readonly<{
|
|
65
72
|
copy: EncounterTransferConsoleCopy;
|
|
@@ -69,7 +76,7 @@ export type EncounterTransferConsoleProps = Readonly<{
|
|
|
69
76
|
destinationEncounterId: string;
|
|
70
77
|
contacts: readonly EmergencyContactAction[];
|
|
71
78
|
demo?: boolean;
|
|
72
|
-
onApplyTransfer?: (sourceOutcome: EncounterSourceOutcome) => void
|
|
79
|
+
onApplyTransfer?: (sourceOutcome: EncounterSourceOutcome) => void | EncounterTransferApplyOutcome | Promise<void | EncounterTransferApplyOutcome>;
|
|
73
80
|
}>;
|
|
74
81
|
/**
|
|
75
82
|
* Shared visual editor for one already-authorized subject transfer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EncounterTransferConsole.d.ts","sourceRoot":"","sources":["../src/EncounterTransferConsole.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,WAAW,CAAA;AACzF,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,WAAW,CAAA;AAE5D,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAA;IAClB,wFAAwF;IACxF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,uBAAuB,CAAA;CAChC,CAAC,CAAA;AAEF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAAA;AAEF,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,kBAAkB,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;IAC7E,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;IACnE,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;IACxF,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;IAChE,gBAAgB,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;IAC9E,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;IAClI,eAAe,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;IACpJ,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;IACrG,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;IACnI,mBAAmB,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"EncounterTransferConsole.d.ts","sourceRoot":"","sources":["../src/EncounterTransferConsole.tsx"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,7 @@ export function EncounterTransferConsole({ copy, patientDisplay, encounters, sou
|
|
|
27
27
|
const [attachments, setAttachments] = useState([]);
|
|
28
28
|
const [showTechnical, setShowTechnical] = useState(false);
|
|
29
29
|
const [status, setStatus] = useState('');
|
|
30
|
+
const [transferSync, setTransferSync] = useState('idle');
|
|
30
31
|
const railRef = useRef(null);
|
|
31
32
|
const source = useMemo(() => encounters.find((item) => item.identifier === sourceEncounterId), [encounters, sourceEncounterId]);
|
|
32
33
|
const destination = useMemo(() => encounters.find((item) => item.identifier === destinationEncounterId), [encounters, destinationEncounterId]);
|
|
@@ -37,11 +38,29 @@ export function EncounterTransferConsole({ copy, patientDisplay, encounters, sou
|
|
|
37
38
|
setAttachments(current => current.includes(kind) ? current.filter(item => item !== kind) : [...current, kind]);
|
|
38
39
|
setStatus(copy.attachmentReady);
|
|
39
40
|
}
|
|
40
|
-
function applyTransfer() {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
async function applyTransfer() {
|
|
42
|
+
setTransferSync('submitting');
|
|
43
|
+
try {
|
|
44
|
+
const outcome = await onApplyTransfer?.(sourceOutcome);
|
|
45
|
+
if (outcome?.state === 'pending' || outcome?.state === 'retryable') {
|
|
46
|
+
setTransferSync('retryable');
|
|
47
|
+
setStatus(outcome.message || copy.transferPending);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (outcome?.state === 'rejected') {
|
|
51
|
+
setTransferSync('idle');
|
|
52
|
+
setStatus(outcome.message || copy.transferRejected);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
setTransferSync('idle');
|
|
56
|
+
setStatus(copy.transferReady);
|
|
57
|
+
}
|
|
58
|
+
catch (reason) {
|
|
59
|
+
setTransferSync('retryable');
|
|
60
|
+
setStatus(reason instanceof Error && reason.message ? reason.message : copy.transferPending);
|
|
61
|
+
}
|
|
43
62
|
}
|
|
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] })] });
|
|
63
|
+
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", disabled: transferSync === 'submitting', onClick: () => void applyTransfer(), children: transferSync === 'retryable' ? copy.retryTransfer : 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
64
|
}
|
|
46
65
|
function EncounterCard({ encounter }) {
|
|
47
66
|
return _jsx("article", { className: "uhcp-transfer-card", children: _jsx(EncounterCardBody, { encounter: encounter }) });
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { type ClinicalResourceBundleLike, type ClinicalSectionView, type ClinicalTerminologyTranslationInput } from 'gdc-common-utils-ts/utils/clinical-resource-view';
|
|
3
|
+
import { type ClinicalTimelineLabels } from './ClinicalTimeline.js';
|
|
3
4
|
export type IpsBundleSummaryProps = Readonly<{
|
|
4
5
|
bundle: ClinicalResourceBundleLike;
|
|
5
6
|
locale?: string;
|
|
7
|
+
timeZone?: string;
|
|
6
8
|
translateCode?: (input: ClinicalTerminologyTranslationInput) => string | undefined;
|
|
7
9
|
emptyLabel: string;
|
|
8
10
|
noSectionsLabel?: string;
|
|
9
11
|
sectionTitle?: (section: ClinicalSectionView) => string;
|
|
10
12
|
resourceTypeLabel?: (resourceType: string) => string;
|
|
11
13
|
sectionActions?: (section: ClinicalSectionView) => ReactNode;
|
|
14
|
+
timeline?: Readonly<{
|
|
15
|
+
labels: ClinicalTimelineLabels;
|
|
16
|
+
sectionFacetLabel: string;
|
|
17
|
+
resourceTypeFacetLabel: string;
|
|
18
|
+
}>;
|
|
12
19
|
}>;
|
|
13
20
|
/**
|
|
14
21
|
* Renders every section declared by an IPS Composition and the cards referenced
|
|
@@ -19,5 +26,5 @@ export type IpsBundleSummaryProps = Readonly<{
|
|
|
19
26
|
* native FHIR fields or canonical `resource.meta.claims` provide card content.
|
|
20
27
|
* This component performs no ingestion, persistence or GW call.
|
|
21
28
|
*/
|
|
22
|
-
export declare function IpsBundleSummary({ bundle, locale, translateCode, emptyLabel, noSectionsLabel, sectionTitle, resourceTypeLabel, sectionActions, }: IpsBundleSummaryProps): import("react").JSX.Element;
|
|
29
|
+
export declare function IpsBundleSummary({ bundle, locale, timeZone, translateCode, emptyLabel, noSectionsLabel, sectionTitle, resourceTypeLabel, sectionActions, timeline, }: IpsBundleSummaryProps): import("react").JSX.Element;
|
|
23
30
|
//# sourceMappingURL=IpsBundleSummary.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IpsBundleSummary.d.ts","sourceRoot":"","sources":["../src/IpsBundleSummary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,mCAAmC,EACzC,MAAM,kDAAkD,CAAA;
|
|
1
|
+
{"version":3,"file":"IpsBundleSummary.d.ts","sourceRoot":"","sources":["../src/IpsBundleSummary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,mCAAmC,EACzC,MAAM,kDAAkD,CAAA;AAEzD,OAAO,EAAgD,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAEjH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,MAAM,EAAE,0BAA0B,CAAA;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,mCAAmC,KAAK,MAAM,GAAG,SAAS,CAAA;IAClF,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,MAAM,CAAA;IACvD,iBAAiB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACpD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,SAAS,CAAA;IAC5D,QAAQ,CAAC,EAAE,QAAQ,CAAC;QAClB,MAAM,EAAE,sBAAsB,CAAA;QAC9B,iBAAiB,EAAE,MAAM,CAAA;QACzB,sBAAsB,EAAE,MAAM,CAAA;KAC/B,CAAC,CAAA;CACH,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,QAAQ,EACR,aAAa,EACb,UAAU,EACV,eAA4B,EAC5B,YAAyC,EACzC,iBAAiB,EACjB,cAAc,EACd,QAAQ,GACT,EAAE,qBAAqB,+BAoEvB"}
|
package/dist/IpsBundleSummary.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { toClinicalSectionViews, } from 'gdc-common-utils-ts/utils/clinical-resource-view';
|
|
3
3
|
import { ClinicalSectionSummary } from './ClinicalSectionSummary.js';
|
|
4
|
+
import { ClinicalTimeline } from './ClinicalTimeline.js';
|
|
4
5
|
/**
|
|
5
6
|
* Renders every section declared by an IPS Composition and the cards referenced
|
|
6
7
|
* by each section.
|
|
@@ -10,19 +11,47 @@ import { ClinicalSectionSummary } from './ClinicalSectionSummary.js';
|
|
|
10
11
|
* native FHIR fields or canonical `resource.meta.claims` provide card content.
|
|
11
12
|
* This component performs no ingestion, persistence or GW call.
|
|
12
13
|
*/
|
|
13
|
-
export function IpsBundleSummary({ bundle, locale, translateCode, emptyLabel, noSectionsLabel = emptyLabel, sectionTitle = (section) => section.title, resourceTypeLabel, sectionActions, }) {
|
|
14
|
+
export function IpsBundleSummary({ bundle, locale, timeZone, translateCode, emptyLabel, noSectionsLabel = emptyLabel, sectionTitle = (section) => section.title, resourceTypeLabel, sectionActions, timeline, }) {
|
|
14
15
|
const sections = toClinicalSectionViews(bundle, { locale, translateCode });
|
|
15
16
|
const nativeResources = new Map((bundle.entry ?? bundle.data ?? []).flatMap((entry) => entry.fullUrl && entry.resource ? [[entry.fullUrl, entry.resource]] : []));
|
|
16
17
|
if (sections.length === 0) {
|
|
17
18
|
return _jsx("p", { className: "uhcp-clinical-empty", "data-ips-summary-empty": true, children: noSectionsLabel });
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const timelineEntries = sections.flatMap((section, sectionIndex) => section.resources.flatMap((resource, resourceIndex) => resource.date ? [{
|
|
21
|
+
id: resource.fullUrl ?? `${sectionIndex}:${resourceIndex}:${resource.resourceType}`,
|
|
22
|
+
title: resource.title,
|
|
23
|
+
dateTime: resource.date,
|
|
24
|
+
facets: {
|
|
25
|
+
section: [section.code ?? section.title],
|
|
26
|
+
resourceType: [resource.resourceType],
|
|
27
|
+
},
|
|
28
|
+
content: _jsx("small", { children: resourceTypeLabel?.(resource.resourceType) ?? resource.resourceType }),
|
|
29
|
+
}] : []));
|
|
30
|
+
const timelineFacets = timeline ? [
|
|
31
|
+
{
|
|
32
|
+
id: 'section',
|
|
33
|
+
label: timeline.sectionFacetLabel,
|
|
34
|
+
options: uniqueOptions(sections.map((section) => ({ id: section.code ?? section.title, label: sectionTitle(section) }))),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'resourceType',
|
|
38
|
+
label: timeline.resourceTypeFacetLabel,
|
|
39
|
+
options: uniqueOptions(sections.flatMap((section) => section.resources.map((resource) => ({
|
|
40
|
+
id: resource.resourceType,
|
|
41
|
+
label: resourceTypeLabel?.(resource.resourceType) ?? resource.resourceType,
|
|
42
|
+
})))),
|
|
43
|
+
},
|
|
44
|
+
] : [];
|
|
45
|
+
return (_jsxs("div", { className: "uhcp-clinical-summary", "data-ips-summary": true, children: [timeline && timeZone && timelineEntries.length ? _jsx(ClinicalTimeline, { entries: timelineEntries, facets: timelineFacets, locale: locale ?? 'en', timeZone: timeZone, labels: timeline.labels }) : null, sections.map((section, sectionIndex) => (_jsx(ClinicalSectionSummary, { code: section.code ?? '', title: sectionTitle(section), resources: section.resources.map((resource, resourceIndex) => ({
|
|
46
|
+
key: resource.fullUrl ?? `${resource.resourceType}:${resourceIndex}:${resource.title}`,
|
|
47
|
+
title: resource.title,
|
|
48
|
+
resourceType: resource.resourceType,
|
|
49
|
+
date: resource.date,
|
|
50
|
+
details: observationComponentDetails(nativeResources.get(resource.fullUrl ?? '')),
|
|
51
|
+
})), emptyLabel: emptyLabel, resourceTypeLabel: resourceTypeLabel, actions: sectionActions?.(section), locale: locale, timeZone: timeZone }, section.code ?? `${section.title}:${sectionIndex}`)))] }));
|
|
52
|
+
}
|
|
53
|
+
function uniqueOptions(options) {
|
|
54
|
+
return [...new Map(options.map((option) => [option.id, option])).values()];
|
|
26
55
|
}
|
|
27
56
|
function observationComponentDetails(resource) {
|
|
28
57
|
const candidate = resource;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type VitalSignKind, type VitalSignMeasurement } from 'fhir-data-utils-ts';
|
|
2
2
|
import type { ClinicalResourceBundleLike } from 'gdc-common-utils-ts/utils/clinical-resource-view';
|
|
3
|
+
import { type ClinicalDateTimeFieldLabels } from './ClinicalTimeline.js';
|
|
3
4
|
export type VitalSignArtifactEditorCopy = Readonly<{
|
|
4
5
|
title: string;
|
|
5
6
|
description: string;
|
|
6
7
|
kind: string;
|
|
7
8
|
measuredAt: string;
|
|
9
|
+
dateTime: ClinicalDateTimeFieldLabels;
|
|
8
10
|
value: string;
|
|
9
11
|
systolic: string;
|
|
10
12
|
diastolic: string;
|
|
@@ -19,6 +21,8 @@ export type VitalSignArtifactEditorProps = Readonly<{
|
|
|
19
21
|
entryId: string;
|
|
20
22
|
subjectReference: string;
|
|
21
23
|
initialMeasurement?: VitalSignMeasurement;
|
|
24
|
+
locale: string;
|
|
25
|
+
timeZone: string;
|
|
22
26
|
copy: VitalSignArtifactEditorCopy;
|
|
23
27
|
kindLabels: VitalSignKindLabels;
|
|
24
28
|
saveMeasurement: (measurement: VitalSignMeasurement) => Promise<ClinicalResourceBundleLike | undefined>;
|
|
@@ -26,5 +30,5 @@ export type VitalSignArtifactEditorProps = Readonly<{
|
|
|
26
30
|
onReadback: (bundle: ClinicalResourceBundleLike) => void;
|
|
27
31
|
}>;
|
|
28
32
|
/** Shared vital-sign capture. Composite values remain one typed measurement. */
|
|
29
|
-
export declare function VitalSignArtifactEditor({ entryId, subjectReference, initialMeasurement, copy, kindLabels, saveMeasurement, onCancel, onReadback, }: VitalSignArtifactEditorProps): import("react").JSX.Element;
|
|
33
|
+
export declare function VitalSignArtifactEditor({ entryId, subjectReference, initialMeasurement, locale, timeZone, copy, kindLabels, saveMeasurement, onCancel, onReadback, }: VitalSignArtifactEditorProps): import("react").JSX.Element;
|
|
30
34
|
//# sourceMappingURL=VitalSignArtifactEditor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VitalSignArtifactEditor.d.ts","sourceRoot":"","sources":["../src/VitalSignArtifactEditor.tsx"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"VitalSignArtifactEditor.d.ts","sourceRoot":"","sources":["../src/VitalSignArtifactEditor.tsx"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAC1B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;AAClG,OAAO,EAA0B,KAAK,2BAA2B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,2BAA2B,CAAA;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,eAAe,EAAE,MAAM,CAAA;CACxB,CAAC,CAAA;AAEF,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;AAEzE,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,MAAM,CAAA;IACxB,kBAAkB,CAAC,EAAE,oBAAoB,CAAA;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,2BAA2B,CAAA;IACjC,UAAU,EAAE,mBAAmB,CAAA;IAC/B,eAAe,EAAE,CAAC,WAAW,EAAE,oBAAoB,KAAK,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAA;IACvG,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAA;CACzD,CAAC,CAAA;AAWF,gFAAgF;AAChF,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,gBAAgB,EAChB,kBAAkB,EAClB,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,eAAe,EACf,QAAQ,EACR,UAAU,GACX,EAAE,4BAA4B,+BAoD9B"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useId, useState } from 'react';
|
|
4
|
-
import { VitalSignUnit, } from 'fhir-data-utils-ts';
|
|
4
|
+
import { composeFhirDateTimeFromLocalParts, splitFhirDateTimeForInput, VitalSignUnit, } from 'fhir-data-utils-ts';
|
|
5
|
+
import { ClinicalDateTimeFields } from './ClinicalTimeline.js';
|
|
5
6
|
const VITAL_SIGN_KINDS = Object.freeze([
|
|
6
7
|
'blood-pressure',
|
|
7
8
|
'temperature',
|
|
@@ -11,11 +12,13 @@ const VITAL_SIGN_KINDS = Object.freeze([
|
|
|
11
12
|
'body-weight',
|
|
12
13
|
]);
|
|
13
14
|
/** Shared vital-sign capture. Composite values remain one typed measurement. */
|
|
14
|
-
export function VitalSignArtifactEditor({ entryId, subjectReference, initialMeasurement, copy, kindLabels, saveMeasurement, onCancel, onReadback, }) {
|
|
15
|
+
export function VitalSignArtifactEditor({ entryId, subjectReference, initialMeasurement, locale, timeZone, copy, kindLabels, saveMeasurement, onCancel, onReadback, }) {
|
|
15
16
|
const titleId = useId();
|
|
16
17
|
const descriptionId = useId();
|
|
17
18
|
const [kind, setKind] = useState(initialMeasurement?.kind ?? 'oxygen-saturation');
|
|
18
|
-
const [measuredAt, setMeasuredAt] = useState(initialMeasurement?.measuredAt
|
|
19
|
+
const [measuredAt, setMeasuredAt] = useState(() => initialMeasurement?.measuredAt
|
|
20
|
+
? splitFhirDateTimeForInput(initialMeasurement.measuredAt, timeZone)
|
|
21
|
+
: { date: '', time: '' });
|
|
19
22
|
const [value, setValue] = useState(initialMeasurement && initialMeasurement.kind !== 'blood-pressure' ? String(initialMeasurement.value) : '');
|
|
20
23
|
const [systolic, setSystolic] = useState(initialMeasurement?.kind === 'blood-pressure' ? String(initialMeasurement.systolic) : '');
|
|
21
24
|
const [diastolic, setDiastolic] = useState(initialMeasurement?.kind === 'blood-pressure' ? String(initialMeasurement.diastolic) : '');
|
|
@@ -25,7 +28,8 @@ export function VitalSignArtifactEditor({ entryId, subjectReference, initialMeas
|
|
|
25
28
|
setSaving(true);
|
|
26
29
|
setError('');
|
|
27
30
|
try {
|
|
28
|
-
const
|
|
31
|
+
const measuredAtInstant = composeFhirDateTimeFromLocalParts({ ...measuredAt, timeZone });
|
|
32
|
+
const common = { entryId, subjectReference, kind, measuredAt: measuredAtInstant, status: 'final' };
|
|
29
33
|
const measurement = kind === 'blood-pressure'
|
|
30
34
|
? { ...common, kind, systolic: Number(systolic), diastolic: Number(diastolic) }
|
|
31
35
|
: { ...common, kind, value: Number(value), unit: unitFor(kind) };
|
|
@@ -41,7 +45,7 @@ export function VitalSignArtifactEditor({ entryId, subjectReference, initialMeas
|
|
|
41
45
|
setSaving(false);
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
|
-
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: [_jsxs("label", { children: [_jsxs("span", { children: [copy.kind, " *"] }), _jsx("select", { "aria-label": copy.kind, value: kind, onChange: (event) => setKind(event.target.value), children: VITAL_SIGN_KINDS.map((option) => _jsx("option", { value: option, children: kindLabels[option] }, option)) })] }), _jsxs("
|
|
48
|
+
return _jsxs("section", { className: "uhcp-clinical-editor", lang: locale, 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: [_jsxs("label", { children: [_jsxs("span", { children: [copy.kind, " *"] }), _jsx("select", { "aria-label": copy.kind, value: kind, onChange: (event) => setKind(event.target.value), children: VITAL_SIGN_KINDS.map((option) => _jsx("option", { value: option, children: kindLabels[option] }, option)) })] }), _jsxs("fieldset", { className: "uhcp-clinical-editor-date-time", children: [_jsxs("legend", { children: [copy.measuredAt, " *"] }), _jsx(ClinicalDateTimeFields, { value: measuredAt, onChange: setMeasuredAt, labels: copy.dateTime, timeZone: timeZone })] }), kind === 'blood-pressure' ? _jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsxs("span", { children: [copy.systolic, " *"] }), _jsx("input", { "aria-label": copy.systolic, required: true, type: "number", value: systolic, onChange: (event) => setSystolic(event.target.value) })] }), _jsxs("label", { children: [_jsxs("span", { children: [copy.diastolic, " *"] }), _jsx("input", { "aria-label": copy.diastolic, required: true, type: "number", value: diastolic, onChange: (event) => setDiastolic(event.target.value) })] })] }) : _jsxs("label", { children: [_jsxs("span", { children: [copy.value, " *"] }), _jsx("input", { "aria-label": copy.value, required: true, type: "number", value: value, onChange: (event) => setValue(event.target.value) })] }), 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 })] })] })] });
|
|
45
49
|
}
|
|
46
50
|
function unitFor(kind) {
|
|
47
51
|
switch (kind) {
|
package/dist/index.d.ts
CHANGED
|
@@ -10,12 +10,14 @@ export * from './ClinicalSectionSummary.js';
|
|
|
10
10
|
export * from './IpsBundleSummary.js';
|
|
11
11
|
export * from './ClinicalDocumentEncounterExplorer.js';
|
|
12
12
|
export * from './AllClinicalSections.js';
|
|
13
|
+
export * from './ClinicalTimeline.js';
|
|
13
14
|
export * from './ClinicalArtifactEditor.js';
|
|
14
15
|
export * from './VitalSignArtifactEditor.js';
|
|
15
16
|
export * from './InvitationAcceptancePanel.js';
|
|
16
17
|
export * from './SubjectAccessRequestPanel.js';
|
|
17
18
|
export * from './SoschainWorkspace.js';
|
|
18
19
|
export * from './EncounterTransferConsole.js';
|
|
20
|
+
export * from './EmergencyContactDirectory.js';
|
|
19
21
|
export * from './OrganizationWorkforceAdministration.js';
|
|
20
22
|
export * from './ProtectedIndexEmergencyData.js';
|
|
21
23
|
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,wCAAwC,CAAA;AACtD,cAAc,0BAA0B,CAAA;AACxC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,8BAA8B,CAAA;AAC5C,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"}
|
|
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,wCAAwC,CAAA;AACtD,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,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
|
@@ -10,12 +10,14 @@ export * from './ClinicalSectionSummary.js';
|
|
|
10
10
|
export * from './IpsBundleSummary.js';
|
|
11
11
|
export * from './ClinicalDocumentEncounterExplorer.js';
|
|
12
12
|
export * from './AllClinicalSections.js';
|
|
13
|
+
export * from './ClinicalTimeline.js';
|
|
13
14
|
export * from './ClinicalArtifactEditor.js';
|
|
14
15
|
export * from './VitalSignArtifactEditor.js';
|
|
15
16
|
export * from './InvitationAcceptancePanel.js';
|
|
16
17
|
export * from './SubjectAccessRequestPanel.js';
|
|
17
18
|
export * from './SoschainWorkspace.js';
|
|
18
19
|
export * from './EncounterTransferConsole.js';
|
|
20
|
+
export * from './EmergencyContactDirectory.js';
|
|
19
21
|
export * from './OrganizationWorkforceAdministration.js';
|
|
20
22
|
export * from './ProtectedIndexEmergencyData.js';
|
|
21
23
|
export * from './PortalSettingsPanel.js';
|
package/dist/styles.css
CHANGED
|
@@ -171,18 +171,28 @@ 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:
|
|
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); display:flex; flex-direction:column; inset:50% auto auto 50%; max-height:calc(100dvh - 2rem); max-width:48rem; overflow:hidden; padding:1.5rem; position:fixed; transform:translate(-50%,-50%); width:calc(100% - 2rem); z-index:60; }
|
|
175
175
|
.uhcp-clinical-editor>header { align-items:flex-start; display:flex; gap:1rem; justify-content:space-between; }
|
|
176
176
|
.uhcp-clinical-editor h2,.uhcp-clinical-editor p { margin:.2rem 0; }
|
|
177
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; }
|
|
178
|
+
.uhcp-clinical-editor form { display:grid; gap:1rem; grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:1.25rem; overflow:auto; }
|
|
179
179
|
.uhcp-clinical-editor label { color:var(--uhcp-text); display:grid; font-weight:700; gap:.35rem; }
|
|
180
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
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; }
|
|
182
|
+
.uhcp-clinical-editor footer { background:var(--uhcp-panel); bottom:0; display:flex; gap:.75rem; justify-content:flex-end; padding:.75rem 0; position:sticky; z-index:2; }
|
|
183
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
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
185
|
.uhcp-clinical-editor-error { color:#b42318; font-weight:700; }
|
|
186
|
+
.uhcp-emergency-contact-directory>header { align-items:center; display:flex; gap:1rem; justify-content:space-between; }
|
|
187
|
+
.uhcp-emergency-contact-directory>ul { display:grid; gap:.75rem; list-style:none; margin:1rem 0; padding:0; }
|
|
188
|
+
.uhcp-emergency-contact-directory>ul>li { align-items:center; background:var(--uhcp-soft); border:1px solid var(--uhcp-border); border-radius:1rem; display:flex; gap:1rem; justify-content:space-between; padding:1rem; }
|
|
189
|
+
.uhcp-emergency-contact-directory>ul>li>div { display:grid; gap:.25rem; }
|
|
190
|
+
.uhcp-emergency-contact-directory nav { display:flex; flex-wrap:wrap; gap:.5rem; justify-content:flex-end; }
|
|
191
|
+
.uhcp-emergency-contact-directory a,.uhcp-emergency-contact-directory button { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:.7rem; color:inherit; cursor:pointer; font:inherit; padding:.55rem .8rem; text-decoration:none; }
|
|
192
|
+
.uhcp-emergency-contact-editor { display:grid; gap:.75rem; grid-template-columns:repeat(2,minmax(0,1fr)); }
|
|
193
|
+
.uhcp-emergency-contact-editor label { display:grid; font-weight:700; gap:.35rem; }
|
|
194
|
+
.uhcp-emergency-contact-editor input { border:1px solid var(--uhcp-border); border-radius:.7rem; font:inherit; padding:.7rem; }
|
|
195
|
+
.uhcp-emergency-contact-editor footer { display:flex; gap:.5rem; grid-column:1/-1; justify-content:flex-end; }
|
|
186
196
|
.uhcp-document-explorer { background:var(--uhcp-surface); color:var(--uhcp-text); display:grid; gap:1rem; padding:1rem; }
|
|
187
197
|
.uhcp-document-explorer button { color:inherit; cursor:pointer; font:inherit; }
|
|
188
198
|
.uhcp-document-explorer button:disabled { cursor:not-allowed; opacity:.45; }
|
|
@@ -218,7 +228,7 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
|
|
|
218
228
|
.uhcp-terminology-results ul { display:grid; gap:.35rem; list-style:none; margin:.25rem 0 0; padding:0; }
|
|
219
229
|
.uhcp-terminology-results button { background:var(--uhcp-surface-muted); color:inherit; display:grid; text-align:start; width:100%; }
|
|
220
230
|
.uhcp-terminology-results small { color:var(--uhcp-muted); display:block; font-weight:400; }
|
|
221
|
-
@media (max-width:44rem) { .uhcp-clinical-editor form { grid-template-columns:1fr; } }
|
|
231
|
+
@media (max-width:44rem) { .uhcp-clinical-editor form,.uhcp-emergency-contact-editor { grid-template-columns:1fr; } .uhcp-emergency-contact-directory>ul>li { align-items:stretch; flex-direction:column; } .uhcp-emergency-contact-directory nav { justify-content:flex-start; } }
|
|
222
232
|
|
|
223
233
|
.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); }
|
|
224
234
|
.uhcp-transfer-console button,.uhcp-transfer-console input,.uhcp-transfer-console textarea { font:inherit; }
|
|
@@ -441,6 +451,27 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
|
|
|
441
451
|
.uhcp-clinical-title { font-weight: 800; margin: 0; }
|
|
442
452
|
.uhcp-clinical-type { color: var(--uhcp-muted); font-size: .72rem; font-weight: 800; letter-spacing: .06em; margin: .35rem 0 0; text-transform: uppercase; }
|
|
443
453
|
.uhcp-clinical-date, .uhcp-clinical-empty { color: var(--uhcp-muted); margin: .35rem 0 0; }
|
|
454
|
+
.uhcp-clinical-timeline { display: grid; gap: 1rem; }
|
|
455
|
+
.uhcp-clinical-filters, .uhcp-clinical-range { display: grid; gap: .75rem; }
|
|
456
|
+
.uhcp-clinical-range { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
457
|
+
.uhcp-clinical-filters fieldset { border: 1px solid var(--uhcp-border); border-radius: 1rem; display: flex; flex-wrap: wrap; gap: .75rem; margin: 0; padding: .75rem; }
|
|
458
|
+
.uhcp-clinical-filters legend { font-weight: 800; padding: 0 .35rem; }
|
|
459
|
+
.uhcp-clinical-filters label { display: grid; gap: .3rem; }
|
|
460
|
+
.uhcp-clinical-filters label:has(input[type='checkbox']) { align-items: center; display: flex; }
|
|
461
|
+
.uhcp-clinical-filters input { background: var(--uhcp-panel); border: 1px solid var(--uhcp-border); border-radius: .65rem; color: var(--uhcp-text); font: inherit; padding: .55rem; }
|
|
462
|
+
.uhcp-clinical-timeline-card { background: var(--uhcp-panel-muted); border-radius: 1rem; display: grid; gap: .4rem; padding: 1rem; }
|
|
463
|
+
.uhcp-clinical-timeline-card h4 { margin: 0; }
|
|
464
|
+
.uhcp-clinical-timeline-card time, .uhcp-clinical-timeline-list time { color: var(--uhcp-muted); }
|
|
465
|
+
.uhcp-clinical-timeline-navigation { align-items: center; display: flex; flex-wrap: wrap; gap: .6rem; }
|
|
466
|
+
.uhcp-clinical-timeline button, .uhcp-clinical-date-time-fields button { background: var(--uhcp-panel); border: 1px solid var(--uhcp-border); border-radius: .7rem; color: var(--uhcp-text); cursor: pointer; font: inherit; min-height: 2.5rem; padding: .55rem .75rem; }
|
|
467
|
+
.uhcp-clinical-timeline button:disabled { cursor: not-allowed; opacity: .45; }
|
|
468
|
+
.uhcp-clinical-timeline-list { display: grid; gap: .5rem; margin: 0; padding-left: 1.5rem; }
|
|
469
|
+
.uhcp-clinical-timeline-list li { padding: .5rem; }
|
|
470
|
+
.uhcp-clinical-timeline-list time { display: block; margin-top: .2rem; }
|
|
471
|
+
.uhcp-clinical-date-time-fields { align-items: end; display: grid; gap: .5rem; grid-template-columns: minmax(9rem, 1fr) auto minmax(7rem, .7fr) auto; }
|
|
472
|
+
.uhcp-clinical-date-time-fields label { display: grid; font-weight: 800; gap: .35rem; }
|
|
473
|
+
.uhcp-clinical-date-time-fields input { border: 1px solid var(--uhcp-border); border-radius: .7rem; color: var(--uhcp-text); font: inherit; padding: .65rem; }
|
|
474
|
+
@media (max-width: 620px) { .uhcp-clinical-range, .uhcp-clinical-date-time-fields { grid-template-columns: 1fr auto; } }
|
|
444
475
|
.uhcp-clinical-details { display: grid; gap: .3rem; margin: .6rem 0 0; }
|
|
445
476
|
.uhcp-clinical-details div { align-items: baseline; display: flex; gap: .75rem; justify-content: space-between; }
|
|
446
477
|
.uhcp-clinical-details dt { color: var(--uhcp-muted); font-size: .78rem; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uhc-portal-components-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
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",
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
"react-dom": ">=18.3.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"fhir-data-utils-ts": "0.6.
|
|
35
|
-
"gdc-common-utils-ts": "
|
|
34
|
+
"fhir-data-utils-ts": "0.6.11",
|
|
35
|
+
"gdc-common-utils-ts": "2.9.27",
|
|
36
|
+
"sos-data-utils-ts": "0.6.3"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@testing-library/react": "^16.3.2",
|