Skip to content

Commit

Permalink
feat: Org unit context in Stages and Events widget
Browse files Browse the repository at this point in the history
  • Loading branch information
henrikmv committed Jul 19, 2024
1 parent 7d27e4c commit 5f24da6
Show file tree
Hide file tree
Showing 15 changed files with 128 additions and 66 deletions.
12 changes: 6 additions & 6 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-06-28T11:23:02.970Z\n"
"PO-Revision-Date: 2024-06-28T11:23:02.970Z\n"
"POT-Creation-Date: 2024-04-26T17:02:42.850Z\n"
"PO-Revision-Date: 2024-04-26T17:02:42.850Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down Expand Up @@ -1274,11 +1274,11 @@ msgstr "Enrollment widget could not be loaded. Please try again later"
msgid "Follow-up"
msgstr "Follow-up"

msgid "Started at {{orgUnitName}}"
msgstr "Started at {{orgUnitName}}"
msgid "Started at "
msgstr "Started at "

msgid "Owned by {{ownerOrgUnit}}"
msgstr "Owned by {{ownerOrgUnit}}"
msgid "Owned by "
msgstr "Owned by "

msgid "Last updated {{date}}"
msgstr "Last updated {{date}}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { searchScopes } from '../SearchBox';
import { enrollmentTypes } from './CardList.constants';
import { ListEntry } from './ListEntry.component';
import { dataElementTypes, getTrackerProgramThrowIfNotFound } from '../../metaData';
import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName';
import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName';
import type { ListItem, RenderCustomCardActions } from './CardList.types';


Expand Down Expand Up @@ -139,7 +139,7 @@ const CardListItemIndex = ({
const enrollments = item.tei ? item.tei.enrollments : [];
const enrollmentType = deriveEnrollmentType(enrollments, currentProgramId);
const { orgUnitId, enrolledAt } = deriveEnrollmentOrgUnitIdAndDate(enrollments, enrollmentType, currentProgramId);
const { displayName: orgUnitName } = useOrgUnitName(orgUnitId);
const { displayName: orgUnitName } = useOrgUnitNameWithAncestors(orgUnitId);
const program = enrollments && enrollments.length
? deriveProgramFromEnrollment(enrollments, currentSearchScopeType)
: undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useScopeInfo } from '../../../hooks/useScopeInfo';
import { scopeTypes } from '../../../metaData';
import { TrackedEntityInstanceDataEntry } from '../TrackedEntityInstance';
import { useCurrentOrgUnitId } from '../../../hooks/useCurrentOrgUnitId';
import { useOrgUnitName } from '../../../metadataRetrieval/orgUnitName';
import { useOrgUnitNameWithAncestors } from '../../../metadataRetrieval/orgUnitName';
import type { Props, PlainProps } from './TeiRegistrationEntry.types';
import { DiscardDialog } from '../../Dialogs/DiscardDialog.component';
import { withSaveHandler } from '../../DataEntry';
Expand Down Expand Up @@ -54,7 +54,7 @@ const TeiRegistrationEntryPlain =
const { scopeType } = useScopeInfo(selectedScopeId);
const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId });
const orgUnitId = useCurrentOrgUnitId();
const { displayName: orgUnitName } = useOrgUnitName(orgUnitId);
const { displayName: orgUnitName } = useOrgUnitNameWithAncestors(orgUnitId);

const handleOnCancel = () => {
if (!isUserInteractionInProgress) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { type ComponentType, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ScopeSelectorComponent } from './ScopeSelector.component';
import type { OwnProps } from './ScopeSelector.types';
import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName';
import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName';
import { resetOrgUnitIdFromScopeSelector } from './ScopeSelector.actions';


Expand Down Expand Up @@ -34,7 +34,7 @@ export const ScopeSelector: ComponentType<OwnProps> = ({
}) => {
const dispatch = useDispatch();
const [selectedOrgUnit, setSelectedOrgUnit] = useState({ name: undefined, id: selectedOrgUnitId });
const { displayName, error: ouNameError } = useOrgUnitName(selectedOrgUnit.id);
const { displayName, error: ouNameError } = useOrgUnitNameWithAncestors(selectedOrgUnit.id);

useEffect(() => {
if (displayName && selectedOrgUnit.name !== displayName) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { Tooltip } from '@dhis2/ui';
import { useFormatOrgUnitNameFullPath } from '../../../metadataRetrieval/orgUnitName';

export const TooltipOrgUnit = ({ orgUnitName, ancestors }) => {
const orgUnitNameFullPath = useFormatOrgUnitNameFullPath(orgUnitName, ancestors);
return (
<Tooltip content={orgUnitNameFullPath} openDelay={400}>
<span style={{ textDecoration: 'underline dotted' }}>
{orgUnitName}
</span>
</Tooltip>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TooltipOrgUnit } from './TooltipOrgUnit.component';
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cx from 'classnames';
import { withStyles } from '@material-ui/core/styles';
import { colors, IconInfo16, IconWarning16 } from '@dhis2/ui';
import i18n from '@dhis2/d2-i18n';
import { useOrgUnitName } from '../../../../metadataRetrieval/orgUnitName';
import { useOrgUnitNameWithAncestors } from '../../../../metadataRetrieval/orgUnitName';
import { OrgUnitScopes } from '../hooks/useTransferValidation';
import { ProgramAccessLevels } from '../hooks/useProgramAccessLevel';

Expand Down Expand Up @@ -48,8 +48,8 @@ const InfoBoxesPlain = ({
orgUnitScopes,
classes,
}: Props) => {
const { displayName: ownerOrgUnitName } = useOrgUnitName(ownerOrgUnitId);
const { displayName: newOrgUnitName } = useOrgUnitName(validOrgUnitId);
const { displayName: ownerOrgUnitName } = useOrgUnitNameWithAncestors(ownerOrgUnitId);
const { displayName: newOrgUnitName } = useOrgUnitNameWithAncestors(validOrgUnitId);

const showWarning = [ProgramAccessLevels.PROTECTED, ProgramAccessLevels.CLOSED].includes(programAccessLevel)
&& orgUnitScopes.destination === OrgUnitScopes.SEARCH;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import { Widget } from '../Widget';
import type { PlainProps } from './enrollment.types';
import { Status } from './Status';
import { dataElementTypes } from '../../metaData';
import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName';
import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName';
import { Date } from './Date';
import { Actions } from './Actions';
import { MiniMap } from './MiniMap';
import { TooltipOrgUnit } from '../Tooltips/TooltipOrgUnit';

const styles = {
enrollment: {
Expand Down Expand Up @@ -72,7 +73,9 @@ export const WidgetEnrollmentPlain = ({
const [open, setOpenStatus] = useState(true);
const { fromServerDate } = useTimeZoneConversion();
const geometryType = getGeometryType(enrollment?.geometry?.type);
const { displayName: orgUnitName } = useOrgUnitName(enrollment?.orgUnit);
const { displayName: orgUnitName, ancestors } = useOrgUnitNameWithAncestors(enrollment?.orgUnit);
const { displayName: ownerOrgUnitName, ancestors: ownerAncestors } = useOrgUnitNameWithAncestors(ownerOrgUnit?.id);


return (
<div data-test="widget-enrollment">
Expand Down Expand Up @@ -129,19 +132,20 @@ export const WidgetEnrollmentPlain = ({
<span className={classes.icon} data-test="widget-enrollment-icon-orgunit">
<IconDimensionOrgUnit16 color={colors.grey600} />
</span>
{i18n.t('Started at {{orgUnitName}}', {
orgUnitName,
interpolation: { escapeValue: false },
})}
<span>
{i18n.t('Started at ')}
<TooltipOrgUnit orgUnitName={orgUnitName} ancestors={ancestors} />
</span>
</div>

<div className={classes.row} data-test="widget-enrollment-owner-orgunit">
<span className={classes.icon} data-test="widget-enrollment-icon-owner-orgunit">
<IconDimensionOrgUnit16 color={colors.grey600} />
</span>
{i18n.t('Owned by {{ownerOrgUnit}}', {
ownerOrgUnit: ownerOrgUnit.displayName,
})}
<span>
{i18n.t('Owned by ')}
<TooltipOrgUnit orgUnitName={ownerOrgUnitName} ancestors={ownerAncestors} />
</span>
</div>

<div className={classes.row} data-test="widget-enrollment-last-update">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useMemo } from 'react';
import { errorCreator } from 'capture-core-utils';
import log from 'loglevel';
import { WidgetEnrollment as WidgetEnrollmentNote } from './WidgetEnrollment.component';
import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName';
import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName';
import { useTrackedEntityInstances } from './hooks/useTrackedEntityInstances';
import { useEnrollment } from './hooks/useEnrollment';
import { useProgram } from './hooks/useProgram';
Expand Down Expand Up @@ -68,7 +68,7 @@ export const WidgetEnrollment = ({
enrollments,
refetch: refetchTEI,
} = useTrackedEntityInstances(teiId, programId);
const { error: errorOrgUnit, displayName } = useOrgUnitName(
const { error: errorOrgUnit, displayName } = useOrgUnitNameWithAncestors(
typeof ownerOrgUnit === 'string' ? ownerOrgUnit : undefined,
);
const { error: errorLocale, locale } = useUserLocale();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import i18n from '@dhis2/d2-i18n';
import { useDispatch } from 'react-redux';
import moment from 'moment';
import { getProgramAndStageForProgram, TrackerProgram, getProgramEventAccess, dataElementTypes } from '../../metaData';
import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName';
import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName';
import { useLocationQuery } from '../../utils/routing';
import type { ContainerProps } from './widgetEventSchedule.types';
import { WidgetEventScheduleComponent } from './WidgetEventSchedule.component';
Expand Down Expand Up @@ -37,7 +37,7 @@ export const WidgetEventSchedule = ({
}: ContainerProps) => {
const { program, stage } = useMemo(() => getProgramAndStageForProgram(programId, stageId), [programId, stageId]);
const dispatch = useDispatch();
const orgUnit = { id: orgUnitId, name: useOrgUnitName(orgUnitId).displayName };
const orgUnit = { id: orgUnitId, name: useOrgUnitNameWithAncestors(orgUnitId).displayName };
const { programStageScheduleConfig } = useScheduleConfigFromProgramStage(stageId);
const { programConfig } = useScheduleConfigFromProgram(programId);
const suggestedScheduleDate = useDetermineSuggestedScheduleDate({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
useUserRoles,
useTeiDisplayName,
} from './hooks';
import { DataEntry, dataEntryActionTypes, TEI_MODAL_STATE, convertClientToView } from './DataEntry';
import { DataEntry, dataEntryActionTypes, TEI_MODAL_STATE } from './DataEntry';
import { ReactQueryAppNamespace } from '../../utils/reactQueryHelpers';
import { CHANGELOG_ENTITY_TYPES } from '../WidgetsChangelog';
import { OverflowMenu } from './OverflowMenu';
Expand Down Expand Up @@ -86,19 +86,22 @@ const WidgetProfilePlain = ({

const loading = programsLoading || trackedEntityInstancesLoading || userRolesLoading;
const error = programsError || trackedEntityInstancesError || userRolesError;
const clientAttributesWithSubvalues = useClientAttributesWithSubvalues(teiId, program, trackedEntityInstanceAttributes);
const clientAttributesWithSubvalues = useClientAttributesWithSubvalues(
teiId,
program,
trackedEntityInstanceAttributes,
);
const teiDisplayName = useTeiDisplayName(program, storedAttributeValues, clientAttributesWithSubvalues, teiId);
const displayChangelog = supportsChangelog && program && program.trackedEntityType?.changelogEnabled;

const displayInListAttributes = useMemo(() => clientAttributesWithSubvalues
.filter(item => item.displayInList)
.map((clientAttribute) => {
const { attribute, key } = clientAttribute;
const value = convertClientToView(clientAttribute);
return {
attribute, key, value, reactKey: attribute,
};
}), [clientAttributesWithSubvalues]);
const displayInListAttributes = useMemo(() =>
clientAttributesWithSubvalues
.filter(({ displayInList }) => displayInList)
.map(({ attribute, key, value, valueType }) => ({
attribute, key, value, valueType, reactKey: attribute,
})),
[clientAttributesWithSubvalues],
);

const onSaveExternal = useCallback(() => {
queryClient.removeQueries([ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.TRACKED_ENTITY, teiId]);
Expand Down Expand Up @@ -132,7 +135,10 @@ const WidgetProfilePlain = ({

return (
<div className={classes.container}>
<FlatList dataTest="profile-widget-flatlist" list={displayInListAttributes} />
<FlatList
dataTest="profile-widget-flatlist"
list={displayInListAttributes}
/>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
export {
useOrgUnitName,
useOrgUnitNameWithAncestors,
useFormatOrgUnitNameFullPath,
useOrgUnitNames,
getOrgUnitNames,
getCachedOrgUnitName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const useOrgUnitNames = (orgUnitIds: Array<string>): {

const onComplete = useCallback(({ organisationUnits }) => {
for (const { id, displayName } of organisationUnits.organisationUnits) {
displayNameCache[id] = displayName;
displayNameCache[id].displayName = displayName;
}
const completeCount = completedBatches + 1;
setCompletedBatches(completeCount);
Expand Down Expand Up @@ -139,35 +139,60 @@ export async function getOrgUnitNames(orgUnitIds: Array<string>, querySingleReso
.map(batch => querySingleResource(displayNamesQuery.organisationUnits, { filter: batch.join(',') })
.then(({ organisationUnits }) => {
for (const { id, displayName } of organisationUnits) {
displayNameCache[id] = displayName;
displayNameCache[id].displayName = displayName;
}
})));

return orgUnitIds.reduce((acc, orgUnitId) => {
acc[orgUnitId] = {
id: orgUnitId,
name: displayNameCache[orgUnitId],
name: displayNameCache[orgUnitId].displayName,
};
return acc;
}, {});
}

export const useOrgUnitName = (orgUnitId: ?string): {
export const useOrgUnitNameWithAncestors = (orgUnitId: ?string): {
displayName?: string,
error?: any,
ancestors?: Array<{| displayName: string, level: number |}>,
error ?: any,
} => {
const cachedOrgUnitName = orgUnitId && displayNameCache[orgUnitId];
const fetchId = cachedOrgUnitName ? undefined : orgUnitId;
const { orgUnit, error } = useOrganisationUnit(fetchId, 'displayName');
if (cachedOrgUnitName) {
return { displayName: cachedOrgUnitName };
const cachedOrgUnitNameAndAncestor = orgUnitId && displayNameCache[orgUnitId];
const fetchId = cachedOrgUnitNameAndAncestor ? undefined : orgUnitId;
const { orgUnit, error } = useOrganisationUnit(fetchId, 'displayName,ancestors[displayName,level]');

if (cachedOrgUnitNameAndAncestor) {
return {
displayName: cachedOrgUnitNameAndAncestor.displayName,
ancestors: cachedOrgUnitNameAndAncestor.ancestors,
error,
};
} else if (orgUnit && fetchId) {
displayNameCache[orgUnit.id] = orgUnit.displayName;
displayNameCache[orgUnit.id] = {
displayName: orgUnit.displayName,
ancestors: orgUnit.ancestors,
};
if (orgUnit.id === fetchId) {
return { displayName: orgUnit.displayName, error };
return { displayName: orgUnit.displayName, ancestors: orgUnit.ancestors, error };
}
return { error };
}

return { error };
};

export const getCachedOrgUnitName = (orgUnitId: string): ?string => displayNameCache[orgUnitId];
export const useFormatOrgUnitNameFullPath = (orgUnitName: ?string, ancestors?: Array<{| displayName: string, level: number |}>,
): ?string => {
const [path, setPath] = useState(null);
useEffect(() => {
if (orgUnitName && ancestors) {
const ancestorNames = ancestors.map(ancestor => ancestor.displayName);
ancestorNames.push(orgUnitName);
const computedPath = ancestorNames.join(' / ');
setPath(computedPath);
}
}, [orgUnitName, ancestors]);
return path;
};

export const getCachedOrgUnitName = (orgUnitId: string): ?string => displayNameCache[orgUnitId].displayName;
Loading

0 comments on commit 5f24da6

Please sign in to comment.