uhc-portal-components-react 0.1.43 → 0.1.45
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 +16 -0
- package/dist/ClinicalArtifactEditor.d.ts +15 -1
- package/dist/ClinicalArtifactEditor.d.ts.map +1 -1
- package/dist/ClinicalArtifactEditor.js +42 -16
- package/dist/ClinicalBundleDraftEditor.d.ts +5 -2
- package/dist/ClinicalBundleDraftEditor.d.ts.map +1 -1
- package/dist/ClinicalBundleDraftEditor.js +27 -3
- package/dist/styles.css +9 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.45 - 2026-10-07
|
|
6
|
+
|
|
7
|
+
- Present governed terminology sources as a compact dropdown with checkboxes,
|
|
8
|
+
retaining the server default while allowing several systems at once.
|
|
9
|
+
- Resolve each terminology default from the exact resource and coded claim;
|
|
10
|
+
no coding system is assumed globally. Render required FHIR R4 code bindings
|
|
11
|
+
from `fhir-data-utils-ts` with localized labels while persisting their
|
|
12
|
+
canonical codes.
|
|
13
|
+
|
|
14
|
+
## 0.1.44 - 2026-10-07
|
|
15
|
+
|
|
16
|
+
- Let the shared IPS editor hydrate claims-first resources from an imported
|
|
17
|
+
document Bundle so they remain locally editable before the single final send.
|
|
18
|
+
- Expose governed terminology source selection with the server default selected
|
|
19
|
+
and optional multi-source searches for typed or dictated clinical text.
|
|
20
|
+
|
|
5
21
|
## 0.1.43 - 2026-10-07
|
|
6
22
|
|
|
7
23
|
- Require the `gdc-common-utils-ts` release that provides the Composition
|
|
@@ -63,7 +63,19 @@ export type ClinicalTerminologySearch = Readonly<{
|
|
|
63
63
|
text: string;
|
|
64
64
|
taxonomyId?: string;
|
|
65
65
|
limit: number;
|
|
66
|
+
sources?: readonly string[];
|
|
66
67
|
}>;
|
|
68
|
+
export type ClinicalTerminologySource = Readonly<{
|
|
69
|
+
id: string;
|
|
70
|
+
label?: string;
|
|
71
|
+
default?: boolean;
|
|
72
|
+
}>;
|
|
73
|
+
export type ClinicalTerminologySourceSearch = (input: Readonly<{
|
|
74
|
+
resourceType: string;
|
|
75
|
+
claim: string;
|
|
76
|
+
language: string;
|
|
77
|
+
taxonomyId?: string;
|
|
78
|
+
}>) => Promise<readonly ClinicalTerminologySource[]>;
|
|
67
79
|
export type ClinicalArtifactEditorCopy = Readonly<{
|
|
68
80
|
title: string;
|
|
69
81
|
description: string;
|
|
@@ -73,6 +85,7 @@ export type ClinicalArtifactEditorCopy = Readonly<{
|
|
|
73
85
|
close: string;
|
|
74
86
|
terminologyLoading: string;
|
|
75
87
|
terminologyUnavailable: string;
|
|
88
|
+
terminologySources?: string;
|
|
76
89
|
readbackMissing: string;
|
|
77
90
|
pending?: string;
|
|
78
91
|
retry?: string;
|
|
@@ -102,6 +115,7 @@ export type ClinicalArtifactEditorProps = Readonly<{
|
|
|
102
115
|
taxonomyId?: string;
|
|
103
116
|
fieldLabel: (claim: string) => string;
|
|
104
117
|
searchTerminology?: (input: ClinicalTerminologySearch) => Promise<readonly ClinicalTerminologyCandidate[]>;
|
|
118
|
+
loadTerminologySources?: ClinicalTerminologySourceSearch;
|
|
105
119
|
saveArtifact: (draft: ClinicalArtifactDraft) => Promise<ClinicalResourceBundleLike | ClinicalArtifactSaveOutcome | undefined>;
|
|
106
120
|
onCancel: () => void;
|
|
107
121
|
onReadback: (bundle: ClinicalResourceBundleLike) => void;
|
|
@@ -111,5 +125,5 @@ export type ClinicalArtifactEditorProps = Readonly<{
|
|
|
111
125
|
* persistence and terminology service access. A successful form submission is
|
|
112
126
|
* published only after the host returns a fresh provider readback Bundle.
|
|
113
127
|
*/
|
|
114
|
-
export declare function ClinicalArtifactEditor({ request, schema, operationSchema, authoringContext, copy, locale, timeZone, taxonomyId, fieldLabel, searchTerminology, saveArtifact, onCancel, onReadback, }: ClinicalArtifactEditorProps): import("react").JSX.Element;
|
|
128
|
+
export declare function ClinicalArtifactEditor({ request, schema, operationSchema, authoringContext, copy, locale, timeZone, taxonomyId, fieldLabel, searchTerminology, loadTerminologySources, saveArtifact, onCancel, onReadback, }: ClinicalArtifactEditorProps): import("react").JSX.Element;
|
|
115
129
|
//# sourceMappingURL=ClinicalArtifactEditor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;
|
|
1
|
+
{"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;AAWlG,OAAO,EAML,KAAK,2BAA2B,EAEhC,KAAK,2BAA2B,EACjC,MAAM,kDAAkD,CAAA;AACzD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAA;AAIxE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACjD,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACjD,2EAA2E;IAC3E,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9E,gFAAgF;IAChF,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClD,CAAC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxC,yFAAyF;IACzF,eAAe,CAAC,EAAE,2BAA2B,CAAA;IAC7C,8DAA8D;IAC9D,aAAa,CAAC,EAAE,6BAA6B,CAAA;CAC9C,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;IACzB,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAC3B,CAAC,CAAA;AAEF,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gBAAgB,EAAE,OAAO,GAAG,WAAW,CAAA;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAC,CAAA;AAEF,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B,CAAC,CAAA;AAEF,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AACnG,MAAM,MAAM,+BAA+B,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC;IAC7D,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC3E,CAAC,KAAK,OAAO,CAAC,SAAS,yBAAyB,EAAE,CAAC,CAAA;AAEpD,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,kBAAkB,EAAE,MAAM,CAAA;IAC1B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,2BAA2B,CAAA;CACvC,CAAC,CAAA;AAEF,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAC9C;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,0BAA0B,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,SAAS,GAAG,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CACzC,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,OAAO,EAAE,6BAA6B,CAAA;IACtC,MAAM,EAAE,sBAAsB,CAAA;IAC9B,oFAAoF;IACpF,eAAe,CAAC,EAAE,2BAA2B,CAAA;IAC7C,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,gCAAgC,CAAA;IACnD,IAAI,EAAE,0BAA0B,CAAA;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,OAAO,CAAC,SAAS,4BAA4B,EAAE,CAAC,CAAA;IAC1G,sBAAsB,CAAC,EAAE,+BAA+B,CAAA;IACxD,YAAY,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,OAAO,CAAC,0BAA0B,GAAG,2BAA2B,GAAG,SAAS,CAAC,CAAA;IAC7H,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAA;CACzD,CAAC,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,OAAO,EACP,MAAM,EACN,eAAe,EACf,gBAAgB,EAChB,IAAI,EACJ,MAAa,EACb,QAA2D,EAC3D,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,YAAY,EACZ,QAAQ,EACR,UAAU,GACX,EAAE,2BAA2B,+BAuO7B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { clinicalArtifactEditorClaimsFromFhirR4, clinicalArtifactFhirR4ToFlat, composeFhirDateTimeFromLocalParts, splitFhirDateTimeForInput, } from 'fhir-data-utils-ts';
|
|
4
|
+
import { clinicalArtifactEditorClaimsFromFhirR4, clinicalArtifactFhirR4ToFlat, clinicalArtifactCodedOptionsForClaim, composeFhirDateTimeFromLocalParts, splitFhirDateTimeForInput, } from 'fhir-data-utils-ts';
|
|
5
5
|
import { buildMultichannelOperationBundle, MultichannelOperationMode, readMultichannelOperationFromBundle, selectMultichannelOperationClaimValues, } from 'sos-data-utils-ts/multichannel-operation-schemas';
|
|
6
6
|
import { ClinicalEditorLayer } from './ClinicalEditorLayer.js';
|
|
7
7
|
import { ClinicalSpeechInputButton } from './ClinicalSpeechInput.js';
|
|
@@ -10,7 +10,7 @@ import { ClinicalSpeechInputButton } from './ClinicalSpeechInput.js';
|
|
|
10
10
|
* persistence and terminology service access. A successful form submission is
|
|
11
11
|
* published only after the host returns a fresh provider readback Bundle.
|
|
12
12
|
*/
|
|
13
|
-
export function ClinicalArtifactEditor({ request, schema, operationSchema, authoringContext, copy, locale = 'en', timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone, taxonomyId, fieldLabel, searchTerminology, saveArtifact, onCancel, onReadback, }) {
|
|
13
|
+
export function ClinicalArtifactEditor({ request, schema, operationSchema, authoringContext, copy, locale = 'en', timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone, taxonomyId, fieldLabel, searchTerminology, loadTerminologySources, saveArtifact, onCancel, onReadback, }) {
|
|
14
14
|
const titleId = useId();
|
|
15
15
|
const descriptionId = useId();
|
|
16
16
|
const acceptedClaims = useMemo(() => operationSchema
|
|
@@ -53,13 +53,40 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
|
|
|
53
53
|
const [candidates, setCandidates] = useState([]);
|
|
54
54
|
const [terminologyBusy, setTerminologyBusy] = useState(false);
|
|
55
55
|
const [terminologyError, setTerminologyError] = useState(false);
|
|
56
|
+
const [terminologySources, setTerminologySources] = useState([]);
|
|
57
|
+
const [selectedTerminologySources, setSelectedTerminologySources] = useState([]);
|
|
58
|
+
const [terminologySourcesClaim, setTerminologySourcesClaim] = useState('');
|
|
56
59
|
const [saving, setSaving] = useState(false);
|
|
57
60
|
const [error, setError] = useState('');
|
|
58
61
|
const [pending, setPending] = useState('');
|
|
59
62
|
const draftId = useRef(request.artifactId ?? crypto.randomUUID());
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!activeTerminologyClaim || !loadTerminologySources)
|
|
65
|
+
return;
|
|
66
|
+
const codeClaim = terminologyCodeClaim(request.resourceType, activeTerminologyClaim);
|
|
67
|
+
let current = true;
|
|
68
|
+
setTerminologySourcesClaim('');
|
|
69
|
+
void loadTerminologySources({ resourceType: request.resourceType, claim: codeClaim, language: locale, taxonomyId }).then((sources) => {
|
|
70
|
+
if (!current)
|
|
71
|
+
return;
|
|
72
|
+
setTerminologySources(sources);
|
|
73
|
+
const defaults = sources.filter(source => source.default).map(source => source.id);
|
|
74
|
+
setSelectedTerminologySources(defaults.length ? defaults : sources[0] ? [sources[0].id] : []);
|
|
75
|
+
setTerminologySourcesClaim(activeTerminologyClaim);
|
|
76
|
+
}).catch(() => {
|
|
77
|
+
if (current) {
|
|
78
|
+
setTerminologySources([]);
|
|
79
|
+
setSelectedTerminologySources([]);
|
|
80
|
+
setTerminologySourcesClaim(activeTerminologyClaim);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return () => { current = false; };
|
|
84
|
+
}, [activeTerminologyClaim, loadTerminologySources, locale, request.resourceType, taxonomyId]);
|
|
60
85
|
useEffect(() => {
|
|
61
86
|
if (!activeTerminologyClaim || !searchTerminology)
|
|
62
87
|
return;
|
|
88
|
+
if (loadTerminologySources && terminologySourcesClaim !== activeTerminologyClaim)
|
|
89
|
+
return;
|
|
63
90
|
const sourceText = claims[activeTerminologyClaim]?.trim() ?? '';
|
|
64
91
|
if (sourceText.length < 2) {
|
|
65
92
|
setCandidates([]);
|
|
@@ -77,6 +104,7 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
|
|
|
77
104
|
text: sourceText,
|
|
78
105
|
taxonomyId,
|
|
79
106
|
limit: 20,
|
|
107
|
+
...(selectedTerminologySources.length ? { sources: selectedTerminologySources } : {}),
|
|
80
108
|
}).then((nextCandidates) => {
|
|
81
109
|
if (current)
|
|
82
110
|
setCandidates(nextCandidates);
|
|
@@ -91,7 +119,7 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
|
|
|
91
119
|
});
|
|
92
120
|
}, 250);
|
|
93
121
|
return () => { current = false; window.clearTimeout(timer); };
|
|
94
|
-
}, [activeTerminologyClaim, claims, locale, request.resourceType, searchTerminology, taxonomyId]);
|
|
122
|
+
}, [activeTerminologyClaim, claims, loadTerminologySources, locale, request.resourceType, searchTerminology, selectedTerminologySources, taxonomyId, terminologySourcesClaim]);
|
|
95
123
|
async function submit() {
|
|
96
124
|
setSaving(true);
|
|
97
125
|
setError('');
|
|
@@ -127,12 +155,19 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
|
|
|
127
155
|
setSaving(false);
|
|
128
156
|
}
|
|
129
157
|
}
|
|
158
|
+
function toggleTerminologySource(sourceId, selected) {
|
|
159
|
+
setSelectedTerminologySources(current => {
|
|
160
|
+
const next = selected ? [...new Set([...current, sourceId])] : current.filter(candidate => candidate !== sourceId);
|
|
161
|
+
return next.length ? next : current;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
130
164
|
return _jsx(ClinicalEditorLayer, { onDismiss: onCancel, children: _jsxs("section", { className: "uhcp-clinical-editor", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, "aria-describedby": descriptionId, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h2", { id: titleId, children: copy.title }), _jsx("p", { id: descriptionId, children: copy.description })] }), _jsx("button", { type: "button", onClick: onCancel, "aria-label": copy.close, children: "\u00D7" })] }), _jsxs("form", { onSubmit: (event) => { event.preventDefault(); void submit(); }, children: [fields.map((claim) => {
|
|
131
165
|
const parameter = claim.slice(request.resourceType.length + 1);
|
|
132
166
|
const label = fieldLabel(claim);
|
|
133
167
|
const terminologyEnabled = Boolean(searchTerminology && terminologyCodeClaim(request.resourceType, claim));
|
|
134
168
|
const inputId = `${titleId}-${claim}`;
|
|
135
169
|
const value = claims[claim] ?? '';
|
|
170
|
+
const codedOptions = clinicalArtifactCodedOptionsForClaim(claim, locale);
|
|
136
171
|
const schemaNode = operationSchema?.properties.claims?.properties?.[claim];
|
|
137
172
|
const update = (event) => {
|
|
138
173
|
setClaims((current) => ({ ...current, [claim]: event.target.value }));
|
|
@@ -150,8 +185,8 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
|
|
|
150
185
|
if (terminologyEnabled)
|
|
151
186
|
setActiveTerminologyClaim(claim);
|
|
152
187
|
};
|
|
153
|
-
return _jsxs("div", { className: fieldClassName, children: [_jsxs("span", { className: "uhcp-clinical-editor-field-heading", children: [_jsxs("label", { htmlFor: inputId, children: [label, requiredClaims.has(claim) ? ' *' : ''] }), isSpeechCompatibleField(parameter, schemaNode) ? _jsx(ClinicalSpeechInputButton, { resourceType: request.resourceType, claim: claim, locale: locale, fieldLabel: label, onTranscript: appendTranscript }) : null] }),
|
|
154
|
-
: _jsx("input", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), type: fieldInputType(parameter, operationSchema?.properties.claims?.properties?.[claim]), onChange: update }), terminologyEnabled && activeTerminologyClaim === claim ? _jsxs("div", { className: "uhcp-terminology-results", children: [terminologyBusy ? _jsx("small", { role: "status", children: copy.terminologyLoading }) : null, terminologyError ? _jsx("small", { role: "alert", children: copy.terminologyUnavailable }) : null, candidates.length ? _jsx("ul", { children: candidates.map((candidate) => _jsx("li", { children: _jsxs("button", { type: "button", onClick: () => {
|
|
188
|
+
return _jsxs("div", { className: fieldClassName, children: [_jsxs("span", { className: "uhcp-clinical-editor-field-heading", children: [_jsxs("label", { htmlFor: inputId, children: [label, requiredClaims.has(claim) ? ' *' : ''] }), isSpeechCompatibleField(parameter, schemaNode, codedOptions.length > 0) ? _jsx(ClinicalSpeechInputButton, { resourceType: request.resourceType, claim: claim, locale: locale, fieldLabel: label, onTranscript: appendTranscript }) : null] }), codedOptions.length ? _jsxs("select", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), onChange: update, children: [!requiredClaims.has(claim) ? _jsx("option", { value: "" }) : null, codedOptions.map((option) => _jsx("option", { value: option.code, children: option.label }, option.code))] }) : isMultiline(parameter) ? _jsx("textarea", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), rows: 3, onChange: update })
|
|
189
|
+
: _jsx("input", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), type: fieldInputType(parameter, operationSchema?.properties.claims?.properties?.[claim]), onChange: update }), terminologyEnabled && activeTerminologyClaim === claim ? _jsxs("div", { className: "uhcp-terminology-results", children: [terminologySources.length ? _jsxs("details", { className: "uhcp-terminology-source-picker", children: [_jsxs("summary", { children: [copy.terminologySources || 'Coding systems', ": ", terminologySources.filter(source => selectedTerminologySources.includes(source.id)).map(source => source.label || source.id).join(', ')] }), _jsxs("fieldset", { "aria-label": copy.terminologySources || 'Coding systems', children: [_jsx("legend", { className: "uhcp-sr-only", children: copy.terminologySources || 'Coding systems' }), terminologySources.map(source => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: selectedTerminologySources.includes(source.id), onChange: event => toggleTerminologySource(source.id, event.currentTarget.checked) }), _jsx("span", { children: source.label || source.id })] }, source.id))] })] }) : null, terminologyBusy ? _jsx("small", { role: "status", children: copy.terminologyLoading }) : null, terminologyError ? _jsx("small", { role: "alert", children: copy.terminologyUnavailable }) : null, candidates.length ? _jsx("ul", { children: candidates.map((candidate) => _jsx("li", { children: _jsxs("button", { type: "button", onClick: () => {
|
|
155
190
|
setClaims((current) => selectedCodingClaims(request.resourceType, claim, candidate, acceptedClaims, current));
|
|
156
191
|
setCandidates([]);
|
|
157
192
|
setActiveTerminologyClaim(undefined);
|
|
@@ -195,8 +230,8 @@ function composeTemporalClaim(parts, timeZone) {
|
|
|
195
230
|
function isDateTimeSchemaNode(schemaNode) {
|
|
196
231
|
return schemaNode?.format === 'date-time' || schemaNode?.format === 'fhir-date-time';
|
|
197
232
|
}
|
|
198
|
-
function isSpeechCompatibleField(parameter, schemaNode) {
|
|
199
|
-
if (
|
|
233
|
+
function isSpeechCompatibleField(parameter, schemaNode, hasCodedOptions = false) {
|
|
234
|
+
if (hasCodedOptions)
|
|
200
235
|
return false;
|
|
201
236
|
if (schemaNode?.type === 'number' || schemaNode?.type === 'integer' || schemaNode?.type === 'boolean')
|
|
202
237
|
return false;
|
|
@@ -247,15 +282,6 @@ function defaultClaims(resourceType) {
|
|
|
247
282
|
return { [`${resourceType}.clinical-status`]: 'active' };
|
|
248
283
|
return {};
|
|
249
284
|
}
|
|
250
|
-
function statusOptions(resourceType, parameter) {
|
|
251
|
-
if (parameter !== 'status' && !parameter.endsWith('-status'))
|
|
252
|
-
return undefined;
|
|
253
|
-
if (resourceType === 'Immunization')
|
|
254
|
-
return ['completed', 'not-done', 'entered-in-error'];
|
|
255
|
-
if (resourceType === 'Observation' || resourceType === 'DiagnosticReport')
|
|
256
|
-
return ['final', 'preliminary', 'amended', 'entered-in-error'];
|
|
257
|
-
return ['active', 'completed', 'inactive', 'entered-in-error'];
|
|
258
|
-
}
|
|
259
285
|
function terminologyCodeClaim(resourceType, inputClaim) {
|
|
260
286
|
const parameter = inputClaim.slice(resourceType.length + 1);
|
|
261
287
|
// `code-text` is the generic primary-concept alias. Immunization deliberately
|
|
@@ -5,7 +5,7 @@ import 'gdc-common-utils-ts/utils/medication-statement-entry-editor';
|
|
|
5
5
|
import 'gdc-common-utils-ts/utils/observation-entry-editor';
|
|
6
6
|
import 'gdc-common-utils-ts/utils/procedure-entry-editor';
|
|
7
7
|
import type { MultichannelOperationSchema } from 'sos-data-utils-ts/multichannel-operation-schemas';
|
|
8
|
-
import { type ClinicalArtifactAuthoringContext, type ClinicalArtifactEditorCopy, type ClinicalArtifactSchema, type ClinicalTerminologySearch, type ClinicalTerminologyCandidate } from './ClinicalArtifactEditor.js';
|
|
8
|
+
import { type ClinicalArtifactAuthoringContext, type ClinicalArtifactEditorCopy, type ClinicalArtifactSchema, type ClinicalTerminologySearch, type ClinicalTerminologyCandidate, type ClinicalTerminologySourceSearch } from './ClinicalArtifactEditor.js';
|
|
9
9
|
export type ClinicalBundleDraftSchema = Readonly<{
|
|
10
10
|
schema: ClinicalArtifactSchema;
|
|
11
11
|
operationSchema: MultichannelOperationSchema;
|
|
@@ -42,6 +42,9 @@ export type ClinicalBundleDraftEditorProps = Readonly<{
|
|
|
42
42
|
taxonomyId?: string;
|
|
43
43
|
fieldLabel: (claim: string) => string;
|
|
44
44
|
searchTerminology?: (input: ClinicalTerminologySearch) => Promise<readonly ClinicalTerminologyCandidate[]>;
|
|
45
|
+
loadTerminologySources?: ClinicalTerminologySourceSearch;
|
|
46
|
+
/** Optional claims-first document Bundle loaded into the local draft; loading it never persists. */
|
|
47
|
+
initialBundle?: ClinicalBundleDraft;
|
|
45
48
|
onSubmitDraft: (bundle: ClinicalBundleDraft) => Promise<ClinicalBundleDraftSubmitOutcome>;
|
|
46
49
|
}>;
|
|
47
50
|
/**
|
|
@@ -51,5 +54,5 @@ export type ClinicalBundleDraftEditorProps = Readonly<{
|
|
|
51
54
|
* It owns no authorization or persistence: the host supplies subject and
|
|
52
55
|
* provenance and only `onSubmitDraft` may cross a persistence boundary.
|
|
53
56
|
*/
|
|
54
|
-
export declare function ClinicalBundleDraftEditor({ schemas, authoringContext, copy, locale, timeZone, taxonomyId, fieldLabel, searchTerminology, onSubmitDraft, }: ClinicalBundleDraftEditorProps): import("react").JSX.Element;
|
|
57
|
+
export declare function ClinicalBundleDraftEditor({ schemas, authoringContext, copy, locale, timeZone, taxonomyId, fieldLabel, searchTerminology, loadTerminologySources, initialBundle, onSubmitDraft, }: ClinicalBundleDraftEditorProps): import("react").JSX.Element;
|
|
55
58
|
//# sourceMappingURL=ClinicalBundleDraftEditor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClinicalBundleDraftEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalBundleDraftEditor.tsx"],"names":[],"mappings":"AAOA,OAAO,4DAA4D,CAAA;AACnE,OAAO,kDAAkD,CAAA;AACzD,OAAO,qDAAqD,CAAA;AAC5D,OAAO,6DAA6D,CAAA;AACpE,OAAO,oDAAoD,CAAA;AAC3D,OAAO,kDAAkD,CAAA;AACzD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kDAAkD,CAAA;AACnG,OAAO,EAEL,KAAK,gCAAgC,EAErC,KAAK,0BAA0B,EAE/B,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,4BAA4B,
|
|
1
|
+
{"version":3,"file":"ClinicalBundleDraftEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalBundleDraftEditor.tsx"],"names":[],"mappings":"AAOA,OAAO,4DAA4D,CAAA;AACnE,OAAO,kDAAkD,CAAA;AACzD,OAAO,qDAAqD,CAAA;AAC5D,OAAO,6DAA6D,CAAA;AACpE,OAAO,oDAAoD,CAAA;AAC3D,OAAO,kDAAkD,CAAA;AACzD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kDAAkD,CAAA;AACnG,OAAO,EAEL,KAAK,gCAAgC,EAErC,KAAK,0BAA0B,EAE/B,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACrC,MAAM,6BAA6B,CAAA;AAGpC,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,MAAM,EAAE,sBAAsB,CAAA;IAC9B,eAAe,EAAE,2BAA2B,CAAA;IAC5C,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;CACtB,CAAC,CAAA;AAEF,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,YAAY,EAAE,QAAQ,CAAA;IACtB,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,SAAS,OAAO,EAAE,CAAA;CAC1B,CAAC,CAAA;AAEF,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAA;IACzD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,0BAA0B,CAAA;CACnC,CAAC,CAAA;AAEF,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,OAAO,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC7C,gBAAgB,EAAE,gCAAgC,CAAA;IAClD,IAAI,EAAE,6BAA6B,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,OAAO,CAAC,SAAS,4BAA4B,EAAE,CAAC,CAAA;IAC1G,sBAAsB,CAAC,EAAE,+BAA+B,CAAA;IACxD,oGAAoG;IACpG,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,aAAa,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,OAAO,CAAC,gCAAgC,CAAC,CAAA;CAC1F,CAAC,CAAA;AAYF;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,OAAO,EACP,gBAAgB,EAChB,IAAI,EACJ,MAAa,EACb,QAAQ,EACR,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,aAAa,GACd,EAAE,8BAA8B,+BAwFhC"}
|
|
@@ -20,8 +20,8 @@ import { ClinicalActionButton } from './ClinicalActionButton.js';
|
|
|
20
20
|
* It owns no authorization or persistence: the host supplies subject and
|
|
21
21
|
* provenance and only `onSubmitDraft` may cross a persistence boundary.
|
|
22
22
|
*/
|
|
23
|
-
export function ClinicalBundleDraftEditor({ schemas, authoringContext, copy, locale = 'en', timeZone, taxonomyId, fieldLabel, searchTerminology, onSubmitDraft, }) {
|
|
24
|
-
const [artifacts, setArtifacts] = useState(
|
|
23
|
+
export function ClinicalBundleDraftEditor({ schemas, authoringContext, copy, locale = 'en', timeZone, taxonomyId, fieldLabel, searchTerminology, loadTerminologySources, initialBundle, onSubmitDraft, }) {
|
|
24
|
+
const [artifacts, setArtifacts] = useState(() => importedArtifacts(initialBundle, schemas));
|
|
25
25
|
const [editor, setEditor] = useState();
|
|
26
26
|
const [notice, setNotice] = useState('');
|
|
27
27
|
const [error, setError] = useState('');
|
|
@@ -68,7 +68,7 @@ export function ClinicalBundleDraftEditor({ schemas, authoringContext, copy, loc
|
|
|
68
68
|
const title = artifactTitle(artifact.draft);
|
|
69
69
|
return _jsxs("li", { children: [_jsxs("span", { children: [_jsx("strong", { children: title }), _jsx("small", { children: artifact.draft.resourceType })] }), _jsxs("span", { children: [_jsx(ClinicalActionButton, { kind: "edit", label: `${copy.edit} ${title}`, onClick: () => setEditor({ schema: artifact.schema, draft: artifact.draft }) }), _jsx(ClinicalActionButton, { kind: "delete", label: `${copy.remove} ${title}`, onClick: () => setArtifacts((current) => current.filter((candidate) => candidate.draft.id !== artifact.draft.id)) })] })] }, artifact.draft.id);
|
|
70
70
|
}) })] }, label);
|
|
71
|
-
}), artifacts.length === 0 && sections.length === 0 ? _jsx("p", { children: copy.empty }) : null, _jsx("footer", { children: _jsx("button", { type: "button", disabled: artifacts.length === 0 || submitting, onClick: () => void submitDraft(), children: submitting ? copy.submitting : copy.submit }) }), editor ? _jsx(ClinicalArtifactEditor, { request: editorRequest(editor), schema: editor.schema.schema, operationSchema: editor.schema.operationSchema, authoringContext: authoringContext, copy: copy.editor, locale: locale, timeZone: timeZone, taxonomyId: taxonomyId, fieldLabel: fieldLabel, searchTerminology: searchTerminology, saveArtifact: async (draft) => stageArtifact(draft), onCancel: () => setEditor(undefined), onReadback: () => setEditor(undefined) }) : null] });
|
|
71
|
+
}), artifacts.length === 0 && sections.length === 0 ? _jsx("p", { children: copy.empty }) : null, _jsx("footer", { children: _jsx("button", { type: "button", disabled: artifacts.length === 0 || submitting, onClick: () => void submitDraft(), children: submitting ? copy.submitting : copy.submit }) }), editor ? _jsx(ClinicalArtifactEditor, { request: editorRequest(editor), schema: editor.schema.schema, operationSchema: editor.schema.operationSchema, authoringContext: authoringContext, copy: copy.editor, locale: locale, timeZone: timeZone, taxonomyId: taxonomyId, fieldLabel: fieldLabel, searchTerminology: searchTerminology, loadTerminologySources: loadTerminologySources, saveArtifact: async (draft) => stageArtifact(draft), onCancel: () => setEditor(undefined), onReadback: () => setEditor(undefined) }) : null] });
|
|
72
72
|
}
|
|
73
73
|
function editorRequest(editor) {
|
|
74
74
|
return {
|
|
@@ -136,3 +136,27 @@ function artifactTitle(draft) {
|
|
|
136
136
|
const preferred = Object.entries(draft.claims).find(([claim, value]) => typeof value === 'string' && value.trim() && (claim.endsWith('-text') || claim.endsWith('.note') || claim.endsWith('.description')))?.[1];
|
|
137
137
|
return typeof preferred === 'string' ? preferred : draft.resourceType;
|
|
138
138
|
}
|
|
139
|
+
function importedArtifacts(bundle, schemas) {
|
|
140
|
+
if (!bundle || bundle.resourceType !== 'Bundle' || bundle.type !== 'document' || !Array.isArray(bundle.entry))
|
|
141
|
+
return [];
|
|
142
|
+
return bundle.entry.flatMap((entry) => {
|
|
143
|
+
const resource = record(record(entry).resource);
|
|
144
|
+
const schema = schemas.find(candidate => candidate.schema.resourceType === resource.resourceType);
|
|
145
|
+
const claims = record(record(resource.meta).claims);
|
|
146
|
+
const id = String(resource.id || '').trim();
|
|
147
|
+
if (!schema || !id || Object.keys(claims).length === 0)
|
|
148
|
+
return [];
|
|
149
|
+
const stringValues = Object.fromEntries(Object.entries(claims).map(([claim, value]) => [claim, String(value ?? '')]));
|
|
150
|
+
return [{ schema, draft: {
|
|
151
|
+
resourceType: schema.schema.resourceType,
|
|
152
|
+
id,
|
|
153
|
+
sectionCode: schema.schema.sectionCode ?? '',
|
|
154
|
+
mode: 'update',
|
|
155
|
+
claims: stringValues,
|
|
156
|
+
changesBundle: { resourceType: 'Bundle', type: 'batch', data: [{ resource }] },
|
|
157
|
+
} }];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function record(value) {
|
|
161
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
162
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -237,6 +237,15 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
|
|
|
237
237
|
.uhcp-terminology-results ul { display:grid; gap:.35rem; list-style:none; margin:.25rem 0 0; padding:0; }
|
|
238
238
|
.uhcp-terminology-results button { background:var(--uhcp-surface-muted); color:inherit; display:grid; text-align:start; width:100%; }
|
|
239
239
|
.uhcp-terminology-results small { color:var(--uhcp-muted); display:block; font-weight:400; }
|
|
240
|
+
.uhcp-terminology-source-picker { border:1px solid var(--uhcp-border); border-radius:.7rem; margin:.25rem 0; position:relative; }
|
|
241
|
+
.uhcp-terminology-source-picker summary { cursor:pointer; font-weight:750; list-style:none; padding:.7rem .8rem; }
|
|
242
|
+
.uhcp-terminology-source-picker summary::-webkit-details-marker { display:none; }
|
|
243
|
+
.uhcp-terminology-source-picker summary::after { content:'▾'; float:inline-end; }
|
|
244
|
+
.uhcp-terminology-source-picker[open] summary::after { content:'▴'; }
|
|
245
|
+
.uhcp-terminology-source-picker fieldset { background:var(--uhcp-panel); border:0; border-top:1px solid var(--uhcp-border); display:grid; gap:.45rem; margin:0; padding:.7rem .8rem; }
|
|
246
|
+
.uhcp-terminology-source-picker label { align-items:center; display:flex; gap:.55rem; }
|
|
247
|
+
.uhcp-terminology-source-picker input { margin:0; width:auto; }
|
|
248
|
+
.uhcp-sr-only { clip:rect(0,0,0,0); clip-path:inset(50%); height:1px; overflow:hidden; position:absolute; white-space:nowrap; width:1px; }
|
|
240
249
|
@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; } }
|
|
241
250
|
|
|
242
251
|
.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); }
|
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.45",
|
|
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.16",
|
|
35
35
|
"gdc-common-utils-ts": "2.9.30",
|
|
36
36
|
"sos-data-utils-ts": "0.6.3"
|
|
37
37
|
},
|