diff --git a/i18n/en.pot b/i18n/en.pot index 0b0a773ff7..ca17eb6a6d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -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: 2020-01-22T07:56:34.947Z\n" -"PO-Revision-Date: 2020-01-22T07:56:34.947Z\n" +"POT-Creation-Date: 2020-02-21T09:38:52.654Z\n" +"PO-Revision-Date: 2020-02-21T09:38:52.655Z\n" msgid "Please provide a valid date" msgstr "" @@ -224,12 +224,6 @@ msgstr "" msgid "Min" msgstr "" -msgid "greater than or equal to" -msgstr "" - -msgid "less than or equal to" -msgstr "" - msgid "Contains text" msgstr "" @@ -356,6 +350,12 @@ msgstr "" msgid "before or equal to {{date}}" msgstr "" +msgid "greater than or equal to" +msgstr "" + +msgid "less than or equal to" +msgstr "" + msgid "More filters" msgstr "" @@ -386,6 +386,15 @@ msgstr "" msgid "Rows per page" msgstr "" +msgid "Working list could not be loaded" +msgstr "" + +msgid "an error occurred loading working lists" +msgstr "" + +msgid "Registered events" +msgstr "" + msgid "Active" msgstr "" @@ -688,6 +697,9 @@ msgstr "" msgid "Could not save event. See log for details" msgstr "" +msgid "Could not delete event" +msgstr "" + msgid "Organisation unit search failed. See log for details" msgstr "" diff --git a/jsconfig.json b/jsconfig.json index 739a408528..6b1d0054db 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,6 +1,14 @@ { "exclude": [ "node_modules", - "**/node_modules/*" + "**/node_modules/*", + "build", + "buildzip", + "coverage", + "docs", + "flow", + "flow-typed", + "i18n", + "public" ] } \ No newline at end of file diff --git a/package.json b/package.json index 1a9f86c698..78a43fa1b1 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "eslint-import-resolver-webpack": "^0.11.1", "eslint-plugin-import": "^2.17.3", "expose-loader": "^0.7.5", - "flow-bin": "^0.87.0", + "flow-bin": "^0.118.0", "jsdoc": "^3.6.2", "jsdoc-babel": "^0.5.0", "jsdoc-export-default-interop": "^0.3.1", diff --git a/src/core_modules/capture-core/HOC/index.js b/src/core_modules/capture-core/HOC/index.js index 6326b7fb1f..a83dbdfff8 100644 --- a/src/core_modules/capture-core/HOC/index.js +++ b/src/core_modules/capture-core/HOC/index.js @@ -1,3 +1,5 @@ // @flow export { default as withTransformPropName } from './withTransformPropName'; +export { default as withLoadingIndicator } from './withLoadingIndicator'; +export { default as withErrorMessageHandler } from './withErrorMessageHandler'; diff --git a/src/core_modules/capture-core/components/App/isSelectionsEqual.js b/src/core_modules/capture-core/components/App/isSelectionsEqual.js index 893cd214de..f3916969da 100644 --- a/src/core_modules/capture-core/components/App/isSelectionsEqual.js +++ b/src/core_modules/capture-core/components/App/isSelectionsEqual.js @@ -1,25 +1,34 @@ // @flow -/* - The first argument must be a complete selections set -*/ const CATEGORIES_KEY = 'categories'; const COMPLETE_KEY = 'complete'; -const isSelectionsEqual = (completeSet1: Object, set2: Object) => - Object - .keys(completeSet1) - .filter(key => key !== COMPLETE_KEY && completeSet1[key] != null) +const isSelectionsEqual = (set1: Object, set2: Object) => { + const set1Keys = Object + .keys(set1) + .filter(key => + key !== COMPLETE_KEY && set1[key] && (typeof set1[key] !== 'object' || Object.keys(set1[key]).length > 0)); + + const set2Keys = Object + .keys(set2) + .filter(key => + key !== COMPLETE_KEY && set2[key] && (typeof set2[key] !== 'object' || Object.keys(set2[key]).length > 0)); + + if (set1Keys.length !== set2Keys.length) { + return false; + } + + return set1Keys .every((key) => { - const value1 = completeSet1[key]; + const value1 = set1[key]; const value2 = set2[key]; if (key === CATEGORIES_KEY) { - const isCategoriesEqual = isSelectionsEqual(completeSet1.categories, set2.categories); - return isCategoriesEqual; + return isSelectionsEqual(set1.categories, set2.categories); } return value1 === value2; }); +}; export default isSelectionsEqual; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Assignee/AssigneeFilter.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/AssigneeFilter.component.js index b059edef7a..1c2a25f42c 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Assignee/AssigneeFilter.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/AssigneeFilter.component.js @@ -5,7 +5,7 @@ import i18n from '@dhis2/d2-i18n'; import { SelectionBoxes, orientations } from '../../FormFields/New'; import { UserField } from '../../FormFields/UserField'; import { getModeOptions, modeKeys } from './modeOptions'; -import { getAssigneeFilterData } from './assigneeFilterData'; +import { getAssigneeFilterData } from './assigneeFilterDataGetter'; import type { UpdatableFilterContent } from '../filters.types'; const getStyles = (theme: Theme) => ({ @@ -21,7 +21,7 @@ const getStyles = (theme: Theme) => ({ type Value = ?{ mode: string, - provided: string, + provided: ?Object, }; type Props = { diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Assignee/AssigneeFilterManager.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/AssigneeFilterManager.component.js new file mode 100644 index 0000000000..7ed55e86f3 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/AssigneeFilterManager.component.js @@ -0,0 +1,57 @@ +// @flow +import * as React from 'react'; +import AssigneeFilter from './AssigneeFilter.component'; +import type { AssigneeFilterData } from '../filters.types'; + +type Props = { + filter: ?AssigneeFilterData, + filterTypeRef: ?Function, +}; + +type State = { + value?: ?{ + mode?: ?string, + provided?: ?Object, + }, +}; + +class AssigneeFilterManager extends React.Component { + static calculateDefaultValueState(filter: ?AssigneeFilterData) { + if (!filter) { + return undefined; + } + + return { + mode: filter.assignedUserMode, + provided: filter.assignedUser, + }; + } + + constructor(props: Props) { + super(props); + this.state = { + value: AssigneeFilterManager.calculateDefaultValueState(this.props.filter), + }; + } + + handleCommitValue = (value: ?Object) => { + this.setState({ + value, + }); + } + + render() { + const { filter, filterTypeRef, ...passOnProps } = this.props; + + return ( + + ); + } +} + +export default AssigneeFilterManager; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Assignee/assigneeFilterData.js b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/assigneeFilterData.js deleted file mode 100644 index c513c2307b..0000000000 --- a/src/core_modules/capture-core/components/FiltersForTypes/Assignee/assigneeFilterData.js +++ /dev/null @@ -1,12 +0,0 @@ -// @flow -import { getModeOptions } from './modeOptions'; - -export function getAssigneeFilterData(value: Object) { - return { - requestData: { - assignedUserMode: value.mode, - assignedUser: value.provided && value.provided.id, - }, - appliedText: value.provided ? value.provided.name : getModeOptions().find(o => o.value === value.mode).name, - }; -} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Assignee/assigneeFilterDataGetter.js b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/assigneeFilterDataGetter.js new file mode 100644 index 0000000000..b5636ab895 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/assigneeFilterDataGetter.js @@ -0,0 +1,9 @@ +// @flow +import type { AssigneeFilterData } from '../filters.types'; + +export function getAssigneeFilterData(value: Object): AssigneeFilterData { + return { + assignedUserMode: value.mode, + assignedUser: value.provided, + }; +} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Assignee/index.js b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/index.js index 23eccbc687..d4b3d653f0 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Assignee/index.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Assignee/index.js @@ -1,4 +1,4 @@ // @flow -export { default as AssigneeFilter } from './AssigneeFilter.component'; -export { getAssigneeFilterData } from './assigneeFilterData'; +export { default as AssigneeFilter } from './AssigneeFilterManager.component'; +export { getAssigneeFilterData } from './assigneeFilterDataGetter'; export { modeKeys } from './modeOptions'; 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 87fe28f71b..9f2338f81a 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 @@ -4,7 +4,7 @@ import { withStyles } from '@material-ui/core/styles'; import elementTypes from '../../../metaData/DataElement/elementTypes'; import D2TrueFalse from '../../FormFields/Generic/D2TrueFalse.component'; import { orientations } from '../../FormFields/Options/MultiSelectBoxes/multiSelectBoxes.const'; -import getBooleanFilterData from './getBooleanFilterData'; +import { getBooleanFilterData } from './booleanFilterDataGetter'; import type { UpdatableFilterContent } from '../filters.types'; const getStyles = (theme: Theme) => ({ diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/BooleanFilterManager.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/BooleanFilterManager.component.js new file mode 100644 index 0000000000..3888978951 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/BooleanFilterManager.component.js @@ -0,0 +1,53 @@ +// @flow +import * as React from 'react'; +import BooleanFilter from './BooleanFilter.component'; +import type { BooleanFilterData } from '../filters.types'; + +type Props = { + filter: ?BooleanFilterData, + filterTypeRef: Function, +}; + +type State = { + value: ?Array, +}; + +class BooleanFilterManager extends React.Component { + static calculateDefaultValueState(filter: ?BooleanFilterData): ?Array { + if (!filter) { + return undefined; + } + + return filter + .values + .map(value => (value ? 'true' : 'false')); + } + + constructor(props: Props) { + super(props); + this.state = { + value: BooleanFilterManager.calculateDefaultValueState(this.props.filter), + }; + } + + handleCommitValue = (value: ?Array) => { + this.setState({ + value, + }); + } + + render() { + const { filter, filterTypeRef, ...passOnProps } = this.props; + + return ( + + ); + } +} + +export default BooleanFilterManager; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/booleanFilterDataGetter.js b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/booleanFilterDataGetter.js new file mode 100644 index 0000000000..6cdeb98c64 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/booleanFilterDataGetter.js @@ -0,0 +1,11 @@ +// @flow +import type { BooleanFilterData } from '../filters.types'; + +export function getBooleanFilterData( + values: Array, +): BooleanFilterData { + return { + values: values + .map(value => (value === 'true')), + }; +} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/getBooleanFilterData.js b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/getBooleanFilterData.js deleted file mode 100644 index 6670d95098..0000000000 --- a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/getBooleanFilterData.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow -import { convertValue as convertToServerValue } from '../../../converters/clientToServer'; -import { convertValue as convertToClientValue } from '../../../converters/formToClient'; -import { dataElementTypes as elementTypes, OptionSet } from '../../../metaData'; - -const getRequestData = (values: Array, type: $Values) => { - const valueString = values - .map((value) => { - const clientValue = convertToClientValue(value, type); - const filterValue = convertToServerValue(clientValue, type); // should work for now - return filterValue; - }) - .join(';'); - - return `in:${valueString}`; -}; - -const getAppliedText = (values: Array, optionSet: ?OptionSet) => { - const valueString = values - .map((value) => { - const text = optionSet ? optionSet.getOptionText(value) : value; - return text; - }) - .join(', '); - - return valueString; -}; - -export default function (values: Array, type: $Values, optionSet: ?OptionSet) { - return { - requestData: getRequestData(values, type), - appliedText: getAppliedText(values, optionSet), - }; -} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Boolean/index.js b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/index.js new file mode 100644 index 0000000000..c35acd19bf --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Boolean/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as BooleanFilter } from './BooleanFilterManager.component'; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilter.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilter.component.js index 3bcbb51bb5..ff34a97c52 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilter.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilter.component.js @@ -8,18 +8,17 @@ import { orientations } from '../../FormFields/Options/SingleSelectBoxes/singleS import OptionSet from '../../../metaData/OptionSet/OptionSet'; import Option from '../../../metaData/OptionSet/Option'; -import moment from 'capture-core-utils/moment/momentResolver'; import From from './From.component'; import To from './To.component'; import { isValidDate, } from '../../../utils/validators/form'; -import { convertValue as convertToClientValue } from '../../../converters/formToClient'; -import elementTypes from '../../../metaData/DataElement/elementTypes'; +import { parseDate } from '../../../utils/converters/date'; +import { dataElementTypes as elementTypes } from '../../../metaData'; import type { UpdatableFilterContent } from '../filters.types'; import './calendarFilterStyles.css'; import { mainOptionKeys, mainOptionTranslatedTexts } from './mainOptions'; -import getDateFilterData from './getDateFilterData'; +import { getDateFilterData } from './dateFilterDataGetter'; const getStyles = (theme: Theme) => ({ fromToContainer: { @@ -45,7 +44,7 @@ const getStyles = (theme: Theme) => ({ }, }); -type Value = ?{ +export type Value = ?{ from?: ?string, to?: ?string, main?: ?string, @@ -117,11 +116,6 @@ class DateFilter extends Component implements UpdatableFilterConte }), ]); - static convertDateFilterValueToClientValue(formValue: string): string { - // $FlowSuppress - return convertToClientValue(formValue, elementTypes.DATE); - } - static validateField(value: ?string, type: $Values) { if (!value) { return { @@ -139,32 +133,38 @@ class DateFilter extends Component implements UpdatableFilterConte }; } + // eslint-disable-next-line complexity static isFilterValid( mainValue?: ?string, fromValue?: ?string, toValue?: ?string, - type: $Values, ) { - if (mainValue === mainOptionKeys.CUSTOM_RANGE && !fromValue && !toValue) { + if (mainValue !== mainOptionKeys.CUSTOM_RANGE) { + return true; + } + + if (!fromValue && !toValue) { return false; } - if (!(DateFilter.validateField(fromValue, type).isValid && - DateFilter.validateField(fromValue, type).isValid)) { + const parseResultFrom = fromValue ? parseDate(fromValue) : { isValid: true, moment: null }; + const parseResultTo = toValue ? parseDate(toValue) : { isValid: true, moment: null }; + + if (!(parseResultFrom.isValid && parseResultTo.isValid)) { return false; } - return !(fromValue && toValue && this.isFromAfterTo(fromValue, toValue)); + return !( + parseResultFrom.momentDate && + parseResultTo.momentDate && + parseResultFrom.momentDate.isAfter(parseResultTo.momentDate) + ); } static isFromAfterTo(valueFrom: string, valueTo: string) { - const valueFromClient = DateFilter.convertDateFilterValueToClientValue(valueFrom); - const valueToClient = DateFilter.convertDateFilterValueToClientValue(valueTo); - - // $FlowSuppress - const momentFrom = moment(valueFromClient); - // $FlowSuppress - const momentTo = moment(valueToClient); + const momentFrom = parseDate(valueFrom).momentDate; + const momentTo = parseDate(valueTo).momentDate; + // $FlowSuppress: Prechecked return momentFrom.isAfter(momentTo); } @@ -186,7 +186,7 @@ class DateFilter extends Component implements UpdatableFilterConte onIsValid() { this.setState({ submitAttempted: true }); const values = this.props.value; - return !values || DateFilter.isFilterValid(values.main, values.from, values.to, this.props.type); + return !values || DateFilter.isFilterValid(values.main, values.from, values.to); } getUpdatedValue(valuePart: {[key: string]: string}) { @@ -220,7 +220,7 @@ class DateFilter extends Component implements UpdatableFilterConte const values = this.getUpdatedValue(value); this.setState({ submitAttempted: true }); - if (values && !DateFilter.isFilterValid(values.main, values.from, values.to, this.props.type)) { + if (values && !DateFilter.isFilterValid(values.main, values.from, values.to)) { this.props.onCommitValue(values); } else { this.props.onUpdate(values || null); @@ -240,6 +240,7 @@ class DateFilter extends Component implements UpdatableFilterConte this.toD2DateTextFieldInstance = instance; } + // eslint-disable-next-line complexity getErrors() { const values = this.props.value; const submitAttempted = this.state.submitAttempted; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilterManager.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilterManager.component.js new file mode 100644 index 0000000000..b2c64fccc9 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilterManager.component.js @@ -0,0 +1,70 @@ +// @flow +import * as React from 'react'; +import { moment } from 'capture-core-utils/moment'; +import { convertMomentToDateFormatString } from '../../../utils/converters/date'; +import DateFilter from './DateFilter.component'; +import { mainOptionKeys } from './mainOptions'; +import { type DateFilterData, dateFilterTypes } from '../filters.types'; +import type { Value } from './DateFilter.component'; + +type Props = { + filter: ?DateFilterData, + filterTypeRef: Function, +}; + +type State = { + value?: Value, +}; + +class DateFilterManager extends React.Component { + static convertDateForEdit(rawValue: string) { + const momentInstance = moment(rawValue); + return convertMomentToDateFormatString(momentInstance); + } + + static calculateDefaultValueState(filter: ?DateFilterData) { + if (!filter) { + return undefined; + } + + if (filter.type === dateFilterTypes.RELATIVE) { + return { + main: filter.period, + }; + } + + return { + main: mainOptionKeys.CUSTOM_RANGE, + from: filter.ge && DateFilterManager.convertDateForEdit(filter.ge), + to: filter.le && DateFilterManager.convertDateForEdit(filter.le), + }; + } + + constructor(props: Props) { + super(props); + this.state = { + value: DateFilterManager.calculateDefaultValueState(this.props.filter), + }; + } + + handleCommitValue = (value: ?Object) => { + this.setState({ + value, + }); + } + + render() { + const { filter, filterTypeRef, ...passOnProps } = this.props; + + return ( + + ); + } +} + +export default DateFilterManager; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Date/dateFilterDataGetter.js b/src/core_modules/capture-core/components/FiltersForTypes/Date/dateFilterDataGetter.js new file mode 100644 index 0000000000..d91ee5523a --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Date/dateFilterDataGetter.js @@ -0,0 +1,40 @@ +// @flow +import { mainOptionKeys } from './mainOptions'; +import { parseDate } from '../../../utils/converters/date'; +import { type AbsoluteDateFilterData, dateFilterTypes } from '../filters.types'; + +type Value = { + main: string, + from?: ?string, + to?: ?string, +} + +function convertAbsoluteDate(fromValue: ?string, toValue: ?string) { + const rangeData: AbsoluteDateFilterData = { + type: dateFilterTypes.ABSOLUTE, + }; + + if (fromValue) { + // $FlowSuppress Prechecked + const fromClientValue: string = parseDate(fromValue).momentDate; + rangeData.ge = fromClientValue; + } + + if (toValue) { + // $FlowSuppress Prechecked + const toClientValue: string = parseDate(toValue).momentDate; + rangeData.le = toClientValue; + } + + return rangeData; +} + +function convertSelections(value: Value) { + return value.main === mainOptionKeys.CUSTOM_RANGE ? + convertAbsoluteDate(value.from, value.to) : + { type: dateFilterTypes.RELATIVE, period: value.main }; +} + +export function getDateFilterData(value: Value) { + return convertSelections(value); +} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Date/getDateFilterData.js b/src/core_modules/capture-core/components/FiltersForTypes/Date/getDateFilterData.js deleted file mode 100644 index 29781e77fd..0000000000 --- a/src/core_modules/capture-core/components/FiltersForTypes/Date/getDateFilterData.js +++ /dev/null @@ -1,49 +0,0 @@ -// @flow -import { mainOptionKeys } from './mainOptions'; -import { dataElementTypes as elementTypes } from '../../../metaData'; -import { convertValue as convertToClientValue } from '../../../converters/formToClient'; - -type Value = { - main: string, - from?: ?string, - to?: ?string, -} - -const filterTypes = { - ABSOLUTE: 'ABSOLUTE', - RELATIVE: 'RELATIVE', -}; - -const convertDateFilterValueToClientValue = (formValue: string): string => - // $FlowFixMe - convertToClientValue(formValue, elementTypes.DATE); - -const converterForMainSelections = { - [mainOptionKeys.CUSTOM_RANGE]: (fromValue: ?string, toValue: ?string) => { - const rangeData: AbsoluteDateFilterData = { - type: filterTypes.ABSOLUTE, - }; - - if (fromValue) { - const fromClientValue: string = convertDateFilterValueToClientValue(fromValue); - rangeData.ge = fromClientValue; - } - - if (toValue) { - const toClientValue: string = convertDateFilterValueToClientValue(toValue); - rangeData.le = toClientValue; - } - - return rangeData; - }, -}; - -function convertSelections(value: Value) { - return converterForMainSelections[value.main] ? - converterForMainSelections[value.main](value.from, value.to) : - { type: filterTypes.RELATIVE, period: value.main }; -} - -export default function getDateFilterData(value: Value) { - return convertSelections(value); -} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Date/index.js b/src/core_modules/capture-core/components/FiltersForTypes/Date/index.js new file mode 100644 index 0000000000..0e7e4de548 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Date/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as DateFilter } from './DateFilterManager.component'; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Numeric/NumericFilter.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/NumericFilter.component.js index 5251a1957e..7b5dcfdca5 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Numeric/NumericFilter.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/NumericFilter.component.js @@ -3,8 +3,6 @@ import React, { Component } from 'react'; import classNames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import i18n from '@dhis2/d2-i18n'; -import Min from './Min.component'; -import Max from './Max.component'; import { isValidNumber, isValidInteger, @@ -12,9 +10,11 @@ import { isValidNegativeInteger, isValidZeroOrPositiveInteger, } from 'capture-core-utils/validators/form'; -import elementTypes from '../../../metaData/DataElement/elementTypes'; +import Min from './Min.component'; +import Max from './Max.component'; +import { dataElementTypes as elementTypes } from '../../../metaData'; import D2TextField from '../../FormFields/Generic/D2TextField.component'; -import getNumericFilterData from './getNumericFilterData'; +import { getNumericFilterData } from './numericFilterDataGetter'; import type { UpdatableFilterContent } from '../filters.types'; const getStyles = (theme: Theme) => ({ diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Numeric/NumericFilterManager.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/NumericFilterManager.component.js new file mode 100644 index 0000000000..12b1dd740d --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/NumericFilterManager.component.js @@ -0,0 +1,54 @@ +// @flow +import * as React from 'react'; +import NumericFilter from './NumericFilter.component'; +import type { NumericFilterData } from '../filters.types'; + +type Props = { + filter: ?NumericFilterData, + filterTypeRef: ?Function, +}; + +type State = { + value: { + min: ?string, + max: ?string, + }, +}; + +class NumericFilterManager extends React.Component { + // eslint-disable-next-line complexity + static calculateDefaultState(filter: ?NumericFilterData) { + return { + min: filter && (filter.ge || filter.ge === 0) ? filter.ge.toString() : undefined, + max: filter && (filter.le || filter.le === 0) ? filter.le.toString() : undefined, + }; + } + + constructor(props: Props) { + super(props); + this.state = { + value: NumericFilterManager.calculateDefaultState(this.props.filter), + }; + } + + handleCommitValue = (value: Object) => { + this.setState({ + value, + }); + } + + render() { + const { filter, filterTypeRef, ...passOnProps } = this.props; + + return ( + + ); + } +} + +export default NumericFilterManager; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Numeric/getNumericFilterData.js b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/getNumericFilterData.js deleted file mode 100644 index 8b6a907faa..0000000000 --- a/src/core_modules/capture-core/components/FiltersForTypes/Numeric/getNumericFilterData.js +++ /dev/null @@ -1,52 +0,0 @@ -// @flow -import i18n from '@dhis2/d2-i18n'; - -type Value = { - min?: ?string, - max?: ?string, -} - -const getRequestData = (value: Value) => { - const requestData = []; - - if (value.min) { - requestData.push(`ge:${value.min}`); - } - if (value.max) { - requestData.push(`le:${value.max}`); - } - - if (requestData.length === 2) { - return requestData.join(':'); - } - - return requestData[0]; -}; - -const getAppliedText = (value: Value) => { - let appliedText = ''; - - if (value.min && value.max) { - if (Number(value.min) === Number(value.max)) { - appliedText = value.min; - } else { - // $FlowSuppress - appliedText = `${value.min} ${i18n.t('to')} ${value.max}`; - } - } else if (value.min) { - // $FlowSuppress - appliedText = `${i18n.t('greater than or equal to')} ${value.min}`; - } else { - // $FlowSuppress - appliedText = `${i18n.t('less than or equal to')} ${value.max}`; - } - - return appliedText; -}; - -export default function (value: Value) { - return { - requestData: getRequestData(value), - appliedText: getAppliedText(value), - }; -} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Numeric/index.js b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/index.js new file mode 100644 index 0000000000..7f54df7a06 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as NumericFilter } from './NumericFilterManager.component'; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Numeric/numericFilterDataGetter.js b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/numericFilterDataGetter.js new file mode 100644 index 0000000000..60f5cdae2a --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Numeric/numericFilterDataGetter.js @@ -0,0 +1,18 @@ +// @flow +import { parseNumber } from 'capture-core-utils/parsers'; +import type { NumericFilterData } from '../filters.types'; + +type Value = { + min?: ?string, + max?: ?string, +} + +export function getNumericFilterData(value: Value): NumericFilterData { + const min = value.min || undefined; + const max = value.max || undefined; + + return { + ge: min && parseNumber(min), + le: max && parseNumber(max), + }; +} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/OptionSetFilter.component.js b/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/OptionSetFilter.component.js index 2061ad3a38..a6009404ec 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/OptionSetFilter.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/OptionSetFilter.component.js @@ -1,16 +1,12 @@ // @flow import React, { Component } from 'react'; import { withStyles } from '@material-ui/core/styles'; -import elementTypes from '../../../metaData/DataElement/elementTypes'; import SelectBoxes from '../../FormFields/Options/SelectBoxes/SelectBoxes.component'; import { orientations } from '../../FormFields/Options/MultiSelectBoxes/multiSelectBoxes.const'; -import withConvertedOptionSet from '../../FormFields/Options/withConvertedOptionSet'; import OptionSet from '../../../metaData/OptionSet/OptionSet'; -import { getSingleSelectOptionSetFilterData, getMultiSelectOptionSetFilterData } from './getOptionSetFilterData'; +import { getSingleSelectOptionSetFilterData, getMultiSelectOptionSetFilterData } from './optionSetFilterDataGetter'; import type { UpdatableFilterContent } from '../filters.types'; -const SelectBoxesWithConvertedOptionSet = withConvertedOptionSet()(SelectBoxes); - const getStyles = (theme: Theme) => ({ selectBoxesContainer: { maxHeight: theme.typography.pxToRem(250), @@ -21,28 +17,28 @@ const getStyles = (theme: Theme) => ({ type Props = { - type: $Values, optionSet: OptionSet, value: any, onCommitValue: (value: any) => void, classes: { selectBoxesContainer: string, }, + singleSelect?: ?boolean, }; // $FlowSuppress class OptionSetFilter extends Component implements UpdatableFilterContent { onGetUpdateData() { - const { value, singleSelect, type } = this.props; + const { value, singleSelect } = this.props; if (!value) { return null; } if (singleSelect) { - return getSingleSelectOptionSetFilterData(value, type); + return getSingleSelectOptionSetFilterData(value); } - return getMultiSelectOptionSetFilterData(value, type); + return getMultiSelectOptionSetFilterData(value); } onIsValid() { //eslint-disable-line @@ -56,7 +52,7 @@ class OptionSetFilter extends Component implements UpdatableFilterContent
- , +}; + +class OptionSetFilterManager extends React.Component { + static calculateDefaultValueState(filter: ?OptionSetFilterData, singleSelect: boolean) { + if (!filter) { + return undefined; + } + + return singleSelect ? filter.values[0] : filter.values; + } + + constructor(props: Props) { + super(props); + this.state = { + value: OptionSetFilterManager.calculateDefaultValueState(this.props.filter, !!this.props.singleSelect), + }; + } + + handleCommitValue = (value: ?Array) => { + this.setState({ + value, + }); + } + + render() { + const { filter, filterTypeRef, ...passOnProps } = this.props; + + return ( + + ); + } +} + +export default OptionSetFilterManager; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/getOptionSetFilterData.js b/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/getOptionSetFilterData.js deleted file mode 100644 index f53d897577..0000000000 --- a/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/getOptionSetFilterData.js +++ /dev/null @@ -1,60 +0,0 @@ -// @flow -import { dataElementTypes as elementTypes, OptionSet } from '../../../metaData'; -import { convertValue as convertToServerValue } from '../../../converters/clientToServer'; -import { convertValue as convertToClientValue } from '../../../converters/formToClient'; - -/* -const getMultiSelectRequestData = (values: Array, type: $Values) => { - const valueString = values - .map((value) => { - const clientValue = convertToClientValue(value, type); - const filterValue = convertToServerValue(clientValue, type); // should work for now - return filterValue; - }) - .join(';'); - - return `in:${valueString}`; -}; - -const getMultiSelectAppliedText = (values: Array, optionSet: OptionSet) => { - const valueString = values - .map((value) => { - const text = optionSet.getOptionText(value); - return text; - }) - .join(', '); - - return valueString; -}; - -export const getMultiSelectOptionSetFilterData = - (value: Array, type: $Values, optionSet: OptionSet) => ({ - requestData: getMultiSelectRequestData(value, type), - appliedText: getMultiSelectAppliedText(value, optionSet), - }); - -export const getSingleSelectOptionSetFilterData = - (value: any, optionSet: OptionSet) => ({ - requestData: `eq:${value}`, - appliedText: optionSet.getOptionText(value), - }); - -*/ - -function getSelectOptionSetFilterData( - values: Array, - type: $Values, -): { optionSet: boolean, values: Array } { - return { - optionSet: true, - values: values - .map(value => convertToClientValue(value, type)), - }; -} - -export const getMultiSelectOptionSetFilterData = getSelectOptionSetFilterData; - -export const getSingleSelectOptionSetFilterData = ( - value: any, - type: $Values, -) => getSelectOptionSetFilterData([value], type); diff --git a/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/index.js b/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/index.js new file mode 100644 index 0000000000..22867677ac --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as OptionSetFilter } from './OptionSetFilterManager.component'; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/optionSetFilterDataGetter.js b/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/optionSetFilterDataGetter.js new file mode 100644 index 0000000000..62f4a5d4fa --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/OptionSet/optionSetFilterDataGetter.js @@ -0,0 +1,17 @@ +// @flow +import type { OptionSetFilterData } from '../filters.types'; + +function getSelectOptionSetFilterData( + values: Array, +): OptionSetFilterData { + return { + usingOptionSet: true, + values, + }; +} + +export const getMultiSelectOptionSetFilterData = getSelectOptionSetFilterData; + +export const getSingleSelectOptionSetFilterData = ( + value: any, +) => getSelectOptionSetFilterData([value]); diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Text/TextFilter.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Text/TextFilter.component.js index 62d5f4c6e2..2297545788 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Text/TextFilter.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Text/TextFilter.component.js @@ -1,7 +1,7 @@ // @flow import React, { Component } from 'react'; import Input from './Input.component'; -import getTextFilterData from './getTextFilterData'; +import { getTextFilterData } from './textFilterDataGetter'; import type { UpdatableFilterContent } from '../filters.types'; type Value = ?string; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Text/TextFilterManager.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Text/TextFilterManager.component.js new file mode 100644 index 0000000000..81372f2434 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Text/TextFilterManager.component.js @@ -0,0 +1,47 @@ +// @flow +import * as React from 'react'; +import TextFilter from './TextFilter.component'; +import type { TextFilterData } from '../filters.types'; + +type Props = { + filter: ?TextFilterData, + filterTypeRef: Function, +}; + +type State = { + value: ?string, +}; + +class TextFilterManager extends React.Component { + static calculateDefaultState(filter: ?TextFilterData) { + return { + value: (filter && filter.value ? filter.value : undefined), + }; + } + + constructor(props: Props) { + super(props); + this.state = TextFilterManager.calculateDefaultState(this.props.filter); + } + + handleCommitValue = (value: ?string) => { + this.setState({ + value, + }); + } + + render() { + const { filter, filterTypeRef, ...passOnProps } = this.props; + + return ( + + ); + } +} + +export default TextFilterManager; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Text/getTextFilterData.js b/src/core_modules/capture-core/components/FiltersForTypes/Text/getTextFilterData.js deleted file mode 100644 index 4a641da31b..0000000000 --- a/src/core_modules/capture-core/components/FiltersForTypes/Text/getTextFilterData.js +++ /dev/null @@ -1,8 +0,0 @@ -// @flow - -export default function (value: string) { - return { - requestData: `like:${value}`, - appliedText: value, - }; -} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Text/index.js b/src/core_modules/capture-core/components/FiltersForTypes/Text/index.js new file mode 100644 index 0000000000..ec99f4709e --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Text/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as TextFilter } from './TextFilterManager.component'; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Text/textFilterDataGetter.js b/src/core_modules/capture-core/components/FiltersForTypes/Text/textFilterDataGetter.js new file mode 100644 index 0000000000..21890f40b9 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/Text/textFilterDataGetter.js @@ -0,0 +1,8 @@ +// @flow +import type { TextFilterData } from '../filters.types'; + +export function getTextFilterData(value: string): TextFilterData { + return { + value, + }; +} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/TrueOnlyFilter.component.js b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/TrueOnlyFilter.component.js index 60ce53b426..23fea108fa 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/TrueOnlyFilter.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/TrueOnlyFilter.component.js @@ -2,10 +2,9 @@ import React, { Component } from 'react'; import i18n from '@dhis2/d2-i18n'; import { withStyles } from '@material-ui/core/styles'; -import elementTypes from '../../../metaData/DataElement/elementTypes'; import D2TrueOnly from '../../FormFields/Generic/D2TrueOnly.component'; import { orientations } from '../../FormFields/Options/MultiSelectBoxes/multiSelectBoxes.const'; -import getTrueOnlyFilterData from './getTrueOnlyFilterData'; +import { getTrueOnlyFilterData } from './trueOnlyFilterDataGetter'; import type { UpdatableFilterContent } from '../filters.types'; const getStyles = (theme: Theme) => ({ @@ -17,7 +16,6 @@ const getStyles = (theme: Theme) => ({ type Value = ?Array; type Props = { - type: $Values, value: Value, onCommitValue: (value: Value) => void, classes: { @@ -33,7 +31,7 @@ class TrueOnlyFilter extends Component implements UpdatableFilterContent< return null; } - return getTrueOnlyFilterData(value, this.props.type); + return getTrueOnlyFilterData(); } onIsValid() { //eslint-disable-line diff --git a/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/TrueOnlyFilterManager.component.js b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/TrueOnlyFilterManager.component.js new file mode 100644 index 0000000000..e5f179c9d5 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/TrueOnlyFilterManager.component.js @@ -0,0 +1,47 @@ +// @flow +import * as React from 'react'; +import TrueOnlyFilter from './TrueOnlyFilter.component'; +import type { TrueOnlyFilterData } from '../filters.types'; + +type Props = { + filter: ?TrueOnlyFilterData, + filterTypeRef: ?Function, +}; + +type State = { + value: ?Array, +}; + +class TrueOnlyFilterManager extends React.Component { + static calculateDefaultState(filter: ?TrueOnlyFilterData) { + return { + value: filter && filter.value ? ['true'] : undefined, + }; + } + + constructor(props: Props) { + super(props); + this.state = TrueOnlyFilterManager.calculateDefaultState(this.props.filter); + } + + handleCommitValue = (value: ?Array) => { + this.setState({ + value, + }); + } + + render() { + const { filter, filterTypeRef, ...passOnProps } = this.props; + + return ( + + ); + } +} + +export default TrueOnlyFilterManager; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/getTrueOnlyFilterData.js b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/getTrueOnlyFilterData.js deleted file mode 100644 index fb6c68e604..0000000000 --- a/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/getTrueOnlyFilterData.js +++ /dev/null @@ -1,35 +0,0 @@ -// @flow -import i18n from '@dhis2/d2-i18n'; -import { convertValue as convertToServerValue } from '../../../converters/clientToServer'; -import { convertValue as convertToClientValue } from '../../../converters/formToClient'; -import { dataElementTypes as elementTypes } from '../../../metaData'; - -const getRequestData = (values: Array, type: $Values) => { - const valueString = values - .map((value) => { - const clientValue = convertToClientValue(value, type); - const filterValue = convertToServerValue(clientValue, type); // should work for now - return filterValue; - }) - .join(';'); - - return `in:${valueString}`; -}; - -const getAppliedText = (values: Array) => { - const valueString = values - .map(() => { - const text = i18n.t('Yes'); - return text; - }) - .join(', '); - - return valueString; -}; - -export default function (value: Array, type: $Values) { - return { - requestData: getRequestData(value, type), - appliedText: getAppliedText(value), - }; -} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/index.js b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/index.js new file mode 100644 index 0000000000..1d6b31f317 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as TrueOnlyFilter } from './TrueOnlyFilterManager.component'; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/trueOnlyFilterDataGetter.js b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/trueOnlyFilterDataGetter.js new file mode 100644 index 0000000000..103eb7c3f7 --- /dev/null +++ b/src/core_modules/capture-core/components/FiltersForTypes/TrueOnly/trueOnlyFilterDataGetter.js @@ -0,0 +1,7 @@ +// @flow + +export function getTrueOnlyFilterData() { + return { + value: true, + }; +} diff --git a/src/core_modules/capture-core/components/FiltersForTypes/filters.types.js b/src/core_modules/capture-core/components/FiltersForTypes/filters.types.js index 7ee69ec0b4..48f0822426 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/filters.types.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/filters.types.js @@ -3,3 +3,59 @@ export interface UpdatableFilterContent { onGetUpdateData: (updatedValue?: T) => any; onIsValid?: () => boolean, } + +export const assigneeFilterModes = Object.freeze({ + PROVIDED: 'PROVIDED', + CURRENT: 'CURRENT', + ANY: 'ANY', + NONE: 'NONE', +}); + +export type AssigneeFilterData = { + assignedUserMode: $Values, + assignedUser?: ?{ + id: string, + username: string, + name: string, + }, +}; + +export const dateFilterTypes = Object.freeze({ + ABSOLUTE: 'ABSOLUTE', + RELATIVE: 'RELATIVE', +}); + +export type AbsoluteDateFilterData = { + type: 'ABSOLUTE', + ge?: string, + le?: string, +}; + +export type RelativeDateFilterData = { + type: 'RELATIVE', + period: string, +}; + +export type DateFilterData = AbsoluteDateFilterData | RelativeDateFilterData; + +export type OptionSetFilterData = { + usingOptionSet: boolean, + values: Array, +}; + +export type BooleanFilterData = { + values: Array, +}; + +export type TrueOnlyFilterData = { + value: true, +}; + +export type TextFilterData = { + value: string, +}; + +export type NumericFilterData = { + ge: ?number, + le: ?number, +}; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/index.js b/src/core_modules/capture-core/components/FiltersForTypes/index.js index 3f1b408c49..f222b0824b 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/index.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/index.js @@ -1,14 +1,8 @@ // @flow - -export { default as getBooleanFilterData } from './Boolean/getBooleanFilterData'; -export { - default as getDateFilterData, -} from './Date/getDateFilterData'; -export { default as getNumericFilterData } from './Numeric/getNumericFilterData'; -export { - getMultiSelectOptionSetFilterData, - getSingleSelectOptionSetFilterData, -} from './OptionSet/getOptionSetFilterData'; -export { default as getTextFilterData } from './Text/getTextFilterData'; -export { default as getTrueOnlyFilterData } from './TrueOnly/getTrueOnlyFilterData'; -export { AssigneeFilter, getAssigneeFilterData, modeKeys as assigneeFilterModeKeys } from './Assignee'; +export { TextFilter } from './Text'; +export { NumericFilter } from './Numeric'; +export { TrueOnlyFilter } from './TrueOnly'; +export { BooleanFilter } from './Boolean'; +export { DateFilter } from './Date'; +export { OptionSetFilter } from './OptionSet'; +export { AssigneeFilter, modeKeys as assigneeFilterModeKeys } from './Assignee'; diff --git a/src/core_modules/capture-core/components/Pages/EditEvent/DataEntry/epics/cancelEditSingleEvent.epics.js b/src/core_modules/capture-core/components/Pages/EditEvent/DataEntry/epics/cancelEditSingleEvent.epics.js index 241efa99e5..0cf0be691d 100644 --- a/src/core_modules/capture-core/components/Pages/EditEvent/DataEntry/epics/cancelEditSingleEvent.epics.js +++ b/src/core_modules/capture-core/components/Pages/EditEvent/DataEntry/epics/cancelEditSingleEvent.epics.js @@ -17,7 +17,7 @@ export const cancelEditEventEpic = (action$: InputObservable, store: ReduxStore) if (!state.offline.online) { return noWorkingListUpdateNeededAfterUpdateCancelled(); } - const listId = state.workingListConfigSelector.eventMainPage.currentListId; + const listId = state.workingListsTemplates.eventList.currentListId; const listSelections = listId && state.workingListsContext[listId]; if (!listSelections) { return updateWorkingListAfterUpdateCancelled(); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/Contents/FilterSelectorContents.component.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/Contents/FilterSelectorContents.component.js index 2b0c589783..1ac5a01418 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/Contents/FilterSelectorContents.component.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/Contents/FilterSelectorContents.component.js @@ -5,18 +5,18 @@ import { filterTypesObject } from '../filterTypes'; import { MAX_OPTIONS_COUNT_FOR_OPTION_SET_CONTENTS } from '../filterSelector.const'; import withButtons from './withButtons'; import withData from './withData'; -import withRef from './withRef'; -import withStyleRef from './withStyleRef'; +// import withRef from './withRef'; +// import withStyleRef from './withStyleRef'; -import TextFilter from '../../../../../FiltersForTypes/Text/TextFilter.component'; -import NumericFilter from '../../../../../FiltersForTypes/Numeric/NumericFilter.component'; -import DateFilter from '../../../../../FiltersForTypes/Date/DateFilter.component'; -import BooleanFilter from '../../../../../FiltersForTypes/Boolean/BooleanFilter.component'; -import TrueOnlyFilter from '../../../../../FiltersForTypes/TrueOnly/TrueOnlyFilter.component'; -import OptionSetFilter from '../../../../../FiltersForTypes/OptionSet/OptionSetFilter.component'; -import { AssigneeFilter } from '../../../../../FiltersForTypes'; - -import OptionSet from '../../../../../../metaData/OptionSet/OptionSet'; +import { + TextFilter, + NumericFilter, + AssigneeFilter, + TrueOnlyFilter, + BooleanFilter, + DateFilter, + OptionSetFilter, +} from '../../../../../FiltersForTypes'; const getStyles = (theme: Theme) => ({ container: { @@ -61,9 +61,7 @@ class FilterSelectorContents extends React.PureComponent { static getOptionSetComponent() { return withButtons()( withData()( - withStyleRef()( - OptionSetFilter, - ), + OptionSetFilter, ), ); } @@ -78,17 +76,13 @@ class FilterSelectorContents extends React.PureComponent { if (FilterSelectorContents.hasStylesHOC.includes(type)) { return withButtons()( withData()( - withStyleRef()( - SelectorContent, - ), + SelectorContent, ), ); } return withButtons()( withData()( - withRef()( - SelectorContent, - ), + SelectorContent, ), ); } diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/Contents/withData.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/Contents/withData.js index 6c4939305f..1c95d578a2 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/Contents/withData.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/Contents/withData.js @@ -1,22 +1,27 @@ // @flow import * as React from 'react'; import { connect } from 'react-redux'; -import isDefined from 'd2-utilizr/lib/isDefined'; +// eslint-disable-next-line complexity const mapStateToProps = (state: ReduxState, props: { listId: string, id: string }) => { const listId = props.listId; + + const nextValue = state.workingListsMeta[listId] && + state.workingListsMeta[listId].next && + state.workingListsMeta[listId].next.filters && + state.workingListsMeta[listId].next.filters[props.id]; + + const currentValue = state.workingListsMeta[listId] && + state.workingListsMeta[listId].filters && + state.workingListsMeta[listId].filters[props.id]; + return { - value: ( - state.workingListFiltersEdit[listId] && - state.workingListFiltersEdit[listId].next && - isDefined(state.workingListFiltersEdit[listId].next[props.id])) ? - state.workingListFiltersEdit[listId].next[props.id] : - (state.workingListFiltersEdit[listId] && state.workingListFiltersEdit[listId][props.id]), + filter: (nextValue !== undefined ? nextValue : currentValue), }; }; const dispatchToProps = (dispatch: ReduxDispatch) => ({ - + }); export default () => (InnerComponent: React.ComponentType) => connect(mapStateToProps, dispatchToProps)(InnerComponent); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/FilterButton.component.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/FilterButton.component.js index fb1549a7f4..ff791abe99 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/FilterButton.component.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/FilterButton.component.js @@ -84,8 +84,6 @@ class FilterButton extends Component { handleCloseFilterSelector = () => { this.closeFilterSelector(); - const { onRevertFilter, listId } = this.props; - onRevertFilter(listId); } handleEditFilterContents = (value: any) => { @@ -114,7 +112,6 @@ class FilterButton extends Component { optionSet={optionSet} singleSelect={singleSelect} id={id} - onCommitValue={this.handleEditFilterContents} onUpdate={this.handleFilterUpdate} onClose={this.handleCloseFilterSelector} /> diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/FilterButton.container.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/FilterButton.container.js index 59950bd82b..7e40b03d1c 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/FilterButton.container.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/FilterButton.container.js @@ -1,9 +1,8 @@ // @flow import { connect } from 'react-redux'; import { batchActions } from 'redux-batched-actions'; -import isDefined from 'd2-utilizr/lib/isDefined'; import FilterButton from './FilterButton.component'; -import { editContents, setFilter, clearFilter, revertFilter, batchActionTypes } from '../filterSelector.actions'; +import { editContents, setFilter, clearFilter, batchActionTypes } from '../filterSelector.actions'; import { workingListUpdating } from '../../eventsList.actions'; import { makeCurrentFilterSelector, makeFilterValueSelector } from './filterButton.selectors'; @@ -20,11 +19,9 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ onEditFilterContents: (listId: string, value: any, itemId: string) => { dispatch(editContents(listId, value, itemId)); }, - onFilterUpdate: (listId: string, data: ?Object, itemId: string, commitValue?: any) => { - const actions = isDefined(commitValue) ? [editContents(listId, commitValue, itemId)] : []; - actions.push(data == null ? clearFilter(listId, itemId) : setFilter(listId, data, itemId)); - actions.push(workingListUpdating(listId)); - dispatch(batchActions(actions, batchActionTypes.SET_FILTER_BATCH)); + onFilterUpdate: (listId: string, data: ?Object, itemId: string) => { + const action = data == null ? clearFilter(listId, itemId) : setFilter(listId, data, itemId); + dispatch(action); }, onClearFilter: (listId: string, itemId: string) => { dispatch(batchActions([ @@ -32,9 +29,6 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ workingListUpdating(listId), ], batchActionTypes.SET_FILTER_BATCH)); }, - onRevertFilter: (listId: string) => { - dispatch(revertFilter(listId)); - }, }); export default connect(mapStateToProps, mapDispatchToProps)(FilterButton); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/buttonTextBuilder.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/buttonTextBuilder.js index 40ea5a0c3a..87c1eaff10 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/buttonTextBuilder.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/buttonTextBuilder.js @@ -1,16 +1,50 @@ // @flow import { dataElementTypes, OptionSet } from '../../../../../../../metaData'; -import { convertDate } from './converters'; +import { + convertText, + convertDate, + convertAssignee, + convertBoolean, + convertNumeric, + convertTrueOnly, +} from './converters'; +import { isEqual } from '../../../../../../../utils/valueEqualityChecker'; +import type { OptionSetFilterData } from '../../../eventList.types'; const convertersForTypes = { + [dataElementTypes.TEXT]: convertText, + [dataElementTypes.NUMBER]: convertNumeric, + [dataElementTypes.INTEGER]: convertNumeric, + [dataElementTypes.INTEGER_POSITIVE]: convertNumeric, + [dataElementTypes.INTEGER_NEGATIVE]: convertNumeric, + [dataElementTypes.INTEGER_ZERO_OR_POSITIVE]: convertNumeric, [dataElementTypes.DATE]: convertDate, + [dataElementTypes.ASSIGNEE]: convertAssignee, + [dataElementTypes.BOOLEAN]: convertBoolean, + [dataElementTypes.TRUE_ONLY]: convertTrueOnly, }; +function getOptionSetText(filter: OptionSetFilterData, optionSet: OptionSet) { + const optionText = filter + .values + .map((value) => { + const option = optionSet.options.find(o => isEqual(o.value, value)); + return option && option.text; + }) + .filter(text => text) + .join(', '); + + return optionText.length > 20 ? `${optionText.substring(0, 18)}..` : optionText; +} + export function buildButtonText( - filter: Object, + filter: any, type: $Values, optionSet: OptionSet, ) { - + if (filter.usingOptionSet && optionSet) { + return getOptionSetText(filter, optionSet); + } + return (convertersForTypes[type] ? convertersForTypes[type](filter) : filter); } diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/assigneeConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/assigneeConverter.js new file mode 100644 index 0000000000..bac4569dfc --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/assigneeConverter.js @@ -0,0 +1,22 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { type AssigneeFilterData, assigneeFilterModes } from '../../../../eventList.types'; + +const getText = (key) => { + const keyToText = { + [assigneeFilterModes.CURRENT]: i18n.t('Me'), + [assigneeFilterModes.ANY]: i18n.t('Anyone'), + [assigneeFilterModes.NONE]: i18n.t('None'), + }; + + // $FlowFixMe + return keyToText[key]; +}; + +export function convertAssignee(filter: AssigneeFilterData) { + return ( + filter.assignedUserMode !== assigneeFilterModes.PROVIDED ? + getText(filter.assignedUserMode) : + filter.assignedUser.name + ); +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/booleanConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/booleanConverter.js new file mode 100644 index 0000000000..78166dbd3c --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/booleanConverter.js @@ -0,0 +1,13 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { pipe } from 'capture-core-utils'; +import type { BooleanFilterData } from '../../../../eventList.types'; + +const getText = (key: boolean) => (key ? i18n.t('Yes') : i18n.t('No')); + +export function convertBoolean(filter: BooleanFilterData) { + return pipe( + values => values.map(value => getText(value)), + values => values.join(', '), + )(filter.values); +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/dateConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/dateConverter.js index a4986b1c80..05b4d1f72a 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/dateConverter.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/dateConverter.js @@ -1,8 +1,9 @@ // @flow import i18n from '@dhis2/d2-i18n'; +import { pipe } from 'capture-core-utils'; import { moment } from 'capture-core-utils/moment'; -import { convertClientToView } from '../../../../../../../../converters'; -import { dataElementTypes } from '../../../../../../../../metaData'; +import { convertMomentToDateFormatString } from '../../../../../../../../utils/converters/date'; +import type { DateFilterData, AbsoluteDateFilterData } from '../../../../eventList.types'; const periods = { TODAY: 'TODAY', @@ -25,7 +26,10 @@ const translatedPeriods = { [periods.LAST_3_MONTHS]: i18n.t('Last 3 months'), }; -const convertToViewValue = (clientValue: string) => convertClientToView(clientValue, dataElementTypes.DATE); +const convertToViewValue = (filterValue: string) => pipe( + value => moment(value), + momentDate => convertMomentToDateFormatString(momentDate), +)(filterValue); function translateAbsoluteDate(filter: AbsoluteDateFilterData) { let appliedText = ''; @@ -36,10 +40,10 @@ function translateAbsoluteDate(filter: AbsoluteDateFilterData) { const momentFrom = moment(fromValue); const momentTo = moment(toValue); if (momentFrom.isSame(momentTo)) { - appliedText = convertToViewValue(fromValue); + appliedText = convertMomentToDateFormatString(momentFrom); } else { - const appliedTextFrom = convertToViewValue(fromValue); - const appliedTextTo = convertToViewValue(toValue); + const appliedTextFrom = convertMomentToDateFormatString(momentFrom); + const appliedTextTo = convertMomentToDateFormatString(momentTo); appliedText = i18n.t('{{fromDate}} to {{toDate}}', { fromDate: appliedTextFrom, toDate: appliedTextTo }); } } else if (fromValue) { diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/index.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/index.js index f21cc00c4c..cef6917466 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/index.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/index.js @@ -1,2 +1,7 @@ // @flow export { convertDate } from './dateConverter'; +export { convertAssignee } from './assigneeConverter'; +export { convertBoolean } from './booleanConverter'; +export { convertText } from './textConverter'; +export { convertNumeric } from './numericConverter'; +export { convertTrueOnly } from './trueOnlyConverter'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/numericConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/numericConverter.js new file mode 100644 index 0000000000..bd609de420 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/numericConverter.js @@ -0,0 +1,27 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import type { NumericFilterData } from '../../../../eventList.types'; + +// eslint-disable-next-line complexity +export function convertNumeric(filter: NumericFilterData) { + let appliedText = ''; + const geHasValue = !!filter.ge || filter.ge === 0; + const leHasValue = !!filter.le || filter.le === 0; + + if (geHasValue && leHasValue) { + if (filter.ge === filter.le) { + appliedText = filter.ge; + } else { + // $FlowSuppress + appliedText = `${filter.ge.toString()} ${i18n.t('to')} ${filter.le.toString()}`; + } + } else if (geHasValue) { + // $FlowSuppress + appliedText = `${i18n.t('greater than or equal to')} ${filter.ge.toString()}`; + } else { + // $FlowSuppress + appliedText = `${i18n.t('less than or equal to')} ${filter.le.toString()}`; + } + + return appliedText; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/textConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/textConverter.js new file mode 100644 index 0000000000..e59c4cb8e1 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/textConverter.js @@ -0,0 +1,6 @@ +// @flow +import type { TextFilterData } from '../../../../eventList.types'; + +export function convertText(filter: TextFilterData) { + return filter.value; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/trueOnlyConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/trueOnlyConverter.js new file mode 100644 index 0000000000..349455db4c --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/FilterButton/buttonTextBuilder/converters/trueOnlyConverter.js @@ -0,0 +1,7 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import type { TrueOnlyFilterData } from '../../../../eventList.types'; + +export function convertTrueOnly(filter: TrueOnlyFilterData) { + return i18n.t('Yes'); +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.actions.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.actions.js index 3ae4755016..d0b2898198 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.actions.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.actions.js @@ -5,7 +5,6 @@ export const actionTypes = { EDIT_CONTENTS: 'EditFilterSelectorContentsForWorkingList', SET_FILTER: 'SetWorkingListFilter', CLEAR_FILTER: 'ClearWorkingListFilter', - REVERT_FILTER: 'RevertWorkingListFilter', REST_MENU_ITEM_SELECTED: 'RestMenuItemSelected', UPDATE_INCLUDED_FILTERS_AFTER_COLUMN_SORTING: 'UpdateIncludedFiltersAfterColumnSortingForWorkingList', }; @@ -23,9 +22,6 @@ export const setFilter = (listId: string, data: Object, itemId: string) => export const clearFilter = (listId: string, itemId: string) => actionCreator(actionTypes.CLEAR_FILTER)({ listId, itemId }); -export const revertFilter = (listId: string) => - actionCreator(actionTypes.REVERT_FILTER)({ listId }); - export const restMenuItemSelected = (id: string, listId: string) => actionCreator(actionTypes.REST_MENU_ITEM_SELECTED)({ id, listId }); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.epics.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.epics.js index 4c7a97d8a5..b5c502e9f9 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.epics.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.epics.js @@ -9,7 +9,7 @@ export const includeFiltersWithValueAfterColumnSortingEpic = (action$: InputObse action$.ofType(columnSelectorActionTypes.UPDATE_WORKINGLIST_ORDER) .map(() => { const state = store.getState(); - const listId = state.workingListConfigSelector.eventMainPage.currentListId; + const listId = state.workingListsTemplates.eventList.currentListId; const appliedFilters = (state.workingListsAppliedFilters && state.workingListsAppliedFilters[listId]) || {}; const nextAppliedFilters = ( state.workingListsAppliedFilters && diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/Header/withHeader.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/Header/withHeader.js deleted file mode 100644 index 7290afae6f..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/Header/withHeader.js +++ /dev/null @@ -1,41 +0,0 @@ -// @flow -import * as React from 'react'; -import { withStyles } from '@material-ui/core/styles'; - -const getStyles = (theme: Theme) => ({ - container: { - backgroundColor: '#fff', - }, - title: { - ...theme.typography.title, - }, -}); - -type Props = { - classes: { - container: string, - title: string, - }, -}; - -export default () => (InnerComponent: React.ComponentType) => - withStyles(getStyles)((props: Props) => { - const { classes, ...passOnProps } = props; - - return ( -
- - Registered events - - } - /> -
- ); - }); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/ListWrapper/saveConfig/saveConfig.epic.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/ListWrapper/saveConfig/saveConfig.epic.js index 8a85214e24..c0d3b3c35f 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/ListWrapper/saveConfig/saveConfig.epic.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/ListWrapper/saveConfig/saveConfig.epic.js @@ -20,7 +20,7 @@ export const saveConfigEpic = (action$: InputObservable, store: ReduxStore) => currentListId: listId, workingListConfigs, currentConfigId, - } = state.workingListConfigSelector.eventMainPage; + } = state.workingListsTemplates.eventList; const listName = workingListConfigs[currentConfigId].displayName; const { filters, sortById, sortByDirection } = state.workingListsMeta[listId]; const columnsOrder = state.workingListsColumnsOrder[listId]; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/Pagination/index.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/Pagination/index.js index 1ba55f5220..ae3c5cabab 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/Pagination/index.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/Pagination/index.js @@ -1,2 +1,3 @@ // @flow export { default as ListPagination } from './ListPagination'; +export { actionTypes } from './pagination.actions'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/WorkingListConfigSelector/WorkingListConfigSelector.component.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/WorkingListConfigSelector/WorkingListConfigSelector.component.js deleted file mode 100644 index 111774c315..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/WorkingListConfigSelector/WorkingListConfigSelector.component.js +++ /dev/null @@ -1,125 +0,0 @@ -// @flow -import * as React from 'react'; -import classNames from 'classnames'; -import { darken, fade, lighten } from '@material-ui/core/styles/colorManipulator'; -import { withStyles, Chip } from '@material-ui/core'; -import EventListLoadWrapper from '../EventsListLoadWrapper.container'; - - -const getBorder = (theme: Theme) => { - const color = theme.palette.type === 'light' - ? lighten(fade(theme.palette.divider, 1), 0.88) - : darken(fade(theme.palette.divider, 1), 0.8); - return `${theme.typography.pxToRem(1)} solid ${color}`; -}; - -const getStyles = (theme: Theme) => ({ - container: { - border: getBorder(theme), - }, - workingListConfigsContainer: { - borderBottom: getBorder(theme), - display: 'flex', - flexWrap: 'wrap', - padding: `${theme.typography.pxToRem(3)} 0rem`, - }, - chipContainer: { - padding: `${theme.typography.pxToRem(5)} ${theme.typography.pxToRem(8)}`, - }, - chip: { - }, - chipSelected: { - color: 'white', - backgroundColor: theme.palette.secondary.main, - '&:focus': { - backgroundColor: theme.palette.secondary.main, - }, - }, -}); - -type WorkingListConfig = { - id: string, - isDefault?: ?boolean, - name: string, - filters: Object, -} - -type Props = { - listId: string, - workingListConfigs: Array, - defaultWorkingListConfig: WorkingListConfig, - onSetWorkingListConfig: (configId: string, listId: string, data?: ?Object) => void, - configId: ?string, - classes: { - container: string, - workingListConfigsContainer: string, - chipContainer: string, - chip: string, - chipSelected: string, - } -}; -class WorkingListConfigSelector extends React.Component { - handleWorkingListConfigClick = (id, data) => { - const { configId, listId } = this.props; - if (id === configId) { - this.handleSetDefaultWorkingListConfig(); - } else { - this.props.onSetWorkingListConfig(id, listId, data); - } - } - - handleSetDefaultWorkingListConfig = () => { - const { id, name, ...data } = this.props.defaultWorkingListConfig; - const listId = this.props.listId; - this.props.onSetWorkingListConfig(id, listId, data); - } - - renderWorkingListConfigs = () => { - const { workingListConfigs, configId, classes } = this.props; - const customConfigs = workingListConfigs - .filter(c => !c.isDefault); - - if (customConfigs.length <= 0) { - return null; - } - - const configElements = customConfigs.map((w) => { - const { id, name, ...data } = w; - return ( -
- this.handleWorkingListConfigClick(id, data)} - /> -
- ); - }); - - return ( -
- {configElements} -
- ); - } - - render() { - const { workingListConfigs, classes, ...passOnProps } = this.props; - return ( -
- {this.renderWorkingListConfigs()} - -
- ); - } -} - -export default withStyles(getStyles)(WorkingListConfigSelector); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/WorkingListConfigSelector/WorkingListConfigSelector.container.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/WorkingListConfigSelector/WorkingListConfigSelector.container.js deleted file mode 100644 index 4905f9bc2a..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/WorkingListConfigSelector/WorkingListConfigSelector.container.js +++ /dev/null @@ -1,28 +0,0 @@ -// @flow -import { connect } from 'react-redux'; -import { setCurrentWorkingListConfig } from '../eventsList.actions'; -import WorkingListConfigSelector from './WorkingListConfigSelector.component'; -import withLoadingIndicator from '../../../../../HOC/withLoadingIndicator'; -import withErrorMessageHandler from '../../../../../HOC/withErrorMessageHandler'; - -const mapStateToProps = (state: ReduxState) => { - const workingListConfigSelector = state.workingListConfigSelector.eventMainPage || {}; - const configId = workingListConfigSelector.currentConfigId; - const workingListConfigs = workingListConfigSelector.workingListConfigs || []; - const defaultWorkingListConfig = workingListConfigs.find(w => w.isDefault); - return { - workingListConfigs, - defaultWorkingListConfig, - configId, - ready: workingListConfigSelector && !workingListConfigSelector.isLoading, - error: workingListConfigSelector && workingListConfigSelector.loadError, - }; -}; - -const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ - onSetWorkingListConfig: (configId: string, listId: string, data?: ?Object) => { - dispatch(setCurrentWorkingListConfig(configId, listId, data)); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(withLoadingIndicator()(withErrorMessageHandler()(WorkingListConfigSelector))); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/defaultColumnConfiguration/index.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/defaultColumnConfiguration/index.js deleted file mode 100644 index f0b9d3b005..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/defaultColumnConfiguration/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// @flow -export { getDefaultMainConfig, getMetaDataConfig } from './getDefaultConfigs'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsList.epics.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsList.epics.js deleted file mode 100644 index 12ad50c669..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsList.epics.js +++ /dev/null @@ -1,232 +0,0 @@ -// @flow -import { fromPromise } from 'rxjs/observable/fromPromise'; -import { batchActions } from 'redux-batched-actions'; -import isSelectionsEqual from '../../../../App/isSelectionsEqual'; -import { - actionTypes as mainSelectionActionTypes, -} from '../../mainSelections.actions'; -import { actionTypes as paginationActionTypes } from '../Pagination/pagination.actions'; -import { - batchActionTypes as eventsListBatchActionTypes, - actionTypes as eventsListActionTypes, - startDeleteEvent, - workingListUpdatingWithDialog, - setCurrentWorkingListConfig, - workingListConfigsRetrieved, -} from '../eventsList.actions'; -import { dataEntryActionTypes as newEventDataEntryActionTypes } from '../../../NewEvent'; -import { actionTypes as editEventDataEntryActionTypes } from '../../../EditEvent/DataEntry/editEventDataEntry.actions'; -import { actionTypes as viewEventActionTypes } from '../../../ViewEvent/viewEvent.actions'; -import { - batchActionTypes as connectivityBatchActionTypes, - actionTypes as connectivityActionTypes, -} from '../../../../Connectivity/connectivity.actions'; -import { actionTypes as mainPageActionTypes } from '../../mainPage.actions'; -import { - actionTypes as filterSelectorActionTypes, - batchActionTypes as filterSelectorBatchActionTypes, -} from '../FilterSelectors/filterSelector.actions'; -import { getWorkingListConfigsAsync } from './workingListConfigDataRetriever'; -import { initEventWorkingListAsync } from './initEventWorkingList'; -import { updateEventWorkingListAsync } from './updateEventWorkingList'; - -export const initEventWorkingListEpic = (action$, store: ReduxStore) => - action$.ofType( - eventsListActionTypes.SET_CURRENT_WORKING_LIST_CONFIG, - eventsListBatchActionTypes.WORKING_LIST_CONFIGS_RETRIEVED_BATCH, - ) - .map(action => (action.type === eventsListBatchActionTypes.WORKING_LIST_CONFIGS_RETRIEVED_BATCH ? - action.payload.find(a => a.type === eventsListActionTypes.SET_CURRENT_WORKING_LIST_CONFIG) : - action)) - .switchMap((action) => { - const { programId, orgUnitId, categories } = store.getState().currentSelections; - const { eventQueryCriteria, listId } = action.payload; - const initialPromise = initEventWorkingListAsync(eventQueryCriteria, { programId, orgUnitId, categories }, listId); - return fromPromise(initialPromise) - .takeUntil( - action$.ofType( - mainPageActionTypes.UPDATE_EVENT_LIST_AFTER_SAVE_OR_UPDATE_FOR_SINGLE_EVENT, - newEventDataEntryActionTypes.CANCEL_SAVE_UPDATE_WORKING_LIST, - editEventDataEntryActionTypes.UPDATE_WORKING_LIST_AFTER_CANCEL_UPDATE, - viewEventActionTypes.UPDATE_WORKING_LIST_ON_BACK_TO_MAIN_PAGE, - paginationActionTypes.CHANGE_PAGE, - paginationActionTypes.CHANGE_ROWS_PER_PAGE, - eventsListActionTypes.SORT_WORKING_LIST, - filterSelectorActionTypes.CLEAR_FILTER, - filterSelectorBatchActionTypes.SET_FILTER_BATCH, - ), - ) - .takeUntil( - action$ - .ofType(connectivityBatchActionTypes.GOING_ONLINE_EXECUTED_BATCH) - .filter(actionBatch => - actionBatch.payload.some(a => - a.type === connectivityActionTypes.GET_EVENT_LIST_ON_RECONNECT)), - ); - }); - -export const getWorkingListOnCancelSaveEpic = (action$, store: ReduxStore) => - action$.ofType( - newEventDataEntryActionTypes.CANCEL_SAVE_UPDATE_WORKING_LIST, - editEventDataEntryActionTypes.UPDATE_WORKING_LIST_AFTER_CANCEL_UPDATE, - viewEventActionTypes.UPDATE_WORKING_LIST_ON_BACK_TO_MAIN_PAGE, - ) - .switchMap(() => { - const initialPromise = updateEventWorkingListAsync(store.getState()); - return fromPromise(initialPromise) - .takeUntil( - action$ - .ofType( - mainSelectionActionTypes.MAIN_SELECTIONS_COMPLETED, - mainPageActionTypes.UPDATE_EVENT_LIST_AFTER_SAVE_OR_UPDATE_FOR_SINGLE_EVENT, - paginationActionTypes.CHANGE_PAGE, - paginationActionTypes.CHANGE_ROWS_PER_PAGE, - eventsListActionTypes.SORT_WORKING_LIST, - filterSelectorActionTypes.CLEAR_FILTER, - filterSelectorBatchActionTypes.SET_FILTER_BATCH, - ), - ) - .takeUntil( - action$ - .ofType(connectivityBatchActionTypes.GOING_ONLINE_EXECUTED_BATCH) - .filter(actionBatch => - actionBatch.payload.some( - action => action.type === connectivityActionTypes.GET_EVENT_LIST_ON_RECONNECT)), - ); - }); - -export const requestDeleteEventEpic = (action$, store: ReduxStore) => - action$.ofType( - eventsListActionTypes.REQUEST_DELETE_EVENT, - ).map((action) => { - const state = store.getState(); - const eventId = action.payload.eventId; - const listId = state.workingListConfigSelector.eventMainPage.currentListId; - return batchActions([ - startDeleteEvent(eventId), - workingListUpdatingWithDialog(listId), - ], eventsListBatchActionTypes.START_DELETE_EVENT_UPDATE_WORKING_LIST); - }); - -export const getWorkingListOnSaveEpic = (action$, store: ReduxStore) => - action$.ofType( - mainPageActionTypes.UPDATE_EVENT_LIST_AFTER_SAVE_OR_UPDATE_FOR_SINGLE_EVENT, - ) - .switchMap(() => { - const state = store.getState(); - const listId = state.workingListConfigSelector.eventMainPage.currentListId; - const listSelections = state.workingListsContext[listId]; - - const cancelActionTypes = [ - mainSelectionActionTypes.MAIN_SELECTIONS_COMPLETED, - newEventDataEntryActionTypes.CANCEL_SAVE_UPDATE_WORKING_LIST, - editEventDataEntryActionTypes.UPDATE_WORKING_LIST_AFTER_CANCEL_UPDATE, - viewEventActionTypes.UPDATE_WORKING_LIST_ON_BACK_TO_MAIN_PAGE, - paginationActionTypes.CHANGE_PAGE, - paginationActionTypes.CHANGE_ROWS_PER_PAGE, - eventsListActionTypes.SORT_WORKING_LIST, - filterSelectorActionTypes.CLEAR_FILTER, - filterSelectorBatchActionTypes.SET_FILTER_BATCH, - ]; - - if (listSelections && isSelectionsEqual(listSelections, state.currentSelections)) { - const updatePromise = updateEventWorkingListAsync(state); - return fromPromise(updatePromise) - .takeUntil(action$.ofType(...cancelActionTypes)) - .takeUntil( - action$ - .ofType(connectivityBatchActionTypes.GOING_ONLINE_EXECUTED_BATCH) - .filter(actionBatch => - actionBatch.payload.some( - action => action.type === connectivityActionTypes.GET_EVENT_LIST_ON_RECONNECT)), - ); - } - - const initialPromise = getWorkingListConfigsAsync(state).then(container => batchActions([ - setCurrentWorkingListConfig(container.default.id, 'eventList', container.default), - workingListConfigsRetrieved(container.workingListConfigs), - ], eventsListBatchActionTypes.WORKING_LIST_CONFIGS_RETRIEVED_BATCH)); - return fromPromise(initialPromise) - .takeUntil(action$.ofType(...cancelActionTypes)) - .takeUntil( - action$ - .ofType(connectivityBatchActionTypes.GOING_ONLINE_EXECUTED_BATCH) - .filter(actionBatch => - actionBatch.payload.some( - action => action.type === connectivityActionTypes.GET_EVENT_LIST_ON_RECONNECT)), - ); - }); - -export const updateWorkingListEpic = (action$, store: ReduxStore) => - action$.ofType( - paginationActionTypes.CHANGE_PAGE, - paginationActionTypes.CHANGE_ROWS_PER_PAGE, - eventsListActionTypes.SORT_WORKING_LIST, - filterSelectorActionTypes.CLEAR_FILTER, - filterSelectorBatchActionTypes.SET_FILTER_BATCH, - eventsListActionTypes.EVENT_DELETED, - ) - .switchMap(() => { - const state = store.getState(); - - const updatePromise = updateEventWorkingListAsync(state); - return fromPromise(updatePromise) - .takeUntil(action$.ofType( - mainSelectionActionTypes.MAIN_SELECTIONS_COMPLETED, - newEventDataEntryActionTypes.CANCEL_SAVE_UPDATE_WORKING_LIST, - editEventDataEntryActionTypes.UPDATE_WORKING_LIST_AFTER_CANCEL_UPDATE, - viewEventActionTypes.UPDATE_WORKING_LIST_ON_BACK_TO_MAIN_PAGE, - mainPageActionTypes.UPDATE_EVENT_LIST_AFTER_SAVE_OR_UPDATE_FOR_SINGLE_EVENT, - )) - .takeUntil( - action$ - .ofType(connectivityBatchActionTypes.GOING_ONLINE_EXECUTED_BATCH) - .filter(actionBatch => - actionBatch.payload.some( - action => action.type === connectivityActionTypes.GET_EVENT_LIST_ON_RECONNECT)), - ); - }); - -export const getEventListOnReconnectEpic = (action$, store: ReduxStore) => - action$.ofType( - connectivityBatchActionTypes.GOING_ONLINE_EXECUTED_BATCH, - ) - .filter(actionBatch => - actionBatch.payload.some(action => action.type === connectivityActionTypes.GET_EVENT_LIST_ON_RECONNECT)) - .switchMap(() => { - const state = store.getState(); - - const currentSelections = { - programId: state.currentSelections.programId, - orgUnitId: state.currentSelections.orgUnitId, - categories: state.currentSelections.categories, - }; - const listId = state.workingListConfigSelector.eventMainPage.currentListId; - const listSelections = state.workingListsContext[listId]; - - const cancelActionTypes = [ - mainSelectionActionTypes.MAIN_SELECTIONS_COMPLETED, - newEventDataEntryActionTypes.CANCEL_SAVE_UPDATE_WORKING_LIST, - editEventDataEntryActionTypes.UPDATE_WORKING_LIST_AFTER_CANCEL_UPDATE, - viewEventActionTypes.UPDATE_WORKING_LIST_ON_BACK_TO_MAIN_PAGE, - mainPageActionTypes.UPDATE_EVENT_LIST_AFTER_SAVE_OR_UPDATE_FOR_SINGLE_EVENT, - paginationActionTypes.CHANGE_PAGE, - paginationActionTypes.CHANGE_ROWS_PER_PAGE, - eventsListActionTypes.SORT_WORKING_LIST, - filterSelectorActionTypes.CLEAR_FILTER, - filterSelectorBatchActionTypes.SET_FILTER_BATCH, - ]; - - if (listSelections && isSelectionsEqual(listSelections, currentSelections)) { - const updatePromise = updateEventWorkingListAsync(state); - return fromPromise(updatePromise) - .takeUntil(action$.ofType(...cancelActionTypes)); - } - - const initialPromise = getWorkingListConfigsAsync(state).then(container => batchActions([ - setCurrentWorkingListConfig(container.default.id, 'eventList', container.default), - workingListConfigsRetrieved(container.workingListConfigs), - ], eventsListBatchActionTypes.WORKING_LIST_CONFIGS_RETRIEVED_BATCH)); - return fromPromise(initialPromise) - .takeUntil(action$.ofType(...cancelActionTypes)); - }); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/converters/index.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/converters/index.js deleted file mode 100644 index 4794b3b7c9..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/converters/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// @flow -export { clearMemoization, convertDate } from './dateConverter'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/queryArgsBuilder.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/queryArgsBuilder.js deleted file mode 100644 index f5f758f03a..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/queryArgsBuilder.js +++ /dev/null @@ -1,59 +0,0 @@ -// @flow -import log from 'loglevel'; -import { errorCreator } from 'capture-core-utils'; -import { getEventProgramThrowIfNotFound, ProgramStage, dataElementTypes } from '../../../../../../metaData'; -import { convertDate } from './converters'; - -type QueryArgsSource = { - programId: string, - filters: Object, - sortById: string, - sortByDirection: string, -}; - - -const mappersForTypes = { - [dataElementTypes.DATE]: convertDate, -}; - -function convertFilters( - filters: Object, - programId: string, - listId: string, - isInit: boolean, -) { - const elementsById = getEventProgramThrowIfNotFound(programId).stage.stageForm.getElementsById(); - const mainPropTypes = ProgramStage.mainPropTypes; - - return Object - .keys(filters) - .filter(key => filters[key]) - .reduce((acc, key) => { - const type = (elementsById[key] && elementsById[key].type) || mainPropTypes[key]; - if (!type) { - log.error(errorCreator('Could not get type for key')({ key, listId, programId })); - } else { - const sourceValue = filters[key]; - const queryArgValue = mappersForTypes[type] ? - mappersForTypes[type](sourceValue, key, listId, isInit) : - sourceValue; - acc[key] = queryArgValue; - } - return acc; - }, {}); -} - - -export function buildQueryArgs( - queryArgsSource: QueryArgsSource, - listId: string, - isInit: boolean = false, -) { - const { programId, filters } = queryArgsSource; - const queryArgs = { - ...queryArgsSource, - filters: convertFilters(filters, programId, listId, isInit), - }; - - return queryArgs; -} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/initEventWorkingList.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/initEventWorkingList.js deleted file mode 100644 index 03501cb2e5..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/initEventWorkingList.js +++ /dev/null @@ -1,90 +0,0 @@ -// @flow -import log from 'loglevel'; -import { errorCreator } from 'capture-core-utils'; -import { convertToEventWorkingListConfig } from '../eventFiltersInterface'; -import { getEventProgramThrowIfNotFound } from '../../../../../metaData'; -import { getEventWorkingListDataAsync } from './eventsRetriever'; -import { - workingListInitialDataRetrieved, - workingListInitialRetrievalFailed, -} from '../../mainSelections.actions'; -import { getDefaultMainConfig, getMetaDataConfig } from '../defaultColumnConfiguration'; -import type { EventQueryCriteria, CommonQueryData, WorkingListConfig } from '../eventList.types'; - -const errorMessages = { - WORKING_LIST_RETRIEVE_ERROR: 'Working list could not be loaded', -}; - -const queryDefaults = { - page: 1, - rowsPerPage: 15, - sortById: 'eventDate', - sortByDirection: 'desc', -}; - -function getQueryDataFromConfig(workingListConfig: ?WorkingListConfig) { - const configOrEmptyObject = workingListConfig || {}; - const { filters = [], sortById, sortByDirection } = configOrEmptyObject; - - return { - filters: filters - .reduce((acc, filter) => ({ - ...acc, - [filter.id]: filter.requestData, - }), {}), - sortById, - sortByDirection, - }; -} - -function addDefaultsToQueryData(queryData: Object) { - const queryDefaultsWithOverrides = Object - .keys(queryDefaults) - .reduce((acc, key) => ({ - ...acc, - [key]: ((queryData[key] || queryData[key] === false) ? queryData[key] : queryDefaults[key]), - }), {}); - - return { - ...queryData, - ...queryDefaultsWithOverrides, - }; -} - -export const initEventWorkingListAsync = async ( - config: ?EventQueryCriteria, - commonQueryData: CommonQueryData, - listId: string, -): Promise> => { - const program = getEventProgramThrowIfNotFound(commonQueryData.programId); - const stage = program.stage; - const workingListConfig: ?WorkingListConfig = await convertToEventWorkingListConfig(config, stage); - - const queryData = getQueryDataFromConfig(workingListConfig) || {}; - const queryDataWithDefaults = addDefaultsToQueryData(queryData); - const customColumnOrder = workingListConfig && workingListConfig.columnOrder; - const columnOrder = customColumnOrder || [ - ...getDefaultMainConfig(stage), - ...getMetaDataConfig(stage.stageForm), - ]; - - return getEventWorkingListDataAsync({ - ...queryDataWithDefaults, - ...commonQueryData, - }, columnOrder) - .then(data => - workingListInitialDataRetrieved(listId, { - ...data, - config: { - ...workingListConfig, - columnOrder, - }, - queryData: queryDataWithDefaults, - selections: commonQueryData, - }), - ) - .catch((error) => { - log.error(errorCreator(errorMessages.WORKING_LIST_RETRIEVE_ERROR)({ error })); - return workingListInitialRetrievalFailed(listId, errorMessages.WORKING_LIST_RETRIEVE_ERROR); - }); -}; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/updateEventWorkingList.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/updateEventWorkingList.js deleted file mode 100644 index 683cd64335..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/updateEventWorkingList.js +++ /dev/null @@ -1,53 +0,0 @@ -// @flow -import log from 'loglevel'; -import { errorCreator } from 'capture-core-utils'; -import { getEventWorkingListDataAsync } from './eventsRetriever'; -import { - workingListUpdateDataRetrieved, - workingListUpdateRetrievalFailed, -} from '../eventsList.actions'; -import { buildQueryArgs } from './eventsQueryArgsBuilder'; - - -const errorMessages = { - WORKING_LIST_UPDATE_ERROR: 'Working list could not be updated', -}; - -const getSourceQueryArgsForUpdateWorkingList = (state: ReduxState, listId: string) => { - const { programId, orgUnitId, categories } = state.workingListsContext[listId]; - - const currentMeta = state.workingListsMeta[listId]; - const nextMeta = state.workingListsMeta[listId].next; - const meta = { - ...currentMeta, - ...nextMeta, - filters: { - ...currentMeta.filters, - ...nextMeta.filters, - }, - }; - meta.hasOwnProperty('next') && delete meta.next; - - return { - programId, - orgUnitId, - categories, - ...meta, - }; -}; - -export const updateEventWorkingListAsync = ( - state: ReduxState, -): Promise> => { - const listId = state.workingListConfigSelector.eventMainPage.currentListId; - const sourceQueryData = getSourceQueryArgsForUpdateWorkingList(state, listId); - const columnOrder = state.workingListsColumnsOrder[listId]; - return getEventWorkingListDataAsync(buildQueryArgs(sourceQueryData, listId), columnOrder) - .then(data => - workingListUpdateDataRetrieved(listId, data), - ) - .catch((error) => { - log.error(errorCreator(errorMessages.WORKING_LIST_UPDATE_ERROR)({ error })); - return workingListUpdateRetrievalFailed(listId, errorMessages.WORKING_LIST_UPDATE_ERROR); - }); -}; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/convertToEventWorkingListConfig.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/convertToEventWorkingListConfig.js deleted file mode 100644 index cc41375da5..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/convertToEventWorkingListConfig.js +++ /dev/null @@ -1,229 +0,0 @@ -// @flow -import i18n from '@dhis2/d2-i18n'; -import log from 'loglevel'; -import { pipe, errorCreator } from 'capture-core-utils'; -import { canViewOtherUsers } from '../../../../../d2'; -import { - getBooleanFilterData, - getMultiSelectOptionSetFilterData, - getSingleSelectOptionSetFilterData, - getNumericFilterData, - getTextFilterData, - getTrueOnlyFilterData, - getDateFilterData, - getAssigneeFilterData, - assigneeFilterModeKeys, -} from '../../../../FiltersForTypes'; -import { - dataElementTypes as elementTypes, - DataElement, - RenderFoundation, - OptionSet, - Option, - ProgramStage, -} from '../../../../../metaData'; -import eventStatusElement from '../../../../../events/eventStatusElement'; -import { - MAX_OPTIONS_COUNT_FOR_OPTION_SET_CONTENTS, -} from '../FilterSelectors/filterSelector.const'; -import { convertServerToClient, convertClientToForm } from '../../../../../converters'; -import { getColumnsConfiguration } from './getColumnsConfiguration'; -import { getApi } from '../../../../../d2/d2Instance'; -import type { DataFilter, EventQueryCriteria } from '../eventList.types'; - -const booleanOptionSet = new OptionSet('booleanOptionSet', [ - new Option((_this) => { _this.text = i18n.t('Yes'); _this.value = 'true'; }), - new Option((_this) => { _this.text = i18n.t('No'); _this.value = 'false'; }), -]); - -const getNumericFilter = (filter: Object) => { - const value = { min: filter.ge, max: filter.le }; - return { - ...getNumericFilterData(value), - value, - }; -}; - -const getBooleanFilter = (filter: Object, element: DataElement) => { - const value = [...(filter.in || [])]; - return { - ...getBooleanFilterData(value, element.type, element.optionSet || booleanOptionSet), - value, - }; -}; - -const getMultiSelectOptionSetFilter = (filter: Object, element: DataElement) => { - const value = [...(filter.in || [])]; - return { - // $FlowFixMe - ...getMultiSelectOptionSetFilterData(value, element.type, element.optionSet), - value, - }; -}; - -const getSingleSelectOptionSetFilter = (filter: Object, element: DataElement) => { - const value = filter.eq; - return { - // $FlowFixMe - ...getSingleSelectOptionSetFilterData(value, element.optionSet), - value, - }; -}; - -const getTrueOnlyFilter = (filter: Object, element: DataElement) => { - const value = [...(filter.in || [])]; - return { - ...getTrueOnlyFilterData(value, element.type), - value, - }; -}; - -const getTextFilter = (filter: Object) => { - const value = filter.like; - return { - ...getTextFilterData(value), - value, - }; -}; - -const getDateFilter = (filter: Object) => { - const value = { - main: filter.period || 'CUSTOM_RANGE', - from: filter.startDate && pipe(convertServerToClient, convertClientToForm)(filter.startDate, elementTypes.DATE), - to: filter.endDate && pipe(convertServerToClient, convertClientToForm)(filter.endDate, elementTypes.DATE), - }; - - return { - ...getDateFilterData(value), - value, - }; -}; - -const getUser = (userId: string) => { - return getApi() - .get(`users/${userId}`, { fields: 'id,name,userCredentials[username]' }) - .then((user: Object) => ({ - id: user.id, - name: user.name, - username: user.userCredentials.username, - })) - .catch((error) => { - log.error( - errorCreator('An error occured retrieving assignee user')({ error, userId }), - ); - return null; - }); -}; - -// eslint-disable-next-line complexity -const getAssigneeFilter = async ( - assignedUserMode: $Values, - assignedUsers: ?Array, -) => { - const value = { - mode: assignedUserMode, - provided: undefined, - }; - - if (assignedUserMode === assigneeFilterModeKeys.PROVIDED) { - const assignedUserId = assignedUsers && assignedUsers.length > 0 && assignedUsers[0]; - if (!assignedUserId) { - return undefined; - } - - const user = await getUser(assignedUserId); - if (!user) { - return undefined; - } - value.provided = user; - } - - return { - ...getAssigneeFilterData(value), - value, - }; -}; - -const getFilterByType = { - [elementTypes.TEXT]: getTextFilter, - [elementTypes.NUMBER]: getNumericFilter, - [elementTypes.INTEGER]: getNumericFilter, - [elementTypes.INTEGER_POSITIVE]: getNumericFilter, - [elementTypes.INTEGER_NEGATIVE]: getNumericFilter, - [elementTypes.INTEGER_ZERO_OR_POSITIVE]: getNumericFilter, - [elementTypes.DATE]: getDateFilter, - [elementTypes.BOOLEAN]: getBooleanFilter, - [elementTypes.TRUE_ONLY]: getTrueOnlyFilter, -}; - -const getSortOrder = (order: ?string) => { - const sortOrderParts = order && order.split(':'); - if (sortOrderParts && sortOrderParts.length === 2) { - return { - sortById: sortOrderParts[0], - sortByDirection: sortOrderParts[1], - }; - } - return null; -}; - -const getDataElementFilters = (filters: ?Array, stageForm: RenderFoundation): Array => { - if (!filters) { - return []; - } - - return filters.map((serverFilter) => { - const element = stageForm.getElement(serverFilter.dataItem); - if (element) { - if (element.optionSet && element.optionSet.options.length <= MAX_OPTIONS_COUNT_FOR_OPTION_SET_CONTENTS) { - return { id: serverFilter.dataItem, ...getMultiSelectOptionSetFilter(serverFilter, element) }; - } - return { - id: serverFilter.dataItem, - // $FlowFixMe - ...(getFilterByType[element.type] ? getFilterByType[element.type](serverFilter, element) : null), - }; - } - // $FlowFixMe - return null; - }).filter(clientFilter => clientFilter); -}; - -const getMainDataFilters = async (eventQueryCriteria: EventQueryCriteria) => { - const { eventDate, status, assignedUserMode, assignedUsers } = eventQueryCriteria; - const filters = []; - if (status) { - filters.push({ id: 'status', ...getSingleSelectOptionSetFilter({ eq: status }, eventStatusElement) }); - } - if (eventDate) { - filters.push({ id: 'eventDate', ...getDateFilter(eventDate) }); - } - if (assignedUserMode && canViewOtherUsers()) { - filters.push({ id: 'assignee', ...(await getAssigneeFilter(assignedUserMode, assignedUsers)) }); - } - return filters; -}; - -export async function convertToEventWorkingListConfig( - eventQueryCriteria: ?EventQueryCriteria, - stage: ProgramStage, -) { - if (!eventQueryCriteria) { - return undefined; - } - - const { sortById, sortByDirection } = getSortOrder(eventQueryCriteria.order) || {}; - const filters = [ - ...getDataElementFilters(eventQueryCriteria.dataFilters, stage.stageForm), - ...(await getMainDataFilters(eventQueryCriteria)), - ]; - - const columnOrder = getColumnsConfiguration(stage, eventQueryCriteria.displayColumnOrder); - - return { - filters, - columnOrder, - sortById, - sortByDirection, - }; -} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/index.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/index.js deleted file mode 100644 index f1ecae8446..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// @flow -export { getEventFilters } from './eventFilterRequests'; -export { convertToEventFilter } from './convertToEventFilter'; -export { convertToEventWorkingListConfig } from './convertToEventWorkingListConfig'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventList.types.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventList.types.js index 3bf3fdf42f..fd47c3d587 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventList.types.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventList.types.js @@ -1,47 +1,22 @@ // @flow - -export type DataFilter = { - dataItem: string, - ge?: any, - le?: any, - in?: any, - like?: any, - eq?: any, - period?: any, -} - -export type EventQueryCriteria = { - dataFilters?: ?Array, - order?: ?string, - eventDate?: ?Object, - status?: ?string, - displayColumnOrder?: ?Array, - assignedUserMode?: 'CURRENT' | 'PROVIDED' | 'NONE' | 'ANY', - assignedUsers?: Array, -} - -export type EventFilter = { - id: string, - name: string, - eventQueryCriteria: EventQueryCriteria, -} +export type { + AssigneeFilterData, + AbsoluteDateFilterData, + RelativeDateFilterData, + DateFilterData, + OptionSetFilterData, + BooleanFilterData, + TrueOnlyFilterData, + TextFilterData, + NumericFilterData, +} from '../../../FiltersForTypes/filters.types'; +export { + assigneeFilterModes, + dateFilterTypes, +} from '../../../FiltersForTypes/filters.types'; export type CommonQueryData = { programId: string, orgUnitId: string, categories: ?Object, } - -export type WorkingListConfig = { - filters: Array<{id: string, requestData: any, appliedText: string, value: any}>, - sortById: ?string, - sortByDirection: ?string, - columnOrder: Array, -} - -export type ColumnConfig = { - id: string, - visible: boolean, - isMainProperty?: ?boolean, - apiName?: ?string, -}; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventsList.actions.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventsList.actions.js index 5ceae97b94..4a70c5dfff 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventsList.actions.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventsList.actions.js @@ -3,8 +3,6 @@ import { actionCreator } from 'capture-core/actions/actions.utils'; import { methods } from '../../../../trackerOffline/trackerOfflineConfig.const'; export const actionTypes = { - WORKING_LIST_UPDATE_DATA_RETRIEVED: 'WorkingListUpdateDataRetrieved', - WORKING_LIST_UPDATE_DATA_RETRIEVAL_FAILED: 'WorkingListUpdateDataRetrievalFailed', SORT_WORKING_LIST: 'SortWorkingList', OPEN_EDIT_EVENT_PAGE: 'OpenEditEventPage', OPEN_VIEW_EVENT_PAGE: 'OpenViewEventPage', @@ -14,10 +12,6 @@ export const actionTypes = { EVENT_DELETED: 'EventDeleted', WORKING_LIST_UPDATING: 'WorkingListUpdating', WORKING_LIST_UPDATING_WITH_DIALOG: 'WorkingListUpdatingWithDialog', - SET_CURRENT_WORKING_LIST_CONFIG: 'SetCurrentWorkingListConfig', - WORKING_LIST_CONFIGS_RETRIEVED: 'WorkingListConfigsRetrieved', - WORKING_LIST_CONFIGS_RETRIEVAL_FAILED: 'WorkingListConfigsRetrievalFailed', - ADD_WORKING_LIST_CONFIG: 'AddWorkingListConfig', }; export const batchActionTypes = { @@ -25,12 +19,6 @@ export const batchActionTypes = { START_DELETE_EVENT_UPDATE_WORKING_LIST: 'StartDeleteEventUpdateWorkingList', }; -export const workingListUpdateDataRetrieved = - (listId: string, data: Object) => actionCreator(actionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVED)({ ...data, listId }); - -export const workingListUpdateRetrievalFailed = - (listId: string, errorMessage: string) => actionCreator(actionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVAL_FAILED)({ listId, errorMessage }); - export const sortWorkingList = (listId: string, id: string, direction: string) => actionCreator(actionTypes.SORT_WORKING_LIST)({ listId, id, direction }); @@ -57,15 +45,3 @@ export const startDeleteEvent = (eventId: string) => rollback: { type: actionTypes.DELETE_EVENT_FAILED }, }, }); - -export const workingListConfigsRetrieved = (workingListConfigs: Array) => - actionCreator(actionTypes.WORKING_LIST_CONFIGS_RETRIEVED)({ workingListConfigs }); - -export const workingListConfigsRetrievalFailed = (error: string) => - actionCreator(actionTypes.WORKING_LIST_CONFIGS_RETRIEVAL_FAILED)({ error }); - -export const setCurrentWorkingListConfig = (configId: string, listId: string, data?: ?Object) => - actionCreator(actionTypes.SET_CURRENT_WORKING_LIST_CONFIG)({ ...data, configId, listId }); - -export const addWorkingListConfig = (name: string, description: string) => - actionCreator(actionTypes.ADD_WORKING_LIST_CONFIG)({ name, description }); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/index.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/index.js new file mode 100644 index 0000000000..c7e963928b --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsList/index.js @@ -0,0 +1,4 @@ +// @flow +export { default as EventList } from './ListWrapper/EventsListWrapper.container'; +export { actionTypes as paginationActionTypes } from './Pagination'; +export { actionTypes } from './eventsList.actions'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsListConnectivityWrapper/EventsListConnectivityWrapper.component.js b/src/core_modules/capture-core/components/Pages/MainPage/EventsListConnectivityWrapper/EventsListConnectivityWrapper.component.js index 1952d99578..2ad136ca8d 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsListConnectivityWrapper/EventsListConnectivityWrapper.component.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventsListConnectivityWrapper/EventsListConnectivityWrapper.component.js @@ -1,17 +1,16 @@ // @flow import React from 'react'; import OfflineEventsList from '../OfflineEventsList/OfflineEventsList.container'; -import WorkingListConfigSelector from '../EventsList/WorkingListConfigSelector/WorkingListConfigSelector.container'; -import withListHeaderWrapper from '../ListHeaderWrapper/withListHeaderWrapper'; -import withEventsListHeader from '../EventsList/Header/withHeader'; +import { withWorkingListsHeader } from '../WorkingListsHeaderHOC'; +import { WorkingListsOnHoldWrapper } from '../WorkingListsOnHoldWrapper'; + +const WorkingListsWithHeader = withWorkingListsHeader()(WorkingListsOnHoldWrapper); type Props = { isOnline: boolean, listId: string, }; -const OnlineEventsListWithHeader = withEventsListHeader()(withListHeaderWrapper()(WorkingListConfigSelector)); - const EventsListConnectivityWrapper = (props: Props) => { const { isOnline, ...passOnProps } = props; return ( @@ -26,9 +25,9 @@ const EventsListConnectivityWrapper = (props: Props) => { ); } return ( - + + + ); })() } diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListConfig.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListConfig.component.js new file mode 100644 index 0000000000..a9a23dc845 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListConfig.component.js @@ -0,0 +1,71 @@ +// @flow +import * as React from 'react'; +import { EventListConfigContext } from './workingLists.context'; +import EventListLoader from './EventListLoader.component'; + +const determinePrimitiveConfigValue = (value, nextValue) => (nextValue !== undefined ? nextValue : value); + +type PassOnProps = { + listId: string, + currentTemplate: Object, + defaultConfig: Map, + eventsData: ?Object, +}; + +type Props = { + ...PassOnProps, +}; + +const EventListConfig = (props: Props) => { + const { ...passOnProps } = props; + const { + listMeta, + } = React.useContext(EventListConfigContext); + const { next: nextMeta = {}, ...mainMeta } = listMeta || {}; + + const { + filters, + sortById, + sortByDirection, + currentPage, + rowsPerPage, + } = mainMeta; + + const { + filters: nextFilters, + sortById: nextSortById, + sortByDirection: nextSortByDirection, + currentPage: nextCurrentPage, + rowsPerPage: nextRowsPerPage, + } = nextMeta; + + const calcFilters = React.useMemo(() => { + const concatenatedFilters = { + ...filters, + ...nextFilters, + }; + return concatenatedFilters; + }, [ + filters, + nextFilters, + ]); + + const calcSortById = determinePrimitiveConfigValue(sortById, nextSortById); + const calcSortByDirection = determinePrimitiveConfigValue(sortByDirection, nextSortByDirection); + const calcPage = determinePrimitiveConfigValue(currentPage, nextCurrentPage); + const calcRowsPerPage = determinePrimitiveConfigValue(rowsPerPage, nextRowsPerPage); + + + return ( + + ); +}; + +export default EventListConfig; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListLoader.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListLoader.component.js new file mode 100644 index 0000000000..e24ee76064 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListLoader.component.js @@ -0,0 +1,83 @@ +// @flow +import * as React from 'react'; +import { withLoadingIndicator, withErrorMessageHandler } from '../../../../HOC'; +import EventListUpdater from './EventListUpdater.component'; +import { EventListLoaderContext } from './workingLists.context'; + +const EventListUpdaterWithLoadingIndicator = withErrorMessageHandler()( + withLoadingIndicator()(EventListUpdater)); + +type Props = { + listId: string, + currentTemplate: Object, + filters: Object, + sortById: ?string, + sortByDirection: ?string, + currentPage: ?number, + rowsPerPage: ?number, + defaultConfig: Map, +}; + +const EventListLoader = (props: Props) => { + const { + currentTemplate, + listId, + defaultConfig, + ...passOnProps + } = props; + + const templateIsChanging = React.useRef(false); + const firstRun = React.useRef(true); + + const { + eventsData, + eventListIsLoading, + onLoadEventList, + loadEventListError: loadError, + onCancelLoadEventList, + onUpdateEventList, + onCancelUpdateEventList, + } = React.useContext(EventListLoaderContext); + + React.useEffect(() => { + if (eventsData !== undefined && (!templateIsChanging.current || firstRun.current)) { + firstRun.current = false; + templateIsChanging.current = false; + return undefined; + } + + onLoadEventList(currentTemplate, defaultConfig, listId); + firstRun.current = false; + templateIsChanging.current = false; + return () => onCancelLoadEventList(listId); + }, [ + eventsData, + listId, + onLoadEventList, + onCancelLoadEventList, + defaultConfig, + currentTemplate, + ]); + + React.useMemo(() => { + templateIsChanging.current = true; + }, [ // eslint-disable-line react-hooks/exhaustive-deps + currentTemplate, + ]); + + const ready = !templateIsChanging.current && !eventListIsLoading; + + return ( + + ); +}; + +export default EventListLoader; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListUpdater.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListUpdater.component.js new file mode 100644 index 0000000000..7dabd979ba --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/EventListUpdater.component.js @@ -0,0 +1,85 @@ +// @flow +import * as React from 'react'; +import { EventList } from '../EventsList'; + +function filtersAreEqual(prevFilters: Object, newFilters: Object) { + if (prevFilters === newFilters) { + return true; + } + + const prevFilterKeys = Object.keys(prevFilters); + const newFilterKeys = Object.keys(newFilters); + if (prevFilterKeys.length !== newFilterKeys.length) { + return false; + } + + return newFilterKeys + .every(key => newFilters[key] === prevFilters[key]); +} + +function useUpdateListMemoize(value) { + const [filters, ...rest] = value; + const filtersRef = React.useRef(filters); + const prevFilters = filtersRef.current; + + if (!filtersAreEqual(prevFilters, filters)) { + filtersRef.current = filters; + } + + return [filtersRef.current, ...rest]; +} + +function useUpdateListEffect(callback, dependencies) { + const firstRun = React.useRef(true); + + React.useEffect(() => { + if (firstRun.current) { + firstRun.current = false; + return undefined; + } + return callback(); + }, useUpdateListMemoize(dependencies)); // eslint-disable-line react-hooks/exhaustive-deps +} + +type Props = { + listId: string, + filters: Object, + sortById: ?string, + sortByDirection: ?string, + currentPage: ?number, + rowsPerPage: ?number, + onUpdateEventList: Function, + onCancelUpdateEventList: Function, +}; + +const EventListUpdater = (props: Props) => { + const { + listId, + filters, + sortById, + sortByDirection, + currentPage, + rowsPerPage, + onUpdateEventList, + onCancelUpdateEventList, + } = props; + + useUpdateListEffect(() => { + onUpdateEventList(listId, { filters, sortById, sortByDirection, currentPage, rowsPerPage }); + return () => onCancelUpdateEventList(listId); + }, [ + filters, + sortById, + sortByDirection, + currentPage, + rowsPerPage, + ]); + + return ( + + ); +}; + +export default EventListUpdater; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplateSelector.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplateSelector.component.js new file mode 100644 index 0000000000..c02e74e8df --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplateSelector.component.js @@ -0,0 +1,78 @@ +// @flow +import * as React from 'react'; +import { fade, lighten } from '@material-ui/core/styles/colorManipulator'; +import { withStyles } from '@material-ui/core'; +import { Chip } from '@dhis2/ui-core'; +import type { WorkingListTemplate } from './workingLists.types'; + +const getBorder = (theme: Theme) => { + const color = lighten(fade(theme.palette.divider, 1), 0.88); + return `${theme.typography.pxToRem(1)} solid ${color}`; +}; + +const getStyles = (theme: Theme) => ({ + configsContainer: { + borderBottom: getBorder(theme), + display: 'flex', + flexWrap: 'wrap', + padding: `${theme.typography.pxToRem(3)} 0rem`, + }, + chipContainer: { + padding: `${theme.typography.pxToRem(5)} ${theme.typography.pxToRem(8)}`, + }, + chip: { + }, + chipSelected: { + color: 'white', + backgroundColor: theme.palette.secondary.main, + '&:focus': { + backgroundColor: theme.palette.secondary.main, + }, + }, +}); + +type Props = { + templates: Array, + currentTemplateId: ?string, + onSelectTemplate: Function, + classes: Object, +}; + +const TemplateSelector = (props: Props) => { + const { templates, currentTemplateId, onSelectTemplate, classes } = props; + + const customTemplates = templates + .filter(c => !c.isDefault); + + if (customTemplates.length <= 0) { + return null; + } + + const configElements = customTemplates.map((customTemplate) => { + const { id, name } = customTemplate; + return ( +
+ onSelectTemplate(customTemplate)} + > + {name} + +
+ ); + }); + + return ( +
+ {configElements} +
+ ); +}; + +export default withStyles(getStyles)(TemplateSelector); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplatesLoader.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplatesLoader.component.js new file mode 100644 index 0000000000..ed5a7d5317 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplatesLoader.component.js @@ -0,0 +1,53 @@ +// @flow +import * as React from 'react'; +import { withLoadingIndicator, withErrorMessageHandler } from '../../../../HOC'; +import TemplatesManager from './TemplatesManager.component'; + +const TemplatesManangerWithLoadingIndicator = withErrorMessageHandler()( + withLoadingIndicator()(TemplatesManager)); + +type Props = { + templates?: ?Object, + loadTemplatesError: ?string, + onLoadTemplates: Function, + onCancelLoadTemplates: Function, + listId: string, +}; + +const TemplatesLoader = (props: Props) => { + const { + templates, + loadTemplatesError, + onLoadTemplates, + onCancelLoadTemplates, + listId, + ...passOnProps + } = props; + + React.useEffect(() => { + if (templates !== undefined) { + return undefined; + } + onLoadTemplates(listId); + return () => onCancelLoadTemplates(listId); + }, [ + onLoadTemplates, + onCancelLoadTemplates, + templates, + listId, + ]); + + const ready = templates !== undefined; + + return ( + + ); +}; + +export default TemplatesLoader; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplatesManager.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplatesManager.component.js new file mode 100644 index 0000000000..3cd62d5687 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/TemplatesManager.component.js @@ -0,0 +1,53 @@ +// @flow +import * as React from 'react'; +import EventListConfig from './EventListConfig.component'; +import TemplateSelector from './TemplateSelector.component'; +import { ManagerContext } from './workingLists.context'; +import { withBorder } from './borderHOC'; +import type { WorkingListTemplate } from './workingLists.types'; + +type PassOnProps = {| + defaultConfig: Map, + eventsData: ?Object, +|}; + +type Props = { + templates: Array, + listId: string, + ...PassOnProps, +}; + +const TemplatesManager = (props: Props) => { + const { templates, listId, ...passOnProps } = props; + const { + currentTemplate, + onSelectTemplate, + } = React.useContext(ManagerContext); + const handleSelectTemplate = (template: WorkingListTemplate) => { + if (template.id === currentTemplate.id) { + const defaultTemplate = templates.find(t => t.isDefault); + // $FlowFixMe + onSelectTemplate(defaultTemplate.id, listId); + return; + } + onSelectTemplate(template.id, listId); + }; + + return ( + + + + + + ); +}; + +export default withBorder()(TemplatesManager); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingLists.container.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingLists.container.js new file mode 100644 index 0000000000..d780b4db9e --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingLists.container.js @@ -0,0 +1,85 @@ +// @flow +import { connect } from 'react-redux'; +import { + preCleanData, + selectTemplate, + fetchTemplates, + fetchTemplatesCancel, + initEventList, + initEventListCancel, + updateEventList, + updateEventListCancel, +} from './workingLists.actions'; +import WorkingListsContextBuilder from './WorkingListsContextBuilder.component'; + +type OwnProps = {| + listId: string, + skipReload: boolean, + onResetSkipReload?: ?Function, + defaultConfig: Object, +|}; + +type StateProps = {| + currentTemplate: ?Object, + templates: ?Object, + loadTemplatesError: ?string, + loadEventListError: ?string, + listMeta: ?Object, + eventsData: ?Object, + eventListIsLoading: boolean, +|}; + +type DispatchProps = {| + onSelectTemplate: Function, + onLoadTemplates: Function, + onPreCleanData: Function, + onLoadEventList: Function, + onUpdateEventList: Function, + onCancelLoadEventList: Function, + onCancelUpdateEventList: Function, + onCancelLoadTemplates: Function, +|}; + +type Props = { + ...OwnProps, + ...StateProps, + ...DispatchProps, +}; + +type MapStateToPropsFactory = (ReduxState, OwnProps) => StateProps; +// eslint-disable-next-line complexity +const mapStateToProps: MapStateToPropsFactory = (state: ReduxState, props: { listId: string }) => { + const listId = props.listId; + return { + currentTemplate: state.workingListsTemplates[listId] && + state.workingListsTemplates[listId].selectedTemplateId && + state.workingListsTemplates[listId].templates && + state.workingListsTemplates[listId].templates.find(template => template.id === state.workingListsTemplates[listId].selectedTemplateId), + templates: state.workingListsTemplates[listId] && + state.workingListsTemplates[listId].templates, + loadTemplatesError: state.workingListsTemplates[listId] && state.workingListsTemplates[listId].loadError, + loadEventListError: state.workingListsUI[listId] && state.workingListsUI[listId].dataLoadingError, + listMeta: state.workingListsMeta[listId], + eventsData: state.workingLists[listId], + eventListIsLoading: !!state.workingListsUI[listId] && !!state.workingListsUI[listId].isLoading, + }; +}; + +type MapDispatchToPropsFactory = (ReduxDispatch) => DispatchProps; +const mapDispatchToProps: MapDispatchToPropsFactory = (dispatch: ReduxDispatch) => { + const basicDispatcher = actionCreator => (...params) => dispatch(actionCreator(...params)); + return { + onSelectTemplate: basicDispatcher(selectTemplate), + onLoadTemplates: basicDispatcher(fetchTemplates), + onPreCleanData: basicDispatcher(preCleanData), + onLoadEventList: basicDispatcher(initEventList), + onUpdateEventList: basicDispatcher(updateEventList), + onCancelLoadEventList: basicDispatcher(initEventListCancel), + onCancelUpdateEventList: basicDispatcher(updateEventListCancel), + onCancelLoadTemplates: basicDispatcher(fetchTemplatesCancel), + }; +}; + +export default connect( + mapStateToProps, mapDispatchToProps)( + WorkingListsContextBuilder); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingListsContextBuilder.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingListsContextBuilder.component.js new file mode 100644 index 0000000000..1af1cff93e --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingListsContextBuilder.component.js @@ -0,0 +1,95 @@ +// @flow +import * as React from 'react'; +import { + ManagerContext, + EventListConfigContext, + EventListLoaderContext, +} from './workingLists.context'; +import WorkingListsPreCleaner from './WorkingListsPreCleaner.component'; + +type PassOnProps = {| + listId: string, + templates: ?Object, + onPreCleanData: Function, + onLoadTemplates: Function, + onCancelLoadTemplates: Function, + skipReload: boolean, + onResetSkipReload?: ?Function, +|}; + +type Props = { + currentTemplate: ?Object, + onSelectTemplate: Function, + onLoadEventList: Function, + loadEventListError: ?string, + onUpdateEventList: Function, + onCancelLoadEventList: Function, + onCancelUpdateEventList: Function, + listMeta: ?Object, + eventsData: ?Object, + eventListIsLoading: boolean, + ...PassOnProps, +}; + +const WorkingListsContextBuilder = (props: Props) => { + const { + currentTemplate, + onSelectTemplate, + onLoadEventList, + loadEventListError, + onUpdateEventList, + onCancelLoadEventList, + onCancelUpdateEventList, + listMeta, + eventsData, + eventListIsLoading, + ...passOnProps + } = props; + + const managerData = React.useMemo(() => ({ + currentTemplate, + onSelectTemplate, + }), [currentTemplate, onSelectTemplate]); + + const eventListConfig = React.useMemo(() => ({ + listMeta, + }), [listMeta]); + + const eventListData = React.useMemo(() => ({ + eventsData, + eventListIsLoading, + onLoadEventList, + loadEventListError, + onUpdateEventList, + onCancelLoadEventList, + onCancelUpdateEventList, + }), [ + eventsData, + eventListIsLoading, + onLoadEventList, + loadEventListError, + onUpdateEventList, + onCancelLoadEventList, + onCancelUpdateEventList, + ]); + + return ( + + + + + + + + ); +}; + +export default WorkingListsContextBuilder; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingListsPreCleaner.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingListsPreCleaner.component.js new file mode 100644 index 0000000000..dd6c5a145b --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/WorkingListsPreCleaner.component.js @@ -0,0 +1,61 @@ +// @flow +import * as React from 'react'; +import TemplatesLoader from './TemplatesLoader.component'; + +type PassOnProps = {| + onLoadTemplates: Function, + onCancelLoadTemplates: Function, + templates: ?Object, + loadTemplatesError: ?string, +|}; + +type Props = { + onPreCleanData: Function, + listId: string, + skipReload: boolean, + onResetSkipReload?: ?Function, + ...PassOnProps, +}; + +const WorkingListsPreCleaner = (props: Props) => { + const { + onPreCleanData, + listId, + skipReload, + onResetSkipReload, + ...passOnProps + } = props; + const [isCleaning, setCleaningStatus] = React.useState(true); + + React.useEffect(() => { + if (skipReload) { + setCleaningStatus(false); + return () => { + onResetSkipReload && onResetSkipReload(listId); + }; + } + + onPreCleanData(listId); + setCleaningStatus(false); + return undefined; + }, [ + listId, + skipReload, + onResetSkipReload, + onPreCleanData, + setCleaningStatus, + ]); + + if (isCleaning && !skipReload) { + return null; + } + + return ( + + ); +}; + +export default React.memo(WorkingListsPreCleaner); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/borderHOC.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/borderHOC.js new file mode 100644 index 0000000000..4956136da4 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/borderHOC.js @@ -0,0 +1,39 @@ +// @flow +import * as React from 'react'; +import { withStyles } from '@material-ui/core/styles'; +import { fade, lighten } from '@material-ui/core/styles/colorManipulator'; + +const getBorder = (theme: Theme) => { + const color = lighten(fade(theme.palette.divider, 1), 0.88); + return `${theme.typography.pxToRem(1)} solid ${color}`; +}; + +const getStyles = (theme: Theme) => ({ + container: { + border: getBorder(theme), + }, +}); + +type Props = { + classes: { + container: string, + }, +}; + +export const withBorder = () => (InnerComponent: React.ComponentType) => + // $FlowFixMe + withStyles(getStyles)(class BorderHOC extends React.Component { + render() { + const { classes, ...passOnProps } = this.props; + return ( +
+ +
+ ); + } + }); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/getColumnsConfiguration.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/columnsConfigurationGetter.js similarity index 81% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/getColumnsConfiguration.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/columnsConfigurationGetter.js index 191cba81cd..0574591013 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/getColumnsConfiguration.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/columnsConfigurationGetter.js @@ -1,9 +1,5 @@ // @flow -import { - ProgramStage, -} from '../../../../../metaData'; -import { getDefaultMainConfig, getMetaDataConfig } from '../defaultColumnConfiguration'; -import type { ColumnConfig } from '../eventList.types'; +import type { ColumnConfig } from '../../workingLists.types'; const getCustomConfig = ( customOrder: Array, @@ -64,17 +60,13 @@ const getCustomColumnsConfiguration = ( }; export const getColumnsConfiguration = ( - stage: ProgramStage, customOrder?: ?Array, + defaultConfig: Map, ): Array => { - const defaultConfig = [ - ...getDefaultMainConfig(stage), - ...getMetaDataConfig(stage.stageForm), - ]; - + const defaultOrderConfig = [...defaultConfig.entries()].map(entry => entry[1]); if (customOrder && customOrder.length > 0) { - return getCustomColumnsConfiguration(defaultConfig, customOrder); + return getCustomColumnsConfiguration(defaultOrderConfig, customOrder); } - return defaultConfig; + return defaultOrderConfig; }; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/eventFilterRequests.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/eventFilterRequests.js similarity index 95% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/eventFilterRequests.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/eventFilterRequests.js index 32395beeed..831cf3d239 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/eventFilterRequests.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/eventFilterRequests.js @@ -1,5 +1,5 @@ // @flow -import { getApi } from '../../../../../d2/d2Instance'; +import { getApi } from '../../../../../../d2/d2Instance'; // import convertToServerEventWorkingListConfig from './convertToServerEventWorkingListConfig'; type ApiConfig = { diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/index.js new file mode 100644 index 0000000000..af23685776 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/index.js @@ -0,0 +1,4 @@ +// @flow +export { getEventFilters } from './eventFilterRequests'; +// export { convertToEventFilter } from './convertToEventFilter'; +export { convertToListConfig } from './toListConfigConverter'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/convertToEventFilter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toEventFilterConverter.js similarity index 99% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/convertToEventFilter.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toEventFilterConverter.js index 6e2eb740ea..dc2f05e700 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/eventFiltersInterface/convertToEventFilter.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toEventFilterConverter.js @@ -1,4 +1,5 @@ // @flow +/* import { dataElementTypes as elementTypes } from '../../../../../metaData'; import { MAX_OPTIONS_COUNT_FOR_OPTION_SET_CONTENTS, @@ -101,3 +102,4 @@ export function convertToEventFilter( eventQueryCriteria, }; } +*/ \ No newline at end of file diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/index.js new file mode 100644 index 0000000000..5926bd36ae --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/index.js @@ -0,0 +1,2 @@ +// @flow +export { convertToListConfig } from './toListConfigConverter'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/optionSet.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/optionSet.js new file mode 100644 index 0000000000..24dcafdd7e --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/optionSet.js @@ -0,0 +1,33 @@ +// @flow +import { moment } from 'capture-core-utils/moment'; +import { parseNumber } from 'capture-core-utils/parsers'; +import { + dataElementTypes as elementTypes, +} from '../../../../../../../metaData'; + +import type { + ApiDataFilterOptionSet, +} from '../../../workingLists.types'; +import type { + OptionSetFilterData, +} from '../../../../EventsList/eventList.types'; + + +const converterByType = { + [elementTypes.NUMBER]: parseNumber, + [elementTypes.INTEGER]: parseNumber, + [elementTypes.INTEGER_POSITIVE]: parseNumber, + [elementTypes.INTEGER_ZERO_OR_POSITIVE]: parseNumber, + [elementTypes.INTEGER_NEGATIVE]: parseNumber, + [elementTypes.DATE]: (rawValue: string) => moment(rawValue, 'YYYY-MM-DD').toISOString(), + [elementTypes.BOOLEAN]: (rawValue: string) => (rawValue === 'true'), + [elementTypes.TRUE_ONLY]: (rawValue: string) => ((rawValue === 'true') || null), +}; + +export const getOptionSetFilter = + (filter: ApiDataFilterOptionSet, type: $Values): OptionSetFilterData => ({ + usingOptionSet: true, + values: filter + .in + .map(value => (converterByType[type] ? converterByType[type](value) : value)), + }); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/toListConfigConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/toListConfigConverter.js new file mode 100644 index 0000000000..c6e6543e95 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventFiltersInterface/toListConfigConverter/toListConfigConverter.js @@ -0,0 +1,230 @@ +// @flow +import log from 'loglevel'; +import { errorCreator } from 'capture-core-utils'; +import { moment } from 'capture-core-utils/moment'; +import { canViewOtherUsers } from '../../../../../../../d2'; +import { + dataElementTypes as elementTypes, +} from '../../../../../../../metaData'; +import { getColumnsConfiguration } from '../columnsConfigurationGetter'; +import { getApi } from '../../../../../../../d2/d2Instance'; +import { getOptionSetFilter } from './optionSet'; + +import { + assigneeFilterModes, + dateFilterTypes, + type AssigneeFilterData, + type DateFilterData, + type BooleanFilterData, + type TrueOnlyFilterData, + type TextFilterData, + type NumericFilterData, +} from '../../../../EventsList/eventList.types'; +import type { + ApiDataFilter, + ApiDataFilterNumeric, + ApiDataFilterText, + ApiDataFilterBoolean, + ApiDataFilterDate, + ApiEventQueryCriteria, +} from '../../../workingLists.types'; + +const getTextFilter = (filter: ApiDataFilterText): TextFilterData => { + const value = filter.like; + return { + value, + }; +}; + +const getNumericFilter = (filter: ApiDataFilterNumeric): NumericFilterData => ({ + ge: filter.ge ? Number(filter.ge) : undefined, + le: filter.le ? Number(filter.le) : undefined, +}); + +const getBooleanFilter = (filter: ApiDataFilterBoolean): BooleanFilterData => ({ + values: filter.in.map(value => value === 'true'), +}); + +const getTrueOnlyFilter = (/* filter: ApiDataFilterTrueOnly */): TrueOnlyFilterData => ({ + value: true, +}); + +const getDateFilter = (filter: ApiDataFilterDate): DateFilterData => { + if (filter.period) { + return { + type: dateFilterTypes.RELATIVE, + period: filter.period, + }; + } + + return { + type: dateFilterTypes.ABSOLUTE, + startDate: filter.startDate ? moment(filter.startDate, 'YYYY-MM-DD').toISOString() : undefined, + endDate: filter.endDate ? moment(filter.endDate, 'YYYY-MM-DD').toISOString() : undefined, + }; +}; + +const getUser = (userId: string) => { + return getApi() + .get(`users/${userId}`, { fields: 'id,name,userCredentials[username]' }) + .then((user: Object) => ({ + id: user.id, + name: user.name, + username: user.userCredentials.username, + })) + .catch((error) => { + log.error( + errorCreator('An error occured retrieving assignee user')({ error, userId }), + ); + return null; + }); +}; + +// eslint-disable-next-line complexity +const getAssigneeFilter = async ( + assignedUserMode: $Values, + assignedUsers: ?Array, +): Promise => { + if (assignedUserMode === assigneeFilterModes.PROVIDED) { + const assignedUserId = assignedUsers && assignedUsers.length > 0 && assignedUsers[0]; + if (!assignedUserId) { + return undefined; + } + + const user = await getUser(assignedUserId); + if (!user) { + return undefined; + } + + return { + assignedUserMode, + assignedUser: user, + }; + } + + return { + assignedUserMode, + }; +}; + +const getFilterByType = { + [elementTypes.TEXT]: getTextFilter, + [elementTypes.NUMBER]: getNumericFilter, + [elementTypes.INTEGER]: getNumericFilter, + [elementTypes.INTEGER_POSITIVE]: getNumericFilter, + [elementTypes.INTEGER_NEGATIVE]: getNumericFilter, + [elementTypes.INTEGER_ZERO_OR_POSITIVE]: getNumericFilter, + [elementTypes.DATE]: getDateFilter, + [elementTypes.BOOLEAN]: getBooleanFilter, + [elementTypes.TRUE_ONLY]: getTrueOnlyFilter, +}; + +const isOptionSetFilter = (type: $Values, filter: any) => { + if ([ + elementTypes.BOOLEAN, + ].includes(type)) { + const validBooleanValues = ['true', 'false']; + return filter.in.some(value => !validBooleanValues.includes[value]); + } + + return filter.in; +}; + +const getSortOrder = (order: ?string) => { + const sortOrderParts = order && order.split(':'); + if (!sortOrderParts || sortOrderParts.length !== 2) { + return { + sortById: 'eventDate', + sortByDirection: 'desc', + }; + } + + return { + sortById: sortOrderParts[0], + sortByDirection: sortOrderParts[1], + }; +}; + +const getDataElementFilters = ( + filters: ?Array, + defaultSpecs: Map): Array => { + if (!filters) { + return []; + } + + return filters.map((serverFilter) => { + const element = defaultSpecs.get(serverFilter.dataItem); + if (!element || !getFilterByType[element.type]) { + return null; + } + + if (isOptionSetFilter(element.type, serverFilter)) { + return { + // $FlowFixMe + ...getOptionSetFilter(serverFilter, element.type), + id: serverFilter.dataItem, + }; + } + + return { + id: serverFilter.dataItem, + ...(getFilterByType[element.type] ? getFilterByType[element.type](serverFilter, element) : null), + }; + }).filter(clientFilter => clientFilter); +}; + +// eslint-disable-next-line complexity +const getMainDataFilters = async ( + eventQueryCriteria: ?ApiEventQueryCriteria, + defaultSpecs: Map, +) => { + if (!eventQueryCriteria) { + return []; + } + + const { eventDate, status, assignedUserMode, assignedUsers } = eventQueryCriteria; + const filters = []; + if (status) { + // $FlowFixMe + filters.push({ ...getOptionSetFilter({ in: [status] }, defaultSpecs.get('status').type), id: 'status' }); + } + if (eventDate) { + filters.push({ ...getDateFilter(eventDate), id: 'eventDate' }); + } + if (assignedUserMode && canViewOtherUsers()) { + filters.push({ ...(await getAssigneeFilter(assignedUserMode, assignedUsers)), id: 'assignee' }); + } + return filters; +}; + +const listConfigDefaults = { + currentPage: 1, + rowsPerPage: 15, +}; + +export async function convertToListConfig( + eventQueryCriteria: ?ApiEventQueryCriteria, + defaultSpecs: Map, +) { + const { sortById, sortByDirection } = getSortOrder(eventQueryCriteria && eventQueryCriteria.order); + const filters = [ + ...getDataElementFilters(eventQueryCriteria && eventQueryCriteria.dataFilters, defaultSpecs), + ...(await getMainDataFilters(eventQueryCriteria, defaultSpecs)), + ].reduce((acc, filter) => { + const { id, ...filterData } = filter; + acc[id] = filterData; + return acc; + }, {}); + + const columnOrder = + getColumnsConfiguration(eventQueryCriteria && eventQueryCriteria.displayColumnOrder, defaultSpecs); + + + return { + filters, + columnOrder, + sortById, + sortByDirection, + ...listConfigDefaults, + }; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventList.epics.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventList.epics.js new file mode 100644 index 0000000000..88789ff925 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventList.epics.js @@ -0,0 +1,96 @@ +// @flow +import { fromPromise } from 'rxjs/observable/fromPromise'; +import { + actionTypes, + deleteEvent, +} from '../workingLists.actions'; +import { actionTypes as eventListActionTypes } from '../../EventsList'; +import { initEventWorkingListAsync } from './initEventWorkingList'; +import { updateEventWorkingListAsync } from './updateEventWorkingList'; +// import { paginationActionTypes } from '../../EventsList'; +/* + +import { batchActions } from 'redux-batched-actions'; +import isSelectionsEqual from '../../../../App/isSelectionsEqual'; + +import { + actionTypes as mainSelectionActionTypes, +} from '../../mainSelections.actions'; + +import { + batchActionTypes as eventsListBatchActionTypes, + actionTypes as eventsListActionTypes, + startDeleteEvent, + workingListUpdatingWithDialog, + setCurrentWorkingListConfig, + workingListConfigsRetrieved, +} from '../eventsList.actions'; +import { dataEntryActionTypes as newEventDataEntryActionTypes } from '../../../NewEvent'; +import { actionTypes as editEventDataEntryActionTypes } from '../../../EditEvent/DataEntry/editEventDataEntry.actions'; +import { actionTypes as viewEventActionTypes } from '../../../ViewEvent/viewEvent.actions'; +import { + batchActionTypes as connectivityBatchActionTypes, + actionTypes as connectivityActionTypes, +} from '../../../../Connectivity/connectivity.actions'; +import { actionTypes as mainPageActionTypes } from '../../mainPage.actions'; +import { + actionTypes as filterSelectorActionTypes, + batchActionTypes as filterSelectorBatchActionTypes, +} from '../FilterSelectors/filterSelector.actions'; +import { getWorkingListConfigsAsync } from './workingListConfigDataRetriever'; +*/ +export const initEventListEpic = (action$: InputObservable, store: ReduxStore) => + action$.ofType( + actionTypes.EVENT_LIST_INIT, + ) + .switchMap((action) => { + const state = store.getState(); + const { programId, orgUnitId, categories } = state.currentSelections; + const lastTransaction = state.offline.lastTransaction; + const { selectedTemplate, defaultConfig, listId } = action.payload; + const eventQueryCriteria = selectedTemplate.eventQueryCriteria; + const initialPromise = + initEventWorkingListAsync( + eventQueryCriteria, { + commonQueryData: { + programId, + orgUnitId, + categories, + }, + defaultSpecification: defaultConfig, + listId, + lastTransaction, + }); + return fromPromise(initialPromise) + .takeUntil( + action$ + .ofType(actionTypes.EVENT_LIST_INIT_CANCEL) + .filter(cancelAction => cancelAction.payload.listId === listId), + ); + }); + +export const updateEventListEpic = (action$: InputObservable, store: ReduxStore) => + action$.ofType( + actionTypes.EVENT_LIST_UPDATE, + ) + .switchMap((action) => { + const state = store.getState(); + const { listId, queryArgs } = action.payload; + const updatePromise = updateEventWorkingListAsync(listId, queryArgs, state); + return fromPromise(updatePromise) + .takeUntil( + action$ + .ofType(actionTypes.EVENT_LIST_UPDATE_CANCEL) + .filter(cancelAction => cancelAction.payload.listId === listId), + ); + }); + +// TODO: --------------------------------- REFACTOR ----------------------------------- +export const requestDeleteEventEpic = (action$: InputObservable) => + action$.ofType( + eventListActionTypes.REQUEST_DELETE_EVENT, + ).map((action) => { + const eventId = action.payload.eventId; + const listId = 'eventList'; + return deleteEvent(eventId, listId); + }); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/assigneeConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/assigneeConverter.js new file mode 100644 index 0000000000..6a94d502a1 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/assigneeConverter.js @@ -0,0 +1,11 @@ +// @flow +import { type AssigneeFilterData } from '../../../eventList.types'; + +export function convertAssignee( + sourceValue: AssigneeFilterData, +) { + return { + assignedUserMode: sourceValue.assignedUserMode, + assignedUser: sourceValue.assignedUser && sourceValue.assignedUser.id, + }; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/booleanConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/booleanConverter.js new file mode 100644 index 0000000000..e3acd592ed --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/booleanConverter.js @@ -0,0 +1,15 @@ +// @flow +import { pipe } from 'capture-core-utils'; +import type { BooleanFilterData } from '../../../eventList.types'; + +export function convertBoolean(filter: BooleanFilterData) { + return pipe( + values => values.map(filterValue => (filterValue ? 'true' : 'false')), + values => + (values.length > 1 ? + { valueString: values.join(';'), single: false } : + { valueString: values[0], single: true } + ), + ({ valueString, single }) => (single ? `eq:${valueString}` : `in:${valueString}`), + )(filter.values); +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/converters/dateConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/dateConverter.js similarity index 97% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/converters/dateConverter.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/dateConverter.js index e9fc9b3f9c..7a9ebd8e4b 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/converters/dateConverter.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/dateConverter.js @@ -1,6 +1,11 @@ // @flow import { createSelector } from 'reselect'; import { moment } from 'capture-core-utils/moment'; +import type { + DateFilterData, + RelativeDateFilterData, + AbsoluteDateFilterData, +} from '../../../eventList.types'; const periods = { TODAY: 'TODAY', diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/index.js new file mode 100644 index 0000000000..4c85242783 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/index.js @@ -0,0 +1,8 @@ +// @flow +export { clearMemoization, convertDate } from './dateConverter'; +export { convertOptionSet } from './optionSet/optionSetConverter'; +export { convertAssignee } from './assigneeConverter'; +export { convertBoolean } from './booleanConverter'; +export { convertText } from './textConverter'; +export { convertNumeric } from './numericConverter'; +export { convertTrueOnly } from './trueOnlyConverter'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/numericConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/numericConverter.js new file mode 100644 index 0000000000..fe29001e3c --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/numericConverter.js @@ -0,0 +1,15 @@ +// @flow +import type { NumericFilterData } from '../../../eventList.types'; + +export function convertNumeric(filter: NumericFilterData) { + const requestData = []; + + if (filter.ge || filter.ge === 0) { + requestData.push(`ge:${filter.ge}`); + } + if (filter.le || filter.le === 0) { + requestData.push(`le:${filter.le}`); + } + + return requestData.join(':'); +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/optionSet/basicDataTypeConverters.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/optionSet/basicDataTypeConverters.js new file mode 100644 index 0000000000..eec46f67a5 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/optionSet/basicDataTypeConverters.js @@ -0,0 +1,32 @@ +// @flow +import { moment } from 'capture-core-utils/moment'; +import { dataElementTypes as elementTypes } from '../../../../../../../../metaData'; + +const stringifyNumber = (rawValue: number) => rawValue.toString(); + +function convertDate(isoString: string): string { + const momentDate = moment(isoString); + momentDate.locale('en'); + return momentDate.format('YYYY-MM-DD'); +} + +const valueConvertersForType = { + [elementTypes.NUMBER]: stringifyNumber, + [elementTypes.INTEGER]: stringifyNumber, + [elementTypes.INTEGER_POSITIVE]: stringifyNumber, + [elementTypes.INTEGER_ZERO_OR_POSITIVE]: stringifyNumber, + [elementTypes.INTEGER_NEGATIVE]: stringifyNumber, + [elementTypes.DATE]: convertDate, + [elementTypes.TRUE_ONLY]: () => 'true', + [elementTypes.BOOLEAN]: (rawValue: boolean) => (rawValue ? 'true' : 'false'), + [elementTypes.FILE_RESOURCE]: (rawValue: Object) => rawValue.value, + [elementTypes.IMAGE]: (rawValue: Object) => rawValue.value, + [elementTypes.COORDINATE]: (rawValue: Object) => `[${rawValue.longitude},${rawValue.latitude}]`, + [elementTypes.PERCENTAGE]: (rawValue: Object) => rawValue.replace('%', ''), + [elementTypes.ORGANISATION_UNIT]: (rawValue: Object) => rawValue.id, +}; + +export function convertDataTypeValueToRequest(value: any, type: $Values) { + // $FlowSuppress + return valueConvertersForType[type] ? valueConvertersForType[type](value) : value; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/optionSet/optionSetConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/optionSet/optionSetConverter.js new file mode 100644 index 0000000000..f5884105f7 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/optionSet/optionSetConverter.js @@ -0,0 +1,16 @@ +// @flow +import { pipe } from 'capture-core-utils'; +import { convertDataTypeValueToRequest } from './basicDataTypeConverters'; +import { dataElementTypes as elementTypes } from '../../../../../../../../metaData'; +import type { OptionSetFilterData } from '../../../../../EventsList/eventList.types'; + +export function convertOptionSet( + sourceValue: OptionSetFilterData, + type: $Values, +) { + return pipe( + values => values.map(filterValue => convertDataTypeValueToRequest(filterValue, type)), + values => values.join(';'), + valueString => `in:${valueString}`, + )(sourceValue.values); +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/textConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/textConverter.js new file mode 100644 index 0000000000..9984a5ba0a --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/textConverter.js @@ -0,0 +1,6 @@ +// @flow +import type { TextFilterData } from '../../../eventList.types'; + +export function convertText(filter: TextFilterData) { + return `like:${filter.value}`; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/trueOnlyConverter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/trueOnlyConverter.js new file mode 100644 index 0000000000..7772ab74ce --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/filterConverters/trueOnlyConverter.js @@ -0,0 +1,6 @@ +// @flow +import type { TrueOnlyFilterData } from '../../../eventList.types'; + +export function convertTrueOnly(filter: TrueOnlyFilterData) { + return `eq:${filter.value}`; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/index.js similarity index 100% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsQueryArgsBuilder/index.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/index.js diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/queryArgsBuilder.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/queryArgsBuilder.js new file mode 100644 index 0000000000..04101c982d --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsQueryArgsBuilder/queryArgsBuilder.js @@ -0,0 +1,119 @@ +// @flow +import log from 'loglevel'; +import { errorCreator } from 'capture-core-utils'; +import { getEventProgramThrowIfNotFound, dataElementTypes } from '../../../../../../metaData'; +import { + convertText, + convertDate, + convertAssignee, + convertOptionSet, + convertBoolean, + convertNumeric, + convertTrueOnly, +} from './filterConverters'; + +type QueryArgsSource = { + programId: string, + filters: Object, + sortById: string, + sortByDirection: string, +}; + +const mappersForTypes = { + [dataElementTypes.TEXT]: convertText, + [dataElementTypes.NUMBER]: convertNumeric, + [dataElementTypes.INTEGER]: convertNumeric, + [dataElementTypes.INTEGER_POSITIVE]: convertNumeric, + [dataElementTypes.INTEGER_NEGATIVE]: convertNumeric, + [dataElementTypes.INTEGER_ZERO_OR_POSITIVE]: convertNumeric, + [dataElementTypes.DATE]: convertDate, + [dataElementTypes.ASSIGNEE]: convertAssignee, + [dataElementTypes.BOOLEAN]: convertBoolean, + [dataElementTypes.TRUE_ONLY]: convertTrueOnly, +}; + +function convertFilter( + sourceValue: Object, + type: string, + meta: { + key: string, + listId: string, + isInit: boolean, + }, +) { + if (sourceValue.usingOptionSet) { + return convertOptionSet(sourceValue, type); + } + return mappersForTypes[type] ? + mappersForTypes[type](sourceValue, meta.key, meta.listId, meta.isInit) : + sourceValue; +} + +function convertFilters( + filters: Object, + { + mainPropTypes, + programId, + listId, + isInit, + }: { + mainPropTypes: Object, + programId: string, + listId: string, + isInit: boolean, + }, +) { + const elementsById = getEventProgramThrowIfNotFound(programId).stage.stageForm.getElementsById(); + + return Object + .keys(filters) + .filter(key => filters[key]) + .reduce((acc, key) => { + const type = (elementsById[key] && elementsById[key].type) || mainPropTypes[key]; + if (!type) { + log.error(errorCreator('Could not get type for key')({ key, listId, programId })); + } else { + const sourceValue = filters[key]; + const queryArgValue = convertFilter( + sourceValue, + type, + { + key, + listId, + isInit, + }); + acc[key] = queryArgValue; + } + return acc; + }, {}); +} + + +export function buildQueryArgs( + queryArgsSource: QueryArgsSource, + { + mainPropTypes, + listId, + isInit = false, + }: { + mainPropTypes: Object, + listId: string, + isInit: boolean, + }, +) { + const { programId, filters } = queryArgsSource; + const queryArgs = { + ...queryArgsSource, + filters: convertFilters( + filters, + { + mainPropTypes, + programId, + listId, + isInit, + }, + ), + }; + + return queryArgs; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsRetriever.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsRetriever.js similarity index 100% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/eventsRetriever.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/eventsRetriever.js diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/index.js new file mode 100644 index 0000000000..5875e54812 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/index.js @@ -0,0 +1,3 @@ +// @flow +export { initEventListEpic, updateEventListEpic, requestDeleteEventEpic } from './eventList.epics'; +export { retrieveTemplatesEpic } from './templates.epics'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/initEventWorkingList.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/initEventWorkingList.js new file mode 100644 index 0000000000..fa0eeb91ed --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/initEventWorkingList.js @@ -0,0 +1,67 @@ +// @flow +import log from 'loglevel'; +import i18n from '@dhis2/d2-i18n'; +import { errorCreator, pipe } from 'capture-core-utils'; +import { convertToListConfig } from './eventFiltersInterface'; +import { getEventWorkingListDataAsync } from './eventsRetriever'; +import { + initEventListSuccess, + initEventListError, +} from '../workingLists.actions'; +import { buildQueryArgs } from './eventsQueryArgsBuilder'; +import type { ApiEventQueryCriteria, CommonQueryData, ListConfig } from '../workingLists.types'; + +const errorMessages = { + WORKING_LIST_RETRIEVE_ERROR: 'Working list could not be loaded', +}; + +export const initEventWorkingListAsync = async ( + config: ?ApiEventQueryCriteria, + meta: { + commonQueryData: CommonQueryData, + defaultSpecification: Map, + listId: string, + lastTransaction: number, + }, +): Promise> => { + const { commonQueryData, defaultSpecification, listId, lastTransaction } = meta; + const listConfig: ListConfig = await convertToListConfig(config, defaultSpecification); + const { columnOrder, ...queryArgsPart } = listConfig; + const queryArgsSource = { + ...queryArgsPart, + ...commonQueryData, + }; + + const mainColumnTypes = pipe( + columns => columns.filter(column => column.isMainProperty), + columOrderMainOnly => columOrderMainOnly.reduce((acc, column) => ({ + ...acc, + [column.id]: column.type, + }), {}), + )(columnOrder); + + return getEventWorkingListDataAsync( + buildQueryArgs( + queryArgsSource, { + mainPropTypes: mainColumnTypes, + listId, + isInit: true, + }, + ), columnOrder) + .then(data => + initEventListSuccess(listId, { + ...data, + config: { + ...listConfig, + selections: { + ...commonQueryData, + lastTransaction, + }, + }, + }), + ) + .catch((error) => { + log.error(errorCreator(errorMessages.WORKING_LIST_RETRIEVE_ERROR)({ error })); + return initEventListError(listId, i18n.t('Working list could not be loaded')); + }); +}; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/workingListConfig.epics.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/templates.epics.js similarity index 53% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/workingListConfig.epics.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/templates.epics.js index 63fb7128d4..c63d28b456 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/workingListConfig.epics.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/templates.epics.js @@ -2,41 +2,61 @@ import { batchActions } from 'redux-batched-actions'; import { ActionsObservable } from 'redux-observable'; import { fromPromise } from 'rxjs/observable/fromPromise'; +import i18n from '@dhis2/d2-i18n'; +import log from 'loglevel'; +import { errorCreator } from 'capture-core-utils'; + +import { + actionTypes, + batchActionTypes, + fetchTemplatesSuccess, + fetchTemplatesError, + selectTemplate, +} from '../workingLists.actions'; +import { getTemplatesAsync } from './templatesFetcher'; + +/* import { actionTypes as mainSelectionActionTypes, } from '../../mainSelections.actions'; import { actionTypes as viewEventActionTypes } from '../../../ViewEvent/viewEvent.actions'; import { dataEntryActionTypes as newEventDataEntryActionTypes } from '../../../NewEvent'; -import { getWorkingListConfigsAsync } from './workingListConfigDataRetriever'; import { batchActionTypes as eventsListBatchActionTypes, setCurrentWorkingListConfig, workingListConfigsRetrieved, } from '../eventsList.actions'; import { getProgramFromProgramIdThrowIfNotFound, EventProgram } from '../../../../../metaData'; +import { workingListsActions } from '../../../MainPage/WorkingLists'; +*/ -export const retrieveWorkingListConfigsFromServer = (action$: ActionsObservable, store: ReduxStore) => +export const retrieveTemplatesEpic = (action$: ActionsObservable, store: ReduxStore) => action$.ofType( - mainSelectionActionTypes.MAIN_SELECTIONS_COMPLETED, + /*mainSelectionActionTypes.MAIN_SELECTIONS_COMPLETED, viewEventActionTypes.INITIALIZE_WORKING_LISTS_ON_BACK_TO_MAIN_PAGE, - newEventDataEntryActionTypes.CANCEL_SAVE_INITIALIZE_WORKING_LISTS, + newEventDataEntryActionTypes.CANCEL_SAVE_INITIALIZE_WORKING_LISTS,*/ + actionTypes.TEMPLATES_FETCH, ) - .filter(() => { - const state = store.getState(); - if (!state.offline.online) { - return false; - } - const programId = state.currentSelections.programId; - const program = programId && getProgramFromProgramIdThrowIfNotFound(programId); - return (program && program instanceof EventProgram); - }) - .switchMap(() => { - const promise = getWorkingListConfigsAsync(store.getState()).then(container => batchActions([ - setCurrentWorkingListConfig(container.default.id, 'eventList', container.default), - workingListConfigsRetrieved(container.workingListConfigs), - ], eventsListBatchActionTypes.WORKING_LIST_CONFIGS_RETRIEVED_BATCH)); - return fromPromise(promise); + .switchMap((action) => { + const listId = action.payload.listId; + const promise = getTemplatesAsync(store.getState()) + .then(container => batchActions([ + selectTemplate(container.default.id, listId, container.default), + fetchTemplatesSuccess(container.workingListConfigs, listId), + ], batchActionTypes.TEMPLATES_FETCH_SUCCESS_BATCH)) + .catch((error) => { + log.error( + errorCreator(error)({ epic: 'retrieveTemplatesEpic' }), + ); + return fetchTemplatesError(i18n.t('an error occurred loading working lists'), listId); + }); + + return fromPromise(promise) + .takeUntil( + action$.ofType(actionTypes.TEMPLATES_FETCH_CANCEL) + .filter(cancelAction => cancelAction.payload.listId === listId), + ); }); /* diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/workingListConfigDataRetriever.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/templatesFetcher.js similarity index 79% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/workingListConfigDataRetriever.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/templatesFetcher.js index 6c08a58bd6..ec875d6ee9 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/epics/workingListConfigDataRetriever.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/templatesFetcher.js @@ -1,7 +1,7 @@ // @flow -import { getEventFilters } from '../eventFiltersInterface'; +import { getEventFilters } from './eventFiltersInterface'; -export const getWorkingListConfigsAsync = (state: ReduxState) => { +export const getTemplatesAsync = (state: ReduxState) => { const programId = state.currentSelections.programId; return getEventFilters(programId).then((workingListConfigs) => { const defaultWorkingListConfig = { diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/updateEventWorkingList.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/updateEventWorkingList.js new file mode 100644 index 0000000000..9e86153f52 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/epics/updateEventWorkingList.js @@ -0,0 +1,62 @@ +// @flow +import log from 'loglevel'; +import { errorCreator, pipe } from 'capture-core-utils'; +import { getEventWorkingListDataAsync } from './eventsRetriever'; +import { + updateEventListSuccess, + updateEventListError, +} from '../workingLists.actions'; +import { buildQueryArgs } from './eventsQueryArgsBuilder'; + + +const errorMessages = { + WORKING_LIST_UPDATE_ERROR: 'Working list could not be updated', +}; + +const getSourceQueryArgsForUpdateWorkingList = ( + state: ReduxState, + listId: string, + sourceQueryArgsPart: Object, +) => { + const { programId, orgUnitId, categories } = state.workingListsContext[listId]; + + return { + programId, + orgUnitId, + categories, + ...sourceQueryArgsPart, + }; +}; + +export const updateEventWorkingListAsync = ( + listId: string, + sourceQueryArgsPart: Object, + state: ReduxState, +): Promise> => { + const sourceQueryData = getSourceQueryArgsForUpdateWorkingList(state, listId, sourceQueryArgsPart); + const columnOrder = state.workingListsColumnsOrder[listId]; + const mainColumnTypes = pipe( + columns => columns.filter(column => column.isMainProperty), + columOrderMainOnly => columOrderMainOnly.reduce((acc, column) => ({ + ...acc, + [column.id]: column.type, + }), {}), + )(columnOrder); + + return getEventWorkingListDataAsync( + buildQueryArgs( + sourceQueryData, + { + listId, + mainPropTypes: mainColumnTypes, + isInit: false, + }), + columnOrder) + .then(data => + updateEventListSuccess(listId, data), + ) + .catch((error) => { + log.error(errorCreator(errorMessages.WORKING_LIST_UPDATE_ERROR)({ error })); + return updateEventListError(listId, errorMessages.WORKING_LIST_UPDATE_ERROR); + }); +}; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/index.js new file mode 100644 index 0000000000..7a2def916c --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/index.js @@ -0,0 +1,9 @@ +// @flow +export { default as WorkingLists } from './WorkingLists.container'; +export { actionTypes } from './workingLists.actions'; +export { + initEventListEpic, + updateEventListEpic, + retrieveTemplatesEpic, + requestDeleteEventEpic, +} from './epics'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.actions.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.actions.js new file mode 100644 index 0000000000..2b9599e46d --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.actions.js @@ -0,0 +1,82 @@ +// @flow +import { methods } from '../../../../trackerOffline/trackerOfflineConfig.const'; +import { actionCreator } from '../../../../actions/actions.utils'; + +export const actionTypes = { + DATA_PRE_CLEAN: 'EventWorkingListsDataPreClean', + TEMPLATES_FETCH: 'EventWorkingListsTemplatesFetch', + TEMPLATES_FETCH_SUCCESS: 'EventWorkingListsTemplatesFetchSuccess', + TEMPLATES_FETCH_ERROR: 'EventWorkingListsTemplatesFetchError', + TEMPLATES_FETCH_CANCEL: 'EventWorkingListsTemplatesFetchCancel', + TEMPLATE_SELECT: 'EventWorkingListsTemplateSelect', + EVENT_LIST_INIT: 'EventWorkingListsEventListInit', + EVENT_LIST_INIT_SUCCESS: 'EventWorkingListsEventListInitSuccess', + EVENT_LIST_INIT_ERROR: 'EventWorkingListsEventListInitError', + EVENT_LIST_INIT_CANCEL: 'EventWorkingListsEventListInitCancel', + EVENT_LIST_UPDATE: 'EventWorkingListsEventListUpdate', + EVENT_LIST_UPDATE_SUCCESS: 'EventWorkingListsEventListUpdateSuccess', + EVENT_LIST_UPDATE_ERROR: 'EventWorkingListsEventListUpdateError', + EVENT_LIST_UPDATE_CANCEL: 'EventWorkingListsEventListUpdateCancel', + EVENT_DELETE: 'EventWorkingListsEventListEventDelete', + EVENT_DELETE_SUCCESS: 'EventWorkingListsEventListEventDeleteSuccess', + EVENT_DELETE_ERROR: 'EventWorkingListsEventListEventDeleteError', +}; + +export const batchActionTypes = { + TEMPLATES_FETCH_SUCCESS_BATCH: 'EventWorkingListTemplatesFetchSuccessBatch', // shouldn't be used eventually +}; + +export const preCleanData = + (listId: string) => actionCreator(actionTypes.DATA_PRE_CLEAN)({ listId }); + +export const fetchTemplates = + (listId: string) => actionCreator(actionTypes.TEMPLATES_FETCH)({ listId }); + +export const fetchTemplatesSuccess = (templates: Array, listId: string) => + actionCreator(actionTypes.TEMPLATES_FETCH_SUCCESS)({ templates, listId }); + +export const fetchTemplatesError = (error: string, listId: string) => + actionCreator(actionTypes.TEMPLATES_FETCH_ERROR)({ error, listId }); + +export const fetchTemplatesCancel = (listId: string) => + actionCreator(actionTypes.TEMPLATES_FETCH_CANCEL)({ listId }); + +export const selectTemplate = (templateId: string, listId: string) => + actionCreator(actionTypes.TEMPLATE_SELECT)({ templateId, listId }); + +export const initEventList = + (selectedTemplate: Object, defaultConfig: Map, listId: string) => actionCreator(actionTypes.EVENT_LIST_INIT)({ selectedTemplate, defaultConfig, listId }); + +export const initEventListSuccess = + (listId: string, data: Object) => actionCreator(actionTypes.EVENT_LIST_INIT_SUCCESS)({ ...data, listId }); + +export const initEventListError = + (listId: string, errorMessage: string) => actionCreator(actionTypes.EVENT_LIST_INIT_ERROR)({ listId, errorMessage }); + +export const initEventListCancel = + (listId: string) => actionCreator(actionTypes.EVENT_LIST_INIT_CANCEL)({ listId }); + +export const updateEventList = + (listId: string, queryArgs: Object) => actionCreator(actionTypes.EVENT_LIST_UPDATE)({ listId, queryArgs }); + +export const updateEventListSuccess = + (listId: string, data: Object) => actionCreator(actionTypes.EVENT_LIST_UPDATE_SUCCESS)({ ...data, listId }); + +export const updateEventListError = + (listId: string, errorMessage: string) => + actionCreator(actionTypes.EVENT_LIST_UPDATE_ERROR)({ listId, errorMessage }); + +export const updateEventListCancel = + (listId: string) => actionCreator(actionTypes.EVENT_LIST_UPDATE_CANCEL)({ listId }); + +export const deleteEvent = + (eventId: string, listId: string) => actionCreator(actionTypes.EVENT_DELETE)({ listId }, { + offline: { + effect: { + url: `events/${eventId}`, + method: methods.DELETE, + }, + commit: { type: actionTypes.EVENT_DELETE_SUCCESS }, + rollback: { type: actionTypes.EVENT_DELETE_ERROR }, + }, + }); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.context.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.context.js new file mode 100644 index 0000000000..db03afcfa7 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.context.js @@ -0,0 +1,6 @@ +// @flow +import { createContext } from 'react'; + +export const ManagerContext = createContext(); +export const EventListConfigContext = createContext(); +export const EventListLoaderContext = createContext(); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.types.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.types.js new file mode 100644 index 0000000000..5c20ed9da6 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingLists/workingLists.types.js @@ -0,0 +1,91 @@ +// @flow +export type WorkingListTemplate = { + id: string, + isDefault?: ?boolean, + name: string, + filters: Object, +} + +export type ApiDataFilter = { + dataItem: string, + ge?: any, + le?: any, + in?: any, + like?: any, + eq?: any, + period?: any, +} + +export type ApiDataFilterCommon = {| + dataItem: string, +|} + +export type ApiDataFilterText = { + like: string, +} + +export type ApiDataFilterNumeric = { + ge?: ?string, + le?: ?string, +} + +export type ApiDataFilterBoolean = { + in: Array, +} + +export type ApiDataFilterTrueOnly = { + eq: string, +} + +export type ApiDataFilterDateAbsolute = {| + startDate?: ?string, + endDate?: ?string, +|} + +export type ApiDataFilterDateRelative = {| + period: string, +|} + +export type ApiDataFilterOptionSet = {| + in: Array, +|}; + +export type ApiDataFilterDate = ApiDataFilterDateAbsolute | ApiDataFilterDateRelative; + +export type ApiEventQueryCriteria = { + dataFilters?: ?Array, + order?: ?string, + eventDate?: ?Object, + status?: ?string, + displayColumnOrder?: ?Array, + assignedUserMode?: 'CURRENT' | 'PROVIDED' | 'NONE' | 'ANY', + assignedUsers?: Array, +} + +export type ListConfig = { + filters: { [id: string]: any }, + sortById: string, + sortByDirection: string, + currentPage: number, + rowsPerPage: number, + columnOrder: Array, +} + +export type EventFilter = { + id: string, + name: string, + eventQueryCriteria: ApiEventQueryCriteria, +} + +export type CommonQueryData = {| + programId: string, + orgUnitId: string, + categories: ?Object, +|} + +export type ColumnConfig = { + id: string, + visible: boolean, + isMainProperty?: ?boolean, + apiName?: ?string, +}; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsHeaderHOC/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsHeaderHOC/index.js new file mode 100644 index 0000000000..4b93b6c056 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsHeaderHOC/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as withWorkingListsHeader } from './workingListsHeaderHOC'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/ListHeaderWrapper/withListHeaderWrapper.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsHeaderHOC/workingListsHeaderHOC.js similarity index 68% rename from src/core_modules/capture-core/components/Pages/MainPage/ListHeaderWrapper/withListHeaderWrapper.js rename to src/core_modules/capture-core/components/Pages/MainPage/WorkingListsHeaderHOC/workingListsHeaderHOC.js index 788155cf9d..9d5748ac0d 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/ListHeaderWrapper/withListHeaderWrapper.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsHeaderHOC/workingListsHeaderHOC.js @@ -1,4 +1,5 @@ // @flow +import i18n from '@dhis2/d2-i18n'; import * as React from 'react'; import { withStyles } from '@material-ui/core/styles'; import { darken, fade, lighten } from '@material-ui/core/styles/colorManipulator'; @@ -16,26 +17,34 @@ const getStyles = (theme: Theme) => ({ listContainer: { padding: theme.typography.pxToRem(24), }, + title: { + ...theme.typography.title, + }, }); type Props = { - header: React.Node, classes: { headerContainer: string, listContainer: string, + title: string, }, }; -export default () => (InnerComponent: React.ComponentType) => - withStyles(getStyles)(class withListHeaderWrapper extends React.Component { +const withWorkingListsHeader = () => (InnerComponent: React.ComponentType) => + // $FlowFixMe + withStyles(getStyles)(class WorkingListsHeaderHOC extends React.Component { render() { - const { header, classes, ...passOnProps } = this.props; + const { classes, ...passOnProps } = this.props; return (
- {header} + + {i18n.t('Registered events')} +
(InnerComponent: React.ComponentType) => ); } }); + +export default withWorkingListsHeader; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/WorkingListsOnHoldWrapper.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/WorkingListsOnHoldWrapper.component.js new file mode 100644 index 0000000000..2221fd53c3 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/WorkingListsOnHoldWrapper.component.js @@ -0,0 +1,21 @@ +// @flow +import * as React from 'react'; +import { withLoadingIndicator } from '../../../../HOC'; +import { WorkingListsSetup } from '../WorkingListsSetup'; + +const WorkingListsSetupWithLoadingIndicator = withLoadingIndicator()(WorkingListsSetup); + +type Props = { + onHold: boolean, +}; + +const WorkingListsOnHoldWrapper = (props: Props) => { + const { onHold } = props; + return ( + + ); +}; + +export default WorkingListsOnHoldWrapper; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/WorkingListsOnHoldWrapper.container.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/WorkingListsOnHoldWrapper.container.js new file mode 100644 index 0000000000..33ef77cde6 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/WorkingListsOnHoldWrapper.container.js @@ -0,0 +1,10 @@ +// @flow +import { connect } from 'react-redux'; +import WorkingListsOnHoldWrapper from './WorkingListsOnHoldWrapper.component'; + +const mapStateToProps = (state: ReduxState) => ({ + onHold: !!state.offline.outbox && state.offline.outbox.length > 0, +}); + +// $FlowSuppress +export default connect(mapStateToProps)(WorkingListsOnHoldWrapper); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/index.js new file mode 100644 index 0000000000..93073b069c --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsOnHoldWrapper/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as WorkingListsOnHoldWrapper } from './WorkingListsOnHoldWrapper.container'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsManualSetup.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsManualSetup.component.js new file mode 100644 index 0000000000..8652a892b5 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsManualSetup.component.js @@ -0,0 +1,11 @@ +// @flow +import * as React from 'react'; +import WorkingListsSetup from './WorkingListsSetup.container'; + +const WorkingListsManualSetup = () => ( + +); + +export default WorkingListsManualSetup; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsSetup.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsSetup.component.js new file mode 100644 index 0000000000..eed456743d --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsSetup.component.js @@ -0,0 +1,58 @@ +// @flow +import * as React from 'react'; +import { getDefaultConfig } from './defaultConfiguration'; +import { shouldSkipReload } from './skipReloadCalculator'; +import { WorkingLists } from '../WorkingLists'; + +type PassOnProps = {| + listId: string, +|}; + +type Props = {| + programId: string, + orgUnitId: string, + categories: Object, + lastTransaction: number, + listContext: ?Object, + ...PassOnProps, +|}; + +const WorkingListsSetup = (props: Props) => { + const { + programId, + orgUnitId, + categories, + lastTransaction, + listContext, + ...passOnProps + } = props; + + const defaultConfig = React.useMemo(() => getDefaultConfig(programId), [ + programId, + ]); + + const skipReload = React.useMemo(() => + shouldSkipReload(programId, orgUnitId, categories, lastTransaction, listContext), [ // eslint-disable-line react-hooks/exhaustive-deps + programId, + orgUnitId, + categories, + lastTransaction, + ]); + + const workingListsKey = React.useMemo(() => { + const categoriesString = categories ? Object.keys(categories).map(key => categories[key]).join('_') : ''; + return `${programId}_${orgUnitId}_${categoriesString}`; + }, [programId, orgUnitId, categories]); + + return ( + + ); +}; + +export default WorkingListsSetup; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsSetup.container.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsSetup.container.js new file mode 100644 index 0000000000..74e2692d0d --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/WorkingListsSetup.container.js @@ -0,0 +1,32 @@ +// @flow +import { connect } from 'react-redux'; +import WorkingListsSetup from './WorkingListsSetup.component'; + +type OwnProps = {| + listId: string, +|}; + +type StateProps = {| + programId: string, + orgUnitId: string, + categories: Object, + lastTransaction: number, + listContext: ?Object, +|}; + +type Props = {| + ...OwnProps, + ...StateProps, +|}; + +type MapStateToPropsFactory = (ReduxState, OwnProps) => StateProps; +const mapStateToProps: MapStateToPropsFactory = (state: ReduxState, props: OwnProps) => ({ + programId: state.currentSelections.programId, + orgUnitId: state.currentSelections.orgUnitId, + categories: state.currentSelections.categories, + lastTransaction: state.offline.lastTransaction, + listContext: state.workingListsContext[props.listId], +}); + +export default connect( + mapStateToProps, () => ({}))(WorkingListsSetup); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/defaultConfiguration/defaultConfigGetter.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/defaultConfiguration/defaultConfigGetter.js new file mode 100644 index 0000000000..1027b364e7 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/defaultConfiguration/defaultConfigGetter.js @@ -0,0 +1,67 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { canViewOtherUsers } from '../../../../../d2'; +import { + ProgramStage, + dataElementTypes as elementTypeKeys, + getEventProgramThrowIfNotFound, +} from '../../../../../metaData'; +import mainPropertyNames from '../../../../../events/mainPropertyNames.const'; + +const getDefaultMainConfig = (stage: ProgramStage) => { + const baseFields = [ + [mainPropertyNames.EVENT_DATE, { + id: mainPropertyNames.EVENT_DATE, + visible: true, + isMainProperty: true, + type: elementTypeKeys.DATE, + }], + [mainPropertyNames.EVENT_STATUS, { + id: mainPropertyNames.EVENT_STATUS, + header: 'Status', + visible: true, + isMainProperty: true, + type: elementTypeKeys.TEXT, + singleSelect: true, + options: [ + { text: i18n.t('Active'), value: 'ACTIVE' }, + { text: i18n.t('Completed'), value: 'COMPLETED' }, + ], + }], + ]; + + const extraFields = []; + if (stage.enableUserAssignment && canViewOtherUsers()) { + const assigneeField = { + id: mainPropertyNames.ASSIGNEE, + type: 'ASSIGNEE', + apiName: 'assignedUser', + header: 'Assigned to', + visible: true, + isMainProperty: true, + }; + extraFields.push([mainPropertyNames.ASSIGNEE, assigneeField]); + } + + return [...baseFields, ...extraFields]; +}; + +const getMetaDataConfig = (stage: ProgramStage): Array> => + stage + .stageForm + .getElements() + .map(element => ([ + element.id, { + id: element.id, + visible: element.displayInReports, + }]), + ); + +export const getDefaultConfig = (programId: string): Map => { + const stage = getEventProgramThrowIfNotFound(programId).stage; + // $FlowFixMe + return new Map([ + ...getDefaultMainConfig(stage), + ...getMetaDataConfig(stage), + ]); +}; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/defaultConfiguration/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/defaultConfiguration/index.js new file mode 100644 index 0000000000..40b647c99a --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/defaultConfiguration/index.js @@ -0,0 +1,2 @@ +// @flow +export { getDefaultConfig } from './defaultConfigGetter'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/index.js new file mode 100644 index 0000000000..c35b18effb --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/index.js @@ -0,0 +1,3 @@ +// @flow +export { default as WorkingListsSetup } from './WorkingListsManualSetup.component'; +export { actionTypes } from './workingListsSetup.actions'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/skipReloadCalculator/index.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/skipReloadCalculator/index.js new file mode 100644 index 0000000000..a758be2aae --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/skipReloadCalculator/index.js @@ -0,0 +1,2 @@ +// @flow +export { shouldSkipReload } from './skipReloadCalculator'; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/skipReloadCalculator/skipReloadCalculator.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/skipReloadCalculator/skipReloadCalculator.js new file mode 100644 index 0000000000..660bd58de9 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/skipReloadCalculator/skipReloadCalculator.js @@ -0,0 +1,36 @@ +// @flow +import { moment } from 'capture-core-utils/moment'; +import isSelectionsEqual from '../../../../App/isSelectionsEqual'; + +export function shouldSkipReload( + programId: string, + orgUnitId: string, + categories: string, + lastTransaction: number, + listContext: ?Object, +) { + if (!listContext) { + return false; + } + const currentSelections = { + programId, + orgUnitId, + categories, + }; + + const { + lastTransaction: contextLastTransaction, + timestamp: contextTimestamp, + ...listSelections + } = listContext; + + if (lastTransaction > contextLastTransaction) { + return false; + } + + if (!isSelectionsEqual(currentSelections, listSelections)) { + return false; + } + + return moment().diff(moment(contextTimestamp), 'minutes') < 5; +} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/workingListsSetup.actions.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/workingListsSetup.actions.js new file mode 100644 index 0000000000..55245bad80 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/workingListsSetup.actions.js @@ -0,0 +1,8 @@ +// @flow +import { actionCreator } from '../../../../actions/actions.utils'; + +export const actionTypes = { + SKIP_RELOAD_RESET: 'MainPageEventsWorkingListsSkipReloadReset', +}; + +export const resetSkipReload = () => actionCreator(actionTypes.SKIP_RELOAD_RESET)(); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/workingListsSetup.selectors.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/workingListsSetup.selectors.js new file mode 100644 index 0000000000..05c7d789af --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsSetup/workingListsSetup.selectors.js @@ -0,0 +1,4 @@ +// @flow +import { createSelector } from 'reselect'; + +const listContextSelector = (state) => state.workingLists.eventList \ No newline at end of file diff --git a/src/core_modules/capture-core/components/Pages/MainPage/mainPage.epics.js b/src/core_modules/capture-core/components/Pages/MainPage/mainPage.epics.js deleted file mode 100644 index 788fa8ac47..0000000000 --- a/src/core_modules/capture-core/components/Pages/MainPage/mainPage.epics.js +++ /dev/null @@ -1,44 +0,0 @@ -// @flow -import { ActionsObservable } from 'redux-observable'; -import { updateEventListAfterSaveOrUpdateSingleEvent } from './mainPage.actions'; -import { dataEntryActionTypes as newEventDataEntryActionTypes } from '../NewEvent'; -import { actionTypes as editEventDataEntryActionTypes } from '../EditEvent/DataEntry/editEventDataEntry.actions'; -import { actionTypes as viewEventEditEventDataEntryActionTypes } from '../ViewEvent/EventDetailsSection/EditEventDataEntry/editEventDataEntry.actions'; -import { assigneeSectionActionTypes } from '../ViewEvent/RightColumn/AssigneeSection'; -import isSelectionsEqual from '../../App/isSelectionsEqual'; - - -const selectionsFilter = (state: ReduxState, action: Object) => { - const isCurrentSelectionsComplete = state.currentSelections.complete; - if (!isCurrentSelectionsComplete) { - return false; - } - - const savedSelectons = action.meta.selections; - const currentSelections = state.currentSelections; - return isSelectionsEqual(savedSelectons, currentSelections); -}; - -export const updateEventListAfterSaveOrUpdateEventEpic = (action$: ActionsObservable, store: ReduxStore) => - action$ - .ofType( - newEventDataEntryActionTypes.NEW_EVENT_SAVED_AFTER_RETURNED_TO_MAIN_PAGE, - editEventDataEntryActionTypes.EVENT_UPDATED_AFTER_RETURN_TO_MAIN_PAGE, - ) - .filter((action) => { - const state = store.getState(); - return selectionsFilter(state, action); - }) - .map(() => updateEventListAfterSaveOrUpdateSingleEvent()); - -export const updateEventListAfterUpdateEventEpic = (action$: ActionsObservable, store: ReduxStore) => - action$ - .ofType( - viewEventEditEventDataEntryActionTypes.EDIT_EVENT_DATA_ENTRY_SAVED, - assigneeSectionActionTypes.VIEW_EVENT_ASSIGNEE_SAVE_COMPLETED, - ).filter((action) => { - const state = store.getState(); - const currentPageIsMainPage = state.app.page === null; - return currentPageIsMainPage && selectionsFilter(state, action); - }) - .map(() => updateEventListAfterSaveOrUpdateSingleEvent()); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/mainSelections.actions.js b/src/core_modules/capture-core/components/Pages/MainPage/mainSelections.actions.js index a5326cc6cd..5060e01dfc 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/mainSelections.actions.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/mainSelections.actions.js @@ -4,9 +4,6 @@ import { actionCreator } from 'capture-core/actions/actions.utils'; export const actionTypes = { UPDATE_MAIN_SELECTIONS: 'UpdateMainSelections', MAIN_SELECTIONS_COMPLETED: 'MainSelectionsCompleted', - WORKING_LIST_DATA_RETRIEVED: 'WorkingListDataRetrieved', - WORKING_LIST_DATA_RETRIEVAL_FAILED: 'WorkingListDataRetrievalFailed', - WORKING_LIST_DATA_RETRIEVAL_CANCELED: 'WorkingListDataRetrievalCanceled', ORG_UNIT_DATA_RETRIVED: 'OrgUnitDataRetrived', UPDATE_MAIN_SELECTIONS_FROM_URL: 'UpdateMainSelectionsFromUrl', SET_ORG_UNIT_BASED_ON_URL: 'SetOrgUnitBasedOnUrl', @@ -23,15 +20,6 @@ export const updateMainSelections = export const mainSelectionCompleted = () => actionCreator(actionTypes.MAIN_SELECTIONS_COMPLETED)(); -export const workingListInitialDataRetrieved = - (listId: string, data: Object) => actionCreator(actionTypes.WORKING_LIST_DATA_RETRIEVED)({ ...data, listId }); - -export const workingListInitialRetrievalFailed = - (listId: string, errorMessage: string) => actionCreator(actionTypes.WORKING_LIST_DATA_RETRIEVAL_FAILED)({ listId, errorMessage }); - -export const workingListDataRetrievalCanceled = - () => actionCreator(actionTypes.WORKING_LIST_DATA_RETRIEVAL_CANCELED)(); - export const orgUnitDataRetrived = (orgUnit: Object) => actionCreator(actionTypes.ORG_UNIT_DATA_RETRIVED)(orgUnit); diff --git a/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/cancelNewSingleEvent.epics.js b/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/cancelNewSingleEvent.epics.js index d709a9c59f..6b01916a9b 100644 --- a/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/cancelNewSingleEvent.epics.js +++ b/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/cancelNewSingleEvent.epics.js @@ -18,7 +18,7 @@ export const cancelNewEventEpic = (action$: InputObservable, store: ReduxStore) return cancelNewEventNoWorkingListUpdateNeeded(); } - const listId = state.workingListConfigSelector.eventMainPage && state.workingListConfigSelector.eventMainPage.currentListId; + const listId = state.workingListsTemplates.eventList && state.workingListsTemplates.eventList.currentListId; const listSelections = listId && state.workingListsContext[listId]; if (!listSelections) { return cancelNewEventInitializeWorkingLists(); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/defaultColumnConfiguration/getDefaultConfigs.js b/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/defaultColumnConfiguration/defaultConfigGetter.js similarity index 89% rename from src/core_modules/capture-core/components/Pages/MainPage/EventsList/defaultColumnConfiguration/getDefaultConfigs.js rename to src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/defaultColumnConfiguration/defaultConfigGetter.js index b86545cffe..75cb0af440 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventsList/defaultColumnConfiguration/getDefaultConfigs.js +++ b/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/defaultColumnConfiguration/defaultConfigGetter.js @@ -1,12 +1,12 @@ // @flow import i18n from '@dhis2/d2-i18n'; -import { canViewOtherUsers } from '../../../../../d2'; +import { canViewOtherUsers } from '../../../../../../d2'; import { RenderFoundation, ProgramStage, dataElementTypes as elementTypeKeys, -} from '../../../../../metaData'; -import mainPropertyNames from '../../../../../events/mainPropertyNames.const'; +} from '../../../../../../metaData'; +import mainPropertyNames from '../../../../../../events/mainPropertyNames.const'; export const getDefaultMainConfig = (stage: ProgramStage) => { const baseFields = [ @@ -53,4 +53,3 @@ export const getMetaDataConfig = (stage: RenderFoundation): Array<{id: string, v id: element.id, visible: element.displayInReports, })); - diff --git a/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/defaultColumnConfiguration/index.js b/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/defaultColumnConfiguration/index.js new file mode 100644 index 0000000000..cce8f57465 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/defaultColumnConfiguration/index.js @@ -0,0 +1,2 @@ +// @flow +export { getDefaultMainConfig, getMetaDataConfig } from './defaultConfigGetter'; diff --git a/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/newEventDataEntry.epics.js b/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/newEventDataEntry.epics.js index 1be556c3cb..01ff0af346 100644 --- a/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/newEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/Pages/NewEvent/DataEntry/epics/newEventDataEntry.epics.js @@ -1,6 +1,7 @@ // @flow import log from 'loglevel'; import { batchActions } from 'redux-batched-actions'; +import { errorCreator } from 'capture-core-utils'; import { rulesExecutedPostUpdateField } from '../../../../DataEntry/actions/dataEntry.actions'; import { actionTypes as editEventSelectorActionTypes, @@ -36,8 +37,10 @@ import { } from '../../../../../rulesEngineActionsCreator/inputHelpers'; import getProgramAndStageFromProgramId from '../../../../../metaData/helpers/EventProgram/getProgramAndStageFromProgramId'; -import { errorCreator } from 'capture-core-utils'; -import { getDefaultMainConfig as getDefaultMainColumnConfig, getMetaDataConfig as getColumnMetaDataConfig } from '../../../MainPage/EventsList/defaultColumnConfiguration'; +import { + getDefaultMainConfig as getDefaultMainColumnConfig, + getMetaDataConfig as getColumnMetaDataConfig, +} from './defaultColumnConfiguration'; import { resetList, } from '../../../../List/list.actions'; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EditEventDataEntry/index.js b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EditEventDataEntry/index.js new file mode 100644 index 0000000000..312b174b35 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EditEventDataEntry/index.js @@ -0,0 +1,2 @@ +// @flow +export { actionTypes } from './editEventDataEntry.actions'; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/epics/viewEvent.epics.js b/src/core_modules/capture-core/components/Pages/ViewEvent/epics/viewEvent.epics.js index 7fa9af0439..c4d5ad5569 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/epics/viewEvent.epics.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/epics/viewEvent.epics.js @@ -115,7 +115,7 @@ export const backToMainPageEpic = (action$: InputObservable, store: ReduxStore) if (!state.offline.online) { return noWorkingListUpdateNeededOnBackToMainPage(); } - const listId = state.workingListConfigSelector.eventMainPage && state.workingListConfigSelector.eventMainPage.currentListId; + const listId = state.workingListsTemplates.eventList && state.workingListsTemplates.eventList.currentListId; const listSelections = listId && state.workingListsContext[listId]; if (!listSelections) { return initializeWorkingListsOnBackToMainPage(); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/index.js b/src/core_modules/capture-core/components/Pages/ViewEvent/index.js new file mode 100644 index 0000000000..cb724882a0 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/index.js @@ -0,0 +1,3 @@ +// @flow +export { actionTypes as editEventDataEntryActionTypes } from './EventDetailsSection/EditEventDataEntry'; +export { assigneeSectionActionTypes } from '../ViewEvent/RightColumn/AssigneeSection'; diff --git a/src/core_modules/capture-core/flow/typeDeclarations.js b/src/core_modules/capture-core/flow/typeDeclarations.js index 315475b171..8ffb475d23 100644 --- a/src/core_modules/capture-core/flow/typeDeclarations.js +++ b/src/core_modules/capture-core/flow/typeDeclarations.js @@ -189,17 +189,3 @@ declare type OfflineEffect = { data: any, method: $Values, }; - -// Event list -declare type AbsoluteDateFilterData = { - type: 'ABSOLUTE', - ge?: string, - le?: string, -}; - -declare type RelativeDateFilterData = { - type: 'RELATIVE', - period: string, -}; - -declare type DateFilterData = AbsoluteDateFilterData | RelativeDateFilterData; \ No newline at end of file diff --git a/src/core_modules/capture-core/metaData/DataElement/elementTypes.js b/src/core_modules/capture-core/metaData/DataElement/elementTypes.js index be22e7d3a4..4cebec96e9 100644 --- a/src/core_modules/capture-core/metaData/DataElement/elementTypes.js +++ b/src/core_modules/capture-core/metaData/DataElement/elementTypes.js @@ -1,7 +1,7 @@ // @flow const unknownTypeCode = 'UNKNOWN'; -const elementTypeCodes = [ +const elementTypeCodes: Array = [ 'TEXT', 'LONG_TEXT', 'NUMBER', @@ -37,10 +37,11 @@ const elementTypeCodes = [ unknownTypeCode, ]; -const elementTypeKeys = elementTypeCodes.reduce((accKeys, code) => { - accKeys[code] = code; - return accKeys; -}, {}); +const elementTypeKeys = + elementTypeCodes.reduce((accKeys, code) => { + accKeys[code] = code; + return accKeys; + }, {}); export default elementTypeKeys; diff --git a/src/core_modules/capture-core/reducers/descriptions/events.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/events.reducerDescription.js index 86924b602b..aa6fe31c9a 100644 --- a/src/core_modules/capture-core/reducers/descriptions/events.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/events.reducerDescription.js @@ -2,11 +2,10 @@ import { createReducerDescription } from '../../trackerRedux/trackerReducer'; import { actionTypes as dataEntryActionTypes } from '../../components/DataEntry/actions/dataEntry.actions'; -import { actionTypes as mainSelectionsActionTypes } from '../../components/Pages/MainPage/mainSelections.actions'; import { actionTypes as enrollmentActionTypes } from '../../actions/__TEMP__/enrollment.actions'; -import { actionTypes as eventListActionTypes } from '../../components/Pages/MainPage/EventsList/eventsList.actions'; import { actionTypes as editEventActionTypes } from '../../components/Pages/EditEvent/editEvent.actions'; import { actionTypes as viewEventActionTypes } from '../../components/Pages/ViewEvent/viewEvent.actions'; +import { actionTypes as workingListsActionTypes } from '../../components/Pages/MainPage/WorkingLists'; const getFromWorkingListRetrieval = (eventContainers, containerProperty) => { if (!eventContainers || eventContainers.length === 0) { @@ -22,13 +21,13 @@ const getFromWorkingListRetrieval = (eventContainers, containerProperty) => { }; export const eventsDesc = createReducerDescription({ - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_INIT_SUCCESS]: (state, action) => { const eventContainers = action.payload.eventContainers; const newEventsById = getFromWorkingListRetrieval(eventContainers, 'event'); const newState = { ...newEventsById }; return newState; }, - [eventListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_UPDATE_SUCCESS]: (state, action) => { const eventContainers = action.payload.eventContainers; const newEventsById = getFromWorkingListRetrieval(eventContainers, 'event'); const newState = { ...newEventsById }; @@ -96,13 +95,13 @@ export const eventsDesc = createReducerDescription({ }, 'events', {}); export const eventsValuesDesc = createReducerDescription({ - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_INIT_SUCCESS]: (state, action) => { const eventContainers = action.payload.eventContainers; const newEventsValuesById = getFromWorkingListRetrieval(eventContainers, 'values'); const newState = { ...newEventsValuesById }; return newState; }, - [eventListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_UPDATE_SUCCESS]: (state, action) => { const eventContainers = action.payload.eventContainers; const newEventsValuesById = getFromWorkingListRetrieval(eventContainers, 'values'); const newState = { ...newEventsValuesById }; diff --git a/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js b/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js index 265cd87a5d..597469a059 100644 --- a/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js +++ b/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js @@ -9,20 +9,19 @@ import { createReducerDescription } from '../../trackerRedux/trackerReducer'; import { actionTypes as feedbackActionTypes } from '../../components/FeedbackBar/actions/feedback.actions'; import { actionTypes as dataEntryActionTypes } from '../../components/DataEntry/actions/dataEntry.actions'; import { actionTypes as enrollmentActionTypes } from '../../actions/__TEMP__/enrollment.actions'; -import { actionTypes as mainSelectionsActionTypes } from '../../components/Pages/MainPage/mainSelections.actions'; import { dataEntryActionTypes as newEventDataEntryActionTypes, } from '../../components/Pages/NewEvent'; import { actionTypes as editEventDataEntryActionTypes, } from '../../components/Pages/EditEvent/DataEntry/editEventDataEntry.actions'; -import { actionTypes as eventsListActionTypes } from '../../components/Pages/MainPage/EventsList/eventsList.actions'; import { orgUnitListActionTypes } from '../../components/QuickSelector'; import { actionTypes as viewEventNewRelationshipActionTypes, } from '../../components/Pages/ViewEvent/Relationship/ViewEventRelationships.actions'; import { asyncHandlerActionTypes } from '../../components/D2Form'; import { registrationSectionActionTypes } from '../../components/Pages/NewRelationship/RegisterTei'; +import { actionTypes as workingListActionTypes } from '../../components/Pages/MainPage/WorkingLists'; import type { Updaters } from '../../trackerRedux/trackerReducer'; function addErrorFeedback(state: ReduxState, message: string, action?: ?React.Node) { @@ -54,7 +53,7 @@ export const getFeedbackDesc = (appUpdaters: Updaters) => createReducerDescripti addErrorFeedback(state, action.payload.error, action.payload.action), [enrollmentActionTypes.ENROLLMENT_LOAD_FAILED]: (state, action) => addErrorFeedback(state, action.payload), - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVAL_FAILED]: (state, action) => + [workingListActionTypes.EVENT_LIST_INIT_ERROR]: (state, action) => addErrorFeedback(state, action.payload.errorMessage), [newEventDataEntryActionTypes.SAVE_FAILED_FOR_NEW_EVENT_AFTER_RETURNED_TO_MAIN_PAGE]: (state, action) => { const error = action.payload; @@ -78,10 +77,14 @@ export const getFeedbackDesc = (appUpdaters: Updaters) => createReducerDescripti ]; return newState; }, - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVAL_FAILED]: (state, action) => [ + [workingListActionTypes.EVENT_LIST_UPDATE_ERROR]: (state, action) => [ ...state, getErrorFeedback(action.payload.errorMessage), ], + [workingListActionTypes.EVENT_DELETE_ERROR]: state => [ + ...state, + getErrorFeedback(i18n.t('Could not delete event')), + ], [asyncHandlerActionTypes.ASYNC_UPDATE_FIELD_FAILED]: (state, action) => addErrorFeedback(state, action.payload.message), [newEventDataEntryActionTypes.SAVE_FAILED_FOR_NEW_EVENT_ADD_ANOTHER]: (state, action) => { diff --git a/src/core_modules/capture-core/reducers/descriptions/mainPage.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/mainPage.reducerDescription.js index a5e8f5b21e..789cdb9138 100644 --- a/src/core_modules/capture-core/reducers/descriptions/mainPage.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/mainPage.reducerDescription.js @@ -1,7 +1,9 @@ // @flow import { createReducerDescription } from '../../trackerRedux/trackerReducer'; import { actionTypes as selectionsActionTypes } from '../../components/Pages/MainPage/mainSelections.actions'; -import { dataEntryUrlActionTypes as newEventPageUrlActionTypes } from '../../components/Pages/NewEvent'; +import { + dataEntryUrlActionTypes as newEventPageUrlActionTypes, +} from '../../components/Pages/NewEvent'; import { actionTypes as editEventPageUrlActionTypes } from '../../components/Pages/EditEvent/editEvent.actions'; import { actionTypes as viewEventPageUrlActionTypes } from '../../components/Pages/ViewEvent/viewEvent.actions'; diff --git a/src/core_modules/capture-core/reducers/descriptions/workingLists/appliedFilters/appliedFilters.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/workingLists/appliedFilters/appliedFilters.reducerDescription.js deleted file mode 100644 index 0dfa525629..0000000000 --- a/src/core_modules/capture-core/reducers/descriptions/workingLists/appliedFilters/appliedFilters.reducerDescription.js +++ /dev/null @@ -1,71 +0,0 @@ -// @flow -import { createReducerDescription } from '../../../../trackerRedux/trackerReducer'; -import { - actionTypes as eventsListActionTypes, -} from '../../../../components/Pages/MainPage/EventsList/eventsList.actions'; -import { actionTypes as mainSelectionsActionTypes } from '../../../../components/Pages/MainPage/mainSelections.actions'; -import { - actionTypes as filterSelectorActionTypes, -} from '../../../../components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.actions'; - -export const workingListsAppliedFiltersDesc = createReducerDescription({ - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { - const newState = { ...state }; - const { listId, config } = action.payload; - const filters = config && config.filters; - const appliedFilters = filters ? - filters - .reduce((acc, filter) => ({ - ...acc, - [filter.id]: filter.appliedText, - }), {}) : - {}; - newState[listId] = appliedFilters; - return newState; - }, - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVED]: (state, action) => { - const newState = { ...state }; - const listId = action.payload.listId; - const next = newState[listId].next; - newState[listId] = { - ...newState[listId], - ...next, - next: {}, - }; - return newState; - }, - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVAL_FAILED]: (state, action) => { - const newState = { ...state }; - const listId = action.payload.listId; - newState[listId] = { - ...newState[listId], - next: {}, - }; - return newState; - }, - [filterSelectorActionTypes.SET_FILTER]: (state, action) => { - const newState = { ...state }; - const payload = action.payload; - const listId = payload.listId; - newState[listId] = { - ...newState[listId], - next: { - ...(newState[listId] ? newState[listId].next : null), - [payload.itemId]: payload.appliedText, - }, - }; - return newState; - }, - [filterSelectorActionTypes.CLEAR_FILTER]: (state, action) => { - const newState = { ...state }; - const { itemId, listId } = action.payload; - newState[listId] = { - ...newState[listId], - next: { - ...(newState[listId] ? newState[listId].next : null), - [itemId]: null, - }, - }; - return newState; - }, -}, 'workingListsAppliedFilters'); diff --git a/src/core_modules/capture-core/reducers/descriptions/workingLists/filtersEdit/filtersEdit.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/workingLists/filtersEdit/filtersEdit.reducerDescription.js deleted file mode 100644 index eba1a743fe..0000000000 --- a/src/core_modules/capture-core/reducers/descriptions/workingLists/filtersEdit/filtersEdit.reducerDescription.js +++ /dev/null @@ -1,121 +0,0 @@ -// @flow -import { createReducerDescription } from '../../../../trackerRedux/trackerReducer'; -import { - actionTypes as filterSelectorActionTypes, -} from '../../../../components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.actions'; -import { - actionTypes as quickSelectorActionTypes, -} from '../../../../components/QuickSelector/actions/QuickSelector.actions'; -import { - actionTypes as mainSelectionsActionTypes, -} from '../../../../components/Pages/MainPage/mainSelections.actions'; -import { - actionTypes as editEventSelectorActionTypes, -} from '../../../../components/Pages/EditEvent/editEvent.actions'; -import { - actionTypes as viewEventActionTypes, -} from '../../../../components/Pages/ViewEvent/viewEvent.actions'; - -const updateFiltersEditOnUrlUpdate = (state, action) => { - const payload = action.payload; - const nextProgramId = payload.nextProps.programId; - const prevProgramId = payload.prevProps.programId; - if (nextProgramId !== prevProgramId) { - return { - ...state, - main: {}, - }; - } - return state; -}; - -export const workingListFiltersEditDesc = createReducerDescription({ - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { - const newState = { ...state }; - const { listId, config } = action.payload; - const filters = config && config.filters; - const filtersEdit = filters ? - filters - .reduce((acc, filter) => ({ - ...acc, - [filter.id]: filter.value, - }), {}) : - {}; - newState[listId] = filtersEdit; - return newState; - }, - [filterSelectorActionTypes.EDIT_CONTENTS]: (state, action) => { - const { itemId, listId, value } = action.payload; - const newState = { ...state }; - newState[listId] = { - ...newState[listId], - next: { - ...(newState[listId] ? newState[listId].next : null), - [itemId]: value, - }, - }; - return newState; - }, - [filterSelectorActionTypes.CLEAR_FILTER]: (state, action) => { - const { itemId, listId } = action.payload; - const newState = { ...state }; - newState[listId] = { - ...newState[listId], - [itemId]: null, - next: {}, - }; - return newState; - }, - [filterSelectorActionTypes.SET_FILTER]: (state, action) => { - const newState = { ...state }; - const listId = action.payload.listId; - newState[listId] = { - ...newState[listId], - ...(newState[listId] ? newState[listId].next : null), - next: {}, - }; - return newState; - }, - [filterSelectorActionTypes.REVERT_FILTER]: (state, action) => { - const newState = { ...state }; - const listId = action.payload.listId; - newState[listId] = { - ...newState[listId], - next: {}, - }; - return newState; - }, - // Reason about this!! - [quickSelectorActionTypes.RESET_PROGRAM_ID_BASE]: state => ({ - ...state, - main: {}, - }), - // Reason about this!! - [mainSelectionsActionTypes.UPDATE_MAIN_SELECTIONS_FROM_URL]: updateFiltersEditOnUrlUpdate, - // Reason about this!! - [editEventSelectorActionTypes.EVENT_FROM_URL_RETRIEVED]: (state, action) => { - const payload = action.payload; - const nextProgramId = payload.eventContainer.event.programId; - const prevProgramId = payload.prevProgramId; - if (nextProgramId !== prevProgramId) { - return { - ...state, - main: {}, - }; - } - return state; - }, - // Reason about this!! - [viewEventActionTypes.EVENT_FROM_URL_RETRIEVED]: (state, action) => { - const payload = action.payload; - const nextProgramId = payload.eventContainer.event.programId; - const prevProgramId = payload.prevProgramId; - if (nextProgramId !== prevProgramId) { - return { - ...state, - main: {}, - }; - } - return state; - }, -}, 'workingListFiltersEdit'); diff --git a/src/core_modules/capture-core/reducers/descriptions/workingLists/index.js b/src/core_modules/capture-core/reducers/descriptions/workingLists/index.js index 52f1a79c37..605c2701d8 100644 --- a/src/core_modules/capture-core/reducers/descriptions/workingLists/index.js +++ b/src/core_modules/capture-core/reducers/descriptions/workingLists/index.js @@ -1,9 +1,7 @@ // @flow export { workingListsMetaDesc } from './meta/meta.reducerDescription'; -export { workingListsAppliedFiltersDesc } from './appliedFilters/appliedFilters.reducerDescription'; -export { workingListFiltersEditDesc } from './filtersEdit/filtersEdit.reducerDescription'; export { - workingListConfigSelectorDesc, + workingListsTemplatesDesc, workingListsColumnsOrderDesc, workingListsContextDesc, workingListsDesc, diff --git a/src/core_modules/capture-core/reducers/descriptions/workingLists/meta/meta.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/workingLists/meta/meta.reducerDescription.js index 15ea5baa7a..1a6a7cdef1 100644 --- a/src/core_modules/capture-core/reducers/descriptions/workingLists/meta/meta.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/workingLists/meta/meta.reducerDescription.js @@ -3,7 +3,6 @@ import { createReducerDescription } from '../../../../trackerRedux/trackerReduce import { actionTypes as eventsListActionTypes, } from '../../../../components/Pages/MainPage/EventsList/eventsList.actions'; -import { actionTypes as mainSelectionsActionTypes } from '../../../../components/Pages/MainPage/mainSelections.actions'; import { actionTypes as paginationActionTypes, } from '../../../../components/Pages/MainPage/EventsList/Pagination/pagination.actions'; @@ -13,23 +12,41 @@ import { import { actionTypes as listActionTypes, } from '../../../../components/List/list.actions'; +import { actionTypes as workingListsActionTypes } from '../../../../components/Pages/MainPage/WorkingLists'; export const workingListsMetaDesc = createReducerDescription({ - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.DATA_PRE_CLEAN]: (state, action) => { + const { listId } = action.payload; + return { + ...state, + [listId]: undefined, + }; + }, + [workingListsActionTypes.EVENT_LIST_INIT_SUCCESS]: (state, action) => { const newState = { ...state }; - const { listId, queryData, pagingData } = action.payload; + const { listId, config, pagingData } = action.payload; + const { + filters, + rowsPerPage, + currentPage, + sortById, + sortByDirection, + } = config; const listState = { - ...queryData, + filters, + rowsPerPage, + currentPage, + sortById, + sortByDirection, ...pagingData, next: {}, }; - listState.hasOwnProperty('page') && delete listState.page; newState[listId] = listState; return newState; }, - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_UPDATE_SUCCESS]: (state, action) => { const newState = { ...state }; const { listId, pagingData } = action.payload; const next = newState[listId].next; @@ -45,7 +62,7 @@ export const workingListsMetaDesc = createReducerDescription({ }; return newState; }, - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVAL_FAILED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_UPDATE_ERROR]: (state, action) => { const newState = { ...state }; const listId = action.payload.listId; newState[listId] = { diff --git a/src/core_modules/capture-core/reducers/descriptions/workingLists/workingLists.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/workingLists/workingLists.reducerDescription.js index 512e6b1405..24782564f7 100644 --- a/src/core_modules/capture-core/reducers/descriptions/workingLists/workingLists.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/workingLists/workingLists.reducerDescription.js @@ -1,101 +1,67 @@ // @flow +import { moment } from 'capture-core-utils/moment'; import { createReducerDescription } from '../../../trackerRedux/trackerReducer'; -import { actionTypes as mainSelectionsActionTypes } from '../../../components/Pages/MainPage/mainSelections.actions'; -import { - actionTypes as paginationActionTypes, -} from '../../../components/Pages/MainPage/EventsList/Pagination/pagination.actions'; -import { actionTypes as eventsListActionTypes } from '../../../components/Pages/MainPage/EventsList/eventsList.actions'; -import { - dataEntryActionTypes as newEventDataEntryActionTypes, -} from '../../../components/Pages/NewEvent'; -import { - actionTypes as connectivityActionTypes, -} from '../../../components/Connectivity/connectivity.actions'; import { actionTypes as columnSelectorActionTypes, } from '../../../components/Pages/MainPage/EventsList/ListWrapper/actions/columnSelectorDialog.actions'; import { actionTypes as filterSelectorActionTypes, } from '../../../components/Pages/MainPage/EventsList/FilterSelectors/filterSelector.actions'; -import { - actionTypes as quickSelectorActionTypes, -} from '../../../components/QuickSelector/actions/QuickSelector.actions'; -import { - actionTypes as editEventSelectorActionTypes, -} from '../../../components/Pages/EditEvent/editEvent.actions'; -import { - actionTypes as viewEventActionTypes, -} from '../../../components/Pages/ViewEvent/viewEvent.actions'; import { actionTypes as listActionTypes, } from '../../../components/List/list.actions'; +import { actionTypes as workingListsActionTypes } from '../../../components/Pages/MainPage/WorkingLists'; -const setMainEventPageWorkingListConfigSelectorLoading = state => ({ - ...state, - eventMainPage: { - ...state.eventMainPage, - isLoading: true, +export const workingListsTemplatesDesc = createReducerDescription({ + [workingListsActionTypes.DATA_PRE_CLEAN]: (state, action) => { + const { listId } = action.payload; + return { + ...state, + [listId]: undefined, + }; }, -}); - -export const workingListConfigSelectorDesc = createReducerDescription({ - [connectivityActionTypes.GET_EVENT_LIST_ON_RECONNECT]: setMainEventPageWorkingListConfigSelectorLoading, - [mainSelectionsActionTypes.MAIN_SELECTIONS_COMPLETED]: setMainEventPageWorkingListConfigSelectorLoading, - [viewEventActionTypes.INITIALIZE_WORKING_LISTS_ON_BACK_TO_MAIN_PAGE]: setMainEventPageWorkingListConfigSelectorLoading, - [viewEventActionTypes.UPDATE_WORKING_LIST_ON_BACK_TO_MAIN_PAGE]: setMainEventPageWorkingListConfigSelectorLoading, - [newEventDataEntryActionTypes.CANCEL_SAVE_INITIALIZE_WORKING_LISTS]: setMainEventPageWorkingListConfigSelectorLoading, - [newEventDataEntryActionTypes.CANCEL_SAVE_UPDATE_WORKING_LIST]: setMainEventPageWorkingListConfigSelectorLoading, - [newEventDataEntryActionTypes.REQUEST_SAVE_RETURN_TO_MAIN_PAGE]: setMainEventPageWorkingListConfigSelectorLoading, - [eventsListActionTypes.WORKING_LIST_CONFIGS_RETRIEVED]: (state, action) => ({ - ...state, - eventMainPage: { - ...state.eventMainPage, - workingListConfigs: action.payload.workingListConfigs, - }, - }), - [eventsListActionTypes.WORKING_LIST_CONFIGS_RETRIEVAL_FAILED]: (state, action) => ({ - ...state, - eventMainPage: { - ...state.eventMainPage, - isLoading: false, - loadError: action.payload.error, - }, - }), - [eventsListActionTypes.SET_CURRENT_WORKING_LIST_CONFIG]: (state, action) => ({ - ...state, - eventMainPage: { - ...state.eventMainPage, - currentConfigId: action.payload.configId, - currentListId: action.payload.listId, - }, - }), - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: state => ({ - ...state, - eventMainPage: { - ...state.eventMainPage, - isLoading: false, - loadError: null, - }, - }), - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVED]: state => ({ - ...state, - eventMainPage: { - ...state.eventMainPage, - isLoading: false, - loadError: null, - }, - }), - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVAL_FAILED]: state => ({ - ...state, - eventMainPage: { - ...state.eventMainPage, - isLoading: false, - }, - }), -}, 'workingListConfigSelector'); + [workingListsActionTypes.TEMPLATES_FETCH_SUCCESS]: (state, action) => { + const { listId, templates } = action.payload; + return { + ...state, + [listId]: { + ...state[listId], + templates, + }, + }; + }, + [workingListsActionTypes.TEMPLATES_FETCH_ERROR]: (state, action) => { + const { listId, error } = action.payload; + return { + ...state, + [listId]: { + ...state[listId], + loadError: error, + }, + }; + }, + [workingListsActionTypes.TEMPLATE_SELECT]: (state, action) => { + const { listId, templateId } = action.payload; + return { + ...state, + [listId]: { + ...state[listId], + selectedTemplateId: templateId, + currentListId: listId, + }, + }; + }, +}, 'workingListsTemplates'); export const workingListsDesc = createReducerDescription({ - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.DATA_PRE_CLEAN]: (state, action) => { + const { listId } = action.payload; + return { + ...state, + [listId]: undefined, + }; + }, + [workingListsActionTypes.EVENT_LIST_INIT_SUCCESS]: (state, action) => { const newState = { ...state }; const { listId, eventContainers, request } = action.payload; newState[listId] = { @@ -107,7 +73,7 @@ export const workingListsDesc = createReducerDescription({ }; return newState; }, - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_UPDATE_SUCCESS]: (state, action) => { const newState = { ...state }; const { listId, eventContainers, request } = action.payload; newState[listId] = { @@ -149,13 +115,8 @@ export const workingListsDesc = createReducerDescription({ newState[action.payload.listId] = { order: [] }; return newState; }, - }, 'workingLists'); -const getLoadingState = oldState => ({ - ...oldState, - isLoading: true, -}); const getReadyState = (oldState, more) => ({ ...oldState, ...more, @@ -165,43 +126,26 @@ const getReadyState = (oldState, more) => ({ }); export const workingListsUIDesc = createReducerDescription({ - [eventsListActionTypes.SET_CURRENT_WORKING_LIST_CONFIG]: (state, action) => { - const newState = { ...state }; - const listId = action.payload.listId; - newState[listId] = getLoadingState(newState[listId]); - return newState; - }, - [paginationActionTypes.CHANGE_PAGE]: (state, action) => { - const newState = { ...state }; - const listId = action.payload.listId; - newState[listId] = getLoadingState(newState[listId]); - return newState; - }, - [paginationActionTypes.CHANGE_ROWS_PER_PAGE]: (state, action) => { - const newState = { ...state }; - const listId = action.payload.listId; - newState[listId] = getLoadingState(newState[listId]); - return newState; + [workingListsActionTypes.DATA_PRE_CLEAN]: (state, action) => { + const { listId } = action.payload; + return { + ...state, + [listId]: undefined, + }; }, - [eventsListActionTypes.SORT_WORKING_LIST]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_INIT]: (state, action) => { const newState = { ...state }; const listId = action.payload.listId; - newState[listId] = getLoadingState(newState[listId]); + newState[listId] = { ...newState[listId], isLoading: true }; return newState; }, - [eventsListActionTypes.WORKING_LIST_UPDATING]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_UPDATE]: (state, action) => { const newState = { ...state }; const listId = action.payload.listId; newState[listId] = { ...newState[listId], isUpdating: true }; return newState; }, - [eventsListActionTypes.WORKING_LIST_UPDATING_WITH_DIALOG]: (state, action) => { - const newState = { ...state }; - const listId = action.payload.listId; - newState[listId] = { ...newState[listId], isUpdatingWithDialog: true }; - return newState; - }, - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_INIT_SUCCESS]: (state, action) => { const newState = { ...state }; const listId = action.payload.listId; newState[listId] = getReadyState(newState[listId], { @@ -210,7 +154,7 @@ export const workingListsUIDesc = createReducerDescription({ }); return newState; }, - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_UPDATE_SUCCESS]: (state, action) => { const newState = { ...state }; const listId = action.payload.listId; newState[listId] = getReadyState(newState[listId], { @@ -218,7 +162,7 @@ export const workingListsUIDesc = createReducerDescription({ }); return newState; }, - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVAL_FAILED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_INIT_ERROR]: (state, action) => { const newState = { ...state }; const payload = action.payload; newState[payload.listId] = getReadyState({}, { @@ -226,7 +170,7 @@ export const workingListsUIDesc = createReducerDescription({ }); return newState; }, - [eventsListActionTypes.WORKING_LIST_UPDATE_DATA_RETRIEVAL_FAILED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_UPDATE_ERROR]: (state, action) => { const newState = { ...state }; const listId = action.payload.listId; newState[listId] = getReadyState({}, { @@ -234,10 +178,20 @@ export const workingListsUIDesc = createReducerDescription({ }); return newState; }, + [workingListsActionTypes.EVENT_DELETE]: (state, action) => { + const listId = action.payload.listId; + return { + ...state, + [listId]: { + ...state[listId], + isUpdatingWithDialog: true, + }, + }; + }, }, 'workingListsUI'); export const workingListsColumnsOrderDesc = createReducerDescription({ - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.EVENT_LIST_INIT_SUCCESS]: (state, action) => { const { listId, config } = action.payload; const columnOrder = config.columnOrder; return { @@ -269,70 +223,48 @@ export const workingListsColumnsOrderDesc = createReducerDescription({ newState[action.payload.listId] = [...action.payload.columnOrder]; return newState; }, + [workingListsActionTypes.DATA_PRE_CLEAN]: (state, action) => { + const { listId } = action.payload; + return { + ...state, + [listId]: undefined, + }; + }, }, 'workingListsColumnsOrder'); export const workingListsContextDesc = createReducerDescription({ - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { + [workingListsActionTypes.DATA_PRE_CLEAN]: (state, action) => { + const { listId } = action.payload; + return { + ...state, + [listId]: undefined, + }; + }, + [workingListsActionTypes.EVENT_LIST_INIT_SUCCESS]: (state, action) => { const newState = { ...state }; - const payload = action.payload; - newState[payload.listId] = payload.selections; + const { listId, config } = action.payload; + newState[listId] = { + ...config.selections, + timestamp: moment().toISOString(), + }; return newState; }, + // TODO: WHAT IS THIS!??!?!? [listActionTypes.RESET_LIST]: (state, action) => { const newState = { ...state }; newState[action.payload.listId] = action.payload.selections; return newState; }, - // TOO COMPLICATED?? Reason about this - [quickSelectorActionTypes.RESET_PROGRAM_ID_BASE]: (state) => { - const newState = { - ...state, - eventList: null, - }; - return newState; - }, - // TOO COMPLICATED?? Reason about this - [mainSelectionsActionTypes.UPDATE_MAIN_SELECTIONS_FROM_URL]: (state, action) => { - const payload = action.payload; - const nextProgramId = payload.nextProps.programId; - const prevProgramId = payload.prevProps.programId; - if (nextProgramId !== prevProgramId) { - return { - ...state, - eventList: null, - }; - } - return state; - }, - // TOO COMPLICATED?? Reason about this - [editEventSelectorActionTypes.EVENT_FROM_URL_RETRIEVED]: (state, action) => { - const payload = action.payload; - const nextProgramId = payload.eventContainer.event.programId; - const prevProgramId = payload.prevProgramId; - if (nextProgramId !== prevProgramId) { - return { - ...state, - eventList: null, - }; - } - return state; - }, - // TOO COMPLICATED?? Reason about this - [viewEventActionTypes.EVENT_FROM_URL_RETRIEVED]: (state, action) => { - const payload = action.payload; - const nextProgramId = payload.eventContainer.event.programId; - const prevProgramId = payload.prevProgramId; - if (nextProgramId !== prevProgramId) { - return { - ...state, - eventList: null, - }; - } - return state; - }, }, 'workingListsContext'); export const workingListsUserSelectedFiltersDesc = createReducerDescription({ + [workingListsActionTypes.DATA_PRE_CLEAN]: (state, action) => { + const { listId } = action.payload; + return { + ...state, + [listId]: undefined, + }; + }, [filterSelectorActionTypes.REST_MENU_ITEM_SELECTED]: (state, action) => { const { id, listId } = action.payload; const currentListState = { @@ -345,12 +277,12 @@ export const workingListsUserSelectedFiltersDesc = createReducerDescription({ [listId]: currentListState, }; }, - [mainSelectionsActionTypes.WORKING_LIST_DATA_RETRIEVED]: (state, action) => { - const { listId, config = {} } = action.payload; + [workingListsActionTypes.EVENT_LIST_INIT_SUCCESS]: (state, action) => { + const { listId, config } = action.payload; const filters = config.filters; - const selectedFilters = filters ? filters.reduce((acc, filter) => ({ + const selectedFilters = filters ? Object.keys(filters).reduce((acc, key) => ({ ...acc, - [filter.id]: true, + [key]: true, }), {}) : {}; return { diff --git a/src/core_modules/capture-core/utils/valueEqualityChecker/__tests__/valueEqualityChecker.test.js b/src/core_modules/capture-core/utils/valueEqualityChecker/__tests__/valueEqualityChecker.test.js new file mode 100644 index 0000000000..4d6e826740 --- /dev/null +++ b/src/core_modules/capture-core/utils/valueEqualityChecker/__tests__/valueEqualityChecker.test.js @@ -0,0 +1,231 @@ +import { isEqual } from '../valueEqualityChecker'; + +it('objects are equal', () => { + const objA = { + propA: 'test', + }; + + const objB = { + propA: 'test', + }; + + expect(isEqual(objA, objB)).toBeTruthy(); +}); + +it('objects are different', () => { + const objA = { + propA: 'test2', + }; + + const objB = { + propA: 'test1', + }; + + expect(isEqual(objA, objB)).toBeFalsy(); +}); + +it('objects within objects are equal', () => { + const objA = { + propA: { + propInner: 1, + }, + }; + + const objB = { + propA: { + propInner: 1, + }, + }; + + expect(isEqual(objA, objB)).toBeTruthy(); +}); + +it('objects within objects are different', () => { + const objA = { + propA: { + propInner: 1, + }, + }; + + const objB = { + propA: { + propInner: 2, + }, + }; + + expect(isEqual(objA, objB)).toBeFalsy(); +}); + +it('array within objects are equal', () => { + const objA = { + propA: { + propInner: [ + 'element1', + 'element2', + ], + }, + }; + + const objB = { + propA: { + propInner: [ + 'element1', + 'element2', + ], + }, + }; + + expect(isEqual(objA, objB)).toBeTruthy(); +}); + +it('array within objects are different', () => { + const objA = { + propA: { + propInner: [ + 'element1', + 'element2', + ], + }, + }; + + const objB = { + propA: { + propInner: [ + 'element1', + 'element3', + ], + }, + }; + + expect(isEqual(objA, objB)).toBeFalsy(); +}); + +it('object compositions are different', () => { + const objA = { + propA: { + propInner: [ + 'element1', + 'element2', + ], + }, + }; + + const objB = { + propA: { + propInner: [ + 'element1', + 'element2', + ], + propFound: true, + }, + }; + + expect(isEqual(objA, objB)).toBeFalsy(); +}); + + +it('string arrays are equal', () => { + const arrA = [ + 'element1', + 'element2', + ]; + + const arrB = [ + 'element1', + 'element2', + ]; + + expect(isEqual(arrA, arrB)).toBeTruthy(); +}); + +it('string arrays are different', () => { + const arrA = [ + 'element1', + 'element2', + ]; + + const arrB = [ + 'element1', + 'element3', + ]; + + expect(isEqual(arrA, arrB)).toBeFalsy(); +}); + +it('strings are equal', () => { + const stringA = 'stringEqual'; + const stringB = 'stringEqual'; + + expect(isEqual(stringA, stringB)).toBeTruthy(); +}); + +it('strings are different', () => { + const stringA = 'stringA'; + const stringB = 'stringB'; + + expect(isEqual(stringA, stringB)).toBeFalsy(); +}); + +it('numbers are equal', () => { + const numberA = 1; + const numberB = 1; + + expect(isEqual(numberA, numberB)).toBeTruthy(); +}); + +it('numbers are different', () => { + const numberA = 1; + const numberB = 2; + + expect(isEqual(numberA, numberB)).toBeFalsy(); +}); + +it('booleans true are equal', () => { + const boolA = true; + const boolB = true; + + expect(isEqual(boolA, boolB)).toBeTruthy(); +}); + +it('booleans false are equal', () => { + const boolA = false; + const boolB = false; + + expect(isEqual(boolA, boolB)).toBeTruthy(); +}); + +it('booleans are different', () => { + const boolA = false; + const boolB = true; + + expect(isEqual(boolA, boolB)).toBeFalsy(); +}); + +it('null are equal', () => { + const a = null; + const b = null; + + expect(isEqual(a, b)).toBeTruthy(); +}); + +it('null and undefined are different', () => { + const a = null; + const b = undefined; + + expect(isEqual(a, b)).toBeFalsy(); +}); + +it('null and string are different', () => { + const a = null; + const b = 'stringB'; + + expect(isEqual(a, b)).toBeFalsy(); +}); + + +it('null and object are different', () => { + const a = null; + const b = {}; + + expect(isEqual(a, b)).toBeFalsy(); +}); diff --git a/src/core_modules/capture-core/utils/valueEqualityChecker/index.js b/src/core_modules/capture-core/utils/valueEqualityChecker/index.js new file mode 100644 index 0000000000..25f56ddd44 --- /dev/null +++ b/src/core_modules/capture-core/utils/valueEqualityChecker/index.js @@ -0,0 +1,2 @@ +// @flow +export { isEqual } from './valueEqualityChecker'; diff --git a/src/core_modules/capture-core/utils/valueEqualityChecker/valueEqualityChecker.js b/src/core_modules/capture-core/utils/valueEqualityChecker/valueEqualityChecker.js new file mode 100644 index 0000000000..0240d2c830 --- /dev/null +++ b/src/core_modules/capture-core/utils/valueEqualityChecker/valueEqualityChecker.js @@ -0,0 +1,29 @@ + +// @flow +/* eslint-disable complexity */ + +export function isEqual(a: any, b: any) { + if (a === b) { + return true; + } + + if (!a || !b) { + return false; + } + + const typeOfA = typeof a; + const typeOfB = typeof b; + if (typeOfA !== 'object' || typeOfA !== typeOfB || Array.isArray(a) !== Array.isArray(b)) { + return false; + } + + if (Array.isArray(a)) { + return a.every((value, index) => isEqual(value, b[index])); + } + + const allKeys = [...Object.keys(a), ...Object.keys(b)]; + const uniqueKeys = [...new Set(allKeys).values()]; + + return uniqueKeys + .every(key => isEqual(a[key], b[key])); +} diff --git a/src/epics/trackerCapture.epics.js b/src/epics/trackerCapture.epics.js index 33da0306a8..057038cdb9 100644 --- a/src/epics/trackerCapture.epics.js +++ b/src/epics/trackerCapture.epics.js @@ -37,18 +37,19 @@ import { import { openNewEventPageLocationChangeEpic, } from 'capture-core/components/Pages/NewEvent/epics/newEvent.epics'; + import { - initEventWorkingListEpic, - // retrieveWorkingListOnMainSelectionsCompletedEpic, - getWorkingListOnCancelSaveEpic, - getWorkingListOnSaveEpic, - updateWorkingListEpic, - getEventListOnReconnectEpic, + initEventListEpic, + updateEventListEpic, + retrieveTemplatesEpic, requestDeleteEventEpic, -} from 'capture-core/components/Pages/MainPage/EventsList/epics/eventsList.epics'; +} from 'capture-core/components/Pages/MainPage/WorkingLists'; +/* import { retrieveWorkingListConfigsFromServer, } from 'capture-core/components/Pages/MainPage/EventsList/epics/workingListConfig.epics'; +*/ + import { getEventFromUrlEpic, getOrgUnitOnUrlUpdateEpic, @@ -98,10 +99,6 @@ import { import { goingOnlineEpic, } from 'capture-core/components/Connectivity/connectivity.epics'; -import { - updateEventListAfterSaveOrUpdateEventEpic, - updateEventListAfterUpdateEventEpic, -} from 'capture-core/components/Pages/MainPage/mainPage.epics'; import { networkMonitorStatusEpic, } from 'capture-core/components/NetworkStatusBadge/NetworkStatusBadge.epics'; @@ -204,13 +201,10 @@ export default combineEpics( loadCoreFailedEpic, mainSelectionsCompletedEpic, orgUnitDataRetrivedEpic, - retrieveWorkingListConfigsFromServer, - initEventWorkingListEpic, - // retrieveWorkingListOnMainSelectionsCompletedEpic, - getWorkingListOnCancelSaveEpic, - getWorkingListOnSaveEpic, - updateWorkingListEpic, - getEventListOnReconnectEpic, + initEventListEpic, + updateEventListEpic, + retrieveTemplatesEpic, + requestDeleteEventEpic, mainSelectionsFromUrlGetOrgUnitDataEpic, mainSelectionsFromUrlEmptyOrgUnitEpic, mainSelectionsFromUrlValidationEpic, @@ -244,7 +238,6 @@ export default combineEpics( getEventOpeningFromEventListEpic, networkMonitorStatusEpic, goingOnlineEpic, - updateEventListAfterSaveOrUpdateEventEpic, setOrgUnit, setProgram, goBackToListContext, @@ -255,7 +248,6 @@ export default combineEpics( includeFiltersWithValueAfterColumnSortingEpic, saveNewEventAddAnotherEpic, saveNewEventAddAnotherFailedEpic, - requestDeleteEventEpic, searchRegisteringUnitListEpic, showRegisteringUnitListIndicatorEpic, openRelationshipTeiSearchEpic, @@ -301,7 +293,6 @@ export default combineEpics( loadEditEventDataEntryEpic, saveEditedEventEpic, saveEditedEventFailedEpic, - updateEventListAfterUpdateEventEpic, openNewRelationshipRegisterTeiEpic, openNewRelationshipRegisterTeiDataEntryEpic, loadSearchGroupDuplicatesForReviewEpic, diff --git a/src/locales/en/translations.json b/src/locales/en/translations.json index bd4baa1c58..2dde0098f0 100644 --- a/src/locales/en/translations.json +++ b/src/locales/en/translations.json @@ -71,8 +71,6 @@ "Custom range": "", "Max": "", "Min": "", - "greater than or equal to": "", - "less than or equal to": "", "Contains text": "", "Yes": "", "mm/dd/yyyy": "", @@ -115,6 +113,8 @@ "{{fromDate}} to {{toDate}}": "", "after or equal to {{date}}": "", "before or equal to {{date}}": "", + "greater than or equal to": "", + "less than or equal to": "", "More filters": "", "Select columns": "", "Download as JSON": "", @@ -125,6 +125,9 @@ "Download as...": "", "Save ": "", "Rows per page": "", + "Working list could not be loaded": "", + "an error occurred loading working lists": "", + "Registered events": "", "Active": "", "Completed": "", "Could not get organisation unit": "", @@ -221,6 +224,7 @@ "Profile": "", "Error saving event": "", "Could not save event. See log for details": "", + "Could not delete event": "", "Organisation unit search failed. See log for details": "", "tracked entity instance": "", "Set coordinate": "", diff --git a/src/reducers/descriptions/trackerCapture.reducerDescriptions.js b/src/reducers/descriptions/trackerCapture.reducerDescriptions.js index 335772bd45..1be38439d8 100644 --- a/src/reducers/descriptions/trackerCapture.reducerDescriptions.js +++ b/src/reducers/descriptions/trackerCapture.reducerDescriptions.js @@ -36,10 +36,8 @@ import { workingListsUIDesc, workingListsColumnsOrderDesc, workingListsContextDesc, - workingListFiltersEditDesc, - workingListsAppliedFiltersDesc, workingListsUserSelectedFiltersDesc, - workingListConfigSelectorDesc, + workingListsTemplatesDesc, } from 'capture-core/reducers/descriptions/workingLists'; import { mainPageDesc } from 'capture-core/reducers/descriptions/mainPage.reducerDescription'; import { newEventPageDesc } from 'capture-core/reducers/descriptions/newEvent.reducerDescription'; @@ -110,10 +108,8 @@ export default [ workingListsUIDesc, workingListsColumnsOrderDesc, workingListsContextDesc, - workingListFiltersEditDesc, - workingListsAppliedFiltersDesc, workingListsUserSelectedFiltersDesc, - workingListConfigSelectorDesc, + workingListsTemplatesDesc, mainPageDesc, newEventPageDesc, editEventPageDesc, diff --git a/yarn.lock b/yarn.lock index 800a69300b..48c4bacfc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4985,9 +4985,10 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" -flow-bin@^0.87.0: - version "0.87.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.87.0.tgz#fab7f984d8cc767e93fa9eb01cf7d57ed744f19d" +flow-bin@^0.118.0: + version "0.118.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.118.0.tgz#fb706364a58c682d67a2ca7df39396467dc397d1" + integrity sha512-jlbUu0XkbpXeXhan5xyTqVK1jmEKNxE8hpzznI3TThHTr76GiFwK0iRzhDo4KNy+S9h/KxHaqVhTP86vA6wHCg== flush-write-stream@^1.0.0: version "1.0.3"