diff --git a/src/__demo__/PeriodDimension.stories.js b/src/__demo__/PeriodDimension.stories.js index 9c70f91d6..2ac01b27d 100644 --- a/src/__demo__/PeriodDimension.stories.js +++ b/src/__demo__/PeriodDimension.stories.js @@ -1,4 +1,4 @@ -import { DataProvider } from '@dhis2/app-runtime' +import { CustomDataProvider } from '@dhis2/app-runtime' import { ConfigProvider } from '@dhis2/app-service-config' import React from 'react' import PeriodDimension from '../components/PeriodDimension/PeriodDimension.js' @@ -13,32 +13,83 @@ import { BIWEEKLY, BIMONTHLY, } from '../components/PeriodDimension/utils/index.js' +import { periodTypesV44 } from './data/periodTypes.v44.js' -const Wrapper = (story) => ( - - - {story()} - - -) +// The period selector has one implementation per server version (42, 43, 44), +// selected by serverVersion.minor. The server responses are mocked. +const withServer = ({ minor, data }) => + function MockServer(Story) { + return ( + + + + + + ) + } + +// Mimics the v44 server: relative period labels are stored per relative +// period and returned under every period type that has the relative period +const applyV44Labels = ( + periodTypes, + { periodTypeLabels, relativePeriodLabels } +) => + periodTypes.map((pt) => ({ + ...pt, + displayName: periodTypeLabels[pt.name] || pt.displayName, + relativePeriods: Object.fromEntries( + Object.entries(pt.relativePeriods).map(([id, rp]) => [ + id, + { displayName: relativePeriodLabels[id] || rp.displayName }, + ]) + ), + })) + +const v44Data = ({ + // the server returns all period types when none are configured + enabledPeriodTypes = periodTypesV44.map(({ name }) => name), + periodTypeLabels = {}, + relativePeriodLabels = {}, + weeklyStart = 'WEEKLY', + financialYearStart = 'FINANCIAL_YEAR_OCTOBER', + analysisRelativePeriod = 'LAST_12_MONTHS', +} = {}) => ({ + // PeriodDimension reads keyUiLocale for generating fixed periods + userSettings: { keyUiLocale: 'en' }, + 'configuration/dataOutputPeriodTypes': applyV44Labels( + periodTypesV44.filter(({ name }) => enabledPeriodTypes.includes(name)), + { periodTypeLabels, relativePeriodLabels } + ), + 'systemSettings/keyAnalysisRelativePeriod': { + keyAnalysisRelativePeriod: analysisRelativePeriod, + }, + 'systemSettings/analyticsWeeklyStart': { + analyticsWeeklyStart: weeklyStart, + }, + 'systemSettings/analyticsFinancialYearStart': { + analyticsFinancialYearStart: financialYearStart, + }, +}) const selectedPeriods = [{ id: 'LAST_12_MONTHS', name: 'Last 12 months' }] export default { title: 'PeriodDimension', - decorators: [Wrapper], + decorators: [withServer({ minor: 44, data: v44Data() })], } export const NoneSelected = () => { return console.log(selected)} /> } -NoneSelected.story = { - name: 'None selected', -} +NoneSelected.storyName = 'None selected' export const OneSelected = () => { return ( @@ -49,9 +100,7 @@ export const OneSelected = () => { ) } -OneSelected.story = { - name: 'One selected', -} +OneSelected.storyName = 'One selected' export const MonthlyExcluded = () => { return ( @@ -62,9 +111,7 @@ export const MonthlyExcluded = () => { ) } -MonthlyExcluded.story = { - name: 'Monthly excluded', -} +MonthlyExcluded.storyName = 'Monthly excluded' export const WeeklyExcluded = () => { return ( @@ -81,9 +128,7 @@ export const WeeklyExcluded = () => { ) } -WeeklyExcluded.story = { - name: 'Weekly excluded', -} +WeeklyExcluded.storyName = 'Weekly excluded' export const AllBelowQuarterlyExcluded = () => { return ( @@ -104,9 +149,7 @@ export const AllBelowQuarterlyExcluded = () => { ) } -AllBelowQuarterlyExcluded.story = { - name: 'All below Quarterly excluded', -} +AllBelowQuarterlyExcluded.storyName = 'All below Quarterly excluded' export const UsingRightFooter = () => { return ( @@ -127,6 +170,151 @@ export const UsingRightFooter = () => { ) } -UsingRightFooter.story = { - name: 'Using right footer', +UsingRightFooter.storyName = 'Using right footer' + +export const V44CustomLabels = () => { + return ( + console.log(selected)} + /> + ) +} + +V44CustomLabels.storyName = + 'v44: custom labels, only Weekly Sunday and Financial April enabled' +V44CustomLabels.decorators = [ + withServer({ + minor: 44, + data: v44Data({ + enabledPeriodTypes: [ + 'Daily', + 'WeeklySunday', + 'Monthly', + 'Quarterly', + 'Yearly', + 'FinancialApril', + ], + weeklyStart: 'WEEKLY_SUNDAY', + financialYearStart: 'FINANCIAL_YEAR_APRIL', + // opens the weekly relative periods + analysisRelativePeriod: 'LAST_4_WEEKS', + periodTypeLabels: { + WeeklySunday: 'EPI week', + FinancialApril: 'Fiscal year', + }, + relativePeriodLabels: { + THIS_WEEK: 'This EPI week', + LAST_WEEK: 'Last EPI week', + LAST_4_WEEKS: 'Last 4 EPI weeks', + LAST_12_WEEKS: 'Last 12 EPI weeks', + LAST_52_WEEKS: 'Last 52 EPI weeks', + WEEKS_THIS_YEAR: 'EPI weeks this year', + THIS_FINANCIAL_YEAR: 'This fiscal year', + LAST_FINANCIAL_YEAR: 'Last fiscal year', + LAST_5_FINANCIAL_YEARS: 'Last 5 fiscal years', + LAST_10_FINANCIAL_YEARS: 'Last 10 fiscal years', + }, + }), + }), +] + +export const V44Translated = () => { + return ( + console.log(selected)} + /> + ) +} + +V44Translated.storyName = 'v44: label translations (db locale fr)' +V44Translated.decorators = [ + withServer({ + minor: 44, + data: v44Data({ + enabledPeriodTypes: ['Daily', 'Weekly', 'Monthly', 'Yearly'], + periodTypeLabels: { + Daily: 'Quotidien', + Weekly: 'Hebdomadaire', + Monthly: 'Mensuel', + Yearly: 'Annuel', + }, + relativePeriodLabels: { + TODAY: "Aujourd'hui", + YESTERDAY: 'Hier', + THIS_WEEK: 'Cette semaine', + LAST_WEEK: 'La semaine dernière', + THIS_MONTH: 'Ce mois-ci', + LAST_MONTH: 'Le mois dernier', + LAST_3_MONTHS: '3 derniers mois', + LAST_6_MONTHS: '6 derniers mois', + LAST_12_MONTHS: '12 derniers mois', + THIS_YEAR: 'Cette année', + LAST_YEAR: "L'année dernière", + }, + }), + }), +] + +export const V44WeeklyStartDisabled = () => { + return console.log(selected)} /> } + +V44WeeklyStartDisabled.storyName = + 'v44: weekly start Sunday, but only Weekly enabled (no weekly relative periods)' +V44WeeklyStartDisabled.decorators = [ + withServer({ + minor: 44, + data: v44Data({ + enabledPeriodTypes: ['Weekly', 'Monthly'], + weeklyStart: 'WEEKLY_SUNDAY', + }), + }), +] + +export const V43Labels = () => { + return console.log(selected)} /> +} + +V43Labels.storyName = + 'v43: period type labels, relative period names derived from them' +V43Labels.decorators = [ + withServer({ + minor: 43, + data: { + userSettings: { keyUiLocale: 'en' }, + 'configuration/dataOutputPeriodTypes': [ + { name: 'Daily' }, + { name: 'WeeklySunday', displayLabel: 'EPI week' }, + { name: 'Monthly' }, + { name: 'Quarterly' }, + { name: 'Yearly' }, + { name: 'FinancialApril', displayLabel: 'Fiscal year' }, + ], + 'systemSettings/keyAnalysisRelativePeriod': { + keyAnalysisRelativePeriod: 'LAST_12_MONTHS', + }, + 'systemSettings/analyticsWeeklyStart': { + analyticsWeeklyStart: 'WEEKLY_SUNDAY', + }, + 'systemSettings/analyticsFinancialYearStart': { + analyticsFinancialYearStart: 'FINANCIAL_YEAR_APRIL', + }, + }, + }), +] + +export const V42 = () => { + return console.log(selected)} /> +} + +V42.storyName = 'v42: client period types and relative periods' +V42.decorators = [ + withServer({ + minor: 42, + data: { userSettings: { keyUiLocale: 'en' } }, + }), +] diff --git a/src/__demo__/data/periodTypes.v44.js b/src/__demo__/data/periodTypes.v44.js new file mode 100644 index 000000000..fc0468060 --- /dev/null +++ b/src/__demo__/data/periodTypes.v44.js @@ -0,0 +1,472 @@ +// All period types from /api/periodTypes (v44) with default names, in server order. +// Fields as requested by the v44 period selector: name,displayName,relativePeriods +export const periodTypesV44 = [ + { + name: 'Daily', + displayName: 'Daily', + relativePeriods: { + TODAY: { + displayName: 'Today', + }, + YESTERDAY: { + displayName: 'Yesterday', + }, + LAST_3_DAYS: { + displayName: 'Last 3 days', + }, + LAST_7_DAYS: { + displayName: 'Last 7 days', + }, + LAST_14_DAYS: { + displayName: 'Last 14 days', + }, + LAST_30_DAYS: { + displayName: 'Last 30 days', + }, + LAST_60_DAYS: { + displayName: 'Last 60 days', + }, + LAST_90_DAYS: { + displayName: 'Last 90 days', + }, + LAST_180_DAYS: { + displayName: 'Last 180 days', + }, + }, + }, + { + name: 'Weekly', + displayName: 'Weekly', + relativePeriods: { + WEEKS_THIS_YEAR: { + displayName: 'Weeks this year', + }, + THIS_WEEK: { + displayName: 'This week', + }, + LAST_WEEK: { + displayName: 'Last week', + }, + LAST_4_WEEKS: { + displayName: 'Last 4 weeks', + }, + LAST_12_WEEKS: { + displayName: 'Last 12 weeks', + }, + LAST_52_WEEKS: { + displayName: 'Last 52 weeks', + }, + }, + }, + { + name: 'WeeklyWednesday', + displayName: 'Weekly Wednesday', + relativePeriods: { + WEEKS_THIS_YEAR: { + displayName: 'Weeks this year', + }, + THIS_WEEK: { + displayName: 'This week', + }, + LAST_WEEK: { + displayName: 'Last week', + }, + LAST_4_WEEKS: { + displayName: 'Last 4 weeks', + }, + LAST_12_WEEKS: { + displayName: 'Last 12 weeks', + }, + LAST_52_WEEKS: { + displayName: 'Last 52 weeks', + }, + }, + }, + { + name: 'WeeklyThursday', + displayName: 'Weekly Thursday', + relativePeriods: { + WEEKS_THIS_YEAR: { + displayName: 'Weeks this year', + }, + THIS_WEEK: { + displayName: 'This week', + }, + LAST_WEEK: { + displayName: 'Last week', + }, + LAST_4_WEEKS: { + displayName: 'Last 4 weeks', + }, + LAST_12_WEEKS: { + displayName: 'Last 12 weeks', + }, + LAST_52_WEEKS: { + displayName: 'Last 52 weeks', + }, + }, + }, + { + name: 'WeeklyFriday', + displayName: 'WeeklyFriday', + relativePeriods: { + WEEKS_THIS_YEAR: { + displayName: 'Weeks this year', + }, + THIS_WEEK: { + displayName: 'This week', + }, + LAST_WEEK: { + displayName: 'Last week', + }, + LAST_4_WEEKS: { + displayName: 'Last 4 weeks', + }, + LAST_12_WEEKS: { + displayName: 'Last 12 weeks', + }, + LAST_52_WEEKS: { + displayName: 'Last 52 weeks', + }, + }, + }, + { + name: 'WeeklySaturday', + displayName: 'Weekly Saturday', + relativePeriods: { + WEEKS_THIS_YEAR: { + displayName: 'Weeks this year', + }, + THIS_WEEK: { + displayName: 'This week', + }, + LAST_WEEK: { + displayName: 'Last week', + }, + LAST_4_WEEKS: { + displayName: 'Last 4 weeks', + }, + LAST_12_WEEKS: { + displayName: 'Last 12 weeks', + }, + LAST_52_WEEKS: { + displayName: 'Last 52 weeks', + }, + }, + }, + { + name: 'WeeklySunday', + displayName: 'Weekly Sunday', + relativePeriods: { + WEEKS_THIS_YEAR: { + displayName: 'Weeks this year', + }, + THIS_WEEK: { + displayName: 'This week', + }, + LAST_WEEK: { + displayName: 'Last week', + }, + LAST_4_WEEKS: { + displayName: 'Last 4 weeks', + }, + LAST_12_WEEKS: { + displayName: 'Last 12 weeks', + }, + LAST_52_WEEKS: { + displayName: 'Last 52 weeks', + }, + }, + }, + { + name: 'BiWeekly', + displayName: 'Bi-weekly', + relativePeriods: { + THIS_BIWEEK: { + displayName: 'This bi-week', + }, + LAST_BIWEEK: { + displayName: 'Last bi-week', + }, + LAST_4_BIWEEKS: { + displayName: 'Last 4 bi-weeks', + }, + }, + }, + { + name: 'Monthly', + displayName: 'Monthly', + relativePeriods: { + THIS_MONTH: { + displayName: 'This month', + }, + LAST_MONTH: { + displayName: 'Last month', + }, + MONTHS_THIS_YEAR: { + displayName: 'Months this year', + }, + MONTHS_LAST_YEAR: { + displayName: 'Months last year', + }, + LAST_12_MONTHS: { + displayName: 'Last 12 months', + }, + LAST_6_MONTHS: { + displayName: 'Last 6 months', + }, + LAST_3_MONTHS: { + displayName: 'Last 3 months', + }, + }, + }, + { + name: 'BiMonthly', + displayName: 'Bi-monthly', + relativePeriods: { + THIS_BIMONTH: { + displayName: 'This bi-month', + }, + LAST_BIMONTH: { + displayName: 'Last bi-month', + }, + BIMONTHS_THIS_YEAR: { + displayName: 'Bi-months this year', + }, + LAST_6_BIMONTHS: { + displayName: 'Last 6 bi-months', + }, + }, + }, + { + name: 'Quarterly', + displayName: 'Quarterly', + relativePeriods: { + THIS_QUARTER: { + displayName: 'This quarter', + }, + LAST_QUARTER: { + displayName: 'Last quarter', + }, + QUARTERS_THIS_YEAR: { + displayName: 'Quarters this year', + }, + QUARTERS_LAST_YEAR: { + displayName: 'Quarters last year', + }, + LAST_4_QUARTERS: { + displayName: 'Last 4 quarters', + }, + }, + }, + { + name: 'QuarterlyNov', + displayName: 'Quarterly November', + relativePeriods: { + THIS_QUARTER: { + displayName: 'This quarter', + }, + LAST_QUARTER: { + displayName: 'Last quarter', + }, + QUARTERS_THIS_YEAR: { + displayName: 'Quarters this year', + }, + QUARTERS_LAST_YEAR: { + displayName: 'Quarters last year', + }, + LAST_4_QUARTERS: { + displayName: 'Last 4 quarters', + }, + }, + }, + { + name: 'SixMonthly', + displayName: 'Six-monthly', + relativePeriods: { + THIS_SIX_MONTH: { + displayName: 'This six-month', + }, + LAST_SIX_MONTH: { + displayName: 'Last six-month', + }, + LAST_2_SIXMONTHS: { + displayName: 'Last 2 six-months', + }, + }, + }, + { + name: 'SixMonthlyApril', + displayName: 'Six-monthly April', + relativePeriods: { + THIS_SIX_MONTH: { + displayName: 'This six-month', + }, + LAST_SIX_MONTH: { + displayName: 'Last six-month', + }, + LAST_2_SIXMONTHS: { + displayName: 'Last 2 six-months', + }, + }, + }, + { + name: 'SixMonthlyNov', + displayName: 'Six-monthly November', + relativePeriods: { + THIS_SIX_MONTH: { + displayName: 'This six-month', + }, + LAST_SIX_MONTH: { + displayName: 'Last six-month', + }, + LAST_2_SIXMONTHS: { + displayName: 'Last 2 six-months', + }, + }, + }, + { + name: 'Yearly', + displayName: 'Yearly', + relativePeriods: { + THIS_YEAR: { + displayName: 'This year', + }, + LAST_YEAR: { + displayName: 'Last year', + }, + LAST_5_YEARS: { + displayName: 'Last 5 years', + }, + LAST_10_YEARS: { + displayName: 'Last 10 years', + }, + }, + }, + { + name: 'FinancialFeb', + displayName: 'Financial February', + relativePeriods: { + THIS_FINANCIAL_YEAR: { + displayName: 'This financial year', + }, + LAST_FINANCIAL_YEAR: { + displayName: 'Last financial year', + }, + LAST_5_FINANCIAL_YEARS: { + displayName: 'Last 5 financial years', + }, + LAST_10_FINANCIAL_YEARS: { + displayName: 'LAST_10_FINANCIAL_YEARS', + }, + }, + }, + { + name: 'FinancialApril', + displayName: 'Financial April', + relativePeriods: { + THIS_FINANCIAL_YEAR: { + displayName: 'This financial year', + }, + LAST_FINANCIAL_YEAR: { + displayName: 'Last financial year', + }, + LAST_5_FINANCIAL_YEARS: { + displayName: 'Last 5 financial years', + }, + LAST_10_FINANCIAL_YEARS: { + displayName: 'LAST_10_FINANCIAL_YEARS', + }, + }, + }, + { + name: 'FinancialJuly', + displayName: 'Financial July', + relativePeriods: { + THIS_FINANCIAL_YEAR: { + displayName: 'This financial year', + }, + LAST_FINANCIAL_YEAR: { + displayName: 'Last financial year', + }, + LAST_5_FINANCIAL_YEARS: { + displayName: 'Last 5 financial years', + }, + LAST_10_FINANCIAL_YEARS: { + displayName: 'LAST_10_FINANCIAL_YEARS', + }, + }, + }, + { + name: 'FinancialAug', + displayName: 'Financial August', + relativePeriods: { + THIS_FINANCIAL_YEAR: { + displayName: 'This financial year', + }, + LAST_FINANCIAL_YEAR: { + displayName: 'Last financial year', + }, + LAST_5_FINANCIAL_YEARS: { + displayName: 'Last 5 financial years', + }, + LAST_10_FINANCIAL_YEARS: { + displayName: 'LAST_10_FINANCIAL_YEARS', + }, + }, + }, + { + name: 'FinancialSep', + displayName: 'FinancialSep', + relativePeriods: { + THIS_FINANCIAL_YEAR: { + displayName: 'This financial year', + }, + LAST_FINANCIAL_YEAR: { + displayName: 'Last financial year', + }, + LAST_5_FINANCIAL_YEARS: { + displayName: 'Last 5 financial years', + }, + LAST_10_FINANCIAL_YEARS: { + displayName: 'LAST_10_FINANCIAL_YEARS', + }, + }, + }, + { + name: 'FinancialOct', + displayName: 'Financial October', + relativePeriods: { + THIS_FINANCIAL_YEAR: { + displayName: 'This financial year', + }, + LAST_FINANCIAL_YEAR: { + displayName: 'Last financial year', + }, + LAST_5_FINANCIAL_YEARS: { + displayName: 'Last 5 financial years', + }, + LAST_10_FINANCIAL_YEARS: { + displayName: 'LAST_10_FINANCIAL_YEARS', + }, + }, + }, + { + name: 'FinancialNov', + displayName: 'Financial November', + relativePeriods: { + THIS_FINANCIAL_YEAR: { + displayName: 'This financial year', + }, + LAST_FINANCIAL_YEAR: { + displayName: 'Last financial year', + }, + LAST_5_FINANCIAL_YEARS: { + displayName: 'Last 5 financial years', + }, + LAST_10_FINANCIAL_YEARS: { + displayName: 'LAST_10_FINANCIAL_YEARS', + }, + }, + }, +] diff --git a/src/components/PeriodDimension/PeriodDimension.js b/src/components/PeriodDimension/PeriodDimension.js index 9e7b47607..d7c4b6949 100644 --- a/src/components/PeriodDimension/PeriodDimension.js +++ b/src/components/PeriodDimension/PeriodDimension.js @@ -1,6 +1,8 @@ import { useConfig, useDataQuery } from '@dhis2/app-runtime' +import { CircularLoader } from '@dhis2/ui' import PropTypes from 'prop-types' import React from 'react' +import { TRANSFER_HEIGHT } from '../../modules/dimensionSelectorHelper.js' import { DIMENSION_ID_PERIOD } from '../../modules/predefinedDimensions.js' import PeriodTransfer from './PeriodTransfer.js' import { useDataOutputPeriodTypes } from './useDataOutputPeriodTypes.js' @@ -29,8 +31,7 @@ const PeriodDimension = ({ const { systemInfo } = config const userSettingsResult = useDataQuery(userSettingsQuery) - const { supportsEnabledPeriodTypes, enabledPeriodTypesData } = - useDataOutputPeriodTypes() + const { enabledPeriodTypesData, loading } = useDataOutputPeriodTypes() const { calendar = 'gregory' } = systemInfo const { data: { userSettings: { keyUiLocale: locale } = {} } = {} } = @@ -50,6 +51,24 @@ const PeriodDimension = ({ enabledPeriodTypesData?.metaData ) + // wait for the enabled period types, otherwise the transfer briefly shows + // the full client lists and picks its default period type from them + if (loading) { + return ( +
+ +
+ ) + } + return ( ) } diff --git a/src/components/PeriodDimension/PeriodTransfer.js b/src/components/PeriodDimension/PeriodTransfer.js index 7d776d982..5440d83c0 100644 --- a/src/components/PeriodDimension/PeriodTransfer.js +++ b/src/components/PeriodDimension/PeriodTransfer.js @@ -12,24 +12,9 @@ import { import styles from '../styles/DimensionSelector.style.js' import { TransferOption } from '../TransferOption.js' import FixedPeriodFilter from './FixedPeriodFilter.js' +import { getPeriodTypeOptions } from './periodTypeOptions.js' import RelativePeriodFilter from './RelativePeriodFilter.js' -import { - applyDisplayLabelOverrides, - applyFixedPeriodTypeDisplayLabels, - filterEnabledFixedPeriodTypes, - filterEnabledRelativePeriodTypes, -} from './utils/enabledPeriodTypes.js' -import { getFixedPeriodsOptions } from './utils/fixedPeriods.js' -import { - FYFEB, - FYAUG, - FYSEP, - MONTHLY, - QUARTERLY, - WEEKLYFRI, - filterPeriodTypesById, -} from './utils/index.js' -import { getRelativePeriodsOptions } from './utils/relativePeriods.js' +import { MONTHLY, QUARTERLY } from './utils/index.js' const RightHeader = ({ infoBoxMessage }) => ( <> @@ -63,65 +48,19 @@ const PeriodTransfer = ({ infoBoxMessage, height = TRANSFER_HEIGHT, enabledPeriodTypesData = null, - supportsEnabledPeriodTypes = false, }) => { - const { filteredFixedOptions, filteredRelativeOptions } = useMemo(() => { - if (supportsEnabledPeriodTypes && enabledPeriodTypesData) { - const { - enabledTypes, - financialYearStart, - financialYearDisplayLabel, - weeklyDisplayLabel, - metaData, - } = enabledPeriodTypesData - - const filteredFixed = applyFixedPeriodTypeDisplayLabels( - filterEnabledFixedPeriodTypes( - getFixedPeriodsOptions(periodsSettings), - enabledTypes - ), - enabledTypes - ) - - const filteredRelative = applyDisplayLabelOverrides( - filterEnabledRelativePeriodTypes( - getRelativePeriodsOptions(), - enabledTypes, - financialYearStart - ), - { financialYearDisplayLabel, weeklyDisplayLabel, metaData } - ) - - return { - filteredFixedOptions: filteredFixed, - filteredRelativeOptions: filteredRelative, - } - } else { - const allFixed = getFixedPeriodsOptions(periodsSettings) - const allRelative = getRelativePeriodsOptions() - const v43PeriodTypes = [WEEKLYFRI, FYFEB, FYAUG, FYSEP] - const allExcludedPeriodTypes = [ - ...excludedPeriodTypes, - ...v43PeriodTypes, - ] - - return { - filteredFixedOptions: filterPeriodTypesById( - allFixed, - allExcludedPeriodTypes - ), - filteredRelativeOptions: filterPeriodTypesById( - allRelative, - allExcludedPeriodTypes - ), - } - } - }, [ - supportsEnabledPeriodTypes, - enabledPeriodTypesData, - excludedPeriodTypes, - periodsSettings, - ]) + const { + fixedOptions: filteredFixedOptions, + relativeOptions: filteredRelativeOptions, + } = useMemo( + () => + getPeriodTypeOptions({ + enabledPeriodTypesData, + periodsSettings, + excludedPeriodTypes, + }), + [enabledPeriodTypesData, excludedPeriodTypes, periodsSettings] + ) const analysisRelativePeriod = enabledPeriodTypesData?.analysisRelativePeriod @@ -389,9 +328,18 @@ PeriodTransfer.propTypes = { enabledPeriodTypesData: PropTypes.shape({ analysisRelativePeriod: PropTypes.string, enabledTypes: PropTypes.array, + // v43 only financialYearDisplayLabel: PropTypes.string, financialYearStart: PropTypes.string, + metaData: PropTypes.objectOf( + PropTypes.shape({ name: PropTypes.string }) + ), noEnabledTypes: PropTypes.bool, + // v44 only + relativePeriodStartTypes: PropTypes.objectOf(PropTypes.string), + version: PropTypes.oneOf([43, 44]), + // v43 only + weeklyDisplayLabel: PropTypes.string, }), excludedPeriodTypes: PropTypes.arrayOf(PropTypes.string), height: PropTypes.string, @@ -408,7 +356,6 @@ PeriodTransfer.propTypes = { name: PropTypes.string, }) ), - supportsEnabledPeriodTypes: PropTypes.bool, } export default PeriodTransfer diff --git a/src/components/PeriodDimension/__tests__/PeriodDimension.spec.js b/src/components/PeriodDimension/__tests__/PeriodDimension.spec.js index ad8b0506d..522bad6a7 100644 --- a/src/components/PeriodDimension/__tests__/PeriodDimension.spec.js +++ b/src/components/PeriodDimension/__tests__/PeriodDimension.spec.js @@ -3,10 +3,12 @@ import userEvent from '@testing-library/user-event' import React from 'react' import PeriodDimension from '../PeriodDimension.js' +let mockServerMinorVersion = 42 + jest.mock('@dhis2/app-runtime', () => ({ useConfig: () => ({ systemInfo: {}, - serverVersion: { minor: 42 }, + serverVersion: { minor: mockServerMinorVersion }, }), useDataQuery: jest.fn().mockImplementation((_query, options) => { if (options?.lazy) { @@ -27,7 +29,10 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({ disconnect: jest.fn(), })) -afterEach(jest.clearAllMocks) +afterEach(() => { + jest.clearAllMocks() + mockServerMinorVersion = 42 +}) const props = { selectedPeriods: [], @@ -62,3 +67,14 @@ test('PeriodDimension can toggle between relative and fixed period tab', async ( screen.getByTestId('period-dimension-fixed-period-filter') ).toBeInTheDocument() }) + +test('PeriodDimension shows a loader while the enabled period types are loading', () => { + mockServerMinorVersion = 44 + + const { container } = render() + + expect( + container.querySelector('[data-test="period-dimension-loader"]') + ).toBeInTheDocument() + expect(screen.queryByText('Relative periods')).not.toBeInTheDocument() +}) diff --git a/src/components/PeriodDimension/__tests__/v43.spec.js b/src/components/PeriodDimension/__tests__/v43.spec.js new file mode 100644 index 000000000..fe5e6568f --- /dev/null +++ b/src/components/PeriodDimension/__tests__/v43.spec.js @@ -0,0 +1,141 @@ +import { getRelativePeriodsOptions } from '../utils/relativePeriods.js' +import { + applyDisplayLabelOverrides, + applyFixedPeriodTypeDisplayLabels, + filterEnabledRelativePeriodTypes, + getV43PeriodTypeOptions, +} from '../versions/v43.js' + +// Shape of /api/configuration/dataOutputPeriodTypes (v43) +const enabledTypes = [ + { name: 'Daily', displayLabel: 'Day' }, + { name: 'Weekly', displayLabel: 'Week' }, + { name: 'Monthly', displayLabel: 'Month' }, +] + +const getOptions = (excludedPeriodTypes) => + getV43PeriodTypeOptions({ + enabledPeriodTypesData: { + version: 43, + enabledTypes, + financialYearStart: null, + metaData: {}, + }, + periodsSettings: { calendar: 'gregory', locale: 'en' }, + excludedPeriodTypes, + }) + +describe('v43 period type options', () => { + it('shows all enabled period types when nothing is excluded', () => { + const { fixedOptions, relativeOptions } = getOptions([]) + + expect(fixedOptions.map(({ id }) => id)).toEqual([ + 'DAILY', + 'WEEKLY', + 'MONTHLY', + ]) + expect(relativeOptions.map(({ id }) => id)).toEqual([ + 'DAILY', + 'WEEKLY', + 'MONTHLY', + ]) + }) + + it('applies excludedPeriodTypes to both fixed and relative options', () => { + const { fixedOptions, relativeOptions } = getOptions([ + 'DAILY', + 'WEEKLY', + ]) + + expect(fixedOptions.map(({ id }) => id)).toEqual(['MONTHLY']) + expect(relativeOptions.map(({ id }) => id)).toEqual(['MONTHLY']) + }) +}) + +describe('applyFixedPeriodTypeDisplayLabels', () => { + const fixedOptions = [ + { id: 'WEEKLY', name: 'Weekly' }, + { id: 'MONTHLY', name: 'Monthly' }, + ] + + it('uses the displayLabel of the server period type as name', () => { + expect( + applyFixedPeriodTypeDisplayLabels(fixedOptions, [ + { name: 'Weekly', displayLabel: 'Week' }, + { name: 'Monthly' }, + ]) + ).toEqual([ + { id: 'WEEKLY', name: 'Week' }, + { id: 'MONTHLY', name: 'Monthly' }, + ]) + }) + + it('returns the options unchanged when there are no labels', () => { + expect(applyFixedPeriodTypeDisplayLabels(fixedOptions, null)).toBe( + fixedOptions + ) + expect( + applyFixedPeriodTypeDisplayLabels(fixedOptions, [ + { name: 'Weekly' }, + ]) + ).toBe(fixedOptions) + }) +}) + +describe('filterEnabledRelativePeriodTypes', () => { + const filter = (enabledServerPeriodTypes, financialYearStart) => + filterEnabledRelativePeriodTypes( + getRelativePeriodsOptions(), + enabledServerPeriodTypes, + financialYearStart + ).map(({ id }) => id) + + it('returns no categories when no period types are enabled', () => { + expect(filter([])).toEqual([]) + expect(filter(null)).toEqual([]) + }) + + it('shows a category when any period type of its family is enabled', () => { + expect( + filter([{ name: 'WeeklySunday' }, { name: 'SixMonthlyApril' }]) + ).toEqual(['WEEKLY', 'SIXMONTHLY']) + }) + + it('shows financial years only when the financial year start is set', () => { + const enabledTypes = [{ name: 'Monthly' }, { name: 'FinancialOct' }] + + expect(filter(enabledTypes)).toEqual(['MONTHLY']) + expect(filter(enabledTypes, 'FINANCIAL_YEAR_OCTOBER')).toEqual([ + 'MONTHLY', + 'FINANCIAL', + ]) + }) +}) + +describe('applyDisplayLabelOverrides', () => { + const relativeOptions = getRelativePeriodsOptions().filter(({ id }) => + ['WEEKLY', 'MONTHLY'].includes(id) + ) + + it('renames the weekly category and its relative periods', () => { + const [weeklyOption, monthlyOption] = applyDisplayLabelOverrides( + relativeOptions, + { + weeklyDisplayLabel: 'EPI week', + metaData: { THIS_WEEK: { name: 'This EPI week' } }, + } + ) + + expect(weeklyOption.name).toBe('EPI week') + expect( + weeklyOption.getPeriods().find(({ id }) => id === 'THIS_WEEK').name + ).toBe('This EPI week') + expect(monthlyOption).toBe(relativeOptions[1]) + }) + + it('returns the options unchanged when there are no labels', () => { + expect(applyDisplayLabelOverrides(relativeOptions, {})).toBe( + relativeOptions + ) + }) +}) diff --git a/src/components/PeriodDimension/__tests__/v44.spec.js b/src/components/PeriodDimension/__tests__/v44.spec.js new file mode 100644 index 000000000..b233ddbe0 --- /dev/null +++ b/src/components/PeriodDimension/__tests__/v44.spec.js @@ -0,0 +1,420 @@ +import { renderHook } from '@testing-library/react' +import { + getV44PeriodTypeOptions, + useV44DataOutputPeriodTypes, +} from '../versions/v44.js' + +jest.mock('@dhis2/app-runtime', () => ({ + useDataQuery: jest.fn(), +})) + +// Shape of /api/configuration/dataOutputPeriodTypes?fields=name,displayName,relativePeriods (v44) +const daily = { + name: 'Daily', + displayName: 'Quotidien', + relativePeriods: { + TODAY: { displayName: "Aujourd'hui" }, + YESTERDAY: { displayName: 'Hier' }, + }, +} +// relative periods are nested under every period type of the family, +// with the same labels +const weeklyRelativePeriods = { + THIS_WEEK: { displayName: 'Cette semaine' }, + LAST_WEEK: { displayName: 'Semaine dernière' }, +} +const weekly = { + name: 'Weekly', + displayName: 'Hebdomadaire', + relativePeriods: weeklyRelativePeriods, +} +const weeklySunday = { + name: 'WeeklySunday', + displayName: 'EPI Week', + relativePeriods: weeklyRelativePeriods, +} +const monthly = { + name: 'Monthly', + displayName: 'Mois', + relativePeriods: { + THIS_MONTH: { displayName: 'Ce mois-ci' }, + LAST_MONTH: { displayName: 'x-Last_Month' }, + MONTHS_LAST_YEAR: { displayName: "Mois de l'an dernier" }, + }, +} +const quarterlyRelativePeriods = { + THIS_QUARTER: { displayName: 'Ce trimestre' }, +} +const quarterly = { + name: 'Quarterly', + displayName: 'Trimestriel', + relativePeriods: quarterlyRelativePeriods, +} +const quarterlyNov = { + name: 'QuarterlyNov', + displayName: 'Quarterly Nov', + relativePeriods: quarterlyRelativePeriods, +} +const sixMonthlyApril = { + name: 'SixMonthlyApril', + displayName: 'Six-monthly April', + relativePeriods: { + THIS_SIX_MONTH: { displayName: 'Ce semestre' }, + }, +} +const financialRelativePeriods = { + THIS_FINANCIAL_YEAR: { displayName: 'Cet exercice' }, + LAST_FINANCIAL_YEAR: { displayName: 'Exercice précédent' }, +} +const financialApril = { + name: 'FinancialApril', + displayName: 'FY April', + relativePeriods: financialRelativePeriods, +} +const financialOct = { + name: 'FinancialOct', + displayName: 'FY October', + relativePeriods: financialRelativePeriods, +} + +const DEFAULT_START_TYPES = { WEEKLY: 'Weekly', FINANCIAL: 'FinancialOct' } + +const getOptions = ( + enabledTypes, + excludedPeriodTypes = [], + relativePeriodStartTypes = DEFAULT_START_TYPES +) => + getV44PeriodTypeOptions({ + enabledPeriodTypesData: { + version: 44, + enabledTypes, + relativePeriodStartTypes, + }, + periodsSettings: { calendar: 'gregory', locale: 'en' }, + excludedPeriodTypes, + }) + +const summarize = (options) => + options.map(({ id, name, getPeriods }) => ({ + id, + name, + periods: getPeriods().map((p) => `${p.id}:${p.name}`), + })) + +describe('v44 period type options', () => { + it('uses server displayName for fixed period types', () => { + const { fixedOptions } = getOptions([ + daily, + weekly, + weeklySunday, + monthly, + financialApril, + ]) + + expect(fixedOptions.map(({ id, name }) => ({ id, name }))).toEqual([ + { id: 'DAILY', name: 'Quotidien' }, + { id: 'WEEKLY', name: 'Hebdomadaire' }, + { id: 'WEEKLYSUN', name: 'EPI Week' }, + { id: 'MONTHLY', name: 'Mois' }, + { id: 'FYAPR', name: 'FY April' }, + ]) + expect(typeof fixedOptions[0].getPeriods).toBe('function') + }) + + it('skips server period types the client cannot generate periods for', () => { + const { fixedOptions } = getOptions([monthly, quarterlyNov]) + + expect(fixedOptions.map(({ id }) => id)).toEqual(['MONTHLY']) + }) + + it('uses the nested relative periods with server displayName', () => { + const { relativeOptions } = getOptions([daily, weekly, monthly]) + + expect(summarize(relativeOptions)).toEqual([ + { + id: 'DAILY', + name: 'Quotidien', + periods: ["TODAY:Aujourd'hui", 'YESTERDAY:Hier'], + }, + { + id: 'WEEKLY', + name: 'Hebdomadaire', + periods: [ + 'THIS_WEEK:Cette semaine', + 'LAST_WEEK:Semaine dernière', + ], + }, + { + id: 'MONTHLY', + name: 'Mois', + periods: [ + 'THIS_MONTH:Ce mois-ci', + 'LAST_MONTH:x-Last_Month', + "MONTHS_LAST_YEAR:Mois de l'an dernier", + ], + }, + ]) + }) + + it('shows one weekly category when several weekly types are enabled', () => { + const { relativeOptions } = getOptions([weekly, weeklySunday]) + + expect(relativeOptions.map(({ id, name }) => ({ id, name }))).toEqual([ + { id: 'WEEKLY', name: 'Hebdomadaire' }, + ]) + }) + + it('names the weekly category after the weekly start variant from the system setting', () => { + const { relativeOptions } = getOptions([weekly, weeklySunday], [], { + ...DEFAULT_START_TYPES, + WEEKLY: 'WeeklySunday', + }) + + expect(summarize(relativeOptions)).toEqual([ + { + id: 'WEEKLY', + name: 'EPI Week', + periods: [ + 'THIS_WEEK:Cette semaine', + 'LAST_WEEK:Semaine dernière', + ], + }, + ]) + }) + + it('hides weekly relative periods when the weekly start variant is disabled', () => { + const { relativeOptions } = getOptions([weeklySunday, monthly]) + + expect(relativeOptions.map(({ id }) => id)).toEqual(['MONTHLY']) + }) + + it('shows weekly relative periods when only the weekly start variant is enabled', () => { + const { relativeOptions } = getOptions([weeklySunday], [], { + ...DEFAULT_START_TYPES, + WEEKLY: 'WeeklySunday', + }) + + expect(summarize(relativeOptions)).toEqual([ + { + id: 'WEEKLY', + name: 'EPI Week', + periods: [ + 'THIS_WEEK:Cette semaine', + 'LAST_WEEK:Semaine dernière', + ], + }, + ]) + }) + + it('shows financial year relative periods for the financial year start variant from the system setting', () => { + const { relativeOptions } = getOptions([financialApril, financialOct]) + + expect(summarize(relativeOptions)).toEqual([ + { + id: 'FINANCIAL', + name: 'FY October', + periods: [ + 'THIS_FINANCIAL_YEAR:Cet exercice', + 'LAST_FINANCIAL_YEAR:Exercice précédent', + ], + }, + ]) + }) + + it('shows quarterly relative periods for the standard quarterly type', () => { + expect( + summarize(getOptions([quarterly, quarterlyNov]).relativeOptions) + ).toEqual([ + { + id: 'QUARTERLY', + name: 'Trimestriel', + periods: ['THIS_QUARTER:Ce trimestre'], + }, + ]) + }) + + it('hides quarterly and six-monthly relative periods when only a start variant is enabled', () => { + const { fixedOptions, relativeOptions } = getOptions([ + quarterlyNov, + sixMonthlyApril, + ]) + + expect(fixedOptions.map(({ id }) => id)).toEqual(['SIXMONTHLYAPR']) + expect(relativeOptions).toEqual([]) + }) + + it('hides a category when the server returns no relative periods for it', () => { + const { relativeOptions } = getOptions([ + { ...monthly, relativePeriods: {} }, + ]) + + expect(relativeOptions).toEqual([]) + }) + + it('hides financial year relative periods when the start variant is disabled or not set', () => { + expect(getOptions([financialApril]).relativeOptions).toEqual([]) + expect( + getOptions([financialOct], [], { + ...DEFAULT_START_TYPES, + FINANCIAL: null, + }).relativeOptions + ).toEqual([]) + }) + + it('applies excludedPeriodTypes to both fixed and relative options', () => { + const { fixedOptions, relativeOptions } = getOptions( + [daily, weekly, monthly], + ['DAILY', 'WEEKLY'] + ) + + expect(fixedOptions.map(({ id }) => id)).toEqual(['MONTHLY']) + expect(relativeOptions.map(({ id }) => id)).toEqual(['MONTHLY']) + }) + + it('returns no options when no period types are enabled', () => { + expect(getOptions([])).toEqual({ + fixedOptions: [], + relativeOptions: [], + }) + }) +}) + +describe('useV44DataOutputPeriodTypes', () => { + const mockQueryState = {} + + beforeEach(() => { + const { useDataQuery } = require('@dhis2/app-runtime') + mockQueryState.v44Refetch = jest.fn() + mockQueryState.v44Data = undefined + mockQueryState.v44Error = undefined + + useDataQuery.mockImplementation(() => ({ + data: mockQueryState.v44Data, + error: mockQueryState.v44Error, + refetch: mockQueryState.v44Refetch, + })) + }) + + it('does not fetch when disabled', () => { + const { result } = renderHook(() => + useV44DataOutputPeriodTypes({ enabled: false }) + ) + + expect(mockQueryState.v44Refetch).not.toHaveBeenCalled() + expect(result.current).toEqual({ + enabledPeriodTypesData: null, + loading: false, + }) + }) + + it('is loading until the period types have been fetched', () => { + const { result } = renderHook(() => + useV44DataOutputPeriodTypes({ enabled: true }) + ) + + expect(result.current).toEqual({ + enabledPeriodTypesData: null, + loading: true, + }) + }) + + it('stops loading without data when the request fails', () => { + mockQueryState.v44Error = new Error('Request failed') + + const { result } = renderHook(() => + useV44DataOutputPeriodTypes({ enabled: true }) + ) + + expect(result.current).toEqual({ + enabledPeriodTypesData: null, + loading: false, + }) + }) + + it('fetches period types without a locale (server uses the user db locale)', () => { + const { useDataQuery } = require('@dhis2/app-runtime') + + renderHook(() => useV44DataOutputPeriodTypes({ enabled: true })) + + expect(mockQueryState.v44Refetch).toHaveBeenCalledTimes(1) + expect(mockQueryState.v44Refetch).toHaveBeenCalledWith() + const [query] = useDataQuery.mock.calls[0] + expect(query.periodTypes.params).not.toHaveProperty('locale') + }) + + it('returns enabled types and relative period name overrides', () => { + mockQueryState.v44Data = { + periodTypes: [daily, monthly], + analysisRelativePeriod: { + keyAnalysisRelativePeriod: 'LAST_12_MONTHS', + }, + weeklyStart: { analyticsWeeklyStart: 'WEEKLY_SUNDAY' }, + financialYearStart: { + analyticsFinancialYearStart: 'FINANCIAL_YEAR_APRIL', + }, + } + + const { result } = renderHook(() => + useV44DataOutputPeriodTypes({ enabled: true }) + ) + + expect(result.current.enabledPeriodTypesData).toEqual({ + version: 44, + enabledTypes: [daily, monthly], + analysisRelativePeriod: 'LAST_12_MONTHS', + relativePeriodStartTypes: { + WEEKLY: 'WeeklySunday', + FINANCIAL: 'FinancialApril', + }, + noEnabledTypes: false, + metaData: { + TODAY: { name: "Aujourd'hui" }, + YESTERDAY: { name: 'Hier' }, + THIS_MONTH: { name: 'Ce mois-ci' }, + LAST_MONTH: { name: 'x-Last_Month' }, + MONTHS_LAST_YEAR: { name: "Mois de l'an dernier" }, + }, + }) + }) + + it('defaults the weekly start to Weekly when the setting is missing', () => { + mockQueryState.v44Data = { periodTypes: [weekly] } + + const { result } = renderHook(() => + useV44DataOutputPeriodTypes({ enabled: true }) + ) + + expect( + result.current.enabledPeriodTypesData.relativePeriodStartTypes + ).toEqual({ + WEEKLY: 'Weekly', + FINANCIAL: null, + }) + }) + + it('keeps the server order of period types', () => { + mockQueryState.v44Data = { + periodTypes: [daily, monthly, financialOct], + } + + const { result } = renderHook(() => + useV44DataOutputPeriodTypes({ enabled: true }) + ) + + expect( + result.current.enabledPeriodTypesData.enabledTypes.map( + ({ name }) => name + ) + ).toEqual(['Daily', 'Monthly', 'FinancialOct']) + }) + + it('flags when no period types are enabled', () => { + mockQueryState.v44Data = { periodTypes: [] } + + const { result } = renderHook(() => + useV44DataOutputPeriodTypes({ enabled: true }) + ) + + expect(result.current.enabledPeriodTypesData.noEnabledTypes).toBe(true) + }) +}) diff --git a/src/components/PeriodDimension/periodTypeOptions.js b/src/components/PeriodDimension/periodTypeOptions.js new file mode 100644 index 000000000..6603a0fe0 --- /dev/null +++ b/src/components/PeriodDimension/periodTypeOptions.js @@ -0,0 +1,31 @@ +import { getV42PeriodTypeOptions } from './versions/v42.js' +import { getV43PeriodTypeOptions } from './versions/v43.js' +import { getV44PeriodTypeOptions } from './versions/v44.js' + +const getPeriodTypeOptions = ({ + enabledPeriodTypesData, + periodsSettings, + excludedPeriodTypes, +}) => { + switch (enabledPeriodTypesData?.version) { + case 44: + return getV44PeriodTypeOptions({ + enabledPeriodTypesData, + periodsSettings, + excludedPeriodTypes, + }) + case 43: + return getV43PeriodTypeOptions({ + enabledPeriodTypesData, + periodsSettings, + excludedPeriodTypes, + }) + default: + return getV42PeriodTypeOptions({ + periodsSettings, + excludedPeriodTypes, + }) + } +} + +export { getPeriodTypeOptions } diff --git a/src/components/PeriodDimension/useDataOutputPeriodTypes.js b/src/components/PeriodDimension/useDataOutputPeriodTypes.js index f76031fdb..9659c6362 100644 --- a/src/components/PeriodDimension/useDataOutputPeriodTypes.js +++ b/src/components/PeriodDimension/useDataOutputPeriodTypes.js @@ -1,162 +1,29 @@ -import { useConfig, useDataQuery } from '@dhis2/app-runtime' -import { useEffect, useMemo } from 'react' - -const v43Query = { - enabledPeriodTypes: { - resource: 'configuration/dataOutputPeriodTypes', - }, - // v43-only: analyticsFinancialYearStart is removed in v44 - financialYearStart: { - resource: 'systemSettings/analyticsFinancialYearStart', - }, - analysisRelativePeriod: { - resource: 'systemSettings/keyAnalysisRelativePeriod', - }, - // v43-only: analyticsWeeklyStart is removed in v44 - weeklyStart: { - resource: 'systemSettings/analyticsWeeklyStart', - }, -} - -// v43-only: analyticsFinancialYearStart is removed in v44 -const FY_SETTING_TO_SERVER_PT = { - FINANCIAL_YEAR_FEBRUARY: 'FinancialFeb', - FINANCIAL_YEAR_APRIL: 'FinancialApril', - FINANCIAL_YEAR_JULY: 'FinancialJuly', - FINANCIAL_YEAR_AUGUST: 'FinancialAug', - FINANCIAL_YEAR_SEPTEMBER: 'FinancialSep', - FINANCIAL_YEAR_OCTOBER: 'FinancialOct', - FINANCIAL_YEAR_NOVEMBER: 'FinancialNov', -} - -// v43-only: analyticsWeeklyStart is removed in v44 -const WEEKLY_START_TO_SERVER_PT = { - WEEKLY: 'Weekly', - WEEKLY_WEDNESDAY: 'WeeklyWednesday', - WEEKLY_THURSDAY: 'WeeklyThursday', - WEEKLY_FRIDAY: 'WeeklyFriday', - WEEKLY_SATURDAY: 'WeeklySaturday', - WEEKLY_SUNDAY: 'WeeklySunday', -} +import { useConfig } from '@dhis2/app-runtime' +import { useV43DataOutputPeriodTypes } from './versions/v43.js' +import { useV44DataOutputPeriodTypes } from './versions/v44.js' const useDataOutputPeriodTypes = () => { const { serverVersion } = useConfig() const supportsEnabledPeriodTypes = serverVersion.minor >= 43 + const supportsPeriodLabelTranslations = serverVersion.minor >= 44 - const { - data: v43Data, - error: v43Error, - refetch: v43Refetch, - } = useDataQuery(v43Query, { lazy: true }) - - useEffect(() => { - if (supportsEnabledPeriodTypes) { - v43Refetch() - } - }, [supportsEnabledPeriodTypes, v43Refetch]) - - const enabledPeriodTypesData = useMemo(() => { - if (!supportsEnabledPeriodTypes) { - return null - } - - if (v43Error || !v43Data?.enabledPeriodTypes) { - return null - } - - const enabledTypes = v43Data.enabledPeriodTypes - - if (!enabledTypes || enabledTypes.length === 0) { - return { - enabledTypes: [], - financialYearStart: null, - analysisRelativePeriod: null, - noEnabledTypes: true, - } - } - - // v43-only: financial year logic goes away in v44 - let financialYearStart = null - let financialYearDisplayLabel = null - if (v43Data.financialYearStart?.analyticsFinancialYearStart) { - const fyStartValue = - v43Data.financialYearStart.analyticsFinancialYearStart - - const mappedFyPt = FY_SETTING_TO_SERVER_PT[fyStartValue] - const matchingPt = enabledTypes.find((pt) => pt.name === mappedFyPt) - if (matchingPt) { - financialYearStart = fyStartValue - if (matchingPt.displayLabel) { - financialYearDisplayLabel = matchingPt.displayLabel - } - } - } - - // v43-only: weekly start logic goes away in v44 - let weeklyDisplayLabel = null - if (v43Data.weeklyStart?.analyticsWeeklyStart) { - const weeklyStartValue = v43Data.weeklyStart.analyticsWeeklyStart - - const mappedWeeklyPt = WEEKLY_START_TO_SERVER_PT[weeklyStartValue] - const matchingWeeklyPt = enabledTypes.find( - (pt) => pt.name === mappedWeeklyPt - ) - if (matchingWeeklyPt?.displayLabel) { - weeklyDisplayLabel = matchingWeeklyPt.displayLabel - } - } - - const analysisRelativePeriod = - v43Data.analysisRelativePeriod?.keyAnalysisRelativePeriod || null - - const metaData = { - ...(financialYearDisplayLabel && { - THIS_FINANCIAL_YEAR: { - name: `This ${financialYearDisplayLabel}`, - }, - LAST_FINANCIAL_YEAR: { - name: `Last ${financialYearDisplayLabel}`, - }, - LAST_5_FINANCIAL_YEARS: { - name: `Last 5 ${financialYearDisplayLabel}`, - }, - }), - ...(weeklyDisplayLabel && { - THIS_WEEK: { - name: `This ${weeklyDisplayLabel}`, - }, - LAST_WEEK: { - name: `Last ${weeklyDisplayLabel}`, - }, - LAST_4_WEEKS: { - name: `Last 4 ${weeklyDisplayLabel}`, - }, - LAST_12_WEEKS: { - name: `Last 12 ${weeklyDisplayLabel}`, - }, - LAST_52_WEEKS: { - name: `Last 52 ${weeklyDisplayLabel}`, - }, - WEEKS_THIS_YEAR: { - name: `${weeklyDisplayLabel} this year`, - }, - }), - } + const v43 = useV43DataOutputPeriodTypes({ + enabled: supportsEnabledPeriodTypes && !supportsPeriodLabelTranslations, + }) + const v44 = useV44DataOutputPeriodTypes({ + enabled: supportsPeriodLabelTranslations, + }) - return { - enabledTypes, - financialYearStart, - financialYearDisplayLabel, - weeklyDisplayLabel, - analysisRelativePeriod, - metaData, - noEnabledTypes: false, - } - }, [supportsEnabledPeriodTypes, v43Data, v43Error]) + // enabledPeriodTypesData stays null on v42 and when the request fails, + // loading tells consumers whether to wait for it + const { enabledPeriodTypesData, loading } = supportsPeriodLabelTranslations + ? v44 + : v43 return { supportsEnabledPeriodTypes, enabledPeriodTypesData, + loading, } } diff --git a/src/components/PeriodDimension/utils/enabledPeriodTypes.js b/src/components/PeriodDimension/utils/enabledPeriodTypes.js index 7cadc0833..2737949a7 100644 --- a/src/components/PeriodDimension/utils/enabledPeriodTypes.js +++ b/src/components/PeriodDimension/utils/enabledPeriodTypes.js @@ -25,6 +25,27 @@ export const SERVER_PT_TO_MULTI_CALENDAR_PT = { FinancialNov: 'FYNOV', } +// Mapping from the analyticsFinancialYearStart system setting to server period type names +export const FY_SETTING_TO_SERVER_PT = { + FINANCIAL_YEAR_FEBRUARY: 'FinancialFeb', + FINANCIAL_YEAR_APRIL: 'FinancialApril', + FINANCIAL_YEAR_JULY: 'FinancialJuly', + FINANCIAL_YEAR_AUGUST: 'FinancialAug', + FINANCIAL_YEAR_SEPTEMBER: 'FinancialSep', + FINANCIAL_YEAR_OCTOBER: 'FinancialOct', + FINANCIAL_YEAR_NOVEMBER: 'FinancialNov', +} + +// Mapping from the analyticsWeeklyStart system setting to server period type names +export const WEEKLY_START_TO_SERVER_PT = { + WEEKLY: 'Weekly', + WEEKLY_WEDNESDAY: 'WeeklyWednesday', + WEEKLY_THURSDAY: 'WeeklyThursday', + WEEKLY_FRIDAY: 'WeeklyFriday', + WEEKLY_SATURDAY: 'WeeklySaturday', + WEEKLY_SUNDAY: 'WeeklySunday', +} + // Mapping from relative period categories to their corresponding fixed period types export const RP_CATEGORY_TO_FP_DEPENDENCIES = { DAILY: ['Daily'], @@ -77,48 +98,8 @@ export const filterEnabledFixedPeriodTypes = ( ) } -/** - * Apply displayLabel overrides to fixed period type names - * v43-only: in v44 the API provides these names directly - */ -export const applyFixedPeriodTypeDisplayLabels = ( - filteredFixedOptions, - enabledServerPeriodTypes -) => { - if (!enabledServerPeriodTypes) { - return filteredFixedOptions - } - - const displayLabelMap = new Map() - enabledServerPeriodTypes.forEach((pt) => { - if (pt.displayLabel) { - const multiCalendarPt = SERVER_PT_TO_MULTI_CALENDAR_PT[pt.name] - if (multiCalendarPt) { - displayLabelMap.set(multiCalendarPt, pt.displayLabel) - } - } - }) - - if (displayLabelMap.size === 0) { - return filteredFixedOptions - } - - return filteredFixedOptions.map((option) => { - const displayLabel = displayLabelMap.get(option.id) - return displayLabel ? { ...option, name: displayLabel } : option - }) -} - -/** - * Filter relative period categories based on enabled server period types - * @param {Array} allRelativePeriodOptions - All available relative period options - * @param {Array} enabledServerPeriodTypes - Enabled period types from server - * @param {string|null} financialYearStart - Financial year start setting (if enabled) - * @returns {Array} Filtered relative period options - */ /** * Apply metaData name overrides to a list of periods - * v43-only: in v44 the API provides these names directly */ export const applyPeriodNameOverrides = (periods, metaData) => { if (!metaData) { @@ -130,69 +111,3 @@ export const applyPeriodNameOverrides = (periods, metaData) => { : period ) } - -/** - * Apply display label overrides to relative period options - * v43-only: in v44 the API provides these names directly - */ -export const applyDisplayLabelOverrides = ( - filteredRelativeOptions, - { financialYearDisplayLabel, weeklyDisplayLabel, metaData } -) => { - const overrides = {} - - if (financialYearDisplayLabel) { - overrides['FINANCIAL'] = { name: financialYearDisplayLabel } - } - if (weeklyDisplayLabel) { - overrides['WEEKLY'] = { name: weeklyDisplayLabel } - } - - if (Object.keys(overrides).length === 0) { - return filteredRelativeOptions - } - - return filteredRelativeOptions.map((option) => { - const override = overrides[option.id] - if (!override) { - return option - } - return { - ...option, - name: override.name, - getPeriods: () => - applyPeriodNameOverrides(option.getPeriods(), metaData), - } - }) -} - -export const filterEnabledRelativePeriodTypes = ( - allRelativePeriodOptions, - enabledServerPeriodTypes, - financialYearStart = null -) => { - if (!enabledServerPeriodTypes || enabledServerPeriodTypes.length === 0) { - return [] - } - - const enabledServerPtNames = new Set( - enabledServerPeriodTypes.map((pt) => pt.name) - ) - - return allRelativePeriodOptions.filter((option) => { - // Special handling for financial years - if (option.id === 'FINANCIAL') { - return financialYearStart !== null - } - - // Check if any of the required FP dependencies are enabled - const requiredFpTypes = RP_CATEGORY_TO_FP_DEPENDENCIES[option.id] - if (!requiredFpTypes) { - return true // Show if no dependency mapping (shouldn't happen) - } - - return requiredFpTypes.some((fpType) => - enabledServerPtNames.has(fpType) - ) - }) -} diff --git a/src/components/PeriodDimension/versions/v42.js b/src/components/PeriodDimension/versions/v42.js new file mode 100644 index 000000000..a91f4effa --- /dev/null +++ b/src/components/PeriodDimension/versions/v42.js @@ -0,0 +1,33 @@ +// v42: no enabled period types API, all period types and relative periods +// come from the hardcoded client lists. +// Remove this file (and its branch in periodTypeOptions.js) when v42 is no longer supported. +import { getFixedPeriodsOptions } from '../utils/fixedPeriods.js' +import { + FYAUG, + FYFEB, + FYSEP, + WEEKLYFRI, + filterPeriodTypesById, +} from '../utils/index.js' +import { getRelativePeriodsOptions } from '../utils/relativePeriods.js' + +// period types that were introduced in v43 +const V43_PERIOD_TYPES = [WEEKLYFRI, FYFEB, FYAUG, FYSEP] + +export const getV42PeriodTypeOptions = ({ + periodsSettings, + excludedPeriodTypes, +}) => { + const allExcludedPeriodTypes = [...excludedPeriodTypes, ...V43_PERIOD_TYPES] + + return { + fixedOptions: filterPeriodTypesById( + getFixedPeriodsOptions(periodsSettings), + allExcludedPeriodTypes + ), + relativeOptions: filterPeriodTypesById( + getRelativePeriodsOptions(), + allExcludedPeriodTypes + ), + } +} diff --git a/src/components/PeriodDimension/versions/v43.js b/src/components/PeriodDimension/versions/v43.js new file mode 100644 index 000000000..3bcb990c6 --- /dev/null +++ b/src/components/PeriodDimension/versions/v43.js @@ -0,0 +1,292 @@ +// v43: enabled period types from /configuration/dataOutputPeriodTypes, with a +// single `label` per period type. Relative period names are derived client side +// from that label ("This {label}", "Last {label}", ...). +// Remove this file (and its branches in useDataOutputPeriodTypes and +// periodTypeOptions.js) when v43 is no longer supported. +import { useDataQuery } from '@dhis2/app-runtime' +import { useEffect, useMemo } from 'react' +import { + RP_CATEGORY_TO_FP_DEPENDENCIES, + SERVER_PT_TO_MULTI_CALENDAR_PT, + FY_SETTING_TO_SERVER_PT, + WEEKLY_START_TO_SERVER_PT, + applyPeriodNameOverrides, + filterEnabledFixedPeriodTypes, +} from '../utils/enabledPeriodTypes.js' +import { getFixedPeriodsOptions } from '../utils/fixedPeriods.js' +import { filterPeriodTypesById } from '../utils/index.js' +import { getRelativePeriodsOptions } from '../utils/relativePeriods.js' + +const v43Query = { + enabledPeriodTypes: { + resource: 'configuration/dataOutputPeriodTypes', + }, + financialYearStart: { + resource: 'systemSettings/analyticsFinancialYearStart', + }, + analysisRelativePeriod: { + resource: 'systemSettings/keyAnalysisRelativePeriod', + }, + weeklyStart: { + resource: 'systemSettings/analyticsWeeklyStart', + }, +} + +export const useV43DataOutputPeriodTypes = ({ enabled }) => { + const { + data: v43Data, + error: v43Error, + refetch: v43Refetch, + } = useDataQuery(v43Query, { lazy: true }) + + useEffect(() => { + if (enabled) { + v43Refetch() + } + }, [enabled, v43Refetch]) + + const enabledPeriodTypesData = useMemo(() => { + if (!enabled) { + return null + } + + if (v43Error || !v43Data?.enabledPeriodTypes) { + return null + } + + const enabledTypes = v43Data.enabledPeriodTypes + + if (!enabledTypes || enabledTypes.length === 0) { + return { + version: 43, + enabledTypes: [], + financialYearStart: null, + analysisRelativePeriod: null, + noEnabledTypes: true, + } + } + + let financialYearStart = null + let financialYearDisplayLabel = null + if (v43Data.financialYearStart?.analyticsFinancialYearStart) { + const fyStartValue = + v43Data.financialYearStart.analyticsFinancialYearStart + + const mappedFyPt = FY_SETTING_TO_SERVER_PT[fyStartValue] + const matchingPt = enabledTypes.find((pt) => pt.name === mappedFyPt) + if (matchingPt) { + financialYearStart = fyStartValue + if (matchingPt.displayLabel) { + financialYearDisplayLabel = matchingPt.displayLabel + } + } + } + + let weeklyDisplayLabel = null + if (v43Data.weeklyStart?.analyticsWeeklyStart) { + const weeklyStartValue = v43Data.weeklyStart.analyticsWeeklyStart + + const mappedWeeklyPt = WEEKLY_START_TO_SERVER_PT[weeklyStartValue] + const matchingWeeklyPt = enabledTypes.find( + (pt) => pt.name === mappedWeeklyPt + ) + if (matchingWeeklyPt?.displayLabel) { + weeklyDisplayLabel = matchingWeeklyPt.displayLabel + } + } + + const analysisRelativePeriod = + v43Data.analysisRelativePeriod?.keyAnalysisRelativePeriod || null + + const metaData = { + ...(financialYearDisplayLabel && { + THIS_FINANCIAL_YEAR: { + name: `This ${financialYearDisplayLabel}`, + }, + LAST_FINANCIAL_YEAR: { + name: `Last ${financialYearDisplayLabel}`, + }, + LAST_5_FINANCIAL_YEARS: { + name: `Last 5 ${financialYearDisplayLabel}`, + }, + }), + ...(weeklyDisplayLabel && { + THIS_WEEK: { + name: `This ${weeklyDisplayLabel}`, + }, + LAST_WEEK: { + name: `Last ${weeklyDisplayLabel}`, + }, + LAST_4_WEEKS: { + name: `Last 4 ${weeklyDisplayLabel}`, + }, + LAST_12_WEEKS: { + name: `Last 12 ${weeklyDisplayLabel}`, + }, + LAST_52_WEEKS: { + name: `Last 52 ${weeklyDisplayLabel}`, + }, + WEEKS_THIS_YEAR: { + name: `${weeklyDisplayLabel} this year`, + }, + }), + } + + return { + version: 43, + enabledTypes, + financialYearStart, + financialYearDisplayLabel, + weeklyDisplayLabel, + analysisRelativePeriod, + metaData, + noEnabledTypes: false, + } + }, [enabled, v43Data, v43Error]) + + return { + enabledPeriodTypesData, + loading: enabled && !v43Data && !v43Error, + } +} + +/** + * Apply displayLabel overrides to fixed period type names + */ +export const applyFixedPeriodTypeDisplayLabels = ( + filteredFixedOptions, + enabledServerPeriodTypes +) => { + if (!enabledServerPeriodTypes) { + return filteredFixedOptions + } + + const displayLabelMap = new Map() + enabledServerPeriodTypes.forEach((pt) => { + if (pt.displayLabel) { + const multiCalendarPt = SERVER_PT_TO_MULTI_CALENDAR_PT[pt.name] + if (multiCalendarPt) { + displayLabelMap.set(multiCalendarPt, pt.displayLabel) + } + } + }) + + if (displayLabelMap.size === 0) { + return filteredFixedOptions + } + + return filteredFixedOptions.map((option) => { + const displayLabel = displayLabelMap.get(option.id) + return displayLabel ? { ...option, name: displayLabel } : option + }) +} + +/** + * Apply display label overrides to relative period options + */ +export const applyDisplayLabelOverrides = ( + filteredRelativeOptions, + { financialYearDisplayLabel, weeklyDisplayLabel, metaData } +) => { + const overrides = {} + + if (financialYearDisplayLabel) { + overrides['FINANCIAL'] = { name: financialYearDisplayLabel } + } + if (weeklyDisplayLabel) { + overrides['WEEKLY'] = { name: weeklyDisplayLabel } + } + + if (Object.keys(overrides).length === 0) { + return filteredRelativeOptions + } + + return filteredRelativeOptions.map((option) => { + const override = overrides[option.id] + if (!override) { + return option + } + return { + ...option, + name: override.name, + getPeriods: () => + applyPeriodNameOverrides(option.getPeriods(), metaData), + } + }) +} + +/** + * Filter relative period categories based on enabled server period types + * @param {Array} allRelativePeriodOptions - All available relative period options + * @param {Array} enabledServerPeriodTypes - Enabled period types from server + * @param {string|null} financialYearStart - Financial year start setting (if enabled) + * @returns {Array} Filtered relative period options + */ +export const filterEnabledRelativePeriodTypes = ( + allRelativePeriodOptions, + enabledServerPeriodTypes, + financialYearStart = null +) => { + if (!enabledServerPeriodTypes || enabledServerPeriodTypes.length === 0) { + return [] + } + + const enabledServerPtNames = new Set( + enabledServerPeriodTypes.map((pt) => pt.name) + ) + + return allRelativePeriodOptions.filter((option) => { + // Special handling for financial years + if (option.id === 'FINANCIAL') { + return financialYearStart !== null + } + + // Check if any of the required FP dependencies are enabled + const requiredFpTypes = RP_CATEGORY_TO_FP_DEPENDENCIES[option.id] + if (!requiredFpTypes) { + return true // Show if no dependency mapping (shouldn't happen) + } + + return requiredFpTypes.some((fpType) => + enabledServerPtNames.has(fpType) + ) + }) +} + +export const getV43PeriodTypeOptions = ({ + enabledPeriodTypesData, + periodsSettings, + excludedPeriodTypes, +}) => { + const { + enabledTypes, + financialYearStart, + financialYearDisplayLabel, + weeklyDisplayLabel, + metaData, + } = enabledPeriodTypesData + + return { + fixedOptions: filterPeriodTypesById( + applyFixedPeriodTypeDisplayLabels( + filterEnabledFixedPeriodTypes( + getFixedPeriodsOptions(periodsSettings), + enabledTypes + ), + enabledTypes + ), + excludedPeriodTypes + ), + relativeOptions: filterPeriodTypesById( + applyDisplayLabelOverrides( + filterEnabledRelativePeriodTypes( + getRelativePeriodsOptions(), + enabledTypes, + financialYearStart + ), + { financialYearDisplayLabel, weeklyDisplayLabel, metaData } + ), + excludedPeriodTypes + ), + } +} diff --git a/src/components/PeriodDimension/versions/v44.js b/src/components/PeriodDimension/versions/v44.js new file mode 100644 index 000000000..b0c7775bb --- /dev/null +++ b/src/components/PeriodDimension/versions/v44.js @@ -0,0 +1,194 @@ +// v44: /configuration/dataOutputPeriodTypes returns the enabled period types +// with their relative periods nested, all with a `displayName` that already +// resolves label translation -> label -> default name. No locale is passed, so +// the server uses the user's db locale (keyDbLocale), like for other metadata. +// The client no longer derives any names, it only maps server period types to +// the client period type ids used for generating fixed periods. +// Weekly and financial year relative periods have one set of labels; the +// analyticsWeeklyStart and analyticsFinancialYearStart system settings decide +// which start variant they refer to. +import { useDataQuery } from '@dhis2/app-runtime' +import { useEffect, useMemo } from 'react' +import { + FY_SETTING_TO_SERVER_PT, + RP_CATEGORY_TO_FP_DEPENDENCIES, + SERVER_PT_TO_MULTI_CALENDAR_PT, + WEEKLY_START_TO_SERVER_PT, +} from '../utils/enabledPeriodTypes.js' +import { getFixedPeriodsOptions } from '../utils/fixedPeriods.js' +import { FINANCIAL, WEEKLY, filterPeriodTypesById } from '../utils/index.js' + +const v44Query = { + periodTypes: { + resource: 'configuration/dataOutputPeriodTypes', + params: { + fields: 'name,displayName,relativePeriods', + }, + }, + analysisRelativePeriod: { + resource: 'systemSettings/keyAnalysisRelativePeriod', + }, + weeklyStart: { + resource: 'systemSettings/analyticsWeeklyStart', + }, + financialYearStart: { + resource: 'systemSettings/analyticsFinancialYearStart', + }, +} + +// Server period types grouped by the relative period category they belong to. +// The category ids (e.g. WEEKLY, FINANCIAL) are part of the public API +// (excludedPeriodTypes, default selection) and the server has no equivalent. +// The first period type of each family is the standard one. +const RP_CATEGORY_TO_SERVER_PTS = { + ...RP_CATEGORY_TO_FP_DEPENDENCIES, + [FINANCIAL]: Object.values(FY_SETTING_TO_SERVER_PT), +} + +// Relative period categories whose relative periods refer to the start variant +// selected in the system settings (see relativePeriodStartTypes) +const START_SETTING_CATEGORIES = new Set([WEEKLY, FINANCIAL]) + +const getRelativeCategoryId = (serverPtName) => + Object.keys(RP_CATEGORY_TO_SERVER_PTS).find((categoryId) => + RP_CATEGORY_TO_SERVER_PTS[categoryId].includes(serverPtName) + ) + +const getRelativePeriodsFromPeriodType = (periodType) => + Object.entries(periodType.relativePeriods || {}).map( + ([id, { displayName }]) => ({ id, name: displayName }) + ) + +export const useV44DataOutputPeriodTypes = ({ enabled }) => { + const { data, error, refetch } = useDataQuery(v44Query, { lazy: true }) + + useEffect(() => { + if (enabled) { + refetch() + } + }, [enabled, refetch]) + + const enabledPeriodTypesData = useMemo(() => { + if (!enabled || error || !data?.periodTypes) { + return null + } + + // the server returns period types in natural order (days -> financial years) + const enabledTypes = data.periodTypes + + // relative period id -> name, used for overriding stored names + // (i.e. selected items in a saved visualization) + const metaData = enabledTypes + .flatMap(getRelativePeriodsFromPeriodType) + .reduce((acc, { id, name }) => { + acc[id] = { name } + return acc + }, {}) + + return { + version: 44, + enabledTypes, + analysisRelativePeriod: + data.analysisRelativePeriod?.keyAnalysisRelativePeriod || null, + // server period type names of the start variants the weekly and + // financial year relative periods refer to + relativePeriodStartTypes: { + [WEEKLY]: + WEEKLY_START_TO_SERVER_PT[ + data.weeklyStart?.analyticsWeeklyStart + ] || WEEKLY_START_TO_SERVER_PT.WEEKLY, + [FINANCIAL]: + FY_SETTING_TO_SERVER_PT[ + data.financialYearStart?.analyticsFinancialYearStart + ] || null, + }, + metaData, + noEnabledTypes: enabledTypes.length === 0, + } + }, [enabled, data, error]) + + return { enabledPeriodTypesData, loading: enabled && !data && !error } +} + +const getV44FixedOptions = ({ enabledTypes, periodsSettings }) => { + const clientOptionsById = getFixedPeriodsOptions(periodsSettings).reduce( + (acc, option) => { + acc[option.id] = option + return acc + }, + {} + ) + + return enabledTypes + .map((pt) => { + const option = + clientOptionsById[SERVER_PT_TO_MULTI_CALENDAR_PT[pt.name]] + + // server period types without client support for generating periods are skipped + return option && { ...option, name: pt.displayName || option.name } + }) + .filter(Boolean) +} + +const getV44RelativeOption = ( + categoryId, + enabledTypes, + relativePeriodStartTypes +) => { + // The server nests the relative periods of a family under every period + // type of the family, but they only refer to one of them: the start variant + // selected in the system settings for weekly and financial years, and the + // standard period type (first in the family) for the others, as there are + // no settings for e.g. quarterly or six-monthly start variants. The + // relative periods are only available when that period type is enabled. + const sourceTypeName = START_SETTING_CATEGORIES.has(categoryId) + ? relativePeriodStartTypes?.[categoryId] + : RP_CATEGORY_TO_SERVER_PTS[categoryId][0] + + const sourceType = enabledTypes.find((pt) => pt.name === sourceTypeName) + + const periods = sourceType + ? getRelativePeriodsFromPeriodType(sourceType) + : [] + + if (!periods.length) { + return null + } + + return { + id: categoryId, + name: sourceType.displayName, + getPeriods: () => periods, + } +} + +const getV44RelativeOptions = ({ enabledTypes, relativePeriodStartTypes }) => + [...new Set(enabledTypes.map((pt) => getRelativeCategoryId(pt.name)))] + .filter(Boolean) + .map((categoryId) => + getV44RelativeOption( + categoryId, + enabledTypes, + relativePeriodStartTypes + ) + ) + .filter(Boolean) + +export const getV44PeriodTypeOptions = ({ + enabledPeriodTypesData, + periodsSettings, + excludedPeriodTypes, +}) => { + const { enabledTypes, relativePeriodStartTypes } = enabledPeriodTypesData + + return { + fixedOptions: filterPeriodTypesById( + getV44FixedOptions({ enabledTypes, periodsSettings }), + excludedPeriodTypes + ), + relativeOptions: filterPeriodTypesById( + getV44RelativeOptions({ enabledTypes, relativePeriodStartTypes }), + excludedPeriodTypes + ), + } +}