From 0ad38914ba4674ba2c71377e4f83036f6b4fcbf6 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Wed, 6 Mar 2024 07:28:16 +0700 Subject: [PATCH] feat: [DHIS2-14799] Working list for follow up (#3521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Storløkken Melseth --- i18n/en.pot | 3 + .../Boolean/BooleanFilter.component.js | 20 +++-- .../Boolean/BooleanFilterManager.component.js | 21 +++-- .../Boolean/booleanFilterDataGetter.js | 11 ++- .../Boolean/types/boolean.types.js | 4 + .../FiltersForTypes/Boolean/types/index.js | 2 +- .../Generic/D2TrueFalse.component.js | 4 +- .../FilterSelectorContents.component.js | 1 + .../converters/booleanConverter.js | 7 +- .../ListView/Filters/Filters.component.js | 85 ++++++++++++++----- .../ListView/types/listView.types.js | 1 + .../Setup/hooks/useDefaultColumnConfig.js | 2 +- .../Setup/hooks/useFiltersOnly.js | 10 +++ .../TeiWorkingLists/constants/mainFilters.js | 1 + .../programStageTemplates.epics.js | 4 + .../epics/teiViewEpics/teiTemplates.epics.js | 5 +- .../templates/getProgramStageTemplates.js | 2 + .../teiViewEpics/templates/getTEITemplates.js | 2 + .../convertToClientFilters.js | 10 ++- .../convertors.js | 2 + .../templates/buildArgumentsForTemplate.js | 3 +- .../types/apiTemplate.types.js | 3 +- .../filterConverters/booleanConverter.js | 7 +- 23 files changed, 159 insertions(+), 51 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index d3f459463c..78b2ca8e58 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -1584,6 +1584,9 @@ msgstr "Inactive" msgid "Enrollment status" msgstr "Enrollment status" +msgid "Follow up" +msgstr "Follow up" + msgid "Choose a program stage to filter by {{label}}" msgstr "Choose a program stage to filter by {{label}}" diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/BooleanFilter.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/BooleanFilter.component.js index 8dd1baf627..9c706f8f04 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/BooleanFilter.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/BooleanFilter.component.js @@ -3,7 +3,10 @@ import React, { Component } from 'react'; import { withStyles } from '@material-ui/core/styles'; import { D2TrueFalse } from '../../FormFields/Generic/D2TrueFalse.component'; import { orientations } from '../../FormFields/Options/SelectBoxes'; // TODO: Refactor -import { getBooleanFilterData } from './booleanFilterDataGetter'; +import { + getMultiSelectBooleanFilterData, + getSingleSelectBooleanFilterData, +} from './booleanFilterDataGetter'; import type { UpdatableFilterContent } from '../types'; const getStyles = (theme: Theme) => ({ @@ -12,11 +15,12 @@ const getStyles = (theme: Theme) => ({ }, }); -type Value = ?Array; +type Value = ?Array | string; type Props = { value: Value, onCommitValue: (value: Value) => void, + allowMultiple: boolean, classes: { selectBoxesContainer: string, }, @@ -27,13 +31,17 @@ class BooleanFilterPlain extends Component implements UpdatableFilterCont booleanFieldInstance: ?D2TrueFalse; onGetUpdateData() { - const value = this.props.value; + const { value, allowMultiple } = this.props; - if (!value) { + if (!value && value !== false) { return null; } - return getBooleanFilterData(value); + if (!allowMultiple) { + return getSingleSelectBooleanFilterData(value); + } + + return getMultiSelectBooleanFilterData(value); } onIsValid() { //eslint-disable-line @@ -53,7 +61,7 @@ class BooleanFilterPlain extends Component implements UpdatableFilterCont > void, + singleSelect: boolean, }; type State = { - value: ?Array, + value: ?Array | string, }; export class BooleanFilterManager extends React.Component { - static calculateDefaultValueState(filter: ?BooleanFilterData): ?Array { + static calculateDefaultValueState( + filter: ?BooleanFilterStringified, + singleSelect: boolean, + ): ?(Array | string) { if (!filter) { return undefined; } - return filter - .values - .map(value => (value ? 'true' : 'false')); + return singleSelect ? filter.values[0] : filter.values; } constructor(props: Props) { super(props); this.state = { - value: BooleanFilterManager.calculateDefaultValueState(this.props.filter), + value: BooleanFilterManager.calculateDefaultValueState(this.props.filter, this.props.singleSelect), }; } @@ -39,7 +41,7 @@ export class BooleanFilterManager extends React.Component { } render() { - const { filter, filterTypeRef, ...passOnProps } = this.props; + const { filter, filterTypeRef, singleSelect, ...passOnProps } = this.props; return ( // $FlowFixMe[cannot-spread-inexact] automated comment @@ -47,6 +49,7 @@ export class BooleanFilterManager extends React.Component { value={this.state.value} innerRef={filterTypeRef} onCommitValue={this.handleCommitValue} + allowMultiple={!singleSelect} {...passOnProps} /> ); diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/booleanFilterDataGetter.js b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/booleanFilterDataGetter.js index 6662e60d8a..e7e0524b12 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/booleanFilterDataGetter.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/booleanFilterDataGetter.js @@ -1,11 +1,14 @@ // @flow -import type { BooleanFilterData } from './types'; +import type { BooleanFilterStringified } from './types'; export function getBooleanFilterData( values: Array, -): BooleanFilterData { +): BooleanFilterStringified { return { - values: values - .map(value => (value === 'true')), + values: values.map(value => value), }; } + +export const getSingleSelectBooleanFilterData = (value: any) => getBooleanFilterData([value]); + +export const getMultiSelectBooleanFilterData = (values: any) => getBooleanFilterData(values); diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/types/boolean.types.js b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/types/boolean.types.js index 87b849eed1..c6af3bec99 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/types/boolean.types.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/types/boolean.types.js @@ -2,3 +2,7 @@ export type BooleanFilterData = {| values: Array, |}; + +export type BooleanFilterStringified = {| + values: Array, +|}; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/types/index.js b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/types/index.js index 85db9bad61..66663e0f46 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/types/index.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/types/index.js @@ -1,2 +1,2 @@ // @flow -export { BooleanFilterData } from './boolean.types'; +export { BooleanFilterData, BooleanFilterStringified } from './boolean.types'; diff --git a/src/core_modules/capture-core/components/FormFields/Generic/D2TrueFalse.component.js b/src/core_modules/capture-core/components/FormFields/Generic/D2TrueFalse.component.js index 895a2aef47..de85e488a6 100644 --- a/src/core_modules/capture-core/components/FormFields/Generic/D2TrueFalse.component.js +++ b/src/core_modules/capture-core/components/FormFields/Generic/D2TrueFalse.component.js @@ -16,8 +16,8 @@ export class D2TrueFalse extends Component { const falseText = i18n.t('No'); const optionSet = new OptionSet(); - optionSet.addOption(new Option((o) => { o.text = trueText; o.value = 'true'; })); - optionSet.addOption(new Option((o) => { o.text = falseText; o.value = 'false'; })); + optionSet.addOption(new Option((o) => { o.text = trueText; o.value = true; })); + optionSet.addOption(new Option((o) => { o.text = falseText; o.value = false; })); return optionSet; } diff --git a/src/core_modules/capture-core/components/ListView/Filters/Contents/FilterSelectorContents.component.js b/src/core_modules/capture-core/components/ListView/Filters/Contents/FilterSelectorContents.component.js index c57229193d..d102b2aee7 100644 --- a/src/core_modules/capture-core/components/ListView/Filters/Contents/FilterSelectorContents.component.js +++ b/src/core_modules/capture-core/components/ListView/Filters/Contents/FilterSelectorContents.component.js @@ -68,6 +68,7 @@ const useContents = ({ filterValue, classes, type, options, multiValueFilter, is {...passOnProps} filter={filterValue} type={type} + singleSelect={!multiValueFilter} handleCommitValue={() => setUpdateDisabled(false)} disabledUpdate={disabledUpdate} disabledReset={filterValue === undefined} diff --git a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/buttonTextBuilder/converters/booleanConverter.js b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/buttonTextBuilder/converters/booleanConverter.js index fed9fe8b8b..56c2636d32 100644 --- a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/buttonTextBuilder/converters/booleanConverter.js +++ b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/buttonTextBuilder/converters/booleanConverter.js @@ -3,7 +3,12 @@ import i18n from '@dhis2/d2-i18n'; import { pipe } from 'capture-core-utils'; import type { BooleanFilterData } from '../../../../../FiltersForTypes'; -const getText = (key: boolean) => (key ? i18n.t('Yes') : i18n.t('No')); +const textValuesByKey = { + true: i18n.t('Yes'), + false: i18n.t('No'), +}; + +const getText = (key: string) => textValuesByKey[key]; export function convertBoolean(filter: BooleanFilterData): string { return pipe( diff --git a/src/core_modules/capture-core/components/ListView/Filters/Filters.component.js b/src/core_modules/capture-core/components/ListView/Filters/Filters.component.js index ef7c309d04..e35cb72d5b 100644 --- a/src/core_modules/capture-core/components/ListView/Filters/Filters.component.js +++ b/src/core_modules/capture-core/components/ListView/Filters/Filters.component.js @@ -1,12 +1,12 @@ // @flow -import React, { memo } from 'react'; +import React, { memo, useMemo } from 'react'; import log from 'loglevel'; import { withStyles } from '@material-ui/core/styles'; import { errorCreator } from 'capture-core-utils'; import { FilterButton } from './FilterButton'; import { FilterRestMenu } from './FilterRestMenu/FilterRestMenu.component'; import { filterTypesObject } from './filters.const'; -import type { Column, StickyFilters, FiltersOnly, AdditionalFilters } from '../types'; +import type { Column, StickyFilters, FiltersOnly, AdditionalFilters, FilterOnly } from '../types'; const getStyles = (theme: Theme) => ({ filterButtonContainer: { @@ -54,8 +54,10 @@ const getValidElementConfigsVisiblePrioritized = (columns: Array) => .map(container => [container.element.id, container.element]), ); +const getValidFilterConfigs = (filtersOnly: FiltersOnly) => new Map(filtersOnly.map(filter => [filter.id, filter])); + const splitBasedOnHasValueOnInit = - (elementConfigs: Map, filtersWithValueOnInit: ?Object) => { + (elementConfigs: Map, filtersWithValueOnInit: ?Object) => { const filtersNotEmpty = filtersWithValueOnInit || {}; return Object .keys(filtersNotEmpty) @@ -82,7 +84,7 @@ const splitBasedOnHasValueOnInit = }; const fillUpIndividualElements = ( - elementConfigs: Map, + elementConfigs: Map, occupiedSpots: number, ) => { const INDIVIDUAL_DISPLAY_COUNT_BASE = 4; @@ -104,7 +106,7 @@ const fillUpIndividualElements = ( }; const getUserSelectedElements = ( - elementConfigs: Map, + elementConfigs: Map, userSelectedFilters: ?Object, ) => { const userSelectedFiltersNonEmpty = userSelectedFilters || {}; @@ -131,7 +133,7 @@ const getUserSelectedElements = ( }; const addAdditionalFiltersElements = ( - elementConfigs: Map, + elementConfigs: Map, additionalFilters?: AdditionalFilters, filtersWithValueOnInit: Object = {}, userSelectedFilters: Object = {}, @@ -152,12 +154,34 @@ const addAdditionalFiltersElements = ( return { remainingElements: elementConfigs }; }; +const addShowInMoreFilters = ( + elementConfigs: Map, + filtersOnlyForShowInMoreFilters: FiltersOnly, + filtersWithValueOnInit: Object = {}, + userSelectedFilters: Object = {}, +) => { + const remainingElements: Map = + new Map([...elementConfigs]); + if (filtersOnlyForShowInMoreFilters.length > 0) { + filtersOnlyForShowInMoreFilters.forEach((filter) => { + const addToRemainingElements = + !filtersWithValueOnInit[filter.id] && !userSelectedFilters[filter.id]; + + if (addToRemainingElements) { + remainingElements.set(filter.id, filter); + } + }); + return { remainingElements }; + } + return { remainingElements: elementConfigs }; +}; + const getIndividualElementsArray = ( - validElementConfigs: Map, - initValueElements: Map, - fillUpElements: Map, - userSelectedElements: Map, -): Array => [...validElementConfigs.entries()] + validElementConfigs: Map, + initValueElements: Map, + fillUpElements: Map, + userSelectedElements: Map, +): Array => [...validElementConfigs.entries()] .map(entry => entry[1]) .map((element) => { if (initValueElements.has(element.id) || @@ -180,7 +204,7 @@ const renderIndividualFilterButtons = ({ onRemoveFilter, classes, }: { - individualElementsArray: Array, + individualElementsArray: Array, filtersOnly?: FiltersOnly, visibleSelectorId: ?string, onSetVisibleSelector: Function, @@ -217,7 +241,7 @@ const renderIndividualFilterButtons = ({ ); const renderRestButton = ( - restElementsArray: Array, + restElementsArray: Array, onSelectRestMenuItem: Function, ) => (restElementsArray.length > 0 ? ( ((props: Props) => { } = props; const [visibleSelectorId, setVisibleSelector] = React.useState(props.visibleSelectorId); - const filtersOnlyCount = filtersOnly ? filtersOnly.length : 0; + const defaultFiltersOnly = useMemo(() => + (filtersOnly || []).filter(filter => !filter.showInMoreFilters), [filtersOnly]); + const defaultFiltersOnlyCount = defaultFiltersOnly.length; + const elementsContainer = React.useMemo(() => { const notEmptyColumns = columns || []; - const validElementConfigs = getValidElementConfigsVisiblePrioritized(notEmptyColumns); + const filtersOnlyForShowInMoreFilters: FiltersOnly = (filtersOnly || []) + .filter(filter => filter.showInMoreFilters); + + const validColumnElementConfigs = getValidElementConfigsVisiblePrioritized(notEmptyColumns); + const validFilterConfigs = getValidFilterConfigs(filtersOnlyForShowInMoreFilters); + + const validElementConfigs: Map = new Map([ + ...validColumnElementConfigs, + ...validFilterConfigs, + ]); + const { filtersWithValueOnInit, userSelectedFilters } = stickyFilters; const { initValueElements, remainingElements: remainingElementsAfterInitSplit } = @@ -256,12 +293,19 @@ const FiltersPlain = memo((props: Props) => { const { fillUpElements, remainingElements: remainingElementsAfterFillUp } = fillUpIndividualElements( remainingElementsAfterInitSplit, - initValueElements.size + filtersOnlyCount, + initValueElements.size + defaultFiltersOnlyCount, + ); + + const { remainingElements: remainingElementsWithShowInMoreFilters } = addShowInMoreFilters( + remainingElementsAfterFillUp, + filtersOnlyForShowInMoreFilters, + filtersWithValueOnInit, + userSelectedFilters, ); const { remainingElements: remainingElementsWithAdditionalFilters } = addAdditionalFiltersElements( - remainingElementsAfterFillUp, additionalFilters, filtersWithValueOnInit, userSelectedFilters); + remainingElementsWithShowInMoreFilters, additionalFilters, filtersWithValueOnInit, userSelectedFilters); const { userSelectedElements, remainingElements } = getUserSelectedElements(remainingElementsWithAdditionalFilters, userSelectedFilters); @@ -283,7 +327,8 @@ const FiltersPlain = memo((props: Props) => { }, [ columns, stickyFilters, - filtersOnlyCount, + filtersOnly, + defaultFiltersOnlyCount, additionalFilters, ]); @@ -304,7 +349,7 @@ const FiltersPlain = memo((props: Props) => { onUpdateFilter, onClearFilter, onRemoveFilter, - filtersOnly, + filtersOnly: defaultFiltersOnly, classes, }); @@ -326,7 +371,7 @@ const FiltersPlain = memo((props: Props) => { onUpdateFilter, onClearFilter, onRemoveFilter, - filtersOnly, + defaultFiltersOnly, ]); return ( diff --git a/src/core_modules/capture-core/components/ListView/types/listView.types.js b/src/core_modules/capture-core/components/ListView/types/listView.types.js index 80abbf80bd..011d70d6fb 100644 --- a/src/core_modules/capture-core/components/ListView/types/listView.types.js +++ b/src/core_modules/capture-core/components/ListView/types/listView.types.js @@ -27,6 +27,7 @@ export type FilterOnly = { multiValueFilter?: boolean, tooltipContent?: string, disabled?: boolean, + showInMoreFilters?: boolean, transformRecordsFilter: (rawFilter: any) => Object, }; diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/Setup/hooks/useDefaultColumnConfig.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/Setup/hooks/useDefaultColumnConfig.js index 59c9af6968..9ccc9985d0 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/Setup/hooks/useDefaultColumnConfig.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/Setup/hooks/useDefaultColumnConfig.js @@ -78,7 +78,7 @@ const getTEIMetaDataConfig = (attributes: Array, orgUnitId: ?string type, header: formName || name, options: optionSet && optionSet.options.map(({ text, value }) => ({ text, value })), - multiValueFilter: !!optionSet, + multiValueFilter: !!optionSet || type === dataElementTypes.BOOLEAN, filterHidden: !(orgUnitId || searchable || unique), })); diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/Setup/hooks/useFiltersOnly.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/Setup/hooks/useFiltersOnly.js index aa6cda768e..155d6b9775 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/Setup/hooks/useFiltersOnly.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/Setup/hooks/useFiltersOnly.js @@ -64,6 +64,16 @@ export const useFiltersOnly = ({ }, ] : []), + { + id: MAIN_FILTERS.FOLLOW_UP, + type: dataElementTypes.BOOLEAN, + header: i18n.t('Follow up'), + showInMoreFilters: true, + multiValueFilter: false, + transformRecordsFilter: (rawFilter: string) => ({ + followUp: rawFilter.split(':')[1], + }), + }, ...(enableUserAssignment ? [ { diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/constants/mainFilters.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/constants/mainFilters.js index 2c9b4f6e44..290587017f 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/constants/mainFilters.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/constants/mainFilters.js @@ -3,4 +3,5 @@ export const MAIN_FILTERS = { ENROLLED_AT: 'enrolledAt', OCCURED_AT: 'occurredAt', ASSIGNEE: 'assignee', + FOLLOW_UP: 'followUp', }; diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/programStageTemplates.epics.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/programStageTemplates.epics.js index 03721f8eb0..cbe1cda555 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/programStageTemplates.epics.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/programStageTemplates.epics.js @@ -41,6 +41,7 @@ export const addProgramStageTemplateEpic = (action$: InputObservable, store: Red attributeValueFilters = [], dataFilters = [], order, + followUp, displayColumnOrder, assignedUserMode, assignedUsers, @@ -62,6 +63,7 @@ export const addProgramStageTemplateEpic = (action$: InputObservable, store: Red eventStatus: status, ...(assignedUserMode && { assignedUserMode }), ...(assignedUsers?.length > 0 && { assignedUsers }), + ...(followUp && { followUp }), ...(programStatus && { enrollmentStatus: programStatus }), ...(occurredAt && { enrollmentOccurredAt: occurredAt }), ...(eventOccurredAt && { eventOccurredAt }), @@ -183,6 +185,7 @@ export const updateProgramStageTemplateEpic = (action$: InputObservable, store: attributeValueFilters = [], dataFilters = [], order, + followUp, displayColumnOrder, assignedUserMode, assignedUsers, @@ -209,6 +212,7 @@ export const updateProgramStageTemplateEpic = (action$: InputObservable, store: ...(assignedUsers?.length > 0 && { assignedUsers }), ...(programStatus && { enrollmentStatus: programStatus }), ...(occurredAt && { enrollmentOccurredAt: occurredAt }), + ...(followUp && { followUp }), ...(eventOccurredAt && { eventOccurredAt }), ...(scheduledAt && { eventScheduledAt: scheduledAt }), attributeValueFilters, diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/teiTemplates.epics.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/teiTemplates.epics.js index 5502aa4110..0c275b3b09 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/teiTemplates.epics.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/teiTemplates.epics.js @@ -39,6 +39,7 @@ export const addTEITemplateEpic = (action$: InputObservable, store: ReduxStore, occurredAt, attributeValueFilters, order, + followUp, displayColumnOrder, assignedUserMode, assignedUsers, @@ -54,6 +55,7 @@ export const addTEITemplateEpic = (action$: InputObservable, store: ReduxStore, ...(assignedUserMode && { assignedUserMode }), ...(assignedUsers?.length > 0 && { assignedUsers }), ...(programStatus && { enrollmentStatus: programStatus }), + ...(!!followUp && { followUp }), ...(enrolledAt && { enrollmentCreatedDate: enrolledAt }), ...(occurredAt && { enrollmentIncidentDate: occurredAt }), ...(attributeValueFilters?.length > 0 && { attributeValueFilters }), @@ -156,7 +158,7 @@ export const updateTEITemplateEpic = (action$: InputObservable, store: ReduxStor storeId, criteria, } = action.payload; - const { programStatus, enrolledAt, occurredAt, attributeValueFilters, order, displayColumnOrder, assignedUserMode, assignedUsers } = + const { programStatus, enrolledAt, occurredAt, attributeValueFilters, order, displayColumnOrder, assignedUserMode, assignedUsers, followUp } = criteria; const trackedEntityInstanceFilters = { name, @@ -174,6 +176,7 @@ export const updateTEITemplateEpic = (action$: InputObservable, store: ReduxStor ...(programStatus && { enrollmentStatus: programStatus }), ...(enrolledAt && { enrollmentCreatedDate: enrolledAt }), ...(occurredAt && { enrollmentIncidentDate: occurredAt }), + ...(followUp && { followUp }), ...(attributeValueFilters?.length > 0 && { attributeValueFilters }), }, }; diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/templates/getProgramStageTemplates.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/templates/getProgramStageTemplates.js index 612e9d8e42..b4475eefc7 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/templates/getProgramStageTemplates.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/templates/getProgramStageTemplates.js @@ -43,6 +43,7 @@ export const getProgramStageTemplates = ( eventStatus, eventScheduledAt, eventOccurredAt, + followUp, order, attributeValueFilters, dataFilters, @@ -64,6 +65,7 @@ export const getProgramStageTemplates = ( occurredAt: enrollmentOccurredAt, programStage, eventOccurredAt, + followUp, status: eventStatus, scheduledAt: eventScheduledAt, dataFilters, diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/templates/getTEITemplates.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/templates/getTEITemplates.js index b3490bc804..0f4b42e3ee 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/templates/getTEITemplates.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/templates/getTEITemplates.js @@ -42,6 +42,7 @@ export const getTEITemplates = ( enrollmentIncidentDate, order, attributeValueFilters, + followUp, displayColumnOrder, assignedUserMode, assignedUsers, @@ -59,6 +60,7 @@ export const getTEITemplates = ( programStatus: enrollmentStatus, enrolledAt: enrollmentCreatedDate, occurredAt: enrollmentIncidentDate, + followUp, order, displayColumnOrder, assignedUserMode, diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/apiTEIFilterToClientConfigConverter/convertToClientFilters.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/apiTEIFilterToClientConfigConverter/convertToClientFilters.js index dd9ed517b8..6730b1eba4 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/apiTEIFilterToClientConfigConverter/convertToClientFilters.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/apiTEIFilterToClientConfigConverter/convertToClientFilters.js @@ -4,7 +4,6 @@ import type { QuerySingleResource } from 'capture-core/utils/api'; import { getOptionSetFilter } from './optionSet'; import { filterTypesObject, - type BooleanFilterData, type TrueOnlyFilterData, type TextFilterData, type NumericFilterData, @@ -39,7 +38,11 @@ const getNumericFilter = (filter: ApiDataFilterNumeric): ?NumericFilterData => { return undefined; }; -const getBooleanFilter = (filter: ApiDataFilterBoolean): ?BooleanFilterData => { +// Api returns a boolean as an object if we filter attributes, but it returns a boolean if it's a main filter +const getBooleanFilter = (filter: ApiDataFilterBoolean): any => { + if (typeof filter === 'boolean') { + return { values: [filter] }; + } if (filter.in) { return { values: filter.in.map(value => value === 'true') }; } @@ -137,6 +140,7 @@ const mainFiltersTable = { [MAIN_FILTERS.PROGRAM_STATUS]: getMainFilterOptionSet, [MAIN_FILTERS.ENROLLED_AT]: getDateFilterContent, [MAIN_FILTERS.OCCURED_AT]: getDateFilterContent, + [MAIN_FILTERS.FOLLOW_UP]: getBooleanFilter, [ADDITIONAL_FILTERS.programStage]: getMainFilterOptionSet, [ADDITIONAL_FILTERS.status]: getMainFilterOptionSet, [ADDITIONAL_FILTERS.occurredAt]: getDateFilterContent, @@ -188,7 +192,7 @@ const convertAttributeFilters = ( const convertToClientMainFilters = TEIQueryCriteria => Object.entries(TEIQueryCriteria).reduce((acc, [key, value]) => { // $FlowFixMe I accept that not every filter type is listed, thats why I'm doing this test - if (!mainFiltersTable[key] || !value) { + if (!mainFiltersTable[key] || value === undefined) { return acc; } diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/clientConfigToApiTEIFilterQueryConverter/convertors.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/clientConfigToApiTEIFilterQueryConverter/convertors.js index 06f2d426a8..7236ae345b 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/clientConfigToApiTEIFilterQueryConverter/convertors.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/clientConfigToApiTEIFilterQueryConverter/convertors.js @@ -76,6 +76,7 @@ const mainFiltersTable = { [MAIN_FILTERS.PROGRAM_STATUS]: filter => filter.values[0], [MAIN_FILTERS.ENROLLED_AT]: filter => getDateFilter(filter)?.dateFilter, [MAIN_FILTERS.OCCURED_AT]: filter => getDateFilter(filter)?.dateFilter, + [MAIN_FILTERS.FOLLOW_UP]: filter => Boolean(filter.values[0]), [MAIN_FILTERS.ASSIGNEE]: getAssigneeFilter, [ADDITIONAL_FILTERS.status]: filter => filter.values[0], [ADDITIONAL_FILTERS.occurredAt]: filter => getDateFilter(filter)?.dateFilter, @@ -93,6 +94,7 @@ export const convertMainFilters = ({ occurredAt?: ?ApiDataFilterDateContents, enrolledAt?: ?ApiDataFilterDateContents, status?: ?string, + followUp?: ?boolean, eventOccurredAt?: ?ApiDataFilterDateContents, scheduledAt?: ?ApiDataFilterDateContents, assignedUserMode?: 'CURRENT' | 'PROVIDED' | 'NONE' | 'ANY', diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/templates/buildArgumentsForTemplate.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/templates/buildArgumentsForTemplate.js index 7afb6a89d1..12183c88b1 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/templates/buildArgumentsForTemplate.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/templates/buildArgumentsForTemplate.js @@ -27,7 +27,7 @@ export const buildArgumentsForTemplate = ({ programId: string, programStageId?: string, }) => { - const { programStatus, occurredAt, enrolledAt, assignedUserMode, assignedUsers } = convertMainFilters({ + const { programStatus, occurredAt, enrolledAt, assignedUserMode, assignedUsers, followUp } = convertMainFilters({ filters, mainFilters: filtersOnly, }); @@ -54,6 +54,7 @@ export const buildArgumentsForTemplate = ({ assignedUserMode, assignedUsers, status, + followUp, eventOccurredAt, scheduledAt, attributeValueFilters, diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/types/apiTemplate.types.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/types/apiTemplate.types.js index e7944d1dbe..48a5f47222 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/types/apiTemplate.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/types/apiTemplate.types.js @@ -10,7 +10,7 @@ export type ApiDataFilterNumeric = {| export type ApiDataFilterBoolean = {| in: Array, -|}; +|} | boolean; export type ApiDataFilterTrueOnly = {| eq: string, @@ -47,6 +47,7 @@ export type ApiTrackerQueryCriteria = {| scheduledAt?: ?ApiDataFilterDateContents, occurredAt?: ?ApiDataFilterDateContents, enrolledAt?: ?ApiDataFilterDateContents, + followUp?: ?boolean, order?: ?string, displayColumnOrder?: ?Array, assignedUserMode?: 'CURRENT' | 'PROVIDED' | 'NONE' | 'ANY', diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsCommon/helpers/buildFilterQueryArgs/filterConverters/booleanConverter.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsCommon/helpers/buildFilterQueryArgs/filterConverters/booleanConverter.js index 58e60750cc..e7981d13a0 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsCommon/helpers/buildFilterQueryArgs/filterConverters/booleanConverter.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsCommon/helpers/buildFilterQueryArgs/filterConverters/booleanConverter.js @@ -2,9 +2,14 @@ import { pipe } from 'capture-core-utils'; import type { BooleanFilterData } from '../../../../../ListView'; +const booleanFilterValues = { + true: 'true', + false: 'false', +}; + export function convertBoolean(filter: BooleanFilterData) { return pipe( - values => values.map(filterValue => (filterValue ? 'true' : 'false')), + values => values.map(filterValue => booleanFilterValues[filterValue]), values => (values.length > 1 ? { valueString: values.join(';'), single: false } :