uhc-portal-components-react 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.28
4
+
5
+ - Add a reusable emergency-contact directory with provider-owned create,
6
+ update and delete callbacks plus accessible telephone and email actions.
7
+ - Keep Goals and Preferences and Clinical Tests inside the unified IPS
8
+ presentation instead of duplicating them as top-level sections.
9
+ - Add a locale-aware clinical timeline with inclusive from/to date and time
10
+ filters, OR-within/AND-across multi-select facets, latest-first cards,
11
+ accessible previous/next navigation and an expanded-list alternative.
12
+ - Replace free-text vital-sign timestamps with separate calendar date and time
13
+ controls, optional seconds defaulted to `00`, explicit current-date/current-
14
+ time actions and canonical FHIR instant composition in the selected IANA
15
+ time zone.
16
+ - Keep long clinical editors scrollable on mobile while their save/cancel
17
+ actions remain visible and clickable.
18
+
19
+ ## 0.1.27
20
+
21
+ - Add generic `AllClinicalSections<Person|Animal>` organization with stack or
22
+ tab presentation, nested collapsible groups and host-controlled exclusions.
23
+ - Keep family health history, care-team members, emergency contacts,
24
+ Encounters and IPS summary as independent navigation concepts while the host
25
+ supplies authorized clinical content.
26
+
3
27
  ## 0.1.26
4
28
 
5
29
  - Add a brand-neutral clinical document and Encounter explorer for an
package/README.md CHANGED
@@ -58,6 +58,18 @@ The package deliberately exposes modules instead of a cloned application:
58
58
  journey navigation and renders either IPS or non-IPS sections with the same
59
59
  viewer. It never treats an Encounter as the clinical subject or performs
60
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.
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.
61
73
  - `ClinicalArtifactEditor`: renders the same schema-driven clinical form for
62
74
  human and animal subjects. The host supplies its UHC/Vet schema,
63
75
  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,49 @@
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 = organizeClinicalSections(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 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
+ }
43
+ function ClinicalSection({ definition, excludedSectionIds, }) {
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)))] }));
45
+ if (definition.collapsible !== false) {
46
+ return (_jsxs("details", { className: "uhcp-all-clinical-section", open: definition.defaultOpen, children: [_jsx("summary", { children: definition.title }), body] }));
47
+ }
48
+ 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] }));
49
+ }
@@ -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;AAEtC,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,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,UAAiB,GAClB,EAAE,2BAA2B,+BAiC7B"}
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
+ }
@@ -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;AAGzD,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,MAAM,EAAE,0BAA0B,CAAA;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,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;CAC7D,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,aAAa,EACb,UAAU,EACV,eAA4B,EAC5B,YAAyC,EACzC,iBAAiB,EACjB,cAAc,GACf,EAAE,qBAAqB,+BA+BvB"}
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"}
@@ -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
- return (_jsx("div", { className: "uhcp-clinical-summary", "data-ips-summary": true, children: sections.map((section, sectionIndex) => (_jsx(ClinicalSectionSummary, { code: section.code ?? '', title: sectionTitle(section), resources: section.resources.map((resource, resourceIndex) => ({
20
- key: resource.fullUrl ?? `${resource.resourceType}:${resourceIndex}:${resource.title}`,
21
- title: resource.title,
22
- resourceType: resource.resourceType,
23
- date: resource.date,
24
- details: observationComponentDetails(nativeResources.get(resource.fullUrl ?? '')),
25
- })), emptyLabel: emptyLabel, resourceTypeLabel: resourceTypeLabel, actions: sectionActions?.(section) }, section.code ?? `${section.title}:${sectionIndex}`))) }));
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,EAEL,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAC1B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;AAElG,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,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,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,IAAI,EACJ,UAAU,EACV,eAAe,EACf,QAAQ,EACR,UAAU,GACX,EAAE,4BAA4B,+BA+C9B"}
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 common = { entryId, subjectReference, kind, measuredAt, status: 'final' };
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("label", { children: [_jsxs("span", { children: [copy.measuredAt, " *"] }), _jsx("input", { "aria-label": copy.measuredAt, required: true, value: measuredAt, onChange: (event) => setMeasuredAt(event.target.value) })] }), 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 })] })] })] });
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
@@ -9,12 +9,15 @@ export * from './PortalLoadingSkeleton.js';
9
9
  export * from './ClinicalSectionSummary.js';
10
10
  export * from './IpsBundleSummary.js';
11
11
  export * from './ClinicalDocumentEncounterExplorer.js';
12
+ export * from './AllClinicalSections.js';
13
+ export * from './ClinicalTimeline.js';
12
14
  export * from './ClinicalArtifactEditor.js';
13
15
  export * from './VitalSignArtifactEditor.js';
14
16
  export * from './InvitationAcceptancePanel.js';
15
17
  export * from './SubjectAccessRequestPanel.js';
16
18
  export * from './SoschainWorkspace.js';
17
19
  export * from './EncounterTransferConsole.js';
20
+ export * from './EmergencyContactDirectory.js';
18
21
  export * from './OrganizationWorkforceAdministration.js';
19
22
  export * from './ProtectedIndexEmergencyData.js';
20
23
  export * from './PortalSettingsPanel.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wCAAwC,CAAA;AACtD,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
@@ -9,12 +9,15 @@ export * from './PortalLoadingSkeleton.js';
9
9
  export * from './ClinicalSectionSummary.js';
10
10
  export * from './IpsBundleSummary.js';
11
11
  export * from './ClinicalDocumentEncounterExplorer.js';
12
+ export * from './AllClinicalSections.js';
13
+ export * from './ClinicalTimeline.js';
12
14
  export * from './ClinicalArtifactEditor.js';
13
15
  export * from './VitalSignArtifactEditor.js';
14
16
  export * from './InvitationAcceptancePanel.js';
15
17
  export * from './SubjectAccessRequestPanel.js';
16
18
  export * from './SoschainWorkspace.js';
17
19
  export * from './EncounterTransferConsole.js';
20
+ export * from './EmergencyContactDirectory.js';
18
21
  export * from './OrganizationWorkforceAdministration.js';
19
22
  export * from './ProtectedIndexEmergencyData.js';
20
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:auto; padding:1.5rem; position:fixed; transform:translate(-50%,-50%); width:calc(100% - 2rem); z-index:60; }
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; }
@@ -204,10 +214,21 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
204
214
  .uhcp-document-tabs button[aria-pressed=true] { background:var(--uhcp-turquoise); color:#fff; }
205
215
  .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
216
  .uhcp-document-panel>article>header time { color:var(--uhcp-muted); font-size:.8rem; white-space:nowrap; }
217
+ .uhcp-all-clinical-sections { color:var(--uhcp-text); display:grid; gap:1rem; }
218
+ .uhcp-all-clinical-sections>h2 { margin:0; }
219
+ .uhcp-all-clinical-stack { display:grid; gap:.75rem; }
220
+ .uhcp-all-clinical-section { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:1rem; padding:1rem; }
221
+ .uhcp-all-clinical-section>summary { cursor:pointer; font-size:1.05rem; font-weight:800; }
222
+ .uhcp-all-clinical-section>h3 { margin:0; }
223
+ .uhcp-all-clinical-content { display:grid; gap:.75rem; padding-top:.85rem; }
224
+ .uhcp-all-clinical-content>p { color:var(--uhcp-muted); margin:0; }
225
+ .uhcp-all-clinical-tabs { display:flex; gap:.5rem; overflow:auto; }
226
+ .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; }
227
+ .uhcp-all-clinical-tabs button[aria-selected=true] { background:var(--uhcp-turquoise); color:#fff; }
207
228
  .uhcp-terminology-results ul { display:grid; gap:.35rem; list-style:none; margin:.25rem 0 0; padding:0; }
208
229
  .uhcp-terminology-results button { background:var(--uhcp-surface-muted); color:inherit; display:grid; text-align:start; width:100%; }
209
230
  .uhcp-terminology-results small { color:var(--uhcp-muted); display:block; font-weight:400; }
210
- @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; } }
211
232
 
212
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); }
213
234
  .uhcp-transfer-console button,.uhcp-transfer-console input,.uhcp-transfer-console textarea { font:inherit; }
@@ -430,6 +451,27 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
430
451
  .uhcp-clinical-title { font-weight: 800; margin: 0; }
431
452
  .uhcp-clinical-type { color: var(--uhcp-muted); font-size: .72rem; font-weight: 800; letter-spacing: .06em; margin: .35rem 0 0; text-transform: uppercase; }
432
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; } }
433
475
  .uhcp-clinical-details { display: grid; gap: .3rem; margin: .6rem 0 0; }
434
476
  .uhcp-clinical-details div { align-items: baseline; display: flex; gap: .75rem; justify-content: space-between; }
435
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.26",
3
+ "version": "0.1.28",
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.8",
34
+ "fhir-data-utils-ts": "0.6.10",
35
35
  "gdc-common-utils-ts": "^2.3.12"
36
36
  },
37
37
  "devDependencies": {