diff --git a/src/components/orgunits/OrgUnitData.js b/src/components/orgunits/OrgUnitData.js
index bce650e02..4108d34d1 100644
--- a/src/components/orgunits/OrgUnitData.js
+++ b/src/components/orgunits/OrgUnitData.js
@@ -23,7 +23,7 @@ const ORGUNIT_PROFILE_QUERY = {
// Only YEARLY period type is supported in first version
const periodType = 'YEARLY'
const currentYear = String(new Date().getFullYear())
-const periods = getFixedPeriodsByType(periodType, currentYear)
+const periods = getFixedPeriodsByType({ periodType, currentYear })
const defaultPeriod = filterFuturePeriods(periods)[0] || periods[0]
/*
diff --git a/src/components/periods/PeriodSelect.js b/src/components/periods/PeriodSelect.js
index f020e3ce4..671387b20 100644
--- a/src/components/periods/PeriodSelect.js
+++ b/src/components/periods/PeriodSelect.js
@@ -7,7 +7,8 @@ import {
} from '@dhis2/ui'
import cx from 'classnames'
import PropTypes from 'prop-types'
-import React, { Component } from 'react'
+import React, { useState, useMemo, useCallback, useEffect } from 'react'
+import usePrevious from '../../hooks/usePrevious.js'
import {
getFixedPeriodsByType,
filterFuturePeriods,
@@ -16,124 +17,127 @@ import { getYear } from '../../util/time.js'
import { SelectField } from '../core/index.js'
import styles from './styles/PeriodSelect.module.css'
-class PeriodSelect extends Component {
- static propTypes = {
- onChange: PropTypes.func.isRequired,
- className: PropTypes.string,
- errorText: PropTypes.string,
- period: PropTypes.shape({
- id: PropTypes.string.isRequired,
- startDate: PropTypes.string,
- }),
- periodType: PropTypes.string,
- }
-
- state = {
- year: null,
- periods: null,
- }
-
- componentDidMount() {
- this.setPeriods()
- }
-
- componentDidUpdate(prevProps, prevState) {
- const { periodType, period, onChange } = this.props
- const { year, periods } = this.state
-
- if (periodType !== prevProps.periodType) {
- this.setPeriods()
- } else if (periods && !period) {
- onChange(filterFuturePeriods(periods)[0] || periods[0]) // Autoselect most recent period
- }
-
- // Change period if year is changed (but keep period index)
- if (period && prevState.periods && year !== prevState.year) {
- const periodIndex = prevState.periods.findIndex(
- (item) => item.id === period.id
- )
- onChange(periods[periodIndex])
- }
- }
-
- render() {
- const { periodType, period, onChange, className, errorText } =
- this.props
- const { periods } = this.state
-
- if (!periods) {
- return null
+const PeriodSelect = ({
+ onChange,
+ className,
+ errorText,
+ firstDate,
+ lastDate,
+ period,
+ periodType,
+}) => {
+ const [year, setYear] = useState(getYear(period?.startDate || lastDate))
+ const prevYear = usePrevious(year)
+
+ // Set periods when periodType or year changes
+ /* eslint-disable react-hooks/exhaustive-deps */
+ const periods = useMemo(
+ () =>
+ periodType
+ ? getFixedPeriodsByType({
+ periodType,
+ year,
+ firstDate,
+ lastDate,
+ })
+ : [period], // saved map period (not included in depency array by design)
+ [periodType, year, firstDate, lastDate]
+ )
+ /* eslint-enable react-hooks/exhaustive-deps */
+
+ const periodIndex = useMemo(
+ () => period && periods.findIndex((p) => p.id === period.id),
+ [period, periods]
+ )
+
+ const prevPeriodIndex = usePrevious(periodIndex)
+
+ // Increment/decrement year
+ const changeYear = useCallback(
+ (change) => {
+ const newYear = year + change
+
+ if (
+ (!firstDate || newYear >= getYear(firstDate)) &&
+ (!lastDate || newYear <= getYear(lastDate))
+ ) {
+ setYear(newYear)
+ }
+ },
+ [year, firstDate, lastDate]
+ )
+
+ // Autoselect most recent period
+ useEffect(() => {
+ if (!period && periods) {
+ onChange(filterFuturePeriods(periods)[0] || periods[0])
}
+ }, [period, periods, onChange])
- const value =
- period && periods.some((p) => p.id === period.id) ? period.id : null
-
- return (
-
-
- {periodType && (
-
-
- }
- onClick={this.previousYear}
- dataTest="button-previous-year"
- />
-
-
- }
- onClick={this.nextYear}
- dataTest="button-next-year"
- />
-
-
- )}
-
- )
- }
-
- setPeriods() {
- const { periodType, period } = this.props
- const year = this.state.year || getYear(period && period.startDate)
- let periods
+ // Keep the same period position when year changes
+ useEffect(() => {
+ if (year !== prevYear && prevPeriodIndex >= 0) {
+ const newPeriod = periods[prevPeriodIndex]
- if (periodType) {
- periods = getFixedPeriodsByType(periodType, year)
- } else if (period) {
- periods = [period] // If period is loaded in favorite
+ if (newPeriod) {
+ onChange(newPeriod)
+ }
}
+ }, [year, prevYear, periods, prevPeriodIndex, onChange])
- this.setState({ periods, year })
+ if (!periods) {
+ return null
}
- nextYear = () => {
- this.changeYear(1)
- }
-
- previousYear = () => {
- this.changeYear(-1)
- }
-
- changeYear = (change) => {
- const { periodType } = this.props
- const year = this.state.year + change
+ const value =
+ period && periods.some((p) => p.id === period.id) ? period.id : null
+
+ return (
+
+
+ {periodType && (
+
+
+ }
+ onClick={() => changeYear(-1)}
+ dataTest="button-previous-year"
+ />
+
+
+ }
+ onClick={() => changeYear(1)}
+ dataTest="button-next-year"
+ />
+
+
+ )}
+
+ )
+}
- this.setState({
- year,
- periods: getFixedPeriodsByType(periodType, year),
- })
- }
+PeriodSelect.propTypes = {
+ onChange: PropTypes.func.isRequired,
+ className: PropTypes.string,
+ errorText: PropTypes.string,
+ firstDate: PropTypes.string,
+ lastDate: PropTypes.string,
+ period: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ startDate: PropTypes.string,
+ }),
+ periodType: PropTypes.string,
}
export default PeriodSelect
diff --git a/src/util/__tests__/periods.spec.js b/src/util/__tests__/periods.spec.js
new file mode 100644
index 000000000..0069b2102
--- /dev/null
+++ b/src/util/__tests__/periods.spec.js
@@ -0,0 +1,325 @@
+import { getFixedPeriodsByType } from '../periods.js'
+
+describe('util/periods', () => {
+ test('getFixedPeriodsByType - RELATIVE', () => {
+ expect(
+ getFixedPeriodsByType({ periodType: 'RELATIVE', year: 2020 })
+ ).toBe(null)
+ })
+
+ test('getFixedPeriodsByType - YEAR not provided', () => {
+ expect(getFixedPeriodsByType({ periodType: 'MONTHLY' })).toStrictEqual(
+ []
+ )
+ })
+
+ test('getFixedPeriodsByType - MONTHLY first and last date', () => {
+ expect(
+ getFixedPeriodsByType({
+ periodType: 'MONTHLY',
+ year: 2020,
+ firstDate: '2020-04-01',
+ lastDate: '2020-08-30',
+ })
+ ).toStrictEqual([
+ {
+ endDate: '2020-07-31',
+ id: '202007',
+ iso: '202007',
+ name: 'July 2020',
+ startDate: '2020-07-01',
+ },
+ {
+ endDate: '2020-06-30',
+ id: '202006',
+ iso: '202006',
+ name: 'June 2020',
+ startDate: '2020-06-01',
+ },
+ {
+ endDate: '2020-05-31',
+ id: '202005',
+ iso: '202005',
+ name: 'May 2020',
+ startDate: '2020-05-01',
+ },
+ {
+ endDate: '2020-04-30',
+ id: '202004',
+ iso: '202004',
+ name: 'April 2020',
+ startDate: '2020-04-01',
+ },
+ ])
+ })
+
+ test('getFixedPeriodsByType - YEARLY first and last date', () => {
+ expect(
+ getFixedPeriodsByType({
+ periodType: 'YEARLY',
+ year: 2020,
+ firstDate: '2018-04-01',
+ lastDate: '2021-04-01',
+ })
+ ).toStrictEqual([
+ {
+ endDate: '2020-12-31',
+ id: '2020',
+ iso: '2020',
+ name: '2020',
+ startDate: '2020-01-01',
+ },
+ {
+ endDate: '2019-12-31',
+ id: '2019',
+ iso: '2019',
+ name: '2019',
+ startDate: '2019-01-01',
+ },
+ ])
+ })
+
+ test('getFixedPeriodsByType - YEARLY first date', () => {
+ expect(
+ getFixedPeriodsByType({
+ periodType: 'YEARLY',
+ year: 2020,
+ firstDate: '2020-04-01',
+ })
+ ).toStrictEqual([])
+ })
+
+ test('getFixedPeriodsByType - FYAPR 2020', () => {
+ expect(
+ getFixedPeriodsByType({ periodType: 'FYAPR', year: 2020 })
+ ).toStrictEqual([
+ {
+ endDate: '2021-03-31',
+ id: '2020April',
+ name: 'April 2020 - March 2021',
+ startDate: '2020-04-01',
+ },
+ {
+ endDate: '2020-03-31',
+ id: '2019April',
+ name: 'April 2019 - March 2020',
+ startDate: '2019-04-01',
+ },
+ {
+ endDate: '2019-03-31',
+ id: '2018April',
+ name: 'April 2018 - March 2019',
+ startDate: '2018-04-01',
+ },
+ {
+ endDate: '2018-03-31',
+ id: '2017April',
+ name: 'April 2017 - March 2018',
+ startDate: '2017-04-01',
+ },
+ {
+ endDate: '2017-03-31',
+ id: '2016April',
+ name: 'April 2016 - March 2017',
+ startDate: '2016-04-01',
+ },
+ {
+ endDate: '2016-03-31',
+ id: '2015April',
+ name: 'April 2015 - March 2016',
+ startDate: '2015-04-01',
+ },
+ {
+ endDate: '2015-03-31',
+ id: '2014April',
+ name: 'April 2014 - March 2015',
+ startDate: '2014-04-01',
+ },
+ {
+ endDate: '2014-03-31',
+ id: '2013April',
+ name: 'April 2013 - March 2014',
+ startDate: '2013-04-01',
+ },
+ {
+ endDate: '2013-03-31',
+ id: '2012April',
+ name: 'April 2012 - March 2013',
+ startDate: '2012-04-01',
+ },
+ {
+ endDate: '2012-03-31',
+ id: '2011April',
+ name: 'April 2011 - March 2012',
+ startDate: '2011-04-01',
+ },
+ ])
+ })
+
+ test('getFixedPeriodsByType YEARLY 2020', () => {
+ expect(
+ getFixedPeriodsByType({ periodType: 'YEARLY', year: 2020 })
+ ).toStrictEqual([
+ {
+ endDate: '2020-12-31',
+ id: '2020',
+ iso: '2020',
+ name: '2020',
+ startDate: '2020-01-01',
+ },
+ {
+ endDate: '2019-12-31',
+ id: '2019',
+ iso: '2019',
+ name: '2019',
+ startDate: '2019-01-01',
+ },
+ {
+ endDate: '2018-12-31',
+ id: '2018',
+ iso: '2018',
+ name: '2018',
+ startDate: '2018-01-01',
+ },
+ {
+ endDate: '2017-12-31',
+ id: '2017',
+ iso: '2017',
+ name: '2017',
+ startDate: '2017-01-01',
+ },
+ {
+ endDate: '2016-12-31',
+ id: '2016',
+ iso: '2016',
+ name: '2016',
+ startDate: '2016-01-01',
+ },
+ {
+ endDate: '2015-12-31',
+ id: '2015',
+ iso: '2015',
+ name: '2015',
+ startDate: '2015-01-01',
+ },
+ {
+ endDate: '2014-12-31',
+ id: '2014',
+ iso: '2014',
+ name: '2014',
+ startDate: '2014-01-01',
+ },
+ {
+ endDate: '2013-12-31',
+ id: '2013',
+ iso: '2013',
+ name: '2013',
+ startDate: '2013-01-01',
+ },
+ {
+ endDate: '2012-12-31',
+ id: '2012',
+ iso: '2012',
+ name: '2012',
+ startDate: '2012-01-01',
+ },
+ {
+ endDate: '2011-12-31',
+ id: '2011',
+ iso: '2011',
+ name: '2011',
+ startDate: '2011-01-01',
+ },
+ ])
+ })
+ test('getFixedPeriodsByType - MONTHLY 2020', () => {
+ expect(
+ getFixedPeriodsByType({ periodType: 'MONTHLY', year: 2020 })
+ ).toStrictEqual([
+ {
+ endDate: '2020-12-31',
+ id: '202012',
+ iso: '202012',
+ name: 'December 2020',
+ startDate: '2020-12-01',
+ },
+ {
+ endDate: '2020-11-30',
+ id: '202011',
+ iso: '202011',
+ name: 'November 2020',
+ startDate: '2020-11-01',
+ },
+ {
+ endDate: '2020-10-31',
+ id: '202010',
+ iso: '202010',
+ name: 'October 2020',
+ startDate: '2020-10-01',
+ },
+ {
+ endDate: '2020-09-30',
+ id: '202009',
+ iso: '202009',
+ name: 'September 2020',
+ startDate: '2020-09-01',
+ },
+ {
+ endDate: '2020-08-31',
+ id: '202008',
+ iso: '202008',
+ name: 'August 2020',
+ startDate: '2020-08-01',
+ },
+ {
+ endDate: '2020-07-31',
+ id: '202007',
+ iso: '202007',
+ name: 'July 2020',
+ startDate: '2020-07-01',
+ },
+ {
+ endDate: '2020-06-30',
+ id: '202006',
+ iso: '202006',
+ name: 'June 2020',
+ startDate: '2020-06-01',
+ },
+ {
+ endDate: '2020-05-31',
+ id: '202005',
+ iso: '202005',
+ name: 'May 2020',
+ startDate: '2020-05-01',
+ },
+ {
+ endDate: '2020-04-30',
+ id: '202004',
+ iso: '202004',
+ name: 'April 2020',
+ startDate: '2020-04-01',
+ },
+ {
+ endDate: '2020-03-31',
+ id: '202003',
+ iso: '202003',
+ name: 'March 2020',
+ startDate: '2020-03-01',
+ },
+ {
+ endDate: '2020-02-29',
+ id: '202002',
+ iso: '202002',
+ name: 'February 2020',
+ startDate: '2020-02-01',
+ },
+ {
+ endDate: '2020-01-31',
+ id: '202001',
+ iso: '202001',
+ name: 'January 2020',
+ startDate: '2020-01-01',
+ },
+ ])
+ })
+})
diff --git a/src/util/periods.js b/src/util/periods.js
index 2fdd0f826..cfc89b3e8 100644
--- a/src/util/periods.js
+++ b/src/util/periods.js
@@ -6,25 +6,43 @@ import { periodTypes, periodGroups } from '../constants/periods.js'
const getYearOffsetFromNow = (year) => year - new Date(Date.now()).getFullYear()
+const filterPeriods = (periods, firstDate, lastDate) =>
+ periods.filter(
+ (p) =>
+ (!firstDate || p.startDate >= firstDate) &&
+ (!lastDate || p.endDate <= lastDate)
+ )
+
export const getPeriodTypes = (includeRelativePeriods, hiddenPeriods = []) =>
periodTypes(includeRelativePeriods).filter(
({ group }) => !hiddenPeriods.includes(group)
)
-export const getFixedPeriodsByType = (periodType, year) => {
+export const getFixedPeriodsByType = ({
+ periodType,
+ year,
+ firstDate,
+ lastDate,
+}) => {
const period = getFixedPeriodsOptionsById(periodType)
const forceDescendingForYearTypes = !!periodType.match(/^FY|YEARLY/)
const offset = getYearOffsetFromNow(year)
- const periods = period?.getPeriods({ offset, reversePeriods: true }) || null
- if (periods && forceDescendingForYearTypes) {
- // TODO: the reverse() is a workaround for a bug in the analytics
- // getPeriods function that no longer correctly reverses the order
- // for YEARLY and FY period types
- return periods.reverse()
+ let periods = period?.getPeriods({ offset, reversePeriods: true })
+
+ if (!periods) {
+ return null
}
- return periods
+
+ if (firstDate || lastDate) {
+ periods = filterPeriods(periods, firstDate, lastDate)
+ }
+
+ // TODO: the reverse() is a workaround for a bug in the analytics
+ // getPeriods function that no longer correctly reverses the order
+ // for YEARLY and FY period types
+ return forceDescendingForYearTypes ? periods.reverse() : periods
}
export const getRelativePeriods = (hiddenPeriods = []) =>
@@ -48,7 +66,7 @@ export const getPeriodNames = () => ({
}, {}),
})
-export const filterFuturePeriods = (periods) => {
- const now = new Date(Date.now())
+export const filterFuturePeriods = (periods, date) => {
+ const now = new Date(date || Date.now())
return periods.filter(({ startDate }) => new Date(startDate) < now)
}