uhc-portal-components-react 0.1.25 → 0.1.27
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 +18 -0
- package/README.md +10 -0
- package/dist/AllClinicalSections.d.ts +41 -0
- package/dist/AllClinicalSections.d.ts.map +1 -0
- package/dist/AllClinicalSections.js +35 -0
- package/dist/ClinicalDocumentEncounterExplorer.d.ts +62 -0
- package/dist/ClinicalDocumentEncounterExplorer.d.ts.map +1 -0
- package/dist/ClinicalDocumentEncounterExplorer.js +70 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/styles.css +32 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.27
|
|
4
|
+
|
|
5
|
+
- Add generic `AllClinicalSections<Person|Animal>` organization with stack or
|
|
6
|
+
tab presentation, nested collapsible groups and host-controlled exclusions.
|
|
7
|
+
- Keep family health history, care-team members, emergency contacts,
|
|
8
|
+
Encounters and IPS summary as independent navigation concepts while the host
|
|
9
|
+
supplies authorized clinical content.
|
|
10
|
+
|
|
11
|
+
## 0.1.26
|
|
12
|
+
|
|
13
|
+
- Add a brand-neutral clinical document and Encounter explorer for an
|
|
14
|
+
authorized care journey.
|
|
15
|
+
- Navigate chronologically between supplied Encounters while preserving
|
|
16
|
+
`Encounter.partOf` as hierarchy and rendering only document Bundles whose
|
|
17
|
+
leading Composition references the selected Encounter.
|
|
18
|
+
- Reuse the section viewer for IPS and non-IPS documents; the Composition title
|
|
19
|
+
and type provide the document label.
|
|
20
|
+
|
|
3
21
|
## 0.1.25
|
|
4
22
|
|
|
5
23
|
- Render every labelled quantity component of a composite FHIR `Observation` in the shared IPS card, including systolic and diastolic blood-pressure values and units.
|
package/README.md
CHANGED
|
@@ -53,6 +53,16 @@ The package deliberately exposes modules instead of a cloned application:
|
|
|
53
53
|
- `IpsBundleSummary`: receives one in-memory IPS document Bundle, obtains all
|
|
54
54
|
`Composition.section` entries through Common Utils and renders every
|
|
55
55
|
section's cards. It performs no ingestion or GW call.
|
|
56
|
+
- `ClinicalDocumentEncounterExplorer`: receives host-authorized Encounter
|
|
57
|
+
resources and Composition-first document Bundles, provides backward/forward
|
|
58
|
+
journey navigation and renders either IPS or non-IPS sections with the same
|
|
59
|
+
viewer. It never treats an Encounter as the clinical subject or performs
|
|
60
|
+
authorization, fetching or persistence in the browser.
|
|
61
|
+
- `AllClinicalSections<Person|Animal>`: lets each portal compose the same
|
|
62
|
+
authorized section views as a stack or tabs, nest collapsible subsections and
|
|
63
|
+
exclude inapplicable areas. Family history, care-team members and emergency
|
|
64
|
+
contacts remain independent; the organizer never follows another subject's
|
|
65
|
+
card or documents automatically.
|
|
56
66
|
- `ClinicalArtifactEditor`: renders the same schema-driven clinical form for
|
|
57
67
|
human and animal subjects. The host supplies its UHC/Vet schema,
|
|
58
68
|
terminology-search adapter and authorized persistence callback. Selecting a
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare const ClinicalSectionNavigationIds: Readonly<{
|
|
3
|
+
readonly IpsSummary: "ips-summary";
|
|
4
|
+
readonly Encounters: "encounters";
|
|
5
|
+
readonly HealthStatusAssessments: "health-status-assessments";
|
|
6
|
+
readonly HealthInsuranceInformation: "health-insurance-information";
|
|
7
|
+
readonly GoalsAndPreferences: "goals-and-preferences";
|
|
8
|
+
readonly FamilyHealthHistory: "family-health-history";
|
|
9
|
+
readonly ClinicalTests: "clinical-tests";
|
|
10
|
+
readonly ClinicalNotes: "clinical-notes";
|
|
11
|
+
readonly CareTeamMembers: "care-team-members";
|
|
12
|
+
readonly EmergencyContacts: "emergency-contacts";
|
|
13
|
+
}>;
|
|
14
|
+
export type ClinicalSectionNavigationId = typeof ClinicalSectionNavigationIds[keyof typeof ClinicalSectionNavigationIds];
|
|
15
|
+
export type ClinicalSubjectKind = 'person' | 'animal';
|
|
16
|
+
export type AllClinicalSectionDefinition<SubjectKind extends ClinicalSubjectKind> = Readonly<{
|
|
17
|
+
id: ClinicalSectionNavigationId | (string & {});
|
|
18
|
+
title: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
content?: ReactNode;
|
|
21
|
+
collapsible?: boolean;
|
|
22
|
+
defaultOpen?: boolean;
|
|
23
|
+
children?: readonly AllClinicalSectionDefinition<SubjectKind>[];
|
|
24
|
+
}>;
|
|
25
|
+
export type AllClinicalSectionsProps<SubjectKind extends ClinicalSubjectKind> = Readonly<{
|
|
26
|
+
subjectKind: SubjectKind;
|
|
27
|
+
title: string;
|
|
28
|
+
sections: readonly AllClinicalSectionDefinition<SubjectKind>[];
|
|
29
|
+
excludedSectionIds?: readonly string[];
|
|
30
|
+
presentation?: 'sections' | 'tabs';
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Organizes host-provided clinical views without redefining their FHIR model.
|
|
34
|
+
*
|
|
35
|
+
* The host decides which sections apply to a person or animal, supplies only
|
|
36
|
+
* authorized content and may exclude sections. Family history, emergency
|
|
37
|
+
* contacts and care-team members remain independent domains; this component
|
|
38
|
+
* never follows another subject's card or document links automatically.
|
|
39
|
+
*/
|
|
40
|
+
export declare function AllClinicalSections<SubjectKind extends ClinicalSubjectKind>({ subjectKind, title, sections, excludedSectionIds, presentation, }: AllClinicalSectionsProps<SubjectKind>): import("react").JSX.Element;
|
|
41
|
+
//# sourceMappingURL=AllClinicalSections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AllClinicalSections.d.ts","sourceRoot":"","sources":["../src/AllClinicalSections.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;EAW9B,CAAA;AAEX,MAAM,MAAM,2BAA2B,GAAG,OAAO,4BAA4B,CAAC,MAAM,OAAO,4BAA4B,CAAC,CAAA;AACxH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAErD,MAAM,MAAM,4BAA4B,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IAC3F,EAAE,EAAE,2BAA2B,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;CAChE,CAAC,CAAA;AAEF,MAAM,MAAM,wBAAwB,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IACvF,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;IAC9D,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,YAAY,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACnC,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,mBAAmB,EAAE,EAC3E,WAAW,EACX,KAAK,EACL,QAAQ,EACR,kBAAuB,EACvB,YAAyB,GAC1B,EAAE,wBAAwB,CAAC,WAAW,CAAC,+BAqCvC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
export const ClinicalSectionNavigationIds = Object.freeze({
|
|
4
|
+
IpsSummary: 'ips-summary',
|
|
5
|
+
Encounters: 'encounters',
|
|
6
|
+
HealthStatusAssessments: 'health-status-assessments',
|
|
7
|
+
HealthInsuranceInformation: 'health-insurance-information',
|
|
8
|
+
GoalsAndPreferences: 'goals-and-preferences',
|
|
9
|
+
FamilyHealthHistory: 'family-health-history',
|
|
10
|
+
ClinicalTests: 'clinical-tests',
|
|
11
|
+
ClinicalNotes: 'clinical-notes',
|
|
12
|
+
CareTeamMembers: 'care-team-members',
|
|
13
|
+
EmergencyContacts: 'emergency-contacts',
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Organizes host-provided clinical views without redefining their FHIR model.
|
|
17
|
+
*
|
|
18
|
+
* The host decides which sections apply to a person or animal, supplies only
|
|
19
|
+
* authorized content and may exclude sections. Family history, emergency
|
|
20
|
+
* contacts and care-team members remain independent domains; this component
|
|
21
|
+
* never follows another subject's card or document links automatically.
|
|
22
|
+
*/
|
|
23
|
+
export function AllClinicalSections({ subjectKind, title, sections, excludedSectionIds = [], presentation = 'sections', }) {
|
|
24
|
+
const visibleSections = sections.filter(({ id }) => !excludedSectionIds.includes(id));
|
|
25
|
+
const [selectedId, setSelectedId] = useState(visibleSections[0]?.id ?? '');
|
|
26
|
+
const selectedSection = visibleSections.find(({ id }) => id === selectedId) ?? visibleSections[0];
|
|
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
|
+
}
|
|
29
|
+
function ClinicalSection({ definition, excludedSectionIds, }) {
|
|
30
|
+
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
|
+
if (definition.collapsible !== false) {
|
|
32
|
+
return (_jsxs("details", { className: "uhcp-all-clinical-section", open: definition.defaultOpen, children: [_jsx("summary", { children: definition.title }), body] }));
|
|
33
|
+
}
|
|
34
|
+
return (_jsxs("section", { className: "uhcp-all-clinical-section", "aria-labelledby": `uhcp-all-section-title-${definition.id}`, children: [_jsx("h3", { id: `uhcp-all-section-title-${definition.id}`, children: definition.title }), body] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ClinicalResourceBundleLike } from 'gdc-common-utils-ts/utils/clinical-resource-view';
|
|
2
|
+
export type EncounterJourneyResource = Readonly<{
|
|
3
|
+
resourceType: 'Encounter';
|
|
4
|
+
id?: string;
|
|
5
|
+
status?: string;
|
|
6
|
+
class?: Readonly<{
|
|
7
|
+
display?: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
}>;
|
|
10
|
+
serviceType?: readonly Readonly<{
|
|
11
|
+
concept?: Readonly<{
|
|
12
|
+
text?: string;
|
|
13
|
+
}>;
|
|
14
|
+
}>[];
|
|
15
|
+
location?: readonly Readonly<{
|
|
16
|
+
location?: Readonly<{
|
|
17
|
+
display?: string;
|
|
18
|
+
reference?: string;
|
|
19
|
+
}>;
|
|
20
|
+
}>[];
|
|
21
|
+
actualPeriod?: Readonly<{
|
|
22
|
+
start?: string;
|
|
23
|
+
end?: string;
|
|
24
|
+
}>;
|
|
25
|
+
period?: Readonly<{
|
|
26
|
+
start?: string;
|
|
27
|
+
end?: string;
|
|
28
|
+
}>;
|
|
29
|
+
partOf?: Readonly<{
|
|
30
|
+
reference?: string;
|
|
31
|
+
}>;
|
|
32
|
+
}>;
|
|
33
|
+
export type ClinicalDocumentBundle = ClinicalResourceBundleLike & Readonly<{
|
|
34
|
+
id?: string;
|
|
35
|
+
}>;
|
|
36
|
+
export type ClinicalDocumentEncounterExplorerLabels = Readonly<{
|
|
37
|
+
journeyTitle: string;
|
|
38
|
+
documentsTitle: string;
|
|
39
|
+
previousEncounter: string;
|
|
40
|
+
nextEncounter: string;
|
|
41
|
+
noDocuments: string;
|
|
42
|
+
emptySection: string;
|
|
43
|
+
noSections: string;
|
|
44
|
+
documentTypeLabel: string;
|
|
45
|
+
currentEncounter: string;
|
|
46
|
+
}>;
|
|
47
|
+
export type ClinicalDocumentEncounterExplorerProps = Readonly<{
|
|
48
|
+
encounters: readonly EncounterJourneyResource[];
|
|
49
|
+
documents: readonly ClinicalDocumentBundle[];
|
|
50
|
+
labels: ClinicalDocumentEncounterExplorerLabels;
|
|
51
|
+
locale?: string;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Explores one host-authorized Encounter journey and its FHIR document Bundles.
|
|
55
|
+
*
|
|
56
|
+
* A document Bundle is Composition-first. The Encounter is care context, not
|
|
57
|
+
* the clinical subject, and `Encounter.partOf` remains a hierarchy rather than
|
|
58
|
+
* a substitute for chronological navigation. Fetching, authorization and
|
|
59
|
+
* persistence remain responsibilities of the hosting portal.
|
|
60
|
+
*/
|
|
61
|
+
export declare function ClinicalDocumentEncounterExplorer({ encounters, documents, labels, locale, }: ClinicalDocumentEncounterExplorerProps): import("react").JSX.Element;
|
|
62
|
+
//# sourceMappingURL=ClinicalDocumentEncounterExplorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClinicalDocumentEncounterExplorer.d.ts","sourceRoot":"","sources":["../src/ClinicalDocumentEncounterExplorer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;AAGlG,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,YAAY,EAAE,WAAW,CAAA;IACzB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,QAAQ,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACrD,WAAW,CAAC,EAAE,SAAS,QAAQ,CAAC;QAAE,OAAO,CAAC,EAAE,QAAQ,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,EAAE,CAAA;IAC5E,QAAQ,CAAC,EAAE,SAAS,QAAQ,CAAC;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,EAAE,CAAA;IACjG,YAAY,CAAC,EAAE,QAAQ,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzD,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnD,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC1C,CAAC,CAAA;AAEF,MAAM,MAAM,sBAAsB,GAAG,0BAA0B,GAAG,QAAQ,CAAC;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE3F,MAAM,MAAM,uCAAuC,GAAG,QAAQ,CAAC;IAC7D,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAAC,CAAA;AAEF,MAAM,MAAM,sCAAsC,GAAG,QAAQ,CAAC;IAC5D,UAAU,EAAE,SAAS,wBAAwB,EAAE,CAAA;IAC/C,SAAS,EAAE,SAAS,sBAAsB,EAAE,CAAA;IAC5C,MAAM,EAAE,uCAAuC,CAAA;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAC,CAAA;AASF;;;;;;;GAOG;AACH,wBAAgB,iCAAiC,CAAC,EAChD,UAAU,EACV,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,sCAAsC,+BAgHxC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { IpsBundleSummary } from './IpsBundleSummary.js';
|
|
4
|
+
/**
|
|
5
|
+
* Explores one host-authorized Encounter journey and its FHIR document Bundles.
|
|
6
|
+
*
|
|
7
|
+
* A document Bundle is Composition-first. The Encounter is care context, not
|
|
8
|
+
* the clinical subject, and `Encounter.partOf` remains a hierarchy rather than
|
|
9
|
+
* a substitute for chronological navigation. Fetching, authorization and
|
|
10
|
+
* persistence remain responsibilities of the hosting portal.
|
|
11
|
+
*/
|
|
12
|
+
export function ClinicalDocumentEncounterExplorer({ encounters, documents, labels, locale, }) {
|
|
13
|
+
const orderedEncounters = useMemo(() => [...encounters].sort((left, right) => encounterStart(left).localeCompare(encounterStart(right))), [encounters]);
|
|
14
|
+
const documentHeaders = useMemo(() => documents.map((document) => ({ document, header: compositionHeader(document) })), [documents]);
|
|
15
|
+
const firstDocumentEncounterIndex = orderedEncounters.findIndex((encounter) => documentHeaders.some(({ header }) => referencesEncounter(header.encounterReference, encounter)));
|
|
16
|
+
const [selectedEncounterId, setSelectedEncounterId] = useState(orderedEncounters[Math.max(firstDocumentEncounterIndex, 0)]?.id ?? '');
|
|
17
|
+
const selectedIndex = Math.max(orderedEncounters.findIndex((encounter) => encounter.id === selectedEncounterId), 0);
|
|
18
|
+
const selectedEncounter = orderedEncounters[selectedIndex];
|
|
19
|
+
const encounterDocuments = selectedEncounter
|
|
20
|
+
? documentHeaders.filter(({ header }) => referencesEncounter(header.encounterReference, selectedEncounter))
|
|
21
|
+
: [];
|
|
22
|
+
const [selectedDocumentId, setSelectedDocumentId] = useState('');
|
|
23
|
+
const selectedDocument = encounterDocuments.find(({ document }) => document.id === selectedDocumentId)
|
|
24
|
+
?? encounterDocuments[0];
|
|
25
|
+
function selectEncounter(encounter) {
|
|
26
|
+
setSelectedEncounterId(encounter.id ?? '');
|
|
27
|
+
setSelectedDocumentId('');
|
|
28
|
+
}
|
|
29
|
+
return (_jsxs("section", { className: "uhcp-document-explorer", "data-clinical-document-explorer": true, children: [_jsxs("header", { className: "uhcp-document-explorer-heading", children: [_jsxs("div", { children: [_jsx("p", { children: labels.currentEncounter }), _jsx("h2", { children: selectedEncounter ? encounterName(selectedEncounter) : labels.journeyTitle })] }), _jsxs("nav", { "aria-label": labels.journeyTitle, children: [_jsx("button", { type: "button", "aria-label": labels.previousEncounter, disabled: selectedIndex <= 0, onClick: () => selectEncounter(orderedEncounters[selectedIndex - 1]), children: "\u2190" }), _jsx("button", { type: "button", "aria-label": labels.nextEncounter, disabled: selectedIndex >= orderedEncounters.length - 1, onClick: () => selectEncounter(orderedEncounters[selectedIndex + 1]), children: "\u2192" })] })] }), _jsx("div", { className: "uhcp-encounter-rail", role: "list", "aria-label": labels.journeyTitle, children: orderedEncounters.map((encounter) => (_jsx("div", { role: "listitem", children: _jsxs("button", { type: "button", "aria-label": encounterName(encounter), "aria-current": encounter.id === selectedEncounter?.id ? 'step' : undefined, onClick: () => selectEncounter(encounter), children: [_jsx("strong", { children: encounterName(encounter) }), _jsx("span", { children: encounter.location?.[0]?.location?.display ?? encounter.class?.display }), _jsx("time", { dateTime: encounterStart(encounter), children: formatPeriod(encounter, locale) }), _jsx("small", { "data-status": encounter.status, children: encounter.status })] }) }, encounter.id))) }), _jsxs("section", { className: "uhcp-document-panel", children: [_jsx("h2", { children: labels.documentsTitle }), encounterDocuments.length === 0 ? _jsx("p", { children: labels.noDocuments }) : (_jsxs(_Fragment, { children: [encounterDocuments.length > 1 ? (_jsx("div", { className: "uhcp-document-tabs", role: "list", children: encounterDocuments.map(({ document, header }) => (_jsx("span", { role: "listitem", children: _jsx("button", { type: "button", "aria-pressed": document === selectedDocument?.document, onClick: () => setSelectedDocumentId(document.id ?? ''), children: header.title }) }, document.id ?? header.title))) })) : null, selectedDocument ? (_jsxs("article", { "data-clinical-document": true, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsxs("p", { children: [labels.documentTypeLabel, ": ", selectedDocument.header.type] }), _jsx("h3", { children: selectedDocument.header.title })] }), selectedDocument.header.date ? _jsx("time", { dateTime: selectedDocument.header.date, children: formatInstant(selectedDocument.header.date, locale) }) : null] }), _jsx(IpsBundleSummary, { bundle: selectedDocument.document, locale: locale, emptyLabel: labels.emptySection, noSectionsLabel: labels.noSections })] })) : null] }))] })] }));
|
|
30
|
+
}
|
|
31
|
+
function encounterName(encounter) {
|
|
32
|
+
return encounter.serviceType?.find(({ concept }) => concept?.text)?.concept?.text
|
|
33
|
+
?? encounter.class?.display
|
|
34
|
+
?? encounter.id
|
|
35
|
+
?? 'Encounter';
|
|
36
|
+
}
|
|
37
|
+
function encounterStart(encounter) {
|
|
38
|
+
return encounter.actualPeriod?.start ?? encounter.period?.start ?? '';
|
|
39
|
+
}
|
|
40
|
+
function formatPeriod(encounter, locale) {
|
|
41
|
+
const period = encounter.actualPeriod ?? encounter.period;
|
|
42
|
+
if (!period?.start)
|
|
43
|
+
return '';
|
|
44
|
+
const start = formatInstant(period.start, locale);
|
|
45
|
+
return period.end ? `${start} – ${formatInstant(period.end, locale)}` : start;
|
|
46
|
+
}
|
|
47
|
+
function formatInstant(value, locale) {
|
|
48
|
+
const date = new Date(value);
|
|
49
|
+
return Number.isNaN(date.valueOf()) ? value : new Intl.DateTimeFormat(locale, {
|
|
50
|
+
dateStyle: 'medium',
|
|
51
|
+
timeStyle: 'short',
|
|
52
|
+
timeZone: 'UTC',
|
|
53
|
+
}).format(date);
|
|
54
|
+
}
|
|
55
|
+
function compositionHeader(document) {
|
|
56
|
+
const composition = (document.entry ?? document.data ?? [])
|
|
57
|
+
.map((entry) => entry.resource)
|
|
58
|
+
.find((resource) => resource?.resourceType === 'Composition');
|
|
59
|
+
return {
|
|
60
|
+
title: composition?.title ?? document.id ?? 'Clinical document',
|
|
61
|
+
type: composition?.type?.text
|
|
62
|
+
?? composition?.type?.coding?.find(({ display }) => display)?.display
|
|
63
|
+
?? 'Clinical document',
|
|
64
|
+
encounterReference: composition?.encounter?.reference,
|
|
65
|
+
date: composition?.date,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function referencesEncounter(reference, encounter) {
|
|
69
|
+
return Boolean(encounter.id && (reference === `Encounter/${encounter.id}` || reference === encounter.id));
|
|
70
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export * from './ProfileProtectionGate.js';
|
|
|
8
8
|
export * from './PortalLoadingSkeleton.js';
|
|
9
9
|
export * from './ClinicalSectionSummary.js';
|
|
10
10
|
export * from './IpsBundleSummary.js';
|
|
11
|
+
export * from './ClinicalDocumentEncounterExplorer.js';
|
|
12
|
+
export * from './AllClinicalSections.js';
|
|
11
13
|
export * from './ClinicalArtifactEditor.js';
|
|
12
14
|
export * from './VitalSignArtifactEditor.js';
|
|
13
15
|
export * from './InvitationAcceptancePanel.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,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,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"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export * from './ProfileProtectionGate.js';
|
|
|
8
8
|
export * from './PortalLoadingSkeleton.js';
|
|
9
9
|
export * from './ClinicalSectionSummary.js';
|
|
10
10
|
export * from './IpsBundleSummary.js';
|
|
11
|
+
export * from './ClinicalDocumentEncounterExplorer.js';
|
|
12
|
+
export * from './AllClinicalSections.js';
|
|
11
13
|
export * from './ClinicalArtifactEditor.js';
|
|
12
14
|
export * from './VitalSignArtifactEditor.js';
|
|
13
15
|
export * from './InvitationAcceptancePanel.js';
|
package/dist/styles.css
CHANGED
|
@@ -183,6 +183,38 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
|
|
|
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-document-explorer { background:var(--uhcp-surface); color:var(--uhcp-text); display:grid; gap:1rem; padding:1rem; }
|
|
187
|
+
.uhcp-document-explorer button { color:inherit; cursor:pointer; font:inherit; }
|
|
188
|
+
.uhcp-document-explorer button:disabled { cursor:not-allowed; opacity:.45; }
|
|
189
|
+
.uhcp-document-explorer-heading { align-items:center; display:flex; gap:1rem; justify-content:space-between; }
|
|
190
|
+
.uhcp-document-explorer-heading p,.uhcp-document-panel>article>header p { color:var(--uhcp-muted); font-size:.8rem; font-weight:750; margin:0 0 .25rem; text-transform:uppercase; }
|
|
191
|
+
.uhcp-document-explorer-heading h2,.uhcp-document-panel h2,.uhcp-document-panel h3 { margin:0; }
|
|
192
|
+
.uhcp-document-explorer-heading nav { display:flex; gap:.45rem; }
|
|
193
|
+
.uhcp-document-explorer-heading button { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:50%; height:2.5rem; width:2.5rem; }
|
|
194
|
+
.uhcp-encounter-rail { display:flex; gap:.75rem; overflow-x:auto; padding:.15rem .05rem .65rem; scroll-snap-type:x proximity; }
|
|
195
|
+
.uhcp-encounter-rail>div { flex:0 0 min(17rem,76vw); scroll-snap-align:start; }
|
|
196
|
+
.uhcp-encounter-rail>div>button { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:1rem; display:grid; gap:.45rem; min-height:9.5rem; padding:1rem; text-align:start; width:100%; }
|
|
197
|
+
.uhcp-encounter-rail>div>button[aria-current=step] { border-color:var(--uhcp-turquoise); box-shadow:0 0 0 2px color-mix(in srgb,var(--uhcp-turquoise) 24%,transparent); }
|
|
198
|
+
.uhcp-encounter-rail span,.uhcp-encounter-rail time { color:var(--uhcp-muted); font-size:.78rem; }
|
|
199
|
+
.uhcp-encounter-rail small { background:var(--uhcp-surface-muted); border-radius:999px; justify-self:start; padding:.25rem .55rem; }
|
|
200
|
+
.uhcp-document-panel { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:1.5rem; padding:1.25rem; }
|
|
201
|
+
.uhcp-document-panel>p { color:var(--uhcp-muted); }
|
|
202
|
+
.uhcp-document-tabs { display:flex; gap:.5rem; overflow:auto; padding:.75rem 0; }
|
|
203
|
+
.uhcp-document-tabs button { background:var(--uhcp-surface-muted); border:1px solid var(--uhcp-border); border-radius:999px; padding:.55rem .8rem; white-space:nowrap; }
|
|
204
|
+
.uhcp-document-tabs button[aria-pressed=true] { background:var(--uhcp-turquoise); color:#fff; }
|
|
205
|
+
.uhcp-document-panel>article>header { align-items:start; border-bottom:1px solid var(--uhcp-border); display:flex; gap:1rem; justify-content:space-between; margin:1rem 0; padding-bottom:1rem; }
|
|
206
|
+
.uhcp-document-panel>article>header time { color:var(--uhcp-muted); font-size:.8rem; white-space:nowrap; }
|
|
207
|
+
.uhcp-all-clinical-sections { color:var(--uhcp-text); display:grid; gap:1rem; }
|
|
208
|
+
.uhcp-all-clinical-sections>h2 { margin:0; }
|
|
209
|
+
.uhcp-all-clinical-stack { display:grid; gap:.75rem; }
|
|
210
|
+
.uhcp-all-clinical-section { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:1rem; padding:1rem; }
|
|
211
|
+
.uhcp-all-clinical-section>summary { cursor:pointer; font-size:1.05rem; font-weight:800; }
|
|
212
|
+
.uhcp-all-clinical-section>h3 { margin:0; }
|
|
213
|
+
.uhcp-all-clinical-content { display:grid; gap:.75rem; padding-top:.85rem; }
|
|
214
|
+
.uhcp-all-clinical-content>p { color:var(--uhcp-muted); margin:0; }
|
|
215
|
+
.uhcp-all-clinical-tabs { display:flex; gap:.5rem; overflow:auto; }
|
|
216
|
+
.uhcp-all-clinical-tabs button { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:999px; color:inherit; cursor:pointer; font:inherit; padding:.6rem .85rem; white-space:nowrap; }
|
|
217
|
+
.uhcp-all-clinical-tabs button[aria-selected=true] { background:var(--uhcp-turquoise); color:#fff; }
|
|
186
218
|
.uhcp-terminology-results ul { display:grid; gap:.35rem; list-style:none; margin:.25rem 0 0; padding:0; }
|
|
187
219
|
.uhcp-terminology-results button { background:var(--uhcp-surface-muted); color:inherit; display:grid; text-align:start; width:100%; }
|
|
188
220
|
.uhcp-terminology-results small { color:var(--uhcp-muted); display:block; font-weight:400; }
|
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.27",
|
|
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,7 +31,7 @@
|
|
|
31
31
|
"react-dom": ">=18.3.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"fhir-data-utils-ts": "0.6.
|
|
34
|
+
"fhir-data-utils-ts": "0.6.8",
|
|
35
35
|
"gdc-common-utils-ts": "^2.3.12"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|