-
Notifications
You must be signed in to change notification settings - Fork 242
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
(feat) O3-3773 Support for Patient Program Summary in Patient Chart #1969
base: main
Are you sure you want to change the base?
Conversation
eaf5653
to
6b763ca
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks good, thanks @kajambiya , just a few comments
else return encounter[param] ? encounter[param] : '--'; | ||
} | ||
|
||
export function formatDateTime(dateString: string): any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the already existing Date formatters from the framework
packages/esm-patient-chart-app/src/encounter-tile/utils/helpers.ts
Outdated
Show resolved
Hide resolved
import { formatDate, parseDate } from '@openmrs/esm-framework'; | ||
|
||
export function getEncounterValues(encounter, param: string, isDate?: Boolean) { | ||
if (isDate) return dayjs(encounter[param]).format('DD-MMM-YYYY'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the framework rather than dayjs
for formatting dates and never hard-code the date format.
packages/esm-patient-chart-app/src/encounter-tile/utils/helpers.ts
Outdated
Show resolved
Hide resolved
packages/esm-patient-chart-app/src/encounter-tile/utils/helpers.ts
Outdated
Show resolved
Hide resolved
packages/esm-patient-chart-app/src/encounter-tile/utils/helpers.ts
Outdated
Show resolved
Hide resolved
packages/esm-patient-chart-app/src/encounter-tile/utils/helpers.ts
Outdated
Show resolved
Hide resolved
packages/esm-patient-chart-app/src/encounter-tile/utils/helpers.ts
Outdated
Show resolved
Hide resolved
@@ -112,6 +113,11 @@ export const stopVisitPatientSearchActionButton = getSyncLifecycle(stopVisitActi | |||
moduleName, | |||
}); | |||
|
|||
export const patientProgramSummary = getSyncLifecycle(PatientSummaryOverviewList, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it should be part of the programs app and not the patient-chart-app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The data is related to a program but some people may classify it differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that doesn't explain to me why it belongs here though. If it's program-specific, surely it's part of the programs app?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not program specific, I think the name might be misleading , it’s more of a clinical view summary. I have updated the name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of my point is that this is a public interface intended to be consumed by multiple configurations written by implementations that may or may not understand what the original purpose of a widget is; thus naming things so that they are consistent, discoverable, and not confusing is of vital importance here.
cb49d63
to
cab9b91
Compare
@@ -112,6 +113,11 @@ export const stopVisitPatientSearchActionButton = getSyncLifecycle(stopVisitActi | |||
moduleName, | |||
}); | |||
|
|||
export const patientProgramSummary = getSyncLifecycle(PatientSummaryOverviewList, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of my point is that this is a public interface intended to be consumed by multiple configurations written by implementations that may or may not understand what the original purpose of a widget is; thus naming things so that they are consistent, discoverable, and not confusing is of vital importance here.
|
||
const tilesData = useMemo( | ||
() => | ||
tilesDefinitions?.map((tile: any) => ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be correctly typed.
tilesDefinitions?.map((tile: any) => ({ | |
tilesDefinitions?.map((tile=> ({ |
...ges/esm-patient-chart-app/src/clinical-views/components/clinical-views-summary.component.tsx
Outdated
Show resolved
Hide resolved
...ges/esm-patient-chart-app/src/clinical-views/components/clinical-views-summary.component.tsx
Outdated
Show resolved
Hide resolved
...ges/esm-patient-chart-app/src/clinical-views/components/clinical-views-summary.component.tsx
Outdated
Show resolved
Hide resolved
packages/esm-patient-chart-app/src/encounter-tile/components/encounter-tile.component.tsx
Outdated
Show resolved
Hide resolved
return ( | ||
<div className={styles.tileBox}> | ||
<div className={styles.tileBoxColumn}> | ||
<span className={styles.tileTitle}> {column.header} </span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spaces here seem wrong?
'obs:(uuid,obsDatetime,voided,groupMembers,concept:(uuid,name:(uuid,name)),value:(uuid,name:(uuid,name),' + | ||
'names:(uuid,conceptNameType,name))),form:(uuid,name))'; | ||
|
||
export function useLastEncounter(patientUuid: string, encounterType: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function useLastEncounter(patientUuid: string, encounterType: string) { | |
export function useMostRecentEncounter(patientUuid: string, encounterType: string) { |
packages/esm-patient-chart-app/src/encounter-tile/components/tile.scss
Outdated
Show resolved
Hide resolved
d5c1461
to
b9b0d00
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! This widget is getting much closer. A few more nit-picking comments on the code.
Now looking at the visual of this widget, we should bring it in line with the existing RefApp chart tiles specifically:
- The h4 header font rendering is inconsistent with other tiles in the chart. Both the font-size (20px vs 16px) is off and the font-weight (400 vs 600).
- The header ::after element is not correctly sized (border-bottom-width is 2px too large and padding-top is off by 1px — 2px in this component vs 3px in others)
Oddly, when located in the top-of-all-patient-dashboards-slot, as shown above the box appears too wide, but when located in the same slot as the other elements, the width is too small:
} | ||
|
||
const ClinicalViewsSummary: React.FC<OverviewListProps> = ({ patientUuid }) => { | ||
const config = useConfig() as ConfigObject; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const config = useConfig() as ConfigObject; | |
const config = useConfig<ConfigObject>(); |
packages/esm-patient-chart-app/src/clinical-views/utils/encounter-tile-config-builder.ts
Outdated
Show resolved
Hide resolved
packages/esm-patient-chart-app/src/clinical-views/utils/encounter-tile-config-builder.ts
Outdated
Show resolved
Hide resolved
...ges/esm-patient-chart-app/src/clinical-views/components/clinical-views-summary.component.tsx
Outdated
Show resolved
Hide resolved
Ping @ibacher |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I've covered everything, but here are some things that need to be cleaned-up before this is mergeable. Goal here is to make sure that the code is in a format that the community can support over the long term. Thanks!
...-patient-chart-app/src/clinical-views/components/encounter-tile/encounter-tile.component.tsx
Outdated
Show resolved
Hide resolved
...-patient-chart-app/src/clinical-views/components/encounter-tile/encounter-tile.component.tsx
Outdated
Show resolved
Hide resolved
); | ||
}; | ||
|
||
export const EncounterTile = React.memo(EncounterTileInternal); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why create two separate const
s for this?
export const EncounterTile = React.memo(({ patientUuid, columns, headerTitle }: EncounterTileProps) => {
// code goes here
});
Also works.
key={encounterType} | ||
patientUuid={patientUuid} | ||
encounterType={encounterType} | ||
columns={columns as FormattedCardColumn[]} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to cast the column types here? This seems like an unnecessary escape hatch when we have full control over:
- The types in question
- The thing that's being cast to a type
- The function returning the thing that needs to be cast
<div className={styles.tileBox}> | ||
{columns.map((column, ind) => ( | ||
<div key={ind} className={styles.tileBoxColumn}> | ||
<span className={styles.tileTitle}>{t(column.title)}</span> | ||
<span className={styles.tileValue}>--</span> | ||
</div> | ||
))} | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, here, an error happens and instead of reporting that to the user we just render a blank set of cells? That doesn't seem right... I'd expect us to render a message to the user explaining that the encounter data could not be loaded.
obsConcept: string, | ||
isDate?: Boolean, | ||
isTrueFalseConcept?: Boolean, | ||
type?: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only support a fixed set of types, so the type here should probably reflect that.
isTrueFalseConcept?: Boolean, | ||
type?: string, | ||
fallbackConcepts?: Array<string>, | ||
secondaryConcept?: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a "secondaryConcept"? How does this differ from "fallbackConcepts"? These are the things that we need to explain via comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a secondary concept is used in a question wit the option other and a user is required to add their response in another question, which in his case is captured in the secondary concept.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A fallback concept is used in instances where a tile has an alternative value, for example, enrollment date and re-enrollment date. If there is no enrollment date, then the value of the re-enrollment date can be displayed instead
cc : @eudson
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main point here was that these use-cases need to be clear in the code, so please add some comments explaining that distinction.
|
||
if (secondaryConcept && typeof obs.value === 'object' && obs.value.names) { | ||
const primaryValue = obs.value.name.display; | ||
if (primaryValue === t('Other non-coded')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is, again, not what the translation system should be used for. Capture the UUID of the concept via the configuration system.
packages/esm-patient-chart-app/src/clinical-views/utils/helpers.ts
Outdated
Show resolved
Hide resolved
@@ -1,3 +1,5 @@ | |||
import { OpenmrsResource } from '@openmrs/esm-framework'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { OpenmrsResource } from '@openmrs/esm-framework'; | |
import { type OpenmrsResource } from '@openmrs/esm-framework'; |
8dcedbb
to
bd4e5dc
Compare
I'm confused, is this ready for rull re-review? |
@gracepotma what does ready for full re-review means? |
12d5ba0
to
797618b
Compare
Discussed on today's Content Package check-in call w/ Eudson, Ian, Cynthia, & Jonathan O; apparently this is ready for re-review :) We'll make this a priority to review along with the Tabbed Encounter Widget PR: #1987 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At a high-level, this looks like it isn't massively distinct from #1987, at least in the backing helpers, etc. Since I'd prefer we didn't duplicate code, is it possible to rework this PR to use the same definitions from there at least for getObsFromEncounter()
and similar shared functionality?
return tileDefinitions?.map((tile: MenuCardProps) => ({ | ||
title: t(tile.tileHeader), | ||
columns: getEncounterTileColumns(tile, t), | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return tileDefinitions?.map((tile: MenuCardProps) => ({ | |
title: t(tile.tileHeader), | |
columns: getEncounterTileColumns(tile, t), | |
})); | |
return tileDefinitions?.map((tile: MenuCardProps) => ({ | |
title: t(tile.tileHeader), | |
columns: getEncounterTileColumns(tile, t), | |
})) ?? []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple more small points.
return <CodeSnippetSkeleton type="multi" role="progressbar" />; | ||
} | ||
|
||
if (error || lastEncounter == undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this just be !lastEncounter
?
async (url) => { | ||
const cachedData = cache.get(url); | ||
if (cachedData) { | ||
return cachedData; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The manual caching here seems unnecessary. It would be simpler to use useSWRImmutable
, I think.
if (!response.ok) { | ||
throw new Error('Failed to fetch data'); | ||
} | ||
const responseData = await response.json(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty much everything here is redundant. openmrsFetch()
already checks if response.ok
and raises an error if it isn't. Also await response.json()
is already what's available in the response.data
field.
797618b
to
e146333
Compare
e146333
to
1095bcc
Compare
I have restructured a few files that cut across this pr and the encounter list PR |
Requirements
Summary
This PR introduces summary program summary to patient chart.
Screenshots
Related Issue
Other