Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(refactor) Add workflows to pmtct summary in patient chart #1868

Merged
merged 1 commit into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/esm-commons-lib/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export async function fetchMambaReportData(reportId: string) {
}
}

export function useDataFetch(
export function fetchEtlData(
reportType: 'fetchMambaAncData' | 'MotherHivStatus',
reportId?: string,
patientUuid?: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fetchPatientLastEncounter } from '../../api/api';

export function fetchLatestEncountersOfTypes(patientUuid: string, encounterTypes: string[]) {
return Promise.all(encounterTypes.map((type) => fetchPatientLastEncounter(patientUuid, type)));
return Promise.all(encounterTypes?.map((type) => fetchPatientLastEncounter(patientUuid, type)));
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,27 @@ export const SummaryCard: React.FC<SummaryCardProps> = ({ patientUuid, columns,
const [groupedEncounterMappings, setGroupedEncounterMappings] = useState<Array<any>>([]);

useEffect(() => {
Promise.all(columns.map((column) => fetchLatestEncountersOfTypes(patientUuid, column.encounterTypes))).then(
(results) => {
const filteredResults = results.map((result) => result.filter(Boolean));
setColumnEncountersMappings(columns.map((column, index) => ({ column, encounters: filteredResults[index] })));
setIsLoading(false);
},
);
Promise.all(
columns.map((column) => {
const encounterTypes = Array.isArray(column?.encounterTypes)
? column.encounterTypes
: column?.encounterTypes
? [column.encounterTypes]
: [];

return fetchLatestEncountersOfTypes(patientUuid, encounterTypes);
}),
).then((results) => {
const filteredResults = results.map((result) => result.filter(Boolean));
setColumnEncountersMappings(
columns.map((column, index) => ({
column,
encounters: filteredResults[index],
})),
);

setIsLoading(false);
});
}, [columns, patientUuid]);

useEffect(() => {
Expand Down Expand Up @@ -88,7 +102,7 @@ function SummaryItem({ column, encounters }) {
<div className={styles.tileBoxColumn}>
<span className={styles.tileTitle}> {column.header} </span>
<span className={styles.tileValue}>
<LazyCell lazyValue={column.getObsValue(encounters)} />
{column?.getObsValue ? <LazyCell lazyValue={column?.getObsValue(encounters)} /> : '--'}
</span>
{column.getObsSummary && (
<span className={styles.tileTitle}>
Expand Down
2 changes: 1 addition & 1 deletion packages/esm-commons-lib/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const daysDurationUnit = {
export const basePath = '${openmrsSpaBase}/patient/';
export const encounterRepresentation =
'custom:(uuid,encounterDatetime,encounterType,location:(uuid,name),' +
'patient:(uuid,display),encounterProviders:(uuid,provider:(uuid,name)),' +
'patient:(uuid,display,age),encounterProviders:(uuid,provider:(uuid,name)),' +
'obs:(uuid,obsDatetime,voided,groupMembers,concept:(uuid,name:(uuid,name)),value:(uuid,name:(uuid,name),' +
'names:(uuid,conceptNameType,name))),form:(uuid,name))';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ interface ConditionalActionProps {
label: string;
package: string;
formName: string;
dependsOn: string;
dependantConcept: string;
dependsOn?: string;
dependantConcept?: string;
dependantEncounter?: string;
}

interface ColumnDefinition {
Expand All @@ -52,6 +53,12 @@ interface ColumnDefinition {
statusColorMappings?: Record<string, string>;
isConditionalConcept?: boolean;
conditionalConceptMappings?: Record<string, string>;
conditionalEncounterMappings?: Record<string, ConditionalEncounterMapping>;
}

export interface ConditionalEncounterMapping {
concept: string;
isDate?: boolean;
}

interface LaunchOptions {
Expand Down Expand Up @@ -86,14 +93,13 @@ export const getTabColumns = (columnsDefinition: Array<ColumnDefinition>) => {
getValue: (encounter) => {
if (column.id === 'actions') {
const conditionalActions = [];
const baseActions = column.actionOptions.map((action: ActionProps) => ({
const baseActions = column.actionOptions?.map((action: ActionProps) => ({
form: { name: action.formName, package: action.package },
encounterUuid: encounter.uuid,
intent: '*',
label: action.label,
mode: action.mode,
}));

if (column?.conditionalActionOptions?.length) {
column?.conditionalActionOptions?.map((action) => {
const dependantObsValue = getObsFromEncounter(encounter, action.dependantConcept);
Expand All @@ -107,6 +113,18 @@ export const getTabColumns = (columnsDefinition: Array<ColumnDefinition>) => {
mode: action.mode,
});
}

const dependantEncounterValue = encounter.encounterType.uuid;

if (dependantEncounterValue === action.dependantEncounter) {
return conditionalActions.push({
form: { name: action.formName, package: action.package },
encounterUuid: encounter.uuid,
intent: '*',
label: action.label,
mode: action.mode,
});
}
});
}
return [...baseActions, ...conditionalActions];
Expand Down
18 changes: 17 additions & 1 deletion packages/esm-commons-lib/src/utils/encounter-list-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export function getObsFromEncounter(
) {
let obs = findObs(encounter, obsConcept);

if (!encounter || !obsConcept) {
return '--';
}

if (isTrueFalseConcept) {
if (
(obs?.value?.uuid != 'cf82933b-3f3f-45e7-a5ab-5d31aaee3da3' && obs?.value?.name?.name !== 'Unknown') ||
Expand All @@ -113,6 +117,14 @@ export function getObsFromEncounter(
return fetchMotherName(encounter.patient.uuid);
}

if (type === 'visitType') {
return encounter.encounterType.name;
}

if (type === 'ageAtHivTest') {
return encounter.patient.age;
}

if (secondaryConcept && typeof obs.value === 'object' && obs.value.names) {
const primaryValue =
obs.value.names.find((conceptName) => conceptName.conceptNameType === 'SHORT')?.name || obs.value.name.name;
Expand All @@ -132,7 +144,11 @@ export function getObsFromEncounter(
}

if (isDate) {
return formatDate(parseDate(obs.value), { mode: 'wide' });
if (typeof obs.value === 'object' && obs.value?.names) {
return formatDate(parseDate(obs.obsDatetime), { mode: 'wide' });
} else {
return formatDate(parseDate(obs.value), { mode: 'wide' });
}
}

if (typeof obs.value === 'object' && obs.value?.names) {
Expand Down
2 changes: 1 addition & 1 deletion packages/esm-commons-lib/src/utils/schema-manipulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function extractSchemaValues(schema) {
if (obj === null || obj === undefined || typeof obj !== 'object') {
return;
}
Object.entries(obj)?.forEach(([key, value]) => {
Object.entries(obj).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
if (typeof value === 'object' && !Array.isArray(value)) {
traverse(value);
Expand Down
75 changes: 67 additions & 8 deletions packages/esm-commons-lib/src/utils/summary-card-config-builder.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import isEmpty from 'lodash-es/isEmpty';
import { fetchEtlData } from '../api/api';
import { getObsFromEncounter, getConditionalConceptValue } from './encounter-list-utils';
import { extractSchemaValues, replaceWithConfigDefaults } from './schema-manipulation';

Expand All @@ -13,6 +15,27 @@ export const getSummaryCardProps = (schemaConfig, config = null) => {
if (column.isConditionalConcept) {
return getConditionalConceptValue(encounter, column.conditionalConceptMappings, column.isDate);
}

if (column?.mambaEtlData === 'ancVisits') {
const response = fetchEtlData('fetchMambaAncData', 'no_of_anc_visits', encounter?.patient?.uuid);
return response?.data;
}

if (column?.mambaEtlData === 'motherStatus') {
const response = fetchEtlData('fetchMambaAncData', 'mother_status', encounter?.patient?.uuid);
return response?.data;
}

if (column?.mambaEtlData === 'deliveryDate') {
const response = fetchEtlData('fetchMambaAncData', 'estimated_date_of_delivery', encounter?.patient?.uuid);
return response?.data;
}

if (column?.mambaEtlData === 'motherHivStatus') {
const response = fetchEtlData('fetchMambaAncData', 'mother_hiv_status', encounter?.patient?.uuid);
return response?.data;
}

return getObsFromEncounter(
encounter,
column.concept,
Expand All @@ -23,16 +46,52 @@ export const getSummaryCardProps = (schemaConfig, config = null) => {
);
},
getObsSummary: async (encounters) => {
const summaryValues = encounters.map((encounter) => {
if (encounter && encounter.observation && encounter.observation.value) {
return encounter.observation.value.join(', ');
} else {
return '';
}
});
return summaryValues.join(' | ');
if (column?.conditionalEncounterMappings && Object.keys(column?.conditionalEncounterMappings)?.length > 0 && column.encounterTypes?.length > 0) {
const filteredEncounters = encounters.filter((encounter) =>
column.encounterTypes.includes(encounter.encounterType.uuid),
);
let latestEncounter = null;
let latestValue = null;

filteredEncounters.forEach((encounter) => {
if (encounter.obs && Array.isArray(encounter.obs)) {
encounter.obs.forEach((observation) => {
if (observation.concept.uuid === column.concept) {
if (!latestEncounter || encounter.encounterDatetime > latestEncounter.encounterDatetime) {
latestEncounter = encounter;
}
latestValue = getObsFromEncounter(latestEncounter, column.concept, column.isDate);
}
});
}
});
return latestValue;
} else {
const summaryValues = encounters.map((encounter) => {
if (column.type === 'nextAppointmentDate') {
let nextVisitDate = getObsFromEncounter(encounter[0], column.concept, true);
if (nextVisitDate !== '--') {
const days = calculateDateDifferenceInDate(nextVisitDate);
nextVisitDate = nextVisitDate > 0 ? `In ${days}` : '';
}
return nextVisitDate;
}
if (encounter && encounter.observation && !isEmpty(encounter.observation.value)) {
return encounter.observation.value.join(', ');
} else {
return '';
}
});
return summaryValues?.length > 0 ? summaryValues.filter((val) => val !== '').join(' | ') : '';
}
},
}));

return columns;
};

export const calculateDateDifferenceInDate = (givenDate: string): string => {
const dateDifference = new Date(givenDate).getTime() - new Date().getTime();
const totalDays = Math.floor(dateDifference / (1000 * 3600 * 24));
return `${totalDays} day(s)`;
};
27 changes: 14 additions & 13 deletions packages/esm-ohri-pmtct-app/src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,31 @@ export const configSchema = {
_type: Type.Object,
_description: 'List of forms for PMTCT.',
_default: {
antenatal: 'Antenatal Form',
labourAndDelivery: 'Labour & Delivery Form',
motherPostnatal: 'Mother - Postnatal Form',
infantPostnatal: 'Infant - Postanal Form',
antenatalFormName: 'Antenatal Form',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't changing these keys affect implementations that are already used to these keys.

labourAndDeliveryFormName: 'Labour & Delivery Form',
motherPostnatalFormName: 'Mother - Postnatal Form',
infantPostnatalFormName: 'Infant - Postanal Form',
},
},
formUuids: {
_type: Type.Object,
_description: 'List of uuids for PMTCT forms.',
_default: {
antenatal: '5255a535-2acb-3f44-bd0a-3f80595dece1',
labourAndDelivery: '1e5614d6-5306-11e6-beb8-9e71128cae77',
motherPostnatal: 'e6b67aa4-6c59-4470-8ad5-b994efeda553',
infantPostnatal: '5022c5d7-ea45-47ce-bd65-1ba1d8ad2467',
antenatalFormUuid: '5255a535-2acb-3f44-bd0a-3f80595dece1',
labourAndDeliveryFormUuid: '1e5614d6-5306-11e6-beb8-9e71128cae77',
motherPostnatalFormUuid: 'e6b67aa4-6c59-4470-8ad5-b994efeda553',
infantPostnatalFormUuid: '120048e5-4122-3c6d-8f77-c79e75b7b3fc',
},
},
encounterTypes: {
_type: Type.Object,
_description: 'List of PMTCT encounter type UUIDs',
_default: {
antenatal: '677d1a80-dbbe-4399-be34-aa7f54f11405',
laborAndDelivery: '6dc5308d-27c9-4d49-b16f-2c5e3c759757',
infantPostnatal: 'af1f1b24-d2e8-4282-b308-0bf79b365584',
motherPostnatal: '269bcc7f-04f8-4ddc-883d-7a3a0d569aad',
mchEncounterType: '12de5bc5-352e-4faf-9961-a2125085a75c',
antenatalEncounterType: '677d1a80-dbbe-4399-be34-aa7f54f11405',
laborAndDeliveryEncounterType: '6dc5308d-27c9-4d49-b16f-2c5e3c759757',
infantPostnatalEncounterType: 'af1f1b24-d2e8-4282-b308-0bf79b365584',
motherPostnatalEncounterType: '269bcc7f-04f8-4ddc-883d-7a3a0d569aad',
mchEncounterTypeEncounterType: '12de5bc5-352e-4faf-9961-a2125085a75c',
},
},
obsConcepts: {
Expand Down Expand Up @@ -110,4 +110,5 @@ export interface ConfigObject {
encounterTypes: Object;
obsConcepts: Object;
formNames: Object;
formUuids: Object;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
"tabName": "Infant Postnatal Visit",
"headerTitle": "Infant Postnatal Visit",
"displayText": "Infant Postnatal Visit",
"encounterType": "af1f1b24-d2e8-4282-b308-0bf79b365584",
"encounterType": "infantPostnatalEncounterType",
"columns": [
{
"id": "pTrackerId",
"title": "PTracker Id",
"concept": "6c45421e-2566-47cb-bbb3-07586fffbfe2"
"concept": "pTrackerIdConcept"
},
{
"id": "mothersName",
Expand All @@ -20,41 +20,41 @@
{
"id": "artProphylaxisStatus",
"title": "ART Prophylaxis Status",
"concept": "1148AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"concept": "artProphylaxisStatus"
},
{
"id": "linkedToArt",
"title": "Linked to ART",
"concept": "a40d8bc4-56b8-4f28-a1dd-412da5cf20ed"
"concept": "linkedToArt"
},
{
"id": "breastfeedingStatus",
"title": "Breastfeeding status",
"concept": "1151AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"concept": "breastfeedingStatus"
},
{
"id": "outcomeStatus",
"title": "Outcome Status",
"concept": "160433AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"concept": "outcomeStatus"
},
{
"id": "nextVisitDate",
"isDate": true,
"title": "Next visit date",
"concept": "5096AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"concept": "nextVisitDateConcept"
},
{
"id": "actions",
"title": "Actions",
"actionOptions": [
{
"formName": "Antenatal Form",
"formName": "infantPostnatalFormName",
"package": "pmtct",
"label": "View Details",
"mode": "view"
},
{
"formName": "Antenatal Form",
"formName": "infantPostnatalFormName",
"package": "pmtct",
"label": "Edit Form",
"mode": "edit"
Expand All @@ -68,8 +68,8 @@
},
"formList": [
{
"name": "Infant - Postanal Form",
"uuid": "5022c5d7-ea45-47ce-bd65-1ba1d8ad2467"
"name": "infantPostnatalFormName",
"uuid": "infantPostnatalFormUuid"
}
]
}
Expand Down
Loading
Loading