uhc-portal-components-react 0.1.44 → 0.1.46

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
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.46 - 2026-10-08
6
+
7
+ - Create clinical Bundle entries through explicit section and resource-type
8
+ selectors, followed by one accessible `+ Add entry` action. Keep the staged
9
+ entries grouped in collapsible sections with their existing edit and delete
10
+ actions.
11
+ - Let every schema-driven clinical resource form stage several PDF, PNG or
12
+ JPEG documents. Each file becomes an independent claims-first
13
+ `DocumentReference` with SHA-256 content hash and is linked from the parent
14
+ resource through its canonical `contained-reference-list`; files remain
15
+ local until the final Bundle submission.
16
+
17
+ ## 0.1.45 - 2026-10-07
18
+
19
+ - Present governed terminology sources as a compact dropdown with checkboxes,
20
+ retaining the server default while allowing several systems at once.
21
+ - Resolve each terminology default from the exact resource and coded claim;
22
+ no coding system is assumed globally. Render required FHIR R4 code bindings
23
+ from `fhir-data-utils-ts` with localized labels while persisting their
24
+ canonical codes.
25
+
5
26
  ## 0.1.44 - 2026-10-07
6
27
 
7
28
  - Let the shared IPS editor hydrate claims-first resources from an imported
@@ -22,6 +22,8 @@ export type ClinicalArtifactEditorRequest = Readonly<{
22
22
  }>;
23
23
  /** Authoritative claims-first provider Bundle hydrated through BundleReader. */
24
24
  initialBundle?: Readonly<Record<string, unknown>>;
25
+ /** Documents already staged with this local entry. */
26
+ initialAttachments?: readonly ClinicalArtifactAttachment[];
25
27
  }>;
26
28
  export type ClinicalArtifactDraft = Readonly<{
27
29
  resourceType: string;
@@ -33,6 +35,15 @@ export type ClinicalArtifactDraft = Readonly<{
33
35
  operationValues?: MultichannelOperationValues;
34
36
  /** Claims-first batch produced by the shared BundleEditor. */
35
37
  changesBundle?: ClinicalArtifactChangesBundle;
38
+ /** Local document evidence linked to this entry when the enclosing Bundle is built. */
39
+ attachments?: readonly ClinicalArtifactAttachment[];
40
+ }>;
41
+ export type ClinicalArtifactAttachment = Readonly<{
42
+ id: string;
43
+ name: string;
44
+ contentType: 'application/pdf' | 'image/jpeg' | 'image/png';
45
+ contentHash: string;
46
+ dataBase64: string;
36
47
  }>;
37
48
  export type ClinicalArtifactChangesBundle = Readonly<{
38
49
  resourceType: string;
@@ -89,6 +100,9 @@ export type ClinicalArtifactEditorCopy = Readonly<{
89
100
  readbackMissing: string;
90
101
  pending?: string;
91
102
  retry?: string;
103
+ attachments?: string;
104
+ addAttachments?: string;
105
+ removeAttachment?: string;
92
106
  dateTime?: ClinicalDateTimeFieldLabels;
93
107
  }>;
94
108
  export type ClinicalArtifactSaveOutcome = Readonly<{
@@ -1 +1 @@
1
- {"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"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"}
@@ -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';
@@ -57,6 +57,7 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
57
57
  const [selectedTerminologySources, setSelectedTerminologySources] = useState([]);
58
58
  const [terminologySourcesClaim, setTerminologySourcesClaim] = useState('');
59
59
  const [saving, setSaving] = useState(false);
60
+ const [attachments, setAttachments] = useState(request.initialAttachments ?? []);
60
61
  const [error, setError] = useState('');
61
62
  const [pending, setPending] = useState('');
62
63
  const draftId = useRef(request.artifactId ?? crypto.randomUUID());
@@ -130,6 +131,7 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
130
131
  sectionCode: request.sectionCode,
131
132
  mode: request.mode,
132
133
  claims,
134
+ attachments,
133
135
  ...(operationSchema ? buildOperationDraft(operationSchema, authoringContext, request, draftId.current, claims) : {}),
134
136
  });
135
137
  if (!result)
@@ -155,12 +157,19 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
155
157
  setSaving(false);
156
158
  }
157
159
  }
160
+ function toggleTerminologySource(sourceId, selected) {
161
+ setSelectedTerminologySources(current => {
162
+ const next = selected ? [...new Set([...current, sourceId])] : current.filter(candidate => candidate !== sourceId);
163
+ return next.length ? next : current;
164
+ });
165
+ }
158
166
  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) => {
159
167
  const parameter = claim.slice(request.resourceType.length + 1);
160
168
  const label = fieldLabel(claim);
161
169
  const terminologyEnabled = Boolean(searchTerminology && terminologyCodeClaim(request.resourceType, claim));
162
170
  const inputId = `${titleId}-${claim}`;
163
171
  const value = claims[claim] ?? '';
172
+ const codedOptions = clinicalArtifactCodedOptionsForClaim(claim, locale);
164
173
  const schemaNode = operationSchema?.properties.claims?.properties?.[claim];
165
174
  const update = (event) => {
166
175
  setClaims((current) => ({ ...current, [claim]: event.target.value }));
@@ -178,13 +187,60 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
178
187
  if (terminologyEnabled)
179
188
  setActiveTerminologyClaim(claim);
180
189
  };
181
- 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] }), statusOptions(request.resourceType, parameter) ? _jsxs("select", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), onChange: update, children: [!requiredClaims.has(claim) ? _jsx("option", { value: "" }) : null, statusOptions(request.resourceType, parameter).map((option) => _jsx("option", { value: option, children: humanize(option) }, option))] }) : isMultiline(parameter) ? _jsx("textarea", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), rows: 3, onChange: update })
182
- : _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("label", { children: [copy.terminologySources || 'Coding systems', _jsx("select", { multiple: true, "aria-label": copy.terminologySources || 'Coding systems', value: [...selectedTerminologySources], onChange: (event) => setSelectedTerminologySources([...event.currentTarget.selectedOptions].map(option => option.value)), children: terminologySources.map(source => _jsx("option", { value: source.id, 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: () => {
190
+ 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 })
191
+ : _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: () => {
183
192
  setClaims((current) => selectedCodingClaims(request.resourceType, claim, candidate, acceptedClaims, current));
184
193
  setCandidates([]);
185
194
  setActiveTerminologyClaim(undefined);
186
195
  }, children: [candidate.display, _jsxs("small", { children: [candidate.system, " \u00B7 ", candidate.code] })] }) }, `${candidate.system}|${candidate.code}`)) }) : null] }) : null] }, claim);
187
- }), error ? _jsx("p", { className: "uhcp-clinical-editor-error", role: "alert", children: error }) : null, pending ? _jsx("p", { className: "uhcp-clinical-editor-pending", role: "status", children: copy.pending || pending }) : null, _jsxs("footer", { children: [_jsx("button", { type: "button", onClick: onCancel, children: copy.cancel }), _jsx("button", { type: "submit", disabled: saving, children: saving ? copy.saving : pending ? (copy.retry || copy.save) : copy.save })] })] })] }) });
196
+ }), _jsxs("fieldset", { className: "uhcp-clinical-editor-attachments", children: [_jsx("legend", { children: copy.attachments || 'Documents' }), _jsxs("label", { children: [_jsx("span", { children: copy.addAttachments || 'Add PDF or image' }), _jsx("input", { type: "file", multiple: true, accept: "application/pdf,image/png,image/jpeg,.pdf,.png,.jpg,.jpeg", "aria-label": copy.addAttachments || 'Add PDF or image', onChange: (event) => {
197
+ const files = [...(event.currentTarget.files ?? [])];
198
+ event.currentTarget.value = '';
199
+ void Promise.all(files.map(readClinicalAttachment)).then((next) => {
200
+ setAttachments((current) => [...current, ...next]);
201
+ }).catch((reason) => setError(reason instanceof Error ? reason.message : String(reason)));
202
+ } })] }), attachments.length ? _jsx("ul", { children: attachments.map((attachment) => _jsxs("li", { children: [_jsx("span", { children: attachment.name }), _jsx("button", { type: "button", "aria-label": `${copy.removeAttachment || 'Remove document'} ${attachment.name}`, onClick: () => setAttachments(current => current.filter(candidate => candidate.id !== attachment.id)), children: "\u00D7" })] }, attachment.id)) }) : null] }), error ? _jsx("p", { className: "uhcp-clinical-editor-error", role: "alert", children: error }) : null, pending ? _jsx("p", { className: "uhcp-clinical-editor-pending", role: "status", children: copy.pending || pending }) : null, _jsxs("footer", { children: [_jsx("button", { type: "button", onClick: onCancel, children: copy.cancel }), _jsx("button", { type: "submit", disabled: saving, children: saving ? copy.saving : pending ? (copy.retry || copy.save) : copy.save })] })] })] }) });
203
+ }
204
+ async function readClinicalAttachment(file) {
205
+ const contentType = normalizeClinicalAttachmentType(file);
206
+ const bytes = new Uint8Array(await readFileBytes(file));
207
+ const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
208
+ return Object.freeze({
209
+ id: crypto.randomUUID(),
210
+ name: file.name,
211
+ contentType,
212
+ contentHash: `sha256:${hex(digest)}`,
213
+ dataBase64: bytesToBase64(bytes),
214
+ });
215
+ }
216
+ function normalizeClinicalAttachmentType(file) {
217
+ if (file.type === 'application/pdf' || file.type === 'image/png' || file.type === 'image/jpeg')
218
+ return file.type;
219
+ const extension = file.name.toLowerCase().split('.').pop();
220
+ if (extension === 'pdf')
221
+ return 'application/pdf';
222
+ if (extension === 'png')
223
+ return 'image/png';
224
+ if (extension === 'jpg' || extension === 'jpeg')
225
+ return 'image/jpeg';
226
+ throw new TypeError('clinical_attachment_type_unsupported');
227
+ }
228
+ function readFileBytes(file) {
229
+ return new Promise((resolve, reject) => {
230
+ const reader = new FileReader();
231
+ reader.onerror = () => reject(reader.error ?? new Error('clinical_attachment_read_failed'));
232
+ reader.onload = () => reader.result instanceof ArrayBuffer ? resolve(reader.result) : reject(new Error('clinical_attachment_read_failed'));
233
+ reader.readAsArrayBuffer(file);
234
+ });
235
+ }
236
+ function bytesToBase64(bytes) {
237
+ let binary = '';
238
+ for (let index = 0; index < bytes.length; index += 1)
239
+ binary += String.fromCharCode(bytes[index] ?? 0);
240
+ return btoa(binary);
241
+ }
242
+ function hex(bytes) {
243
+ return [...bytes].map(value => value.toString(16).padStart(2, '0')).join('');
188
244
  }
189
245
  const DEFAULT_DATE_TIME_LABELS = Object.freeze({
190
246
  date: 'Date',
@@ -223,8 +279,8 @@ function composeTemporalClaim(parts, timeZone) {
223
279
  function isDateTimeSchemaNode(schemaNode) {
224
280
  return schemaNode?.format === 'date-time' || schemaNode?.format === 'fhir-date-time';
225
281
  }
226
- function isSpeechCompatibleField(parameter, schemaNode) {
227
- if (statusOptions('', parameter))
282
+ function isSpeechCompatibleField(parameter, schemaNode, hasCodedOptions = false) {
283
+ if (hasCodedOptions)
228
284
  return false;
229
285
  if (schemaNode?.type === 'number' || schemaNode?.type === 'integer' || schemaNode?.type === 'boolean')
230
286
  return false;
@@ -275,15 +331,6 @@ function defaultClaims(resourceType) {
275
331
  return { [`${resourceType}.clinical-status`]: 'active' };
276
332
  return {};
277
333
  }
278
- function statusOptions(resourceType, parameter) {
279
- if (parameter !== 'status' && !parameter.endsWith('-status'))
280
- return undefined;
281
- if (resourceType === 'Immunization')
282
- return ['completed', 'not-done', 'entered-in-error'];
283
- if (resourceType === 'Observation' || resourceType === 'DiagnosticReport')
284
- return ['final', 'preliminary', 'amended', 'entered-in-error'];
285
- return ['active', 'completed', 'inactive', 'entered-in-error'];
286
- }
287
334
  function terminologyCodeClaim(resourceType, inputClaim) {
288
335
  const parameter = inputClaim.slice(resourceType.length + 1);
289
336
  // `code-text` is the generic primary-concept alias. Immunization deliberately
@@ -1,5 +1,6 @@
1
1
  import 'gdc-common-utils-ts/utils/allergy-intolerance-entry-editor';
2
2
  import 'gdc-common-utils-ts/utils/condition-entry-editor';
3
+ import 'gdc-common-utils-ts/utils/document-reference-entry-editor';
3
4
  import 'gdc-common-utils-ts/utils/immunization-entry-editor';
4
5
  import 'gdc-common-utils-ts/utils/medication-statement-entry-editor';
5
6
  import 'gdc-common-utils-ts/utils/observation-entry-editor';
@@ -26,6 +27,9 @@ export type ClinicalBundleDraftEditorCopy = Readonly<{
26
27
  description: string;
27
28
  empty: string;
28
29
  add: string;
30
+ section?: string;
31
+ resourceType?: string;
32
+ addEntry?: string;
29
33
  edit: string;
30
34
  remove: string;
31
35
  submit: string;
@@ -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,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"}
1
+ {"version":3,"file":"ClinicalBundleDraftEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalBundleDraftEditor.tsx"],"names":[],"mappings":"AAQA,OAAO,4DAA4D,CAAA;AACnE,OAAO,kDAAkD,CAAA;AACzD,OAAO,2DAA2D,CAAA;AAClE,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,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,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,+BAgGhC"}
@@ -5,8 +5,10 @@ import { BundleEditor } from 'gdc-common-utils-ts/utils/bundle-editor-core';
5
5
  import { BundleTypes } from 'gdc-common-utils-ts/models/bundle-editor-types';
6
6
  import { HealthcareDocumentTypes } from 'gdc-common-utils-ts/constants/healthcare';
7
7
  import { ClinicalResourceEntryEditor } from 'gdc-common-utils-ts/utils/clinical-resource-entry-editor';
8
+ import { buildDocumentReferenceFlatEntry } from 'fhir-data-utils-ts/flat-claim-resource-graph';
8
9
  import 'gdc-common-utils-ts/utils/allergy-intolerance-entry-editor';
9
10
  import 'gdc-common-utils-ts/utils/condition-entry-editor';
11
+ import 'gdc-common-utils-ts/utils/document-reference-entry-editor';
10
12
  import 'gdc-common-utils-ts/utils/immunization-entry-editor';
11
13
  import 'gdc-common-utils-ts/utils/medication-statement-entry-editor';
12
14
  import 'gdc-common-utils-ts/utils/observation-entry-editor';
@@ -27,6 +29,10 @@ export function ClinicalBundleDraftEditor({ schemas, authoringContext, copy, loc
27
29
  const [error, setError] = useState('');
28
30
  const [submitting, setSubmitting] = useState(false);
29
31
  const sections = useMemo(() => groupSchemas(schemas), [schemas]);
32
+ const [selectedSection, setSelectedSection] = useState(() => groupSchemas(schemas)[0]?.label ?? '');
33
+ const [selectedSchemaKey, setSelectedSchemaKey] = useState(() => schemas[0] ? schemaKey(schemas[0]) : '');
34
+ const selectedSectionSchemas = sections.find(section => section.label === selectedSection)?.schemas ?? [];
35
+ const selectedSchema = selectedSectionSchemas.find(candidate => schemaKey(candidate) === selectedSchemaKey) ?? selectedSectionSchemas[0];
30
36
  function stageArtifact(draft) {
31
37
  if (!editor)
32
38
  throw new TypeError('clinical_bundle_draft_editor_schema_required');
@@ -62,9 +68,14 @@ export function ClinicalBundleDraftEditor({ schemas, authoringContext, copy, loc
62
68
  setSubmitting(false);
63
69
  }
64
70
  }
65
- return _jsxs("section", { className: "uhcp-clinical-bundle-draft", "data-clinical-bundle-draft": true, children: [_jsxs("header", { children: [_jsx("h2", { children: copy.title }), _jsx("p", { children: copy.description })] }), notice ? _jsx("p", { role: "status", children: notice }) : null, error ? _jsx("p", { role: "alert", className: "uhcp-clinical-editor-error", children: error }) : null, sections.map(({ label, schemas: sectionSchemas }) => {
71
+ return _jsxs("section", { className: "uhcp-clinical-bundle-draft", "data-clinical-bundle-draft": true, children: [_jsxs("header", { children: [_jsx("h2", { children: copy.title }), _jsx("p", { children: copy.description })] }), notice ? _jsx("p", { role: "status", children: notice }) : null, error ? _jsx("p", { role: "alert", className: "uhcp-clinical-editor-error", children: error }) : null, sections.length ? _jsxs("div", { className: "uhcp-clinical-bundle-draft-create", children: [_jsxs("label", { children: [copy.section || 'Section', _jsx("select", { value: selectedSection, onChange: (event) => {
72
+ const nextSection = event.currentTarget.value;
73
+ const firstSchema = sections.find(section => section.label === nextSection)?.schemas[0];
74
+ setSelectedSection(nextSection);
75
+ setSelectedSchemaKey(firstSchema ? schemaKey(firstSchema) : '');
76
+ }, children: sections.map(section => _jsx("option", { value: section.label, children: section.label }, section.label)) })] }), _jsxs("label", { children: [copy.resourceType || 'Resource type', _jsx("select", { value: selectedSchema ? schemaKey(selectedSchema) : '', onChange: (event) => setSelectedSchemaKey(event.currentTarget.value), children: selectedSectionSchemas.map(candidate => _jsx("option", { value: schemaKey(candidate), children: candidate.artifactLabel }, schemaKey(candidate))) })] }), _jsxs("button", { type: "button", disabled: !selectedSchema, onClick: () => selectedSchema && setEditor({ schema: selectedSchema }), children: ["+ ", copy.addEntry || copy.add] })] }) : null, sections.map(({ label }) => {
66
77
  const sectionArtifacts = artifacts.filter((artifact) => artifact.schema.sectionLabel === label);
67
- return _jsxs("details", { open: true, children: [_jsxs("summary", { children: [label, " (", sectionArtifacts.length, ")"] }), _jsx("div", { className: "uhcp-clinical-bundle-draft-add", children: sectionSchemas.map((candidate) => _jsx(ClinicalActionButton, { kind: "add", label: `${copy.add} ${candidate.artifactLabel}`, onClick: () => setEditor({ schema: candidate }) }, schemaKey(candidate))) }), sectionArtifacts.length === 0 ? _jsx("p", { children: copy.empty }) : _jsx("ul", { children: sectionArtifacts.map((artifact) => {
78
+ return _jsxs("details", { open: true, children: [_jsxs("summary", { children: [label, " (", sectionArtifacts.length, ")"] }), sectionArtifacts.length === 0 ? _jsx("p", { children: copy.empty }) : _jsx("ul", { children: sectionArtifacts.map((artifact) => {
68
79
  const title = artifactTitle(artifact.draft);
69
80
  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
81
  }) })] }, label);
@@ -75,7 +86,7 @@ function editorRequest(editor) {
75
86
  resourceType: editor.schema.schema.resourceType,
76
87
  sectionCode: editor.schema.schema.sectionCode ?? '',
77
88
  mode: 'create',
78
- ...(editor.draft ? { artifactId: editor.draft.id, initialClaims: editor.draft.claims } : { initialClaims: editor.schema.schema.initialClaims }),
89
+ ...(editor.draft ? { artifactId: editor.draft.id, initialClaims: editor.draft.claims, initialAttachments: editor.draft.attachments } : { initialClaims: editor.schema.schema.initialClaims }),
79
90
  };
80
91
  }
81
92
  function combineDrafts(artifacts, authoringContext, documentTitle) {
@@ -97,13 +108,33 @@ function combineDrafts(artifacts, authoringContext, documentTitle) {
97
108
  }]);
98
109
  }
99
110
  for (const { draft } of artifacts) {
111
+ const attachments = draft.attachments ?? [];
112
+ const claims = {
113
+ ...operationClaims(draft),
114
+ ...(attachments.length ? { [`${draft.resourceType}.contained-reference-list`]: attachments.map(attachment => `DocumentReference/${attachment.id}`).join(',') } : {}),
115
+ };
100
116
  const entry = editor.newEntryAs(draft.resourceType, draft.id);
101
- for (const [claim, value] of Object.entries(operationClaims(draft)))
117
+ for (const [claim, value] of Object.entries(claims))
102
118
  entry.setClaim(claim, value);
103
119
  if (!(entry instanceof ClinicalResourceEntryEditor)) {
104
120
  throw new TypeError(`clinical_bundle_draft_resource_unsupported:${draft.resourceType}`);
105
121
  }
106
122
  entry.setSectionList([draft.sectionCode]);
123
+ for (const attachment of attachments) {
124
+ const document = buildDocumentReferenceFlatEntry({
125
+ entryId: attachment.id,
126
+ identifier: attachment.id,
127
+ contentType: attachment.contentType,
128
+ contentHash: attachment.contentHash,
129
+ title: attachment.name,
130
+ dataBase64: attachment.dataBase64,
131
+ });
132
+ const documentEntry = editor.newEntryAs(document.resourceType, document.entryId);
133
+ for (const [claim, value] of Object.entries(document.claims))
134
+ documentEntry.setClaim(claim, value);
135
+ documentEntry.setClaim('DocumentReference.subject', authoringContext.subjectReference);
136
+ documentEntry.setClaim('DocumentReference.author', authoringContext.dataAuthorReference);
137
+ }
107
138
  }
108
139
  return editor.buildDocument();
109
140
  }
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); }
@@ -469,7 +478,11 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
469
478
  .uhcp-clinical-bundle-draft>header p,.uhcp-clinical-bundle-draft details>p { color:var(--uhcp-muted); }
470
479
  .uhcp-clinical-bundle-draft details { background:var(--uhcp-panel-muted); border:1px solid var(--uhcp-border); border-radius:1rem; padding:.85rem 1rem; }
471
480
  .uhcp-clinical-bundle-draft summary { cursor:pointer; font-weight:800; }
472
- .uhcp-clinical-bundle-draft-add { display:flex; flex-wrap:wrap; gap:.5rem; margin-top:.8rem; }
481
+ .uhcp-clinical-bundle-draft-create { align-items:end; background:var(--uhcp-panel-muted); border:1px solid var(--uhcp-border); border-radius:1rem; display:grid; gap:.8rem; grid-template-columns:minmax(10rem,1fr) minmax(10rem,1fr) auto; padding:1rem; }
482
+ .uhcp-clinical-bundle-draft-create label { display:grid; gap:.35rem; font-weight:700; }
483
+ .uhcp-clinical-bundle-draft-create select { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:.7rem; color:var(--uhcp-text); font:inherit; min-height:2.75rem; padding:.55rem .7rem; }
484
+ .uhcp-clinical-bundle-draft-create button { background:linear-gradient(135deg,var(--uhcp-action-start),var(--uhcp-action-end)); border:0; border-radius:.75rem; color:#fff; cursor:pointer; font:inherit; font-weight:800; min-height:2.75rem; padding:.65rem 1rem; }
485
+ .uhcp-clinical-bundle-draft-create button:disabled { cursor:not-allowed; opacity:.5; }
473
486
  .uhcp-clinical-bundle-draft ul { display:grid; gap:.55rem; list-style:none; margin:.8rem 0 0; padding:0; }
474
487
  .uhcp-clinical-bundle-draft li { align-items:center; background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:.8rem; display:flex; gap:1rem; justify-content:space-between; padding:.7rem; }
475
488
  .uhcp-clinical-bundle-draft li>span { display:flex; gap:.45rem; }
@@ -479,6 +492,13 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
479
492
  .uhcp-clinical-bundle-draft>footer { display:flex; justify-content:flex-end; }
480
493
  .uhcp-clinical-bundle-draft>footer button { background:linear-gradient(135deg,var(--uhcp-action-start),var(--uhcp-action-end)); border:0; border-radius:.75rem; color:#fff; cursor:pointer; font:inherit; font-weight:800; padding:.75rem 1rem; }
481
494
  .uhcp-clinical-bundle-draft>footer button:disabled { cursor:not-allowed; opacity:.5; }
495
+ .uhcp-clinical-editor-attachments { border:1px solid var(--uhcp-border); border-radius:.85rem; display:grid; gap:.65rem; grid-column:1/-1; padding:.85rem; }
496
+ .uhcp-clinical-editor-attachments label { display:grid; gap:.35rem; }
497
+ .uhcp-clinical-editor-attachments input[type=file] { border:1px dashed var(--uhcp-border); border-radius:.7rem; padding:.65rem; }
498
+ .uhcp-clinical-editor-attachments ul { display:grid; gap:.4rem; list-style:none; margin:0; padding:0; }
499
+ .uhcp-clinical-editor-attachments li { align-items:center; background:var(--uhcp-panel-muted); border-radius:.65rem; display:flex; justify-content:space-between; padding:.5rem .65rem; }
500
+ .uhcp-clinical-editor-attachments li button { background:transparent; border:0; color:var(--uhcp-text); cursor:pointer; font-size:1.2rem; }
501
+ @media (max-width:720px) { .uhcp-clinical-bundle-draft-create { grid-template-columns:1fr; } }
482
502
  .uhcp-clinical-title { font-weight: 800; margin: 0; }
483
503
  .uhcp-clinical-type { color: var(--uhcp-muted); font-size: .72rem; font-weight: 800; letter-spacing: .06em; margin: .35rem 0 0; text-transform: uppercase; }
484
504
  .uhcp-clinical-date, .uhcp-clinical-empty { color: var(--uhcp-muted); margin: .35rem 0 0; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
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.12",
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
  },