From 8972018b75b9d3dd617a6b447694d4d305bb0cd8 Mon Sep 17 00:00:00 2001
From: sravandigitalhie
Date: Fri, 24 May 2024 10:46:44 -0400
Subject: [PATCH 1/9] Implemented Sort and Filter funtionality for Activities
Tab
---
.../summaries/ServiceRequestList.tsx | 210 ++++++++++++------
1 file changed, 140 insertions(+), 70 deletions(-)
diff --git a/src/components/summaries/ServiceRequestList.tsx b/src/components/summaries/ServiceRequestList.tsx
index e1fc39a..81cafdd 100644
--- a/src/components/summaries/ServiceRequestList.tsx
+++ b/src/components/summaries/ServiceRequestList.tsx
@@ -11,7 +11,7 @@ interface ServiceRequestListProps {
fhirDataCollection?: FHIRData[];
}
-export const ServiceRequestList: FC = ({fhirDataCollection}) => {
+export const ServiceRequestList: FC = ({ fhirDataCollection }) => {
process.env.REACT_APP_DEBUG_LOG === "true" && console.log("ServiceRequestList component RENDERED!");
const [sortedServiceRequests, setSortedServiceRequests] = useState([]);
@@ -21,7 +21,7 @@ export const ServiceRequestList: FC = ({fhirDataCollect
const [filteringOptions, setFilteringOptions] = useState<{ value: string; label: string }[]>([]);
const [hashMap, setHashMap] = useState>(new Map());
- console.log("fhirDataCollection", fhirDataCollection);
+ console.log("fhirDataCollection for service Request", fhirDataCollection);
useEffect(() => {
applySorting();
@@ -37,8 +37,8 @@ export const ServiceRequestList: FC = ({fhirDataCollect
const handleSortFilterSubmit = (sortOption: string, filterOption?: string[]) => {
setSortOption(sortOption);
- if(filterOption){
- setFilterOption(filterOption)
+ if (filterOption) {
+ setFilterOption(filterOption);
}
setShowModal(false);
};
@@ -49,10 +49,10 @@ export const ServiceRequestList: FC = ({fhirDataCollect
return;
}
- const uniqueServerNames = Array.from(new Set(fhirDataCollection.map(data => data.serverName)));
+ const uniqueServerNames = Array.from(new Set(fhirDataCollection.map(data => data.serverName).filter((name): name is string => !!name)));
const options = uniqueServerNames.map(value => ({
- value: value || '',
- label: value || '',
+ value: value,
+ label: value,
}));
setFilteringOptions(options);
@@ -65,19 +65,38 @@ export const ServiceRequestList: FC = ({fhirDataCollect
{ value: 'oldest', label: 'Date Created: Oldest' },
];
+ function convertDateFormat(dateString: string): string | null {
+ const date = new Date(dateString);
+ if (isNaN(date.getTime())) {
+ return null;
+ }
+ const month = ('0' + (date.getMonth() + 1)).slice(-2);
+ const day = ('0' + date.getDate()).slice(-2);
+ const year = date.getFullYear();
+ return `${month}/${day}/${year}`;
+ }
+
const applySorting = () => {
let serviceRequests: ServiceRequest[] = [];
-
+ const newHashMap = new Map();
+
if (fhirDataCollection) {
- fhirDataCollection.forEach(data => {
- if (data.serviceRequests) {
- serviceRequests = serviceRequests.concat(data.serviceRequests);
- }
- });
+ fhirDataCollection
+ .filter(data => filterOption.length === 0 || (data.serverName && filterOption.includes(data.serverName)))
+ .forEach(data => {
+ if (data.serviceRequests) {
+ serviceRequests = serviceRequests.concat(data.serviceRequests);
+ data.serviceRequests.forEach(service => {
+ if (data.serverName) {
+ newHashMap.set(service, data.serverName);
+ }
+ });
+ }
+ });
}
-
+
let sortedServiceRequests = [...serviceRequests];
-
+
if (sortOption === 'alphabetical-az') {
sortedServiceRequests = sortedServiceRequests.sort((a, b) => {
const nameA = a.code?.text ?? "";
@@ -91,40 +110,96 @@ export const ServiceRequestList: FC = ({fhirDataCollect
return nameB.localeCompare(nameA);
});
} else if (sortOption === 'newest') {
- serviceRequests = serviceRequests.sort((a, b) => {
- const dateA = a.occurrenceTiming?.repeat ?? "";
- const dateB = b.occurrenceTiming?.repeat ?? "";
- console.log("dateA", dateA);
- return 1;
+ sortedServiceRequests = sortedServiceRequests.sort((a, b) => {
+ let trimmedFinalDateA: string | null = null;
+ let trimmedFinalDateB: string | null = null;
+
+ const dateA = a.occurrenceTiming ?? "";
+ const dateB = b.occurrenceTiming ?? "";
+
+ if (dateA) {
+ const parsedDateA = displayTiming(dateA);
+ const indexA = parsedDateA?.search('until');
+ if (indexA !== -1 && parsedDateA) {
+ trimmedFinalDateA = convertDateFormat(String(parsedDateA).slice(0, indexA));
+ console.log("trimmedFinalDateA", trimmedFinalDateA);
+ }
+ }
+
+ if (dateB) {
+ const parsedDateB = displayTiming(dateB);
+ const indexB = parsedDateB?.search('until');
+ if (indexB !== -1 && parsedDateB) {
+ trimmedFinalDateB = convertDateFormat(String(parsedDateB).slice(0, indexB));
+ console.log("trimmedFinalDateB", trimmedFinalDateB);
+ }
+ }
+
+ if (trimmedFinalDateA && trimmedFinalDateB) {
+ return trimmedFinalDateA.localeCompare(trimmedFinalDateB);
+ } else if (trimmedFinalDateA) {
+ return -1;
+ } else if (trimmedFinalDateB) {
+ return 1;
+ } else {
+ return 0;
+ }
});
} else if (sortOption === 'oldest') {
- serviceRequests = serviceRequests.sort((a, b) => {
- const dateA = a.occurrenceTiming?.repeat ?? "";
- const dateB = b.occurrenceTiming?.repeat ?? "";
- return 1;
+ sortedServiceRequests = sortedServiceRequests.sort((a, b) => {
+ let trimmedFinalDateA: string | null = null;
+ let trimmedFinalDateB: string | null = null;
+
+ const dateA = a.occurrenceTiming ?? "";
+ const dateB = b.occurrenceTiming ?? "";
+
+ if (dateA) {
+ const parsedDateA = displayTiming(dateA);
+ const indexA = parsedDateA?.search('until');
+ if (indexA !== -1 && parsedDateA) {
+ trimmedFinalDateA = convertDateFormat(String(parsedDateA).slice(0, indexA));
+ console.log("trimmedFinalDateA", trimmedFinalDateA);
+ }
+ }
+
+ if (dateB) {
+ const parsedDateB = displayTiming(dateB);
+ const indexB = parsedDateB?.search('until');
+ if (indexB !== -1 && parsedDateB) {
+ trimmedFinalDateB = convertDateFormat(String(parsedDateB).slice(0, indexB));
+ console.log("trimmedFinalDateB", trimmedFinalDateB);
+ }
+ }
+
+ if (trimmedFinalDateA && trimmedFinalDateB) {
+ return trimmedFinalDateB.localeCompare(trimmedFinalDateA);
+ } else if (trimmedFinalDateA) {
+ return -1;
+ } else if (trimmedFinalDateB) {
+ return 1;
+ } else {
+ return 0;
+ }
});
}
-
- if (filterOption) {
- }
-
+
setSortedServiceRequests(sortedServiceRequests);
+ setHashMap(newHashMap);
};
-
-
return (
Planned Activities
- {fhirDataCollection === undefined
- && <>
Reading your clinical records...
+ {fhirDataCollection === undefined && (
+ <>
+
Reading your clinical records...
>
- }
+ )}
-{fhirDataCollection && fhirDataCollection.length === 1 ? ( // Checking for single provider
+ {fhirDataCollection && fhirDataCollection.length === 1 ? (
setShowModal(true)}>
SORT
@@ -133,7 +208,8 @@ export const ServiceRequestList: FC
= ({fhirDataCollect
SORT/FILTER
)}
- {showModal && ( // Conditional rendering of modal based on the number of providers
+
+ {showModal && (
fhirDataCollection && fhirDataCollection.length === 1 ? (
= ({fhirDataCollect
) : (
No records found.
)}
-
);
};
const buildRows = (service: ServiceRequest, theSource?: string): SummaryRowItems => {
- let rows: SummaryRowItems =
- [
- {
- isHeader: true,
- twoColumns: false,
- data1: displayConcept(service.code) ?? "No description",
- data2: '',
- },
- {
- isHeader: false,
- twoColumns: false,
- data1: service.requester === undefined ? ''
- : 'Requested by: ' + service.requester?.display,
- data2: '',
- },
- {
- isHeader: false,
- twoColumns: false,
- data1: service.occurrenceTiming === undefined ? ''
- : 'Scheduled on ' + displayTiming(service.occurrenceTiming),
- data2: '',
- },
- {
- isHeader: false,
- twoColumns: false,
- data1: service.reasonCode === undefined ? ''
- : 'Reason: ' + displayConcept(service.reasonCode?.[0]),
- data2: '',
- }
- ];
-
- const notes: SummaryRowItems | undefined = service.note?.map((note, idx) => (
+ let rows: SummaryRowItems = [
+ {
+ isHeader: true,
+ twoColumns: false,
+ data1: displayConcept(service.code) ?? "No description",
+ data2: '',
+ },
{
isHeader: false,
twoColumns: false,
- data1: note.text ? 'Note ' + (idx + 1) + ': ' + note.text : '',
+ data1: service.requester === undefined ? '' : 'Requested by: ' + service.requester?.display,
+ data2: '',
+ },
+ {
+ isHeader: false,
+ twoColumns: false,
+ data1: service.occurrenceTiming === undefined ? '' : 'Scheduled on ' + displayTiming(service.occurrenceTiming),
+ data2: '',
+ },
+ {
+ isHeader: false,
+ twoColumns: false,
+ data1: service.reasonCode === undefined ? '' : 'Reason: ' + displayConcept(service.reasonCode?.[0]),
data2: '',
}
- ));
+ ];
+
+ const notes: SummaryRowItems | undefined = service.note?.map((note, idx) => ({
+ isHeader: false,
+ twoColumns: false,
+ data1: note.text ? 'Note ' + (idx + 1) + ': ' + note.text : '',
+ data2: '',
+ }));
+
if (notes?.length) {
rows = rows.concat(notes);
}
From 4ff7945fdef5bd5c492697e8555501085f1c01b3 Mon Sep 17 00:00:00 2001
From: David Carlson
Date: Fri, 24 May 2024 14:16:23 -0600
Subject: [PATCH 2/9] Add questionnaire for Patient Priorities Care
---
.../ncqa-goal-domain-questionnaire.json | 95 ++++++++++
...patient-priorities-care-questionnaire.json | 171 ++++++++++++++++++
src/Home.tsx | 9 +-
3 files changed, 270 insertions(+), 5 deletions(-)
create mode 100644 public/content/ncqa-goal-domain-questionnaire.json
create mode 100644 public/content/patient-priorities-care-questionnaire.json
diff --git a/public/content/ncqa-goal-domain-questionnaire.json b/public/content/ncqa-goal-domain-questionnaire.json
new file mode 100644
index 0000000..812a7a1
--- /dev/null
+++ b/public/content/ncqa-goal-domain-questionnaire.json
@@ -0,0 +1,95 @@
+{
+ "resourceType": "Questionnaire",
+ "id": "questionnaire-ncqa-goal-domains",
+ "url": "http://hl7.org/Questionnaire/ncqa-goal-domains",
+ "name": "NCQA_Goal_Domains",
+ "title": "NCQA Health Outcomes",
+ "status": "draft",
+ "publisher": "NCQA",
+ "copyright": "Copyright (c) NCQA",
+ "code": [
+ {
+ "system": "http://hl7.org",
+ "code": "ncqa-goal-domains",
+ "display": "NCQA Health Outcomes"
+ }
+ ],
+ "item": [
+ {
+ "linkId": "housing-group",
+ "code": [
+ {
+ "code": "no-code",
+ "display": "No code"
+ }
+ ],
+ "text": "Housing",
+ "type": "group",
+ "required": true,
+ "item": [
+ {
+ "linkId": "housing-q1",
+ "code": [
+ {
+ "system": "http://ncqa.org/fhir/goal-domains",
+ "code": "housing",
+ "display": "Housing"
+ }
+ ],
+ "text": "Do you have acceptable housing that is appropriate for your needs?",
+ "type": "boolean"
+ },
+ {
+ "linkId": "housing-q2",
+ "code": [
+ {
+ "system": "http://ncqa.org/fhir/goal-domains",
+ "code": "housing",
+ "display": "Housing"
+ }
+ ],
+ "text": "What would you like to do?",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "linkId": "accessToServices-group",
+ "code": [
+ {
+ "code": "no-code",
+ "display": "No code"
+ }
+ ],
+ "text": "Access to Services & Supports",
+ "type": "group",
+ "required": true,
+ "item": [
+ {
+ "linkId": "accessToServices-q1",
+ "code": [
+ {
+ "system": "http://ncqa.org/fhir/goal-domains",
+ "code": "accessToServices",
+ "display": "accessToServices"
+ }
+ ],
+ "text": "Do you have the ability to access, afford, and utilize appropriate health and community resources such as transportation, food, and assistance with financial concerns?",
+ "type": "boolean"
+ },
+ {
+ "linkId": "accessToServices-q2",
+ "code": [
+ {
+ "system": "http://ncqa.org/fhir/goal-domains",
+ "code": "accessToServices",
+ "display": "accessToServices"
+ }
+ ],
+ "text": "What would you like to do?",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/public/content/patient-priorities-care-questionnaire.json b/public/content/patient-priorities-care-questionnaire.json
new file mode 100644
index 0000000..7ed3074
--- /dev/null
+++ b/public/content/patient-priorities-care-questionnaire.json
@@ -0,0 +1,171 @@
+{
+ "resourceType": "Questionnaire",
+ "id": "questionnaire-patient-priorities-care",
+ "url": "http://patientprioritiescare.org/Questionnaire/patient-priorities-care",
+ "name": "Patient_Priorities_Care",
+ "title": "Patient Priorities Care",
+ "status": "draft",
+ "publisher": "Mountain Lotus WellBeing LLC",
+ "copyright": "Copyright (c) 2024 Patient Priorities Care",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org",
+ "code": "patient-priorities-care",
+ "display": "Patient Priorities Care"
+ }
+ ],
+ "item": [
+ {
+ "linkId": "connecting-group",
+ "code": [
+ {
+ "code": "no-code",
+ "display": "No code"
+ }
+ ],
+ "text": "Connecting",
+ "type": "group",
+ "required": true,
+ "item": [
+ {
+ "linkId": "connecting-q1",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org/fhir/goal-domains",
+ "code": "connecting",
+ "display": "Connecting"
+ }
+ ],
+ "text": "Do you connect with the people, places, and spiritual practices that provide meaning, happiness, and contentment in your life?",
+ "type": "boolean"
+ },
+ {
+ "linkId": "connecting-q2",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org/fhir/goal-domains",
+ "code": "connecting",
+ "display": "Connecting"
+ }
+ ],
+ "text": "What would you like to do?",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "linkId": "functioning-group",
+ "code": [
+ {
+ "code": "no-code",
+ "display": "No code"
+ }
+ ],
+ "text": "Functioning",
+ "type": "group",
+ "required": true,
+ "item": [
+ {
+ "linkId": "functioning-q1",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org/fhir/goal-domains",
+ "code": "functioning",
+ "display": "Functioning"
+ }
+ ],
+ "text": "Do you have the capacity to perform self-care needs (independence) or maintaining self-respect (dignity) when needing assistance to perform self-care tasks?",
+ "type": "boolean"
+ },
+ {
+ "linkId": "functioning-q2",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org/fhir/goal-domains",
+ "code": "functioning",
+ "display": "Functioning"
+ }
+ ],
+ "text": "What would you like to do?",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "linkId": "managing-health-group",
+ "code": [
+ {
+ "code": "no-code",
+ "display": "No code"
+ }
+ ],
+ "text": "Managing Health",
+ "type": "group",
+ "required": true,
+ "item": [
+ {
+ "linkId": "managing-health-q1",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org/fhir/goal-domains",
+ "code": "managing-health",
+ "display": "Managing Healthe"
+ }
+ ],
+ "text": "How well do you balance the desire to maintain how you feel today (quality of life) with the desire to live as long as possible (longevity)?",
+ "type": "boolean"
+ },
+ {
+ "linkId": "managing-health-q2",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org/fhir/goal-domains",
+ "code": "managing-health",
+ "display": "Managing Health"
+ }
+ ],
+ "text": "What would you like to do?",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "linkId": "enjoying-life-group",
+ "code": [
+ {
+ "code": "no-code",
+ "display": "No code"
+ }
+ ],
+ "text": "Enjoying Life",
+ "type": "group",
+ "required": true,
+ "item": [
+ {
+ "linkId": "enjoying-life-q1",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org/fhir/goal-domains",
+ "code": "enjoying-life",
+ "display": "Enjoying Life"
+ }
+ ],
+ "text": "How well do you balance the desire to be actively engaged in all aspects of decisions regarding your health and health care with the desire to allow family, care partners, and clinicians to help make decisions on your behalf?",
+ "type": "boolean"
+ },
+ {
+ "linkId": "enjoying-life-q2",
+ "code": [
+ {
+ "system": "http://patientprioritiescare.org/fhir/goal-domains",
+ "code": "enjoying-life",
+ "display": "Enjoying Life"
+ }
+ ],
+ "text": "What would you like to do?",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Home.tsx b/src/Home.tsx
index fb06d24..3faa137 100644
--- a/src/Home.tsx
+++ b/src/Home.tsx
@@ -147,11 +147,10 @@ export default class Home extends React.Component {
pathname: '/questionnaire',
state: { patientSummaries: this.props.patientSummaries, questionnaireId: 'caregiver-strain-questionnaire' }
}} >Caregiver Strain Assessment
- {/* My Pain Assessment */}
-
-
+ My Health Priorities
{/* {(tasks === undefined)
? You have no tasks today!
From b3c9d175c9681157b9dc40b887689deb8218ed07 Mon Sep 17 00:00:00 2001
From: David Carlson
Date: Tue, 28 May 2024 08:53:23 -0600
Subject: [PATCH 3/9] Reapply changes to add Goal status that were lost in a
recent merge.
---
src/components/summaries/GoalList.tsx | 76 +++++++++++++++------------
1 file changed, 41 insertions(+), 35 deletions(-)
diff --git a/src/components/summaries/GoalList.tsx b/src/components/summaries/GoalList.tsx
index b6ce42c..d9b7ef2 100644
--- a/src/components/summaries/GoalList.tsx
+++ b/src/components/summaries/GoalList.tsx
@@ -209,35 +209,38 @@ const buildRows = (goal: GoalSummary, theSource?: string): SummaryRowItems => {
rows = rows.concat(targets);
}
- const addresses: SummaryRowItems | undefined = goal.Addresses?.map(concern => ({
+ const status: SummaryRowItem = {
isHeader: false,
twoColumns: false,
- data1: 'Addresses: ' + (concern.DisplayName ?? 'Unknown'),
+ data1: 'Status: ' + (goal.LifecycleStatus ?? 'Unknown') + (goal.AchievementStatus === null ? '' : ' -- ' + goal.AchievementStatus),
+ data2:'',
+ }
+ rows = rows.concat(status)
+
+ const addresses: SummaryRowItems | undefined = goal.Addresses?.map(focus => ({
+ isHeader: false,
+ twoColumns: false,
+ data1: 'Focus: ' + (focus.DisplayName ?? 'Unknown'),
data2: '',
}));
if (addresses?.length) {
rows = rows.concat(addresses);
}
- const learnMore: SummaryRowItem = {
- isHeader: false,
- twoColumns: false,
- data1:
- goal.LearnMore === undefined || goal.LearnMore === null ? '' : (
- {
- event.preventDefault();
- window.open(goal.LearnMore);
- }}
- >
- Learn More
-
- ),
- data2: '',
- };
- rows.push(learnMore);
+ if (goal.LearnMore !== undefined && goal.LearnMore !== null) {
+ const learnMore: SummaryRowItem = {
+ isHeader: false,
+ twoColumns: false,
+ data1:
+ { event.preventDefault(); window.open(goal.LearnMore); }
+ }>Learn More
+ ,
+ data2: '',
+ }
+ rows.push(learnMore)
+ }
const notes: SummaryRowItems | undefined = goal.Notes?.map(note => ({
isHeader: false,
@@ -249,24 +252,27 @@ const buildRows = (goal: GoalSummary, theSource?: string): SummaryRowItems => {
rows = rows.concat(notes);
}
- if (theSource) {
- const source: SummaryRowItem = {
+ const provenance: SummaryRowItems | undefined = goal.Provenance?.map((provenance) => (
+ {
isHeader: false,
twoColumns: false,
- data1: 'From ' + theSource,
- data2: '',
- };
- rows.push(source);
+ data1: 'Source: ' + provenance.Transmitter ?? '',
+ data2: provenance.Author ?? '',
+ }
+ ))
+ if (provenance?.length) {
+ rows = rows.concat(provenance)
}
- const provenance: SummaryRowItems | undefined = goal.Provenance?.map(provenance => ({
- isHeader: false,
- twoColumns: true,
- data1: 'Source: ' + (provenance.Transmitter ?? ''),
- data2: provenance.Author ?? '',
- }));
- if (provenance?.length) {
- rows = rows.concat(provenance);
+ const hasProvenance = goal.Provenance?.length ?? 0 > 0
+ if (theSource && !hasProvenance) {
+ const source: SummaryRowItem = {
+ isHeader: false,
+ twoColumns: false,
+ data1: 'Source ' + theSource,
+ data2: '',
+ }
+ rows.push(source)
}
return rows;
From fbda9bbf9a5aeca6005fb7903b75516ec44c09b1 Mon Sep 17 00:00:00 2001
From: Dan Brown
Date: Wed, 29 May 2024 17:10:31 -0400
Subject: [PATCH 4/9] Update launcher labeling #395 and #396
---
src/App.tsx | 6 +-
src/components/shared-data/ProviderLogin.tsx | 2 +-
.../unshared-data/ProviderLogin.tsx | 554 ------------------
src/data-services/persistenceService.ts | 7 +-
4 files changed, 9 insertions(+), 560 deletions(-)
delete mode 100644 src/components/unshared-data/ProviderLogin.tsx
diff --git a/src/App.tsx b/src/App.tsx
index cc7d360..26d44b5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -300,7 +300,7 @@ class App extends React.Component {
console.log('launcherData: Check for patient name...is it in here to use vs using launcherClient.tokenResponse?.patient from client itself?', launcherData)
const launcherPatientId = launcherData.patient?.id
console.log('launcherPatientId: ', launcherPatientId)
- launcherData.serverName = 'Launch Data'
+ launcherData.serverName = 'Original Provider'
// SDS
// Note that this else always happens at least once, as the launcher is always chosen first
@@ -356,7 +356,7 @@ class App extends React.Component {
const sdsData: FHIRData = await getFHIRData(true, serverUrl, this.state.supplementalDataClient,
this.setAndLogProgressState, this.setResourcesLoadedCountState, this.setAndLogErrorMessageState)
console.log('SDS data: ', sdsData)
- sdsData.serverName = 'SDS Data'
+ sdsData.serverName = 'SDS'
// Merge launcher and SDS Data and set states
const mergedFhirDataCollection: FHIRData[] = [sdsData, launcherData]
@@ -892,7 +892,7 @@ class App extends React.Component {
diff --git a/src/components/shared-data/ProviderLogin.tsx b/src/components/shared-data/ProviderLogin.tsx
index 0a2998f..22c736f 100644
--- a/src/components/shared-data/ProviderLogin.tsx
+++ b/src/components/shared-data/ProviderLogin.tsx
@@ -527,7 +527,7 @@ export default function ProviderLogin(props: Props) {
Health Provider Login
- Original Provider: {launcherEndpointFromForage ?
+ Currently includes: {launcherEndpointFromForage ?
launcherEndpointFromForage?.name :
'Unknown: Please select the original provider manually from the list in addtion to other providers.'}
diff --git a/src/components/unshared-data/ProviderLogin.tsx b/src/components/unshared-data/ProviderLogin.tsx
deleted file mode 100644
index 6bba8a3..0000000
--- a/src/components/unshared-data/ProviderLogin.tsx
+++ /dev/null
@@ -1,554 +0,0 @@
-import React from 'react'
-import { useState, useEffect } from 'react'
-import { RouteComponentProps, useHistory } from 'react-router-dom'
-import FHIR from 'fhirclient'
-import { getFHIRData } from '../../data-services/fhirService'
-import {
- ProviderEndpoint, buildAvailableEndpoints, getMatchingProviderEndpointsFromName,
- isProviderEndpointInProviderEndpoints
-} from '../../data-services/providerEndpointService'
-import {
- isGivenEndpointMatchesLastActiveEndpoint, isEndpointStillAuthorized, saveSelectedEndpoints,
- deleteSelectedEndpoints, getLauncherData
-} from '../../data-services/persistenceService'
-
-import Box from '@mui/material/Box'
-import Button from '@mui/material/Button'
-import Grid from '@mui/material/Grid'
-import Typography from '@mui/material/Typography'
-import FormControl from '@mui/material/FormControl'
-import InputLabel from '@mui/material/InputLabel'
-import Select, { SelectChangeEvent } from '@mui/material/Select'
-import { FHIRData } from '../../data-services/models/fhirResources'
-
-import { Theme, useTheme } from '@mui/material/styles'
-import OutlinedInput from '@mui/material/OutlinedInput'
-import MenuItem from '@mui/material/MenuItem'
-import Chip from '@mui/material/Chip'
-
-import { getSupplementalDataClient } from '../../data-services/fhirService'
-import Client from 'fhirclient/lib/Client'
-
-interface Props extends RouteComponentProps {
- setFhirDataStates: (data: FHIRData[] | undefined) => void,
- setAndLogProgressState: (message: string, value: number) => void,
- setResourcesLoadedCountState: (count: number) => void,
- setAndLogErrorMessageState: (errorType: string, userErrorMessage: string,
- developerErrorMessage: string, errorCaught: Error | string | unknown) => void,
- resetErrorMessageState: () => void,
-}
-
-interface LocationState {
- fhirDataCollection?: FHIRData[],
-}
-
-export default function ProviderLogin(props: Props) {
- const { fhirDataCollection } = props.location.state as LocationState
-
- let history = useHistory()
-
- const [launcherEndpointFromForage, setLauncherEndpointFromForage] =
- useState()
- const [sdsClient, setSdsClient] = useState(null)
-
- useEffect(() => {
- const fetchLauncherData = async () => {
- try {
- setLauncherEndpointFromForage(await getLauncherData())
- } catch (e) {
- console.error(`Error fetching launcher data within ProviderLogin useEffect: ${e}`)
- }
- }
- fetchLauncherData()
- }, []) // Empty for now as should only need to set on component mount because a new launcher is a re-mount
-
- useEffect(() => {
- const fetchSdsClient = async () => {
- try {
- const sdsClient: Client | undefined = await getSupplementalDataClient(null)
- if (sdsClient) {
- setSdsClient(sdsClient)
- } else {
- console.error("SDS client is untruthy")
- }
- } catch (error) {
- console.error("Error fetching SDS Client:", error)
- }
- }
- fetchSdsClient();
- }, []) // Empty dependency array to run only on component mount.
- // If we want this everytime, just call getSupplementalDataClient where needed instead
-
- const availableEndpoints: ProviderEndpoint[] = buildAvailableEndpoints()
- const [selectedEndpointNames, setselectedEndpointNames] = useState([])
-
- const authorizeSelectedEndpoints = async (endpointsToAuthorize: ProviderEndpoint[]): Promise => {
- console.log('authorizeSelectedEndpoints(): endpointsToAuthorize: ', JSON.stringify(endpointsToAuthorize))
-
- if (endpointsToAuthorize && endpointsToAuthorize.length > 0) {
- const endpointsLength = endpointsToAuthorize.length
-
- // Loop endpoints to see if any exist that are not already authorized (however unlikely that may be)
- // TODO: Consider getting all endpoints first, then after fully looping, decide what to do
- for (let i = 0; i < endpointsLength; i++) {
- const curEndpoint: ProviderEndpoint = endpointsToAuthorize[i]
- console.log("curEndpoint", curEndpoint)
- const issServerUrl = curEndpoint.config!.iss
- console.log("issServerUrl", issServerUrl)
- const isLastIndex = i === endpointsLength - 1
- console.log("isLastIndex: " + isLastIndex)
-
- // Check for prior auths from another load or session just in case so we can save some time
- if (await isEndpointStillAuthorized(issServerUrl!, false)) { // false so checking ALL endpoints in local storage vs just last one
- console.log("This endpoint IS authorized")
- console.log("curEndpoint issServerUrl " + issServerUrl + " at index " + i + " and count " + (i + 1) + "/" + endpointsLength +
- " is still authorized. Will not waste time reauthorizing: ", curEndpoint)
-
- if (isLastIndex) {
- console.log("All endpoints are already authorized.")
-
- // Do NOT need to save data for endpoints to be loaded as we don't need to reload the app
- console.log("Deleting multi-select endpoints from local storage so they don't intefere with future selections")
- deleteSelectedEndpoints()
-
- console.log("Loading data from all endpoints without leaving the application")
- await loadSelectedEndpoints(endpointsToAuthorize) // TODO: Consider returning true and having handleSubmit call this instead based on true
- }
-
- } else {
- console.log("This endpoint is NOT authorized")
- console.log("curEndpoint issServerUrl " + issServerUrl + " at index " + i +
- " and count " + (i + 1) + "/" + endpointsLength +
- " is NOT authorized.", curEndpoint)
-
- // Save selected endpoints so app load after exiting app for auth knows that it is a multi load of specific endpoints
- console.log("At Least one endpoint is not authorized yet...Saving multi-select endpoints")
- const selectedEndpointsToSave: string[] =
- endpointsToAuthorize
- .map((curEndpoint, index) => {
- if (curEndpoint.config && curEndpoint.config.iss) {
- console.log("matched endpoint: " + curEndpoint.config.iss)
- return curEndpoint.config.iss
- }
- return undefined
- })
- .filter((endpoint) => endpoint !== undefined)
- .map((endpoint) => endpoint as string)
- console.log("selectedEndpointsToSave: ", JSON.stringify(selectedEndpointsToSave))
- saveSelectedEndpoints(selectedEndpointsToSave)
-
- console.log("Reauthorizing curEndpoint.config!:", curEndpoint.config!)
- // The following authorization will exit the application. Therefore, if it's not the last index,
- // then we will have more endpoints to authorize when we return, on load.
- if (isLastIndex) {
- console.log("Authorizing last index")
- } else {
- console.log("Not last index, Authorizing index " + i)
- }
- console.error("curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
- console.error("curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
- console.error("curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
- console.error("curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
- console.error("curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
-
- FHIR.oauth2.authorize(curEndpoint.config!)
-
- console.error("b curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
- console.error("b curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
- console.error("b curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
- console.error("b curEndpoint.config! "+ JSON.stringify(curEndpoint.config!))
-
-
- break
- }
-
- }
-
- } else {
- console.log("endpointsToAuthorize is untruthy or has no data")
- }
-
- }
-
- const loadAuthorizedSelectedEndpointMulti = async (selectedEndpoint: ProviderEndpoint,
- isMultipleProviders: boolean, fhirDataCollectionIndex: number): Promise => {
- console.log('loadAuthorizedSelectedEndpointMulti(): selectedEndpoint: ' + JSON.stringify(selectedEndpoint))
- console.log('loadAuthorizedSelectedEndpointMulti(): isMultipleProviders: ' + isMultipleProviders)
- console.log('loadAuthorizedSelectedEndpointMulti(): fhirDataCollectionIndex: ' + fhirDataCollectionIndex)
-
- if (selectedEndpoint !== null) {
- const issServerUrl = selectedEndpoint.config!.iss
- console.log('issServerUrl:', issServerUrl)
-
- let fhirDataFromStoredEndpoint: FHIRData | undefined = undefined
-
- console.log("fhirDataFromStoredEndpoint = await getFHIRData(true, issServerUrl!)")
- if (selectedEndpoint.name.includes('SDS') && sdsClient) {
- console.log('loading sds data in ProviderLogin.tsx as part of a multi login')
- fhirDataFromStoredEndpoint = await getFHIRData(true, issServerUrl!, sdsClient,
- props.setAndLogProgressState, props.setResourcesLoadedCountState, props.setAndLogErrorMessageState)
- console.log('sdsData', fhirDataFromStoredEndpoint)
- fhirDataFromStoredEndpoint.serverName = selectedEndpoint.name
- } else {
- fhirDataFromStoredEndpoint = await getFHIRData(true, issServerUrl!, null,
- props.setAndLogProgressState, props.setResourcesLoadedCountState, props.setAndLogErrorMessageState)
- fhirDataFromStoredEndpoint.serverName = selectedEndpoint.name
- }
- console.log("fhirDataFromStoredEndpoint", JSON.stringify(fhirDataFromStoredEndpoint))
-
- return fhirDataFromStoredEndpoint
- } else {
- console.error("endpoint === null")
- }
- }
-
- // Note: We can't load here most of the time (unless all are already authorized, then we load here)
- // as this multiselect can only really kick off the auth logic (vs the data). After application (re)load,
- // after all authorized, we call similar logic again
- const loadSelectedEndpoints = async (endpointsToLoad: ProviderEndpoint[]): Promise => {
- console.log('loadSelectedEndpoints()')
- const fhirDataCollection: FHIRData[] = []
-
- try {
- console.log("redirecting to '/' right away as loading multiple endpoints")
- history.push('/')
-
- let index: number = 0;
- for (const curSelectedEndpoint of endpointsToLoad) {
- console.log('curSelectedEndpoint #' + (index + 1) + ' at index: ' + index + ' with value:', curSelectedEndpoint)
-
- // Reset of state to undefined for loader and error message reset have to happen after each index is loaded
- // in this multi version vs all at end like in singular version
- console.log('setting fhirData to undefined so progess indicator is triggered while new data is loaded subsequently')
- props.setFhirDataStates(undefined)
- props.resetErrorMessageState()
-
- const curFhirDataLoaded: FHIRData | undefined =
- await loadAuthorizedSelectedEndpointMulti(curSelectedEndpoint, true, index)
- if (curFhirDataLoaded) {
- curFhirDataLoaded.serverName = curSelectedEndpoint.name;
-
- console.log("curFhirDataLoaded.serverName:", curFhirDataLoaded.serverName)
- console.log("curFhirDataLoaded:", curFhirDataLoaded)
- console.log("fhirDataCollection:", fhirDataCollection)
- console.log("Adding curFhirDataLoaded to fhirDataCollection")
- fhirDataCollection.push(curFhirDataLoaded)
- console.log("fhirDataCollection:", fhirDataCollection)
- } else {
- console.error("Error: No FHIR Data loaded for the current index (" + index + "). " +
- curSelectedEndpoint?.name + " was not pushed to fhirDataCollection!")
- }
- index++;
- }
- } catch (err) {
- console.log(`Failure in loadSelectedEndpoints: ${err}`)
- // TODO: MULTI-PROVIDER: Make this a terminating error
- } finally {
- props.setFhirDataStates(fhirDataCollection!)
- console.log("fhirDataCollection complete in loadSelectedEndpoints:", fhirDataCollection)
- }
-
- }
-
- const handleSubmit = async (event: React.FormEvent) => {
- event.preventDefault()
-
- if (selectedEndpointNames) {
-
- if (selectedEndpointNames.length === 0) {
- console.log("selectedEndpoint array is empty")
- return // Cannot continue so returning but this should not be possible since we have disabled the login button in this case
- } else if (selectedEndpointNames.length > 0) {
- console.log("selectedEndpoint array has data")
-
- let matchingProviderEndpoints: ProviderEndpoint[] =
- await getMatchingProviderEndpointsFromName(availableEndpoints, selectedEndpointNames)
- console.log('matchingProviderEndpoints: ', matchingProviderEndpoints);
-
- if (matchingProviderEndpoints && matchingProviderEndpoints.length > 0) {
- console.log(`${matchingProviderEndpoints.length} additional providers selected.`)
-
- try {
- // Always include the launcher endpoint in addition to other providers selected:
- if (launcherEndpointFromForage) {
- console.log("launcherEndpointFromForage: ", launcherEndpointFromForage)
- // Only add if NOT already selected/existing somehow in matchingProviderEndpoints
- if (!isProviderEndpointInProviderEndpoints(launcherEndpointFromForage, matchingProviderEndpoints)) {
- console.log(`Adding launcher ${JSON.stringify(launcherEndpointFromForage)}
- to the beginning of the matchingProviderEndpoints ProviderEndpoint[]`)
- matchingProviderEndpoints.unshift(launcherEndpointFromForage)
- } else {
- console.log("Won't add launcher as it is already selected")
- }
- } else {
- console.error(`LauncherEndpointFromLocalStorage is
- ${launcherEndpointFromForage === null ? null : undefined}! Cannot add it to other providers...`)
- }
-
- // Always add SDS, if not null, and add it as the 2nd item in the array so that the order is:
- // 1: Launcher, 2: SDS 1..*, 3: Additional Providers
- if (sdsClient) { // TODO: Either here or in getSupplementalDataClient or in the useEffect, check URL is valid
- console.log("SDS is truthy, adding to selected endpoints")
- const sdsEndpoint: ProviderEndpoint =
- {
- // The name could be an env variable too, everything could be... 'SDS' could be there by default to enforce logic
- // If there's only ever one SDS, name could just be, SDS, and not ever be an env var
- // For now, some of this is hardcoded
- // But, if not using env vars, we could make a function that creates a name based on the client id
- // or other identifying information within the sdsClient
- name: 'SDS',
- config: {
- iss: process.env.REACT_APP_SHARED_DATA_ENDPOINT,
- redirectUri: "./index.html",
- clientId: process.env.REACT_APP_SHARED_DATA_CLIENT_ID, // only used when Shared Data is a separate FHIR server with its own SMART launch flow (which it isn't now)
- scope: process.env.REACT_APP_SHARED_DATA_SCOPE
- }
- }
- matchingProviderEndpoints.splice(1, 0, sdsEndpoint) // inject at index 1 (2nd position)
- } else {
- console.log("SDS is untruthy, not adding to selected endpoints")
- }
-
-
- // Loop selectedEndpoint logic for all available providers
- await authorizeSelectedEndpoints(matchingProviderEndpoints)
- // TODO: MULTI-PROVIDER: Consider calling loadSelectedEndpoints if we have authorizeSelectedEndpoints return
- // a boolean and base the call on that being true in the limited cases
- console.log('Finished loading multiple matching provider endpoints...');
-
- } catch (error) {
- console.error('Error loading multiple matching provider Endpoints:', error);
- }
-
- } else {
- console.error('matchingProviderEndpoints is untruthy or empty')
- }
-
- }
-
- } else {
- console.error('selectedEndpointNames is untruthy', selectedEndpointNames)
- }
-
- }
-
- // TODO: Consider this as a feature for TEST/DEBUG purposes, only visible in debug mode, to call this function, which will only load a single endpoint
- // The ProviderLogin component is for loading additional EHRs which are in addition to the launcher EHR. Therefore, the only single login in the future
- // Would be the launcher. In the future it will always be a multi-login (unless there's an error and the launcher is unknown, but multi can handle that),
- // the launcher EHR, plus 1..* additional EHRs, giving us a 2..* total logins from ProviderLogin at (almost) all times.
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const loadSelectedEndpointSingle = async (selectedEndpoint: ProviderEndpoint,
- isMultipleProviders: boolean, fhirDataCollectionIndex: number): Promise => {
- console.log('loadSelectedEndpointSingle(): selectedEndpoint: ' + selectedEndpoint)
- console.log('loadSelectedEndpointSingle(): isMultipleProviders: ' + isMultipleProviders)
- console.log('loadSelectedEndpointSingle(): fhirDataCollectionIndex: ' + fhirDataCollectionIndex)
-
- if (selectedEndpoint !== null) {
- const issServerUrl = selectedEndpoint.config!.iss
- console.log('issServerUrl:', issServerUrl)
-
- //!FUNCTION DIFF! isLastLoadMultiSelect related code including check in main if case.
- // Otherwise, we'd have a bug where we'd load multi - data when choosing single in some cases
- // Single to multi should be fine, as it will automatically expand, and call a separate function anyway
- const isLastLoadMultiSelect = fhirDataCollection && fhirDataCollection.length > 1
- if (isLastLoadMultiSelect) {
- // TODO: MULTI-PROVIDER: Add logic to allow single load after multi which retains relevant data, if any (may be a new endpoint, so may be none)
- console.log("Last load was multi-select, can't reload single select data without further logic (match url and reduce fhirDataColleciton array")
- } else {
- console.log("Last load was single-select")
- }
- if (await isGivenEndpointMatchesLastActiveEndpoint(issServerUrl!) && !isLastLoadMultiSelect) {
- console.log("is last endpoint")
- if (await isEndpointStillAuthorized(issServerUrl!, true)) { // Only checks last endpoint
- console.log("is last endpoint and IS authorized")
- console.log("Redirect w/o a refresh as the data should be in our React state already")
- // It may seem silly that a user would do this (select the same thing they are already viewing)
- // but it will happen at least by accident at some point, and thus we won't reload the data, but will just go home since the data is already there
- // TODO: Consider more error checking of fhirData, check important properties?
- // TODO: Make sure we are fully handling back button during authorization situations
- // (Should be handled with empty fhir data case, and should be handled by local storage, but need to ensure user experience makes sense in such situations.
- // If local storage becomes corrupt in production, it's external so difficult to manage - so want to ensure that is tested for all edge cases prior to prod deployment)
- if (fhirDataCollection && fhirDataCollection[fhirDataCollectionIndex]) {
- process.env.REACT_APP_TEST_PERSISTENCE === 'true' &&
- console.log("fhirData is truthy, navigating home w/o reload or passing data:",
- JSON.stringify(fhirDataCollection[fhirDataCollectionIndex]))
- if (!isMultipleProviders) {
- // If there is only one provider, and we already have the data, we can just navigate back
- history.push('/')
- } else {
- // Otherwise, if multiple providers, we need to reload the data, if it's not the first item in the colleciton
- // TODO: Add logic to check for and handle above for performance.
- // TODO: Authorization logic has to be rewritten. Need to compare fhirDataCollection to localStorage
- // For now, we need to test reauthorizing everything I think with data aggregation
- // Then support dropping reaut later. So, need to bypass a lot of this logic to test...
- console.log("is last loaded endpoint but we are loading multiple providers, reauthorizing for now",
- fhirDataCollection && fhirDataCollection[fhirDataCollectionIndex])
- FHIR.oauth2.authorize(selectedEndpoint.config!)
- }
- // TODO: Do we need to handle a case where the endpoint is the same, but the user wants to select a different patient?
- // If so, they don't need a reauth, but they do need to be redirected to choose a new patient...
- } else {
- process.env.REACT_APP_TEST_PERSISTENCE === 'true' &&
- console.log("fhirData is falsey, reauthorizing as data cannot be trusted/does not exist:",
- fhirDataCollection && fhirDataCollection[fhirDataCollectionIndex])
- // TODO: Consider externalizing logic in "NOT last endpoint but IS already/still authorized" and use that in this case
- // It should work fine as the local storage fhirAccessState should be in tact and we can fetch the data from the server w/o a reauth
- FHIR.oauth2.authorize(selectedEndpoint.config!)
- }
- } else {
- console.log("is last endpoint but is NOT authorized - reauthorizing")
- // Techincally, if needed, we could redirect w/o refresh as in the "is last endpoint and IS authorized" path,
- // but for now we are going to assume the data may have changed enough at this point to require a reauthorization
- FHIR.oauth2.authorize(selectedEndpoint.config!)
- }
- } else {
- console.log("NOT last endpoint")
- if (await isEndpointStillAuthorized(issServerUrl!, false)) { // This checks all endpoints in array, not just last endpoint accessed
- console.log("NOT last endpoint but IS already/still authorized")
- try {
- console.log('Reload data (which is NOT local at this point, other than the fhirAccessData state object) without requiring reauthorization/redirect')
- let fhirDataFromStoredEndpoint: FHIRData | undefined = undefined
- try {
- console.log('setting fhirData to undefined so progess indicator is triggered while new data is loaded subsequently')
- props.setFhirDataStates(undefined)
- props.resetErrorMessageState()
- console.log("redirecting to '/'")
- history.push('/')
- console.log("fhirDataFromStoredEndpoint = await getFHIRData(true, issServerUrl!)")
- fhirDataFromStoredEndpoint = await getFHIRData(true, issServerUrl!, null,
- props.setAndLogProgressState, props.setResourcesLoadedCountState, props.setAndLogErrorMessageState)
- fhirDataFromStoredEndpoint.serverName = selectedEndpoint.name
- } catch (err) {
- console.log(`Failure calling getFHIRData(true, issServerUrl!) from ProviderLogin.tsx handleSubmit: ${err}`)
- console.log('fallback to authorization due to above failure')
- // TODO: Add logic to ensure that fhirAccess obj and array are not updated (or are reverted) from a faulty no-auth load attempt
- // Note: We don't need to setAndLogErrorMessageState/can catch the error because we have provided an alternative application path
- // Once the application redirects, when it reloads, any error will be handled by the getFhirData call in componentDidMount
- FHIR.oauth2.authorize(selectedEndpoint.config!)
- } finally {
- console.log('Set fhir data states with Route prop directly using App callback function')
- props.setFhirDataStates([fhirDataFromStoredEndpoint!])
- }
- } catch (err) {
- // Catches if setFhirDataStates in finally fails
- console.log(`Failure setting fhir data states after getFHIRData call in ProviderLogin.tsx handleSubmit: ${err}`)
- console.log('fallback to authorization due to above failure')
- // TODO: Add logic to ensure that fhirAccess obj and array are not updated (or are reverted) from a faulty no-auth load attempt
- FHIR.oauth2.authorize(selectedEndpoint.config!)
- }
- } else {
- console.log("NOT last endpoint and NOT still authorized - reauthorizing")
- FHIR.oauth2.authorize(selectedEndpoint.config!).catch(err => {
- // Todo: Handle this (and all other redirects) properly in the UI (notify the user) and in the logic if needed
- // Also, may need a time out if the server is not returning an error and it just infinitely loads otherwise
- console.log("Failed to redirect and authorize: " + err)
- })
- }
- }
- } else {
- console.error("endpoint === null")
- }
- }
-
- const handleReset = (event: React.FormEvent) => {
- history.goBack()
- }
-
- const handleChange = (event: SelectChangeEvent) => {
- const targetVal = event.target.value
- console.log('targetVal:', targetVal)
-
- if (!targetVal) {
- console.error("selectedEndpointNames is somehow untruthy, setting to empty")
- setselectedEndpointNames([])
- } else {
- const parsedProviderEndpoints: string[] =
- typeof targetVal === 'string' ? targetVal.split(',') : targetVal
- setselectedEndpointNames(parsedProviderEndpoints)
- console.log('selectedEndpointNames (parsedProviderEndpoints)', selectedEndpointNames)
- }
- }
-
- const theme = useTheme();
- const getStyles = (availableEndpointName: string, selectedEndpointNames: string[] | null,
- theme: Theme): any => {
- return {
- fontWeight:
- selectedEndpointNames?.indexOf(availableEndpointName) === -1
- ? theme.typography.fontWeightRegular
- : theme.typography.fontWeightBold
- }
- }
-
- return (
- <>
-
-
- {/* selectedEndpointNames: {selectedEndpointNames} */}
-
-
- Health Provider Login
-
-
- Original Provider: {launcherEndpointFromForage ?
- launcherEndpointFromForage?.name :
- 'Unknown: Please select the original provider manually from the list in addtion to other providers.'}
-
-
-
-
-
-
-
-
- Select one or more additional healthcare providers
-
-
- }
- renderValue={(selected) => (
-
- {selected?.map((value) => (
-
- ))}
-
- )}
- >
- {availableEndpoints.map((availableEndpoint: ProviderEndpoint) => (
-
- {availableEndpoint.name}
-
- ))}
-
-
-
-
-
-
-
- Login
-
-
-
-
- Cancel
-
-
-
-
-
- >
- )
-
-}
diff --git a/src/data-services/persistenceService.ts b/src/data-services/persistenceService.ts
index f45bf31..20b09bb 100644
--- a/src/data-services/persistenceService.ts
+++ b/src/data-services/persistenceService.ts
@@ -386,8 +386,10 @@ export const persistLauncherData = async (clientState: fhirclient.ClientState) =
// Otherwise, it's not defined, and we need to create it
// Later, in that case, we persist it so that we can add it if missing on load
// such as would be the case with a launcher that has not been pre-configured
+ // (as is typical in the real world).
+ // TODO: Set name dynamically using get org name from capability resource, Dave knows the logic
const providerEndpointToSave: ProviderEndpoint = convertedProviderEndpoint ?? {
- name: 'Launcher (Dynamic)',
+ name: 'Original provider',
config: {
iss: clientState.serverUrl,
redirectUri: "./index.html",
@@ -398,7 +400,8 @@ export const persistLauncherData = async (clientState: fhirclient.ClientState) =
console.log("providerEndpointToSave: ", providerEndpointToSave)
if (convertedProviderEndpoint === undefined) {
- console.log("convertedProviderEndpoint === undefined, will save a dynamic launcher")
+ console.log(`convertedProviderEndpoint === undefined, will save a dynamic launcher
+ (as "Original provider") as is typical in real-world use cases`)
}
// Persist converted data
From 96f72a63524838cbf22b6a3bd03e96b7ab2dbd1c Mon Sep 17 00:00:00 2001
From: sravandigitalhie
Date: Thu, 30 May 2024 14:43:39 -0400
Subject: [PATCH 5/9] Implemented sorting logic across multiple providers and
fixed No records found bug
---
src/components/summaries/ConditionList.tsx | 130 ++++----
src/components/summaries/GoalList.tsx | 87 ++----
src/components/summaries/ImmunizationList.tsx | 123 ++++----
src/components/summaries/LabResultList.tsx | 201 ++++++-------
src/components/summaries/MedicationList.tsx | 279 ++++++++----------
.../summaries/ServiceRequestList.tsx | 182 ++++++------
src/components/summaries/VitalsList.tsx | 170 +++++------
7 files changed, 498 insertions(+), 674 deletions(-)
diff --git a/src/components/summaries/ConditionList.tsx b/src/components/summaries/ConditionList.tsx
index d947aed..081daf3 100644
--- a/src/components/summaries/ConditionList.tsx
+++ b/src/components/summaries/ConditionList.tsx
@@ -1,5 +1,5 @@
import '../../Home.css';
-import React, { FC, useEffect, useState } from 'react';
+import React, { FC, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { FHIRData, displayDate } from '../../data-services/models/fhirResources';
import { ConditionSummary } from '../../data-services/models/cqlSummary';
@@ -9,9 +9,9 @@ import { SortModal } from '../sort-modal/sortModal';
import { SortOnlyModal } from '../sort-only-modal/sortOnlyModal';
interface ConditionListProps {
- fhirDataCollection?: FHIRData[],
- conditionSummaryMatrix?: ConditionSummary[][],
- canShareData?: boolean,
+ fhirDataCollection?: FHIRData[];
+ conditionSummaryMatrix?: ConditionSummary[][];
+ canShareData?: boolean;
}
export const ConditionList: FC = ({ fhirDataCollection, conditionSummaryMatrix, canShareData }) => {
@@ -19,7 +19,7 @@ export const ConditionList: FC = ({ fhirDataCollection, cond
const [showModal, setShowModal] = useState(false);
const [sortingOption, setSortingOption] = useState('');
const [filteringOption, setFilteringOption] = useState([]);
- const [sortedAndFilteredMatrix, setSortedAndFilteredMatrix] = useState();
+ const [sortedAndFilteredConditions, setSortedAndFilteredConditions] = useState<{ condition: ConditionSummary, provider: string }[]>([]);
const [filteringOptions, setFilteringOptions] = useState<{ value: string; label: string }[]>([]);
useEffect(() => {
@@ -38,9 +38,9 @@ export const ConditionList: FC = ({ fhirDataCollection, cond
const handleSortFilterSubmit = (sortOption: string, filterOption?: string[]) => {
setSortingOption(sortOption);
- if(filterOption){
+ if (filterOption) {
setFilteringOption(filterOption);
- }
+ }
setShowModal(false);
};
@@ -58,7 +58,7 @@ export const ConditionList: FC = ({ fhirDataCollection, cond
setFilteringOptions(options);
};
-
+
const sortingOptions = [
{ value: 'alphabetical-az', label: 'Alphabetical: A-Z' },
{ value: 'alphabetical-za', label: 'Alphabetical: Z-A' },
@@ -67,77 +67,64 @@ export const ConditionList: FC = ({ fhirDataCollection, cond
];
const applySortingAndFiltering = () => {
- if (!conditionSummaryMatrix) return;
-
- let filteredAndSortedMatrix = [...conditionSummaryMatrix];
-
- if (filteringOption.length > 0 && fhirDataCollection) {
- const filteredMatrix: ConditionSummary[][] = [];
-
- // Iterate over the goalSummaryMatrix length and push empty arrays to filteredMatrix
- for (let i = 0; i < conditionSummaryMatrix!.length; i++) {
- filteredMatrix.push([]);
- }
-
- filteringOption.forEach(option => {
- // Find the index of the selected option in the filteringOptions array
- const index = filteringOptions.findIndex(item => item.value === option);
- // If index is found, push the corresponding entry from goalSummaryMatrix to filteredMatrix
- if (index !== -1) {
- filteredMatrix[index] = filteredAndSortedMatrix[index];
- }
+ if (!conditionSummaryMatrix || !fhirDataCollection) return;
+
+ // Flatten the conditionSummaryMatrix to a single array with provider information
+ let combinedConditions: { condition: ConditionSummary, provider: string }[] = [];
+ conditionSummaryMatrix.forEach((providerConditions, providerIndex) => {
+ const providerName = fhirDataCollection[providerIndex].serverName || 'Unknown';
+ providerConditions.forEach(condition => {
+ combinedConditions.push({ condition, provider: providerName });
});
+ });
- filteredAndSortedMatrix = filteredMatrix.filter(matrix => matrix !== undefined);
+ // Apply filtering
+ if (filteringOption.length > 0) {
+ combinedConditions = combinedConditions.filter(({ provider }) => filteringOption.includes(provider));
}
+ // Apply sorting
switch (sortingOption) {
case 'alphabetical-az':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.ConceptName || '').localeCompare(b.ConceptName || ''))
- );
+ combinedConditions.sort((a, b) => (a.condition.ConceptName || '').localeCompare(b.condition.ConceptName || ''));
break;
case 'alphabetical-za':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.ConceptName || '').localeCompare(a.ConceptName || ''))
- );
+ combinedConditions.sort((a, b) => (b.condition.ConceptName || '').localeCompare(a.condition.ConceptName || ''));
break;
case 'newest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.OnsetDate || '').localeCompare(a.OnsetDate || ''))
- );
+ combinedConditions.sort((a, b) => (b.condition.OnsetDate || '').localeCompare(a.condition.OnsetDate || ''));
break;
case 'oldest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.OnsetDate || '').localeCompare(b.OnsetDate || ''))
- );
+ combinedConditions.sort((a, b) => (a.condition.OnsetDate || '').localeCompare(b.condition.OnsetDate || ''));
break;
default:
break;
}
- setSortedAndFilteredMatrix(filteredAndSortedMatrix);
+ setSortedAndFilteredConditions(combinedConditions);
};
return (
-
Current Health Issues
- {fhirDataCollection === undefined
- && <>
Reading your clinical records...
+ {fhirDataCollection === undefined && (
+ <>
+
Reading your clinical records...
>
- }
+ )}
- {canShareData
- ?
- Add a Health Concern
+ {canShareData && (
+
+
+ Add a Health Concern
-
- :
}
- {fhirDataCollection && fhirDataCollection.length === 1 ? ( // Checking for single provider
+
+ )}
+
+ {fhirDataCollection && fhirDataCollection.length === 1 ? (
setShowModal(true)}>
SORT
@@ -146,7 +133,8 @@ export const ConditionList: FC
= ({ fhirDataCollection, cond
SORT/FILTER
)}
- {showModal && ( // Conditional rendering of modal based on the number of providers
+
+ {showModal && (
fhirDataCollection && fhirDataCollection.length === 1 ? (
= ({ fhirDataCollection, cond
/>
)
)}
- {
- sortedAndFilteredMatrix?.map((conditionSummary, index) => {
-
- return (
-
-
- {
- conditionSummary && conditionSummary.length > 0 && conditionSummary[0]?.ConceptName === 'init'
- ?
Loading...
- : (!conditionSummary || conditionSummary.length < 1) && fhirDataCollection !== undefined
- ?
No records found.
- :
-
- {conditionSummary?.map((cond, idx) => (
-
- ))}
-
- }
-
- )
-
- })
- }
+ {sortedAndFilteredConditions.length === 0 ? (
+ No records found.
+ ) : (
+ sortedAndFilteredConditions.map(({ condition, provider }, index) => (
+
+ ))
+ )}
- )
-
-}
+ );
+};
-const buildRows = (cond: ConditionSummary, theSource?:string): SummaryRowItems => {
+const buildRows = (cond: ConditionSummary, theSource?: string): SummaryRowItems => {
let rows: SummaryRowItems = []
const conditionName: SummaryRowItem = {
@@ -266,7 +237,6 @@ const buildRows = (cond: ConditionSummary, theSource?:string): SummaryRowItems =
}
rows.push(source)
}
-
const provenance: SummaryRowItems | undefined = cond.Provenance?.map((provenance) => (
{
diff --git a/src/components/summaries/GoalList.tsx b/src/components/summaries/GoalList.tsx
index d9b7ef2..388e636 100644
--- a/src/components/summaries/GoalList.tsx
+++ b/src/components/summaries/GoalList.tsx
@@ -19,7 +19,7 @@ export const GoalList: FC = ({ fhirDataCollection, goalSummaryMat
const [showModal, setShowModal] = useState(false);
const [sortingOption, setSortingOption] = useState('');
const [filteringOption, setFilteringOption] = useState([]);
- const [sortedAndFilteredMatrix, setSortedAndFilteredMatrix] = useState();
+ const [sortedAndFilteredGoals, setSortedAndFilteredGoals] = useState<{ goal: GoalSummary, provider: string }[]>([]);
const [filteringOptions, setFilteringOptions] = useState<{ value: string; label: string }[]>([]);
useEffect(() => {
@@ -39,7 +39,7 @@ export const GoalList: FC = ({ fhirDataCollection, goalSummaryMat
const handleSortFilterSubmit = (sortOption: string, filterOption?: string[]) => {
setSortingOption(sortOption);
if(filterOption){
- setFilteringOption(filterOption);
+ setFilteringOption(filterOption);
}
setShowModal(false);
};
@@ -67,57 +67,41 @@ export const GoalList: FC = ({ fhirDataCollection, goalSummaryMat
];
const applySortingAndFiltering = () => {
- if (!goalSummaryMatrix) return;
-
- let filteredAndSortedMatrix = [...goalSummaryMatrix];
-
- if (filteringOption.length > 0 && fhirDataCollection) {
- const filteredMatrix: GoalSummary[][] = [];
-
- // Iterate over the goalSummaryMatrix length and push empty arrays to filteredMatrix
- for (let i = 0; i < goalSummaryMatrix!.length; i++) {
- filteredMatrix.push([]);
- }
-
- filteringOption.forEach(option => {
- // Find the index of the selected option in the filteringOptions array
- const index = filteringOptions.findIndex(item => item.value === option);
- // If index is found, push the corresponding entry from goalSummaryMatrix to filteredMatrix
- if (index !== -1) {
- filteredMatrix[index] = filteredAndSortedMatrix[index];
- }
+ if (!goalSummaryMatrix || !fhirDataCollection) return;
+
+ // Flatten the goalSummaryMatrix to a single array with provider information
+ let combinedGoals: { goal: GoalSummary, provider: string }[] = [];
+ goalSummaryMatrix.forEach((providerGoals, providerIndex) => {
+ const providerName = fhirDataCollection[providerIndex].serverName || 'Unknown';
+ providerGoals.forEach(goal => {
+ combinedGoals.push({ goal, provider: providerName });
});
-
- filteredAndSortedMatrix = filteredMatrix.filter(matrix => matrix !== undefined);
+ });
+
+ // Apply filtering
+ if (filteringOption.length > 0) {
+ combinedGoals = combinedGoals.filter(({ provider }) => filteringOption.includes(provider));
}
-
+ // Apply sorting
switch (sortingOption) {
case 'alphabetical-az':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.Description || '').localeCompare(b.Description || ''))
- );
+ combinedGoals.sort((a, b) => (a.goal.Description || '').localeCompare(b.goal.Description || ''));
break;
case 'alphabetical-za':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.Description || '').localeCompare(a.Description || ''))
- );
+ combinedGoals.sort((a, b) => (b.goal.Description || '').localeCompare(a.goal.Description || ''));
break;
case 'newest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.StartDate || '').localeCompare(a.StartDate || ''))
- );
+ combinedGoals.sort((a, b) => (b.goal.StartDate || '').localeCompare(a.goal.StartDate || ''));
break;
case 'oldest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.StartDate || '').localeCompare(b.StartDate || ''))
- );
+ combinedGoals.sort((a, b) => (a.goal.StartDate || '').localeCompare(b.goal.StartDate || ''));
break;
default:
break;
}
- setSortedAndFilteredMatrix(filteredAndSortedMatrix);
+ setSortedAndFilteredGoals(combinedGoals);
};
return (
@@ -140,7 +124,7 @@ export const GoalList: FC = ({ fhirDataCollection, goalSummaryMat
)}
- {fhirDataCollection && fhirDataCollection.length === 1 ? ( // Checking for single provider
+ {fhirDataCollection && fhirDataCollection.length === 1 ? (
setShowModal(true)}>
SORT
@@ -149,7 +133,8 @@ export const GoalList: FC = ({ fhirDataCollection, goalSummaryMat
SORT/FILTER
)}
- {showModal && ( // Conditional rendering of modal based on the number of providers
+
+ {showModal && (
fhirDataCollection && fhirDataCollection.length === 1 ? (
= ({ fhirDataCollection, goalSummaryMat
)
)}
- {sortedAndFilteredMatrix?.map((goalSummary, index) => (
-
- {goalSummary && goalSummary.length > 0 && goalSummary[0]?.Description === 'init' ? (
-
Loading...
- ) : !goalSummary || goalSummary.length < 1 ? (
-
No records found.
- ) : (
-
- {goalSummary?.map((goal, idx) => (
-
- ))}
-
- )}
-
- ))}
+ {sortedAndFilteredGoals.length === 0 ? (
+ No records found.
+ ) : (
+ sortedAndFilteredGoals.map(({ goal, provider }, index) => (
+
+ ))
+ )}
);
@@ -319,4 +296,4 @@ const buildTargetValueAndDueDate = (curTarget: GoalTarget): SummaryRowItem => {
data1: curTarget.TargetValue === null ? '' : 'Target: ' + curTarget.TargetValue,
data2: curTarget.DueDate === null ? '' : 'Due: ' + curTarget.DueDate,
};
-};
+};
\ No newline at end of file
diff --git a/src/components/summaries/ImmunizationList.tsx b/src/components/summaries/ImmunizationList.tsx
index 27e5f6a..724ad31 100644
--- a/src/components/summaries/ImmunizationList.tsx
+++ b/src/components/summaries/ImmunizationList.tsx
@@ -10,12 +10,12 @@ interface ImmunizationListProps {
fhirDataCollection?: FHIRData[];
}
-export const ImmunizationList: FC = ({fhirDataCollection}) => {
+export const ImmunizationList: FC = ({ fhirDataCollection }) => {
process.env.REACT_APP_DEBUG_LOG === "true" && console.log("ImmunizationList component RENDERED!");
- const [sortedImmunizations, setSortedImmunizations] = useState([]);
const [showModal, setShowModal] = useState(false);
const [sortOption, setSortOption] = useState('');
const [filterOption, setFilterOption] = useState([]);
+ const [sortedAndFilteredImmunizations, setSortedAndFilteredImmunizations] = useState<{ immunization: Immunization, provider: string }[]>([]);
const [filteringOptions, setFilteringOptions] = useState<{ value: string; label: string }[]>([]);
useEffect(() => {
@@ -32,8 +32,8 @@ export const ImmunizationList: FC = ({fhirDataCollection}
const handleSortFilterSubmit = (sortOption: string, filterOption?: string[]) => {
setSortOption(sortOption);
- if(filterOption){
- setFilterOption(filterOption);
+ if (filterOption) {
+ setFilterOption(filterOption);
}
setShowModal(false);
};
@@ -62,55 +62,48 @@ export const ImmunizationList: FC = ({fhirDataCollection}
const applySortingAndFiltering = () => {
if (!fhirDataCollection) return;
-
- const filtered = fhirDataCollection.map(data => {
- let immunizations = data?.immunizations;
-
- // Filtering logic modified to filter based on serverName
- if (filterOption.length > 0) {
- immunizations = immunizations?.filter(immunization =>
- data.serverName && filterOption.includes(data.serverName)
- );
- }
-
- return immunizations || [];
- });
-
- // Apply sorting to the filtered immunizations
- const sorted = filtered.map(immunizations => {
- if (!immunizations) return [];
-
- // Sorting logic remains the same as before
- switch (sortOption) {
- case 'newest':
- return immunizations.sort((a, b) => {
- const dateA = a.occurrenceDateTime ?? a.recorded;
- const dateB = b.occurrenceDateTime ?? b.recorded;
- return dateB.localeCompare(dateA);
- });
- case 'oldest':
- return immunizations.sort((a, b) => {
- const dateA = a.occurrenceDateTime ?? a.recorded;
- const dateB = b.occurrenceDateTime ?? b.recorded;
- return dateA.localeCompare(dateB);
- });
- case 'alphabetical-az':
- return immunizations.sort((a, b) => {
- const nameA = a.vaccineCode?.text?.toUpperCase() ?? '';
- const nameB = b.vaccineCode?.text?.toUpperCase() ?? '';
- return nameA.localeCompare(nameB);
- });
- case 'alphabetical-za':
- return immunizations.sort((a, b) => {
- const nameA = a.vaccineCode?.text?.toUpperCase() ?? '';
- const nameB = b.vaccineCode?.text?.toUpperCase() ?? '';
- return nameB.localeCompare(nameA);
- });
- default:
- return immunizations;
- }
+
+ let combinedImmunizations: { immunization: Immunization, provider: string }[] = [];
+
+ fhirDataCollection.forEach((data, providerIndex) => {
+ const providerName = data.serverName || 'Unknown';
+ (data.immunizations || []).forEach(immunization => {
+ combinedImmunizations.push({ immunization, provider: providerName });
+ });
});
- setSortedImmunizations(sorted);
+
+ // Apply filtering
+ if (filterOption.length > 0) {
+ combinedImmunizations = combinedImmunizations.filter(({ provider }) => filterOption.includes(provider));
+ }
+
+ // Apply sorting
+ switch (sortOption) {
+ case 'alphabetical-az':
+ combinedImmunizations.sort((a, b) => (a.immunization.vaccineCode?.text || '').localeCompare(b.immunization.vaccineCode?.text || ''));
+ break;
+ case 'alphabetical-za':
+ combinedImmunizations.sort((a, b) => (b.immunization.vaccineCode?.text || '').localeCompare(a.immunization.vaccineCode?.text || ''));
+ break;
+ case 'newest':
+ combinedImmunizations.sort((a, b) => {
+ const dateA = a.immunization.occurrenceDateTime || a.immunization.recorded || '';
+ const dateB = b.immunization.occurrenceDateTime || b.immunization.recorded || '';
+ return dateB.localeCompare(dateA);
+ });
+ break;
+ case 'oldest':
+ combinedImmunizations.sort((a, b) => {
+ const dateA = a.immunization.occurrenceDateTime || a.immunization.recorded || '';
+ const dateB = b.immunization.occurrenceDateTime || b.immunization.recorded || '';
+ return dateA.localeCompare(dateB);
+ });
+ break;
+ default:
+ break;
+ }
+
+ setSortedAndFilteredImmunizations(combinedImmunizations);
};
return (
@@ -125,7 +118,7 @@ export const ImmunizationList: FC = ({fhirDataCollection}
>
)}
-{fhirDataCollection && fhirDataCollection.length === 1 ? ( // Checking for single provider
+ {fhirDataCollection && fhirDataCollection.length === 1 ? (
setShowModal(true)}>
SORT
@@ -134,7 +127,8 @@ export const ImmunizationList: FC = ({fhirDataCollection}
SORT/FILTER
)}
- {showModal && ( // Conditional rendering of modal based on the number of providers
+
+ {showModal && (
fhirDataCollection && fhirDataCollection.length === 1 ? (
= ({fhirDataCollection}
/>
)
)}
- {sortedImmunizations?.map((immunizations, idx) => (
-
- {immunizations && immunizations.length > 0 ? (
- immunizations.map((imm, mIdx) => (
-
- ))
- ) : (
-
No immunization records for this provider.
- )}
-
- ))}
+
+ {sortedAndFilteredImmunizations.length === 0 ? (
+ No records found.
+ ) : (
+ sortedAndFilteredImmunizations.map(({ immunization, provider }, index) => (
+
+ ))
+ )}
);
@@ -215,4 +206,4 @@ const buildRows = (imm: Immunization, theSource?: string): SummaryRowItems => {
}
return rows;
-};
\ No newline at end of file
+};
diff --git a/src/components/summaries/LabResultList.tsx b/src/components/summaries/LabResultList.tsx
index 6ea4628..d3d8b19 100644
--- a/src/components/summaries/LabResultList.tsx
+++ b/src/components/summaries/LabResultList.tsx
@@ -1,5 +1,5 @@
import '../../Home.css';
-import React, { FC, useState, useEffect } from 'react';
+import React, { FC, useState, useEffect } from 'react';
import { Link } from "react-router-dom";
import { FHIRData, displayDate } from '../../data-services/models/fhirResources';
import { ObservationSummary } from '../../data-services/models/cqlSummary';
@@ -9,8 +9,8 @@ import { SortModal } from '../sort-modal/sortModal';
import { SortOnlyModal } from '../sort-only-modal/sortOnlyModal';
interface LabResultListProps {
- fhirDataCollection?: FHIRData[],
- labResultSummaryMatrix?: ObservationSummary[][],
+ fhirDataCollection?: FHIRData[];
+ labResultSummaryMatrix?: ObservationSummary[][];
}
export const LabResultList: FC = ({ fhirDataCollection, labResultSummaryMatrix }) => {
@@ -18,9 +18,9 @@ export const LabResultList: FC = ({ fhirDataCollection, labR
const [showModal, setShowModal] = useState(false);
const [sortingOption, setSortingOption] = useState('');
const [filteringOption, setFilteringOption] = useState([]);
- const [sortedAndFilteredMatrix,setSortedAndFilteredMatrix] = useState();
- const [filteringOptions, setFilteringOptions] = useState<{value: string; label: string}[]>([]);
-
+ const [sortedAndFilteredLabResults, setSortedAndFilteredLabResults] = useState<{ labResult: ObservationSummary, provider: string }[]>([]);
+ const [filteringOptions, setFilteringOptions] = useState<{ value: string; label: string }[]>([]);
+
useEffect(() => {
applySortingAndFiltering();
}, [labResultSummaryMatrix, sortingOption, filteringOption]);
@@ -37,9 +37,9 @@ export const LabResultList: FC = ({ fhirDataCollection, labR
const handleSortFilterSubmit = (sortOption: string, filterOption?: string[]) => {
setSortingOption(sortOption);
- if(filterOption){
+ if (filterOption) {
setFilteringOption(filterOption);
- }
+ }
setShowModal(false);
};
@@ -66,57 +66,41 @@ export const LabResultList: FC = ({ fhirDataCollection, labR
];
const applySortingAndFiltering = () => {
- if (!labResultSummaryMatrix) return;
-
- let filteredAndSortedMatrix = [...labResultSummaryMatrix];
-
- if (filteringOption.length > 0 && fhirDataCollection) {
- const filteredMatrix: ObservationSummary[][] = [];
-
- // Iterate over the goalSummaryMatrix length and push empty arrays to filteredMatrix
- for (let i = 0; i < labResultSummaryMatrix!.length; i++) {
- filteredMatrix.push([]);
- }
-
- filteringOption.forEach(option => {
- // Find the index of the selected option in the filteringOptions array
- const index = filteringOptions.findIndex(item => item.value === option);
- // If index is found, push the corresponding entry from goalSummaryMatrix to filteredMatrix
- if (index !== -1) {
- filteredMatrix[index] = filteredAndSortedMatrix[index];
- }
+ if (!labResultSummaryMatrix || !fhirDataCollection) return;
+
+ // Flatten the labResultSummaryMatrix to a single array with provider information
+ let combinedLabResults: { labResult: ObservationSummary, provider: string }[] = [];
+ labResultSummaryMatrix.forEach((providerLabResults, providerIndex) => {
+ const providerName = fhirDataCollection[providerIndex].serverName || 'Unknown';
+ providerLabResults.forEach(labResult => {
+ combinedLabResults.push({ labResult, provider: providerName });
});
-
- filteredAndSortedMatrix = filteredMatrix.filter(matrix => matrix !== undefined);
+ });
+
+ // Apply filtering
+ if (filteringOption.length > 0) {
+ combinedLabResults = combinedLabResults.filter(({ provider }) => filteringOption.includes(provider));
}
-
+ // Apply sorting
switch (sortingOption) {
case 'alphabetical-az':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.DisplayName || '').localeCompare(b.DisplayName || ''))
- );
+ combinedLabResults.sort((a, b) => (a.labResult.DisplayName || '').localeCompare(b.labResult.DisplayName || ''));
break;
case 'alphabetical-za':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.DisplayName || '').localeCompare(a.DisplayName || ''))
- );
+ combinedLabResults.sort((a, b) => (b.labResult.DisplayName || '').localeCompare(a.labResult.DisplayName || ''));
break;
case 'newest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.Date || '').localeCompare(a.Date || ''))
- );
+ combinedLabResults.sort((a, b) => (b.labResult.Date || '').localeCompare(a.labResult.Date || ''));
break;
case 'oldest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.Date || '').localeCompare(b.Date || ''))
- );
+ combinedLabResults.sort((a, b) => (a.labResult.Date || '').localeCompare(b.labResult.Date || ''));
break;
default:
break;
}
- setSortedAndFilteredMatrix(filteredAndSortedMatrix);
+ setSortedAndFilteredLabResults(combinedLabResults);
};
return (
@@ -125,13 +109,14 @@ export const LabResultList: FC = ({ fhirDataCollection, labR
Lab Results
- {fhirDataCollection === undefined
- && <> Reading your clinical records...
+ {fhirDataCollection === undefined && (
+ <>
+ Reading your clinical records...
>
- }
+ )}
-{fhirDataCollection && fhirDataCollection.length === 1 ? ( // Checking for single provider
+ {fhirDataCollection && fhirDataCollection.length === 1 ? (
setShowModal(true)}>
SORT
@@ -140,7 +125,8 @@ export const LabResultList: FC = ({ fhirDataCollection, labR
SORT/FILTER
)}
- {showModal && ( // Conditional rendering of modal based on the number of providers
+
+ {showModal && (
fhirDataCollection && fhirDataCollection.length === 1 ? (
= ({ fhirDataCollection, labR
)
)}
- {
- sortedAndFilteredMatrix?.map((labResultSummary, index) => {
-
- return (
-
-
- {
- labResultSummary && labResultSummary.length > 0 && labResultSummary[0]?.ConceptName === 'init'
- ?
Loading...
- : (!labResultSummary || labResultSummary.length < 1) && fhirDataCollection !== undefined
- ?
No records found.
- :
-
- {labResultSummary?.map((obs, idx) => (
-
- ))}
-
- }
-
- )
-
- })
- }
-
+ {sortedAndFilteredLabResults.length === 0 ? (
+ No records found.
+ ) : (
+ sortedAndFilteredLabResults.map(({ labResult, provider }, index) => (
+
+ ))
+ )}
- )
+ );
}
-const buildRows = (obs: ObservationSummary, theSource?:string): SummaryRowItems => {
- let rows: SummaryRowItems =
- [
- {
- isHeader: true,
- twoColumns: true,
- data1: obs.DisplayName,
- data2: obs.LearnMore === undefined || obs.LearnMore === null ? '' :
- { event.preventDefault(); window.open(obs.LearnMore); }
- }>Learn More
- ,
- },
- {
- isHeader: false,
- twoColumns: true,
- data1: obs.ResultText,
- data2: displayDate(obs.Date),
- },
- {
- isHeader: false,
- twoColumns: true,
- data1: obs.ReferenceRange === null ? '' : 'Range: ' + obs.ReferenceRange,
- data2: obs.Interpretation,
- },
- /* May need to be implemented one day...
- {obs.Notes?.map((note, idx) => (
- Note: {note}
- ))} */
- ]
-
- if (theSource) {
- const source: SummaryRowItem = {
- isHeader: false,
- twoColumns: false,
- data1: 'From ' + theSource,
- data2: '',
- }
- rows.push(source)
- }
+const buildRows = (obs: ObservationSummary, theSource?: string): SummaryRowItems => {
+ let rows: SummaryRowItems = [
+ {
+ isHeader: true,
+ twoColumns: true,
+ data1: obs.DisplayName,
+ data2: obs.LearnMore === undefined || obs.LearnMore === null ? '' :
+ { event.preventDefault(); window.open(obs.LearnMore); }
+ }>Learn More
+ ,
+ },
+ {
+ isHeader: false,
+ twoColumns: true,
+ data1: obs.ResultText,
+ data2: displayDate(obs.Date),
+ },
+ {
+ isHeader: false,
+ twoColumns: true,
+ data1: obs.ReferenceRange === null ? '' : 'Range: ' + obs.ReferenceRange,
+ data2: obs.Interpretation,
+ },
+ ];
+
+ if (theSource) {
+ const source: SummaryRowItem = {
+ isHeader: false,
+ twoColumns: false,
+ data1: 'From ' + theSource,
+ data2: '',
+ };
+ rows.push(source);
+ }
+
const provenance: SummaryRowItems | undefined = obs.Provenance?.map((provenance) => (
{
isHeader: false,
twoColumns: true,
- data1: 'Source: ' + provenance.Transmitter ?? '',
+ data1: 'Source: ' + (provenance.Transmitter ?? ''),
data2: provenance.Author ?? '',
}
- ))
+ ));
if (provenance?.length) {
- rows = rows.concat(provenance)
+ rows = rows.concat(provenance);
}
- return rows
+ return rows;
}
diff --git a/src/components/summaries/MedicationList.tsx b/src/components/summaries/MedicationList.tsx
index bf78e03..04bb9db 100644
--- a/src/components/summaries/MedicationList.tsx
+++ b/src/components/summaries/MedicationList.tsx
@@ -18,106 +18,90 @@ export const MedicationList: FC = ({ fhirDataCollection, me
const [showModal, setShowModal] = useState(false);
const [sortingOption, setSortingOption] = useState('');
const [filteringOption, setFilteringOption] = useState([]);
- const [sortedAndFilteredMatrix, setSortedAndFilteredMatrix] = useState();
+ const [sortedAndFilteredMedications, setSortedAndFilteredMedications] = useState<{ medication: MedicationSummary, provider: string }[]>([]);
const [filteringOptions, setFilteringOptions] = useState<{ value: string; label: string }[]>([]);
- useEffect(() => {
- applySortingAndFiltering();
-}, [medicationSummaryMatrix, sortingOption, filteringOption]);
+ useEffect(() => {
+ applySortingAndFiltering();
+ }, [medicationSummaryMatrix, sortingOption, filteringOption]);
-useEffect(() => {
- if (medicationSummaryMatrix) {
- generateFilteringOptions();
- }
-}, [medicationSummaryMatrix]);
-
-const closeModal = () => {
- setShowModal(false);
-};
-
-const handleSortFilterSubmit = (sortOption: string, filterOption?: string[]) => {
- setSortingOption(sortOption);
- if(filterOption){
- setFilteringOption(filterOption);
+ useEffect(() => {
+ if (medicationSummaryMatrix) {
+ generateFilteringOptions();
}
- setShowModal(false);
-};
-
-const generateFilteringOptions = () => {
- if (!fhirDataCollection || fhirDataCollection.length === 0) {
- setFilteringOptions([]);
- return;
- }
-
- const uniqueServerNames = Array.from(new Set(fhirDataCollection.map(data => data.serverName)));
- const options = uniqueServerNames.map(value => ({
- value: value || '',
- label: value || '',
- }));
+ }, [medicationSummaryMatrix]);
- setFilteringOptions(options);
-};
+ const closeModal = () => {
+ setShowModal(false);
+ };
-const sortingOptions = [
- { value: 'alphabetical-az', label: 'Alphabetical: A-Z' },
- { value: 'alphabetical-za', label: 'Alphabetical: Z-A' },
- { value: 'newest', label: 'Date Created: Newest' },
- { value: 'oldest', label: 'Date Created: Oldest' },
-];
+ const handleSortFilterSubmit = (sortOption: string, filterOption?: string[]) => {
+ setSortingOption(sortOption);
+ if (filterOption) {
+ setFilteringOption(filterOption);
+ }
+ setShowModal(false);
+ };
-const applySortingAndFiltering = () => {
- if (!medicationSummaryMatrix) return;
+ const generateFilteringOptions = () => {
+ if (!fhirDataCollection || fhirDataCollection.length === 0) {
+ setFilteringOptions([]);
+ return;
+ }
- let filteredAndSortedMatrix = [...medicationSummaryMatrix];
+ const uniqueServerNames = Array.from(new Set(fhirDataCollection.map(data => data.serverName)));
+ const options = uniqueServerNames.map(value => ({
+ value: value || '',
+ label: value || '',
+ }));
+
+ setFilteringOptions(options);
+ };
+
+ const sortingOptions = [
+ { value: 'alphabetical-az', label: 'Alphabetical: A-Z' },
+ { value: 'alphabetical-za', label: 'Alphabetical: Z-A' },
+ { value: 'newest', label: 'Date Created: Newest' },
+ { value: 'oldest', label: 'Date Created: Oldest' },
+ ];
+
+ const applySortingAndFiltering = () => {
+ if (!medicationSummaryMatrix || !fhirDataCollection) return;
+
+ // Flatten the medicationSummaryMatrix to a single array with provider information
+ let combinedMedications: { medication: MedicationSummary, provider: string }[] = [];
+ medicationSummaryMatrix.forEach((providerMedications, providerIndex) => {
+ const providerName = fhirDataCollection[providerIndex].serverName || 'Unknown';
+ providerMedications.forEach(medication => {
+ combinedMedications.push({ medication, provider: providerName });
+ });
+ });
- if (filteringOption.length > 0 && fhirDataCollection) {
- const filteredMatrix: MedicationSummary[][] = [];
-
- // Iterate over the goalSummaryMatrix length and push empty arrays to filteredMatrix
- for (let i = 0; i < medicationSummaryMatrix!.length; i++) {
- filteredMatrix.push([]);
+ // Apply filtering
+ if (filteringOption.length > 0) {
+ combinedMedications = combinedMedications.filter(({ provider }) => filteringOption.includes(provider));
}
-
- filteringOption.forEach(option => {
- // Find the index of the selected option in the filteringOptions array
- const index = filteringOptions.findIndex(item => item.value === option);
- // If index is found, push the corresponding entry from goalSummaryMatrix to filteredMatrix
- if (index !== -1) {
- filteredMatrix[index] = filteredAndSortedMatrix[index];
- }
- });
-
- filteredAndSortedMatrix = filteredMatrix.filter(matrix => matrix !== undefined);
- }
-
- switch (sortingOption) {
- case 'alphabetical-az':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.ConceptName || '').localeCompare(b.ConceptName || ''))
- );
- break;
- case 'alphabetical-za':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.ConceptName || '').localeCompare(a.ConceptName || ''))
- );
- break;
- case 'newest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.AuthoredOn || '').localeCompare(a.AuthoredOn || ''))
- );
- break;
- case 'oldest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.AuthoredOn || '').localeCompare(b.AuthoredOn || ''))
- );
- break;
- default:
- break;
- }
+ // Apply sorting
+ switch (sortingOption) {
+ case 'alphabetical-az':
+ combinedMedications.sort((a, b) => (a.medication.ConceptName || '').localeCompare(b.medication.ConceptName || ''));
+ break;
+ case 'alphabetical-za':
+ combinedMedications.sort((a, b) => (b.medication.ConceptName || '').localeCompare(a.medication.ConceptName || ''));
+ break;
+ case 'newest':
+ combinedMedications.sort((a, b) => (b.medication.AuthoredOn || '').localeCompare(a.medication.AuthoredOn || ''));
+ break;
+ case 'oldest':
+ combinedMedications.sort((a, b) => (a.medication.AuthoredOn || '').localeCompare(b.medication.AuthoredOn || ''));
+ break;
+ default:
+ break;
+ }
- setSortedAndFilteredMatrix(filteredAndSortedMatrix);
-};
+ setSortedAndFilteredMedications(combinedMedications);
+ };
return (
@@ -125,12 +109,14 @@ const applySortingAndFiltering = () => {
Medications
- {fhirDataCollection === undefined
- && <>
Reading your clinical records...
+ {fhirDataCollection === undefined && (
+ <>
+
Reading your clinical records...
>
- }
- {fhirDataCollection && fhirDataCollection.length === 1 ? ( // Checking for single provider
+ )}
+
+ {fhirDataCollection && fhirDataCollection.length === 1 ? (
setShowModal(true)}>
SORT
@@ -139,7 +125,8 @@ const applySortingAndFiltering = () => {
SORT/FILTER
)}
- {showModal && ( // Conditional rendering of modal based on the number of providers
+
+ {showModal && (
fhirDataCollection && fhirDataCollection.length === 1 ? (
{
)
)}
- {
- sortedAndFilteredMatrix?.map((medicationSummary, index) => {
-
- return (
-
-
- {/* TODO:MULTI-PROVIDER: ConceptName === 'init' needs to refer to either medSumMatrix as a whole,
- or, we need to initialize all possible rows (how do we know the # ahead of time?) to init vs just the first row.
- Or, we need a better solution altogether. This applies to ALL summaries which use a summary matrix for display data
- For now though, it's irrelevant as the data is all loaded ahead of time. If it's a massive data set, it may become relevant */}
- {
- medicationSummary && medicationSummary.length > 0 && medicationSummary[0]?.ConceptName === 'init'
- ?
Loading...
- : (!medicationSummary || medicationSummary.length < 1) && fhirDataCollection !== undefined
- ?
No records found.
- :
-
- {medicationSummary?.map((med, idx) => (
-
- ))}
-
- }
-
- )
-
- })
- }
-
+ {sortedAndFilteredMedications.length === 0 ? (
+ No records found.
+ ) : (
+ sortedAndFilteredMedications.map(({ medication, provider }, index) => (
+
+ ))
+ )}
- )
+ );
}
-const buildRows = (med: MedicationSummary, theSource?:string): SummaryRowItems => {
- let rows: SummaryRowItems =
- [
- {
- isHeader: true,
- twoColumns: true,
- data1: med.ConceptName ?? "No text",
- data2: med.LearnMore === undefined || med.LearnMore === null ? '' :
- { event.preventDefault(); window.open(med.LearnMore); }
- }>Learn More
- ,
- },
- {
- isHeader: false,
- twoColumns: true,
- data1: displayDate(med.AuthoredOn),
- data2: 'By: ' + (med.Requester ?? 'Unknown'),
- },
- {
- isHeader: false,
- twoColumns: false,
- data1: med.DosageInstruction,
- data2: '',
- },
- ]
+const buildRows = (med: MedicationSummary, theSource?: string): SummaryRowItems => {
+ let rows: SummaryRowItems = [
+ {
+ isHeader: true,
+ twoColumns: true,
+ data1: med.ConceptName ?? "No text",
+ data2: med.LearnMore === undefined || med.LearnMore === null ? '' :
+ { event.preventDefault(); window.open(med.LearnMore); }
+ }>Learn More
+ ,
+ },
+ {
+ isHeader: false,
+ twoColumns: true,
+ data1: displayDate(med.AuthoredOn),
+ data2: 'By: ' + (med.Requester ?? 'Unknown'),
+ },
+ {
+ isHeader: false,
+ twoColumns: false,
+ data1: med.DosageInstruction,
+ data2: '',
+ },
+ ];
const notes: SummaryRowItems | undefined = med.Notes?.map((note) => (
{
@@ -227,9 +192,9 @@ const buildRows = (med: MedicationSummary, theSource?:string): SummaryRowItems =
data1: 'Note: ' + note,
data2: '',
}
- ))
+ ));
if (notes?.length) {
- rows = rows.concat(notes)
+ rows = rows.concat(notes);
}
if (theSource) {
@@ -238,21 +203,21 @@ const buildRows = (med: MedicationSummary, theSource?:string): SummaryRowItems =
twoColumns: false,
data1: 'From ' + theSource,
data2: '',
- }
- rows.push(source)
+ };
+ rows.push(source);
}
const provenance: SummaryRowItems | undefined = med.Provenance?.map((provenance) => (
{
isHeader: false,
twoColumns: true,
- data1: 'Source: ' + provenance.Transmitter ?? '',
+ data1: 'Source: ' + (provenance.Transmitter ?? ''),
data2: provenance.Author ?? '',
}
- ))
+ ));
if (provenance?.length) {
- rows = rows.concat(provenance)
+ rows = rows.concat(provenance);
}
- return rows
+ return rows;
}
diff --git a/src/components/summaries/ServiceRequestList.tsx b/src/components/summaries/ServiceRequestList.tsx
index 81cafdd..46c4f3b 100644
--- a/src/components/summaries/ServiceRequestList.tsx
+++ b/src/components/summaries/ServiceRequestList.tsx
@@ -14,17 +14,14 @@ interface ServiceRequestListProps {
export const ServiceRequestList: FC = ({ fhirDataCollection }) => {
process.env.REACT_APP_DEBUG_LOG === "true" && console.log("ServiceRequestList component RENDERED!");
- const [sortedServiceRequests, setSortedServiceRequests] = useState([]);
const [showModal, setShowModal] = useState(false);
const [sortOption, setSortOption] = useState('');
const [filterOption, setFilterOption] = useState([]);
+ const [sortedAndFilteredServiceRequests, setSortedAndFilteredServiceRequests] = useState<{ serviceRequest: ServiceRequest, provider: string }[]>([]);
const [filteringOptions, setFilteringOptions] = useState<{ value: string; label: string }[]>([]);
- const [hashMap, setHashMap] = useState>(new Map());
-
- console.log("fhirDataCollection for service Request", fhirDataCollection);
useEffect(() => {
- applySorting();
+ applySortingAndFiltering();
}, [fhirDataCollection, sortOption, filterOption]);
useEffect(() => {
@@ -49,10 +46,10 @@ export const ServiceRequestList: FC = ({ fhirDataCollec
return;
}
- const uniqueServerNames = Array.from(new Set(fhirDataCollection.map(data => data.serverName).filter((name): name is string => !!name)));
+ const uniqueServerNames = Array.from(new Set(fhirDataCollection.map(data => data.serverName)));
const options = uniqueServerNames.map(value => ({
- value: value,
- label: value,
+ value: value || '',
+ label: value || '',
}));
setFilteringOptions(options);
@@ -65,57 +62,49 @@ export const ServiceRequestList: FC = ({ fhirDataCollec
{ value: 'oldest', label: 'Date Created: Oldest' },
];
- function convertDateFormat(dateString: string): string | null {
- const date = new Date(dateString);
- if (isNaN(date.getTime())) {
- return null;
- }
- const month = ('0' + (date.getMonth() + 1)).slice(-2);
- const day = ('0' + date.getDate()).slice(-2);
- const year = date.getFullYear();
- return `${month}/${day}/${year}`;
- }
+ const applySortingAndFiltering = () => {
+ if (!fhirDataCollection) return;
- const applySorting = () => {
- let serviceRequests: ServiceRequest[] = [];
- const newHashMap = new Map();
-
- if (fhirDataCollection) {
- fhirDataCollection
- .filter(data => filterOption.length === 0 || (data.serverName && filterOption.includes(data.serverName)))
- .forEach(data => {
- if (data.serviceRequests) {
- serviceRequests = serviceRequests.concat(data.serviceRequests);
- data.serviceRequests.forEach(service => {
- if (data.serverName) {
- newHashMap.set(service, data.serverName);
- }
- });
- }
- });
+ let combinedServiceRequests: { serviceRequest: ServiceRequest, provider: string }[] = [];
+
+ fhirDataCollection.forEach((data, providerIndex) => {
+ const providerName = data.serverName || 'Unknown';
+ (data.serviceRequests || []).forEach(serviceRequest => {
+ combinedServiceRequests.push({ serviceRequest, provider: providerName });
+ });
+ });
+
+ // Apply filtering
+ if (filterOption.length > 0) {
+ combinedServiceRequests = combinedServiceRequests.filter(({ provider }) => filterOption.includes(provider));
}
- let sortedServiceRequests = [...serviceRequests];
+ function convertDateFormat(dateString: string): string | null {
+ const date = new Date(dateString);
+ if (isNaN(date.getTime())) {
+ return null;
+ }
+ const month = ('0' + (date.getMonth() + 1)).slice(-2);
+ const day = ('0' + date.getDate()).slice(-2);
+ const year = date.getFullYear();
+ return `${month}/${day}/${year}`;
+ }
- if (sortOption === 'alphabetical-az') {
- sortedServiceRequests = sortedServiceRequests.sort((a, b) => {
- const nameA = a.code?.text ?? "";
- const nameB = b.code?.text ?? "";
- return nameA.localeCompare(nameB);
- });
- } else if (sortOption === 'alphabetical-za') {
- sortedServiceRequests = sortedServiceRequests.sort((a, b) => {
- const nameA = a.code?.text ?? "";
- const nameB = b.code?.text ?? "";
- return nameB.localeCompare(nameA);
- });
- } else if (sortOption === 'newest') {
- sortedServiceRequests = sortedServiceRequests.sort((a, b) => {
- let trimmedFinalDateA: string | null = null;
+ // Apply sorting
+ switch (sortOption) {
+ case 'alphabetical-az':
+ combinedServiceRequests.sort((a, b) => (a.serviceRequest.code?.text || '').localeCompare(b.serviceRequest.code?.text || ''));
+ break;
+ case 'alphabetical-za':
+ combinedServiceRequests.sort((a, b) => (b.serviceRequest.code?.text || '').localeCompare(a.serviceRequest.code?.text || ''));
+ break;
+ case 'newest':
+ combinedServiceRequests.sort((a, b) => {
+ let trimmedFinalDateA: string | null = null;
let trimmedFinalDateB: string | null = null;
- const dateA = a.occurrenceTiming ?? "";
- const dateB = b.occurrenceTiming ?? "";
+ const dateA = a.serviceRequest.occurrenceTiming ?? "";
+ const dateB = b.serviceRequest.occurrenceTiming ?? "";
if (dateA) {
const parsedDateA = displayTiming(dateA);
@@ -144,47 +133,50 @@ export const ServiceRequestList: FC = ({ fhirDataCollec
} else {
return 0;
}
- });
- } else if (sortOption === 'oldest') {
- sortedServiceRequests = sortedServiceRequests.sort((a, b) => {
- let trimmedFinalDateA: string | null = null;
- let trimmedFinalDateB: string | null = null;
-
- const dateA = a.occurrenceTiming ?? "";
- const dateB = b.occurrenceTiming ?? "";
-
- if (dateA) {
- const parsedDateA = displayTiming(dateA);
- const indexA = parsedDateA?.search('until');
- if (indexA !== -1 && parsedDateA) {
- trimmedFinalDateA = convertDateFormat(String(parsedDateA).slice(0, indexA));
- console.log("trimmedFinalDateA", trimmedFinalDateA);
+ });
+ break;
+ case 'oldest':
+ combinedServiceRequests.sort((a, b) => {
+ let trimmedFinalDateA: string | null = null;
+ let trimmedFinalDateB: string | null = null;
+
+ const dateA = a.serviceRequest.occurrenceTiming ?? "";
+ const dateB = b.serviceRequest.occurrenceTiming ?? "";
+
+ if (dateA) {
+ const parsedDateA = displayTiming(dateA);
+ const indexA = parsedDateA?.search('until');
+ if (indexA !== -1 && parsedDateA) {
+ trimmedFinalDateA = convertDateFormat(String(parsedDateA).slice(0, indexA));
+ console.log("trimmedFinalDateA", trimmedFinalDateA);
+ }
}
- }
-
- if (dateB) {
- const parsedDateB = displayTiming(dateB);
- const indexB = parsedDateB?.search('until');
- if (indexB !== -1 && parsedDateB) {
- trimmedFinalDateB = convertDateFormat(String(parsedDateB).slice(0, indexB));
- console.log("trimmedFinalDateB", trimmedFinalDateB);
+
+ if (dateB) {
+ const parsedDateB = displayTiming(dateB);
+ const indexB = parsedDateB?.search('until');
+ if (indexB !== -1 && parsedDateB) {
+ trimmedFinalDateB = convertDateFormat(String(parsedDateB).slice(0, indexB));
+ console.log("trimmedFinalDateB", trimmedFinalDateB);
+ }
}
- }
-
- if (trimmedFinalDateA && trimmedFinalDateB) {
- return trimmedFinalDateB.localeCompare(trimmedFinalDateA);
- } else if (trimmedFinalDateA) {
- return -1;
- } else if (trimmedFinalDateB) {
- return 1;
- } else {
- return 0;
- }
- });
+
+ if (trimmedFinalDateA && trimmedFinalDateB) {
+ return trimmedFinalDateB.localeCompare(trimmedFinalDateA);
+ } else if (trimmedFinalDateA) {
+ return -1;
+ } else if (trimmedFinalDateB) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+ break;
+ default:
+ break;
}
- setSortedServiceRequests(sortedServiceRequests);
- setHashMap(newHashMap);
+ setSortedAndFilteredServiceRequests(combinedServiceRequests);
};
return (
@@ -228,12 +220,12 @@ export const ServiceRequestList: FC = ({ fhirDataCollec
)
)}
- {sortedServiceRequests.length > 0 ? (
- sortedServiceRequests.map((service, idx) => (
-
- ))
- ) : (
+ {sortedAndFilteredServiceRequests.length === 0 ? (
No records found.
+ ) : (
+ sortedAndFilteredServiceRequests.map(({ serviceRequest, provider }, index) => (
+
+ ))
)}
diff --git a/src/components/summaries/VitalsList.tsx b/src/components/summaries/VitalsList.tsx
index fa5aba0..1047810 100644
--- a/src/components/summaries/VitalsList.tsx
+++ b/src/components/summaries/VitalsList.tsx
@@ -1,5 +1,5 @@
import '../../Home.css';
-import React, { FC, useState, useEffect } from 'react';
+import React, { FC, useState, useEffect } from 'react';
import { FHIRData, displayDate } from '../../data-services/models/fhirResources';
import { ObservationSummary } from '../../data-services/models/cqlSummary';
import { Summary, SummaryRowItem, SummaryRowItems } from './Summary';
@@ -8,8 +8,8 @@ import { SortModal } from '../sort-modal/sortModal';
import { SortOnlyModal } from '../sort-only-modal/sortOnlyModal';
interface VitalsListProps {
- fhirDataCollection?: FHIRData[],
- vitalSignSummaryMatrix?: ObservationSummary[][],
+ fhirDataCollection?: FHIRData[];
+ vitalSignSummaryMatrix?: ObservationSummary[][];
}
export const VitalsList: FC = ({ fhirDataCollection, vitalSignSummaryMatrix }) => {
@@ -17,8 +17,8 @@ export const VitalsList: FC = ({ fhirDataCollection, vitalSignS
const [showModal, setShowModal] = useState(false);
const [sortingOption, setSortingOption] = useState('');
const [filteringOption, setFilteringOption] = useState([]);
- const [sortedAndFilteredMatrix,setSortedAndFilteredMatrix] = useState();
- const [filteringOptions, setFilteringOptions] = useState<{value: string;label: string}[]>([]);
+ const [sortedAndFilteredVitals, setSortedAndFilteredVitals] = useState<{ vital: ObservationSummary, provider: string }[]>([]);
+ const [filteringOptions, setFilteringOptions] = useState<{ value: string; label: string }[]>([]);
useEffect(() => {
applySortingAndFiltering();
@@ -36,9 +36,9 @@ export const VitalsList: FC = ({ fhirDataCollection, vitalSignS
const handleSortFilterSubmit = (sortOption: string, filterOption?: string[]) => {
setSortingOption(sortOption);
- if(filterOption){
+ if (filterOption) {
setFilteringOption(filterOption);
- }
+ }
setShowModal(false);
};
@@ -65,72 +65,57 @@ export const VitalsList: FC = ({ fhirDataCollection, vitalSignS
];
const applySortingAndFiltering = () => {
- if (!vitalSignSummaryMatrix) return;
-
- let filteredAndSortedMatrix = [...vitalSignSummaryMatrix];
-
- if (filteringOption.length > 0 && fhirDataCollection) {
- const filteredMatrix: ObservationSummary[][] = [];
-
- // Iterate over the goalSummaryMatrix length and push empty arrays to filteredMatrix
- for (let i = 0; i < vitalSignSummaryMatrix!.length; i++) {
- filteredMatrix.push([]);
- }
-
- filteringOption.forEach(option => {
- // Find the index of the selected option in the filteringOptions array
- const index = filteringOptions.findIndex(item => item.value === option);
- // If index is found, push the corresponding entry from goalSummaryMatrix to filteredMatrix
- if (index !== -1) {
- filteredMatrix[index] = filteredAndSortedMatrix[index];
- }
+ if (!vitalSignSummaryMatrix || !fhirDataCollection) return;
+
+ // Flatten the vitalSignSummaryMatrix to a single array with provider information
+ let combinedVitals: { vital: ObservationSummary, provider: string }[] = [];
+ vitalSignSummaryMatrix.forEach((providerVitals, providerIndex) => {
+ const providerName = fhirDataCollection[providerIndex].serverName || 'Unknown';
+ providerVitals.forEach(vital => {
+ combinedVitals.push({ vital, provider: providerName });
});
-
- filteredAndSortedMatrix = filteredMatrix.filter(matrix => matrix !== undefined);
+ });
+
+ // Apply filtering
+ if (filteringOption.length > 0) {
+ combinedVitals = combinedVitals.filter(({ provider }) => filteringOption.includes(provider));
}
-
+ // Apply sorting
switch (sortingOption) {
case 'alphabetical-az':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.DisplayName || '').localeCompare(b.DisplayName || ''))
- );
+ combinedVitals.sort((a, b) => (a.vital.DisplayName || '').localeCompare(b.vital.DisplayName || ''));
break;
case 'alphabetical-za':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.DisplayName || '').localeCompare(a.DisplayName || ''))
- );
+ combinedVitals.sort((a, b) => (b.vital.DisplayName || '').localeCompare(a.vital.DisplayName || ''));
break;
case 'newest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (b.Date || '').localeCompare(a.Date || ''))
- );
+ combinedVitals.sort((a, b) => (b.vital.Date || '').localeCompare(a.vital.Date || ''));
break;
case 'oldest':
- filteredAndSortedMatrix = filteredAndSortedMatrix.map(providerGoals =>
- providerGoals.sort((a, b) => (a.Date || '').localeCompare(b.Date || ''))
- );
+ combinedVitals.sort((a, b) => (a.vital.Date || '').localeCompare(b.vital.Date || ''));
break;
default:
break;
}
- setSortedAndFilteredMatrix(filteredAndSortedMatrix);
+ setSortedAndFilteredVitals(combinedVitals);
};
-
+
return (
Vitals
- {fhirDataCollection === undefined
- && <>
Reading your clinical records...
+ {fhirDataCollection === undefined && (
+ <>
+
Reading your clinical records...
>
- }
+ )}
-{fhirDataCollection && fhirDataCollection.length === 1 ? ( // Checking for single provider
+ {fhirDataCollection && fhirDataCollection.length === 1 ? (
setShowModal(true)}>
SORT
@@ -139,7 +124,8 @@ export const VitalsList: FC
= ({ fhirDataCollection, vitalSignS
SORT/FILTER
)}
- {showModal && ( // Conditional rendering of modal based on the number of providers
+
+ {showModal && (
fhirDataCollection && fhirDataCollection.length === 1 ? (
= ({ fhirDataCollection, vitalSignS
)
)}
- {
- sortedAndFilteredMatrix?.map((vitalSignSummary, index) => {
-
- return (
-
-
- {
- vitalSignSummary && vitalSignSummary.length > 0 && vitalSignSummary[0]?.ConceptName === 'init'
- ?
Loading...
- : (!vitalSignSummary || vitalSignSummary.length < 1) && fhirDataCollection !== undefined
- ?
No records found.
- :
-
- {vitalSignSummary?.map((obs, idx) => (
-
- ))}
-
- }
-
- )
-
- })
- }
-
+ {sortedAndFilteredVitals.length === 0 ? (
+ No records found.
+ ) : (
+ sortedAndFilteredVitals.map(({ vital, provider }, index) => (
+
+ ))
+ )}
- )
+ );
}
-const buildRows = (obs: ObservationSummary, theSource?:string): SummaryRowItems => {
- let rows: SummaryRowItems =
- [
- {
- isHeader: true,
- twoColumns: false,
- data1: obs.DisplayName,
- data2: '',
- },
- {
- isHeader: false,
- twoColumns: true,
- data1: obs.ResultText,
- data2: displayDate(obs.Date),
- },
- {
- isHeader: false,
- twoColumns: false,
- data1: "Performed by: " + (obs.Performer ?? 'Unknown'),
- data2: '',
- },
- ]
-
+const buildRows = (obs: ObservationSummary, theSource?: string): SummaryRowItems => {
+ let rows: SummaryRowItems = [
+ {
+ isHeader: true,
+ twoColumns: false,
+ data1: obs.DisplayName,
+ data2: '',
+ },
+ {
+ isHeader: false,
+ twoColumns: true,
+ data1: obs.ResultText,
+ data2: displayDate(obs.Date),
+ },
+ {
+ isHeader: false,
+ twoColumns: false,
+ data1: "Performed by: " + (obs.Performer ?? 'Unknown'),
+ data2: '',
+ },
+ ];
if (theSource) {
const rowItem: SummaryRowItem = {
@@ -221,21 +188,18 @@ const buildRows = (obs: ObservationSummary, theSource?:string): SummaryRowItems
};
rows.push(rowItem);
}
-
-
-
const provenance: SummaryRowItems | undefined = obs.Provenance?.map((provenance) => (
{
isHeader: false,
twoColumns: true,
- data1: 'Source: ' + provenance.Transmitter ?? '',
+ data1: 'Source: ' + (provenance.Transmitter ?? ''),
data2: provenance.Author ?? '',
}
- ))
+ ));
if (provenance?.length) {
- rows = rows.concat(provenance)
+ rows = rows.concat(provenance);
}
- return rows
+ return rows;
}
From 92426336582613c80c5fdaa47d2b33d25f1e80cb Mon Sep 17 00:00:00 2001
From: Sai Kiran Reddy Konda
Date: Thu, 30 May 2024 15:39:40 -0400
Subject: [PATCH 6/9] changed the logic to show HHS warning banner only when
the env varibale is set to true
---
.env | 4 ++--
src/Home.tsx | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.env b/.env
index 3e23442..4964048 100644
--- a/.env
+++ b/.env
@@ -17,7 +17,7 @@ REACT_APP_SHARED_DATA_REDIRECT_URI="./index.html"
REACT_APP_MELD_SANDBOX_NAME=MCCDevelopment
-REACT_APP_MELD_SANDBOX_CLIENT_ID=<...>
+REACT_APP_MELD_SANDBOX_CLIENT_ID=5fa54c47-ed80-405b-a0b7-611eee5d0159
# Logica sandbox
REACT_APP_CLIENT_ID_logica=<...>
@@ -91,4 +91,4 @@ REACT_APP_LOG_ENDPOINT_URI="http://localhost:8085"
REACT_APP_VERSION="version - 2.1.2"
# Set to have HHS Banner
-# REACT_APP_HHS_BANNER=true
\ No newline at end of file
+REACT_APP_HHS_BANNER=<...>
\ No newline at end of file
diff --git a/src/Home.tsx b/src/Home.tsx
index 3faa137..1a4ec5c 100644
--- a/src/Home.tsx
+++ b/src/Home.tsx
@@ -61,7 +61,7 @@ export default class Home extends React.Component {
let screenings = this.props.screenings?.filter(s => s.notifyPatient)
// let tasks = this.props.tasks;
- const hhsBanner = process.env.REACT_APP_HHS_BANNER === 'false'
+ const hhsBanner = process.env.REACT_APP_HHS_BANNER === 'true'
return (
From 4f4bd8c8efc0a058b9befbad65816ccfaeddfad8 Mon Sep 17 00:00:00 2001
From: Sai Kiran Reddy Konda
Date: Thu, 30 May 2024 15:41:01 -0400
Subject: [PATCH 7/9] changed the logic to show HHS warning banner only when
the env varibale is set to true
---
.env | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.env b/.env
index 4964048..ac631f3 100644
--- a/.env
+++ b/.env
@@ -17,7 +17,7 @@ REACT_APP_SHARED_DATA_REDIRECT_URI="./index.html"
REACT_APP_MELD_SANDBOX_NAME=MCCDevelopment
-REACT_APP_MELD_SANDBOX_CLIENT_ID=5fa54c47-ed80-405b-a0b7-611eee5d0159
+REACT_APP_MELD_SANDBOX_CLIENT_ID=<...>
# Logica sandbox
REACT_APP_CLIENT_ID_logica=<...>
From 41563712974842e324bbf07775e59302992974da Mon Sep 17 00:00:00 2001
From: Sai Kiran Reddy Konda
Date: Fri, 31 May 2024 10:51:58 -0400
Subject: [PATCH 8/9] increased the Z-Index for banner page to make sure the
progress bar goes behind whenever there is a Warning banner
---
src/components/modal/modal.css | 26 +++++++++++++++++---------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/src/components/modal/modal.css b/src/components/modal/modal.css
index fa57f72..a83b9cf 100644
--- a/src/components/modal/modal.css
+++ b/src/components/modal/modal.css
@@ -9,13 +9,6 @@ p {
color: #0056B3;
}
-.modal-body {
- color: var(--color-dark);
- border: 1px solid #000000;
- padding: 10px;
- border-radius: 0 0 5px 5px;
-}
-
.modal-style {
position: absolute;
top: 50%;
@@ -27,5 +20,20 @@ p {
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
- }
-
\ No newline at end of file
+ z-index: 1001; /* Higher z-index to ensure it appears above other elements */
+}
+
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
+ z-index: 1000; /* Ensure overlay appears above other content */
+}
+
+.modal-content {
+ color: var(--color-dark);
+ padding: 10px;
+}
From 38a7670b8ebaa58ff1a3b395d1ca931ae1e05a42 Mon Sep 17 00:00:00 2001
From: Sean <>
Date: Fri, 31 May 2024 12:38:47 -0400
Subject: [PATCH 9/9] Updates for releae 2.2.1
Modified version
removed health quesitonaire
removed cancel from quesitonaire
---
.env | 4 ++--
src/Home.tsx | 5 +----
.../questionnaire-item/QuestionnaireItemComponent.tsx | 2 +-
3 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/.env b/.env
index ac631f3..4d7f9ef 100644
--- a/.env
+++ b/.env
@@ -89,6 +89,6 @@ REACT_APP_LOG_ENABLED=false
REACT_APP_LOG_API_KEY=<...>
REACT_APP_LOG_ENDPOINT_URI="http://localhost:8085"
-REACT_APP_VERSION="version - 2.1.2"
+REACT_APP_VERSION="version - 2.2.1"
# Set to have HHS Banner
-REACT_APP_HHS_BANNER=<...>
\ No newline at end of file
+REACT_APP_HHS_BANNER=false
diff --git a/src/Home.tsx b/src/Home.tsx
index 1a4ec5c..b88be9b 100644
--- a/src/Home.tsx
+++ b/src/Home.tsx
@@ -147,11 +147,8 @@ export default class Home extends React.Component {
pathname: '/questionnaire',
state: { patientSummaries: this.props.patientSummaries, questionnaireId: 'caregiver-strain-questionnaire' }
}} >Caregiver Strain Assessment
- My Health Priorities
+
{/* {(tasks === undefined)
? You have no tasks today!
:
diff --git a/src/components/questionnaire-item/QuestionnaireItemComponent.tsx b/src/components/questionnaire-item/QuestionnaireItemComponent.tsx
index fb7ee37..680fcf2 100644
--- a/src/components/questionnaire-item/QuestionnaireItemComponent.tsx
+++ b/src/components/questionnaire-item/QuestionnaireItemComponent.tsx
@@ -230,7 +230,7 @@ export default class QuestionnaireItemComponent extends React.Component
this.handleNextQuestionScroll(event.target.value)}>Next
- Cancel
+
Leave questionnaire?