@ministryofjustice/hmpps-digital-prison-reporting-frontend
Version:
The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.
464 lines (461 loc) • 18.6 kB
JavaScript
import dayjs from 'dayjs';
import customParse from '../../../../_virtual/customParseFormat.js';
import StartEndDatetUtils from '../start-end-date/utils.js';
import { QuickFilters, Granularity } from './types.js';
dayjs.extend(customParse);
const hasPartialStartEnd = (granularity, startDate, endDate) => {
let partialStart;
let partialEnd;
switch (granularity) {
case Granularity.DAILY:
partialStart = false;
partialEnd = false;
break;
case Granularity.MONTHLY:
{
const startOfMonth = dayjs(startDate).startOf('month');
const endOfMonth = dayjs(endDate).endOf('month');
partialStart = !startOfMonth.isSame(startDate, 'day');
partialEnd = !endOfMonth.isSame(endDate, 'day');
}
break;
case Granularity.ANNUALLY:
{
const startOfYear = dayjs(startDate).startOf('year');
const endOfYear = dayjs(endDate).endOf('year');
partialStart = !startOfYear.isSame(startDate, 'day');
partialEnd = !endOfYear.isSame(endDate, 'day');
}
break;
default:
partialStart = false;
partialEnd = false;
break;
}
return {
start: partialStart,
end: partialEnd,
};
};
const getQuickFilterOptions = () => {
const options = [
{ value: QuickFilters.NONE, text: 'None' },
{ value: QuickFilters.TODAY, text: 'Today' },
{ value: QuickFilters.PAST, text: 'Past:', disabled: true },
{ value: QuickFilters.YESTERDAY, text: 'Yesterday' },
{ value: QuickFilters.LAST_SEVEN_DAYS, text: 'Last 7 days' },
{ value: QuickFilters.LAST_THIRTY_DAYS, text: 'Last 30 days' },
{ value: QuickFilters.LAST_MONTH, text: 'Last month' },
{ value: QuickFilters.LAST_FULL_MONTH, text: 'Last full month' },
{ value: QuickFilters.LAST_NINETY_DAYS, text: 'Last 90 days' },
{ value: QuickFilters.LAST_THREE_MONTHS, text: 'Last 3 months' },
{ value: QuickFilters.LAST_FULL_THREE_MONTHS, text: 'Last full 3 months' },
{ value: QuickFilters.LAST_SIX_MONTHS, text: 'Last 6 months' },
{ value: QuickFilters.LAST_FULL_SIX_MONTHS, text: 'Last full 6 months' },
{ value: QuickFilters.LAST_YEAR, text: 'Last year' },
{ value: QuickFilters.LAST_FULL_YEAR, text: 'Last full year' },
{ value: QuickFilters.FUTURE, text: 'Future:', disabled: true },
{ value: QuickFilters.TOMORROW, text: 'Tomorrow' },
{ value: QuickFilters.NEXT_SEVEN_DAYS, text: 'Next 7 days' },
{ value: QuickFilters.NEXT_THIRTY_DAYS, text: 'Next 30 days' },
{ value: QuickFilters.NEXT_MONTH, text: 'Next month' },
{ value: QuickFilters.NEXT_FULL_MONTH, text: 'Next full month' },
{ value: QuickFilters.NEXT_NINETY_DAYS, text: 'Next 90 days' },
{ value: QuickFilters.NEXT_THREE_MONTHS, text: 'Next 3 months' },
{ value: QuickFilters.NEXT_FULL_THREE_MONTHS, text: 'Next full 3 months' },
{ value: QuickFilters.NEXT_SIX_MONTHS, text: 'Next 6 months' },
{ value: QuickFilters.NEXT_FULL_SIX_MONTHS, text: 'Next full 6 months' },
{ value: QuickFilters.NEXT_YEAR, text: 'Next year' },
{ value: QuickFilters.NEXT_FULL_YEAR, text: 'Next full year' },
];
return options;
};
const getGranularityOptions = () => {
const options = [
{ value: Granularity.HOURLY, text: 'Hourly' },
{ value: Granularity.DAILY, text: 'Daily' },
// { value: Granularity.WEEKLY, text: 'Weekly' },
{ value: Granularity.MONTHLY, text: 'Monthly' },
// { value: Granularity.QUARTERLY, text: 'Quarterly' },
{ value: Granularity.ANNUALLY, text: 'Annually' },
];
return options;
};
const setGranularityValue = (quickFilterValue, granularityValue) => {
let granularity = granularityValue;
const invalidDailyValues = ['annually', 'monthly'];
const invalidMonthlyValues = ['annually'];
if (quickFilterValue.includes('month') && invalidMonthlyValues.includes(quickFilterValue)) {
granularity = Granularity.MONTHLY;
}
if (quickFilterValue.includes('day') && invalidDailyValues.includes(quickFilterValue)) {
granularity = Granularity.DAILY;
}
return granularity;
};
const setDateRangeFromQuickFilterValue = (value) => {
let startDate;
let endDate;
let granularity;
switch (value) {
case QuickFilters.TODAY:
endDate = dayjs();
startDate = dayjs();
granularity = Granularity.DAILY;
break;
case QuickFilters.YESTERDAY:
endDate = dayjs().subtract(1, 'day');
startDate = dayjs().subtract(1, 'day');
granularity = Granularity.DAILY;
break;
case QuickFilters.LAST_SEVEN_DAYS:
endDate = dayjs();
startDate = endDate.subtract(7, 'day').add(1, 'day');
granularity = Granularity.DAILY;
break;
case QuickFilters.LAST_THIRTY_DAYS:
endDate = dayjs();
startDate = endDate.subtract(30, 'day').add(1, 'day');
granularity = Granularity.DAILY;
break;
case QuickFilters.LAST_MONTH:
endDate = dayjs();
startDate = endDate.subtract(1, 'month').add(1, 'day');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.LAST_FULL_MONTH:
endDate = dayjs().subtract(1, 'month').endOf('month');
startDate = endDate.startOf('month');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.LAST_NINETY_DAYS:
endDate = dayjs();
startDate = endDate.subtract(90, 'day').add(1, 'day');
granularity = Granularity.DAILY;
break;
case QuickFilters.LAST_THREE_MONTHS:
endDate = dayjs();
startDate = endDate.subtract(3, 'month').add(1, 'day');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.LAST_FULL_THREE_MONTHS:
endDate = dayjs().subtract(1, 'month').endOf('month');
startDate = endDate.subtract(2, 'month').startOf('month');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.LAST_SIX_MONTHS:
endDate = dayjs();
startDate = endDate.subtract(6, 'month').add(1, 'day');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.LAST_FULL_SIX_MONTHS:
endDate = dayjs().subtract(1, 'month').endOf('month');
startDate = endDate.subtract(6, 'month').startOf('month');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.LAST_YEAR:
endDate = dayjs();
startDate = endDate.subtract(1, 'year').add(1, 'day');
granularity = Granularity.ANNUALLY;
break;
case QuickFilters.LAST_FULL_YEAR:
endDate = dayjs().subtract(1, 'year').endOf('year');
startDate = endDate.startOf('year');
granularity = Granularity.ANNUALLY;
break;
case QuickFilters.TOMORROW:
endDate = dayjs().add(1, 'day');
startDate = dayjs().add(1, 'day');
granularity = Granularity.DAILY;
break;
case QuickFilters.NEXT_SEVEN_DAYS:
startDate = dayjs();
endDate = dayjs().add(7, 'day').subtract(1, 'day');
granularity = Granularity.DAILY;
break;
case QuickFilters.NEXT_THIRTY_DAYS:
startDate = dayjs();
endDate = dayjs().add(30, 'day').subtract(1, 'day');
granularity = Granularity.DAILY;
break;
case QuickFilters.NEXT_MONTH:
startDate = dayjs();
endDate = dayjs().add(1, 'month').subtract(1, 'day');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.NEXT_FULL_MONTH:
startDate = dayjs().add(1, 'month').startOf('month');
endDate = startDate.endOf('month');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.NEXT_NINETY_DAYS:
startDate = dayjs();
endDate = dayjs().add(90, 'day').subtract(1, 'day');
granularity = Granularity.DAILY;
break;
case QuickFilters.NEXT_THREE_MONTHS:
startDate = dayjs();
endDate = dayjs().add(3, 'month').subtract(1, 'day');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.NEXT_FULL_THREE_MONTHS:
startDate = dayjs().add(1, 'month').startOf('month');
endDate = startDate.add(2, 'month').endOf('month');
granularity = Granularity.MONTHLY;
break;
case QuickFilters.NEXT_YEAR:
startDate = dayjs();
endDate = dayjs().add(1, 'year').subtract(1, 'day');
granularity = Granularity.ANNUALLY;
break;
case QuickFilters.NEXT_FULL_YEAR:
startDate = dayjs().add(1, 'year').startOf('year');
endDate = startDate.endOf('year');
granularity = Granularity.ANNUALLY;
break;
default:
endDate = dayjs();
startDate = dayjs();
granularity = Granularity.DAILY;
break;
}
return {
start: startDate.format('YYYY-MM-DD').toString(),
end: endDate.format('YYYY-MM-DD').toString(),
granularity,
};
};
const getOptionDisplayValue = (value, options) => {
const item = options.find(opt => {
return opt.value === value;
});
return item?.text || value;
};
const setValueFromRequest = (filter, req, prefix) => {
const { preventDefault } = req.query;
const quickFilter = req.query[`${prefix}${filter.name}.quick-filter`];
let granularity = req.query[`${prefix}${filter.name}.granularity`];
let start;
let end;
if (quickFilter && quickFilter !== QuickFilters.NONE) {
({ start, end } = setDateRangeFromQuickFilterValue(quickFilter));
granularity = setGranularityValue(quickFilter, granularity);
}
else {
granularity = req.query[`${prefix}${filter.name}.granularity`];
start = req.query[`${prefix}${filter.name}.start`];
end = req.query[`${prefix}${filter.name}.end`];
}
const defaultStart = preventDefault ? null : filter.value?.start;
const defaultEnd = preventDefault ? null : filter.value?.end;
const defaultGranularity = preventDefault ? Granularity.DAILY : filter.value?.granularity.value;
const defaultQuickFilter = preventDefault ? 'none' : filter.value?.quickFilter?.value;
const granularityOptions = getGranularityOptions();
const quickFilterOptions = getQuickFilterOptions();
const startDate = start || defaultStart || filter.min || '';
const endDate = end || defaultEnd || filter.max || '';
const granularityValue = granularity || defaultGranularity || Granularity.NONE;
const quickFilterValue = quickFilter || defaultQuickFilter || QuickFilters.NONE;
const value = {
start: startDate,
end: endDate,
granularity: {
value: granularityValue,
display: getOptionDisplayValue(granularityValue, granularityOptions),
},
quickFilter: {
value: quickFilterValue,
display: getOptionDisplayValue(quickFilterValue, quickFilterOptions),
},
partialDate: {
...hasPartialStartEnd(granularityValue, startDate, endDate),
},
};
return value;
};
const setDefaultValue = (req, name) => {
const dateRangeName = name.split('.')[0];
const granularDateRangeDefaults = Object.keys(req.body)
.filter(key => key.includes(dateRangeName))
.map(key => {
return { name: key, value: req.body[key] };
});
let granularDateRangeValue = {
start: '',
end: '',
granularity: Granularity.NONE,
quickFilter: QuickFilters.NONE,
};
granularDateRangeDefaults.forEach(dateRangeDefault => {
if (dateRangeDefault.name.includes('start')) {
granularDateRangeValue.start = dateRangeDefault.value;
}
if (dateRangeDefault.name.includes('end')) {
granularDateRangeValue.end = dateRangeDefault.value;
}
if (dateRangeDefault.name.includes('granularity')) {
granularDateRangeValue.granularity = dateRangeDefault.value;
}
if (dateRangeDefault.name.includes('granularity')) {
granularDateRangeValue.quickFilter = dateRangeDefault.value;
}
});
granularDateRangeValue =
granularDateRangeValue.start !== '' &&
granularDateRangeValue.end !== '' &&
granularDateRangeValue.granularity &&
granularDateRangeValue.quickFilter
? granularDateRangeValue
: '';
return { value: granularDateRangeValue, name: dateRangeName };
};
const setFilterValueFromDefault = (defaultValue, filter) => {
const { granularityOptions, quickFilterOptions } = filter;
const { granularity, quickFilter, start, end } = defaultValue.value;
const value = {
start: start ? dayjs(start, 'D/M/YYYY').format('YYYY-MM-DD').toString() : '',
end: end ? dayjs(end, 'D/M/YYYY').format('YYYY-MM-DD').toString() : '',
granularity: {
value: granularity,
display: granularityOptions?.find(o => o.value === granularity)?.text || granularity,
},
quickFilter: {
value: quickFilter,
display: quickFilterOptions?.find(o => o.value === quickFilter)?.text || quickFilter,
},
};
return {
...filter,
value,
};
};
const getFilterFromDefinition = (filter, filterData) => {
let value;
const quickFilterValue = filter.defaultQuickFilterValue;
const granularityOptions = getGranularityOptions();
const quickFilterOptions = getQuickFilterOptions();
if (quickFilterValue) {
const { start, end, granularity } = setDateRangeFromQuickFilterValue(quickFilterValue);
value = {
start,
end,
granularity: {
value: granularity,
display: getOptionDisplayValue(granularity, granularityOptions),
},
quickFilter: {
value: quickFilterValue,
display: getOptionDisplayValue(quickFilterValue, quickFilterOptions),
},
partialDate: {
...hasPartialStartEnd(granularity, start, end),
},
};
}
else {
value = StartEndDatetUtils.getStartAndEndValueFromDefinition(filter);
const granularityValue = value.granularity
? value.granularity.value
: filter.defaultGranularity || Granularity.DAILY;
value = {
...value,
granularity: {
value: granularityValue,
display: getOptionDisplayValue(granularityValue, granularityOptions),
},
quickFilter: {
value: QuickFilters.NONE,
display: 'None',
},
partialDate: {
...hasPartialStartEnd(granularityValue, value.start, value.end),
},
};
}
return {
...filterData,
min: filter.min,
max: filter.max,
value,
quickFilterOptions: getQuickFilterOptions(),
granularityOptions: getGranularityOptions(),
};
};
/**
* Creates and appends the granular daterange query string
* to an existing URLSearchParams
*
* @param {URLSearchParams} params
* @param {string} fieldName
* @param {string} defaultValue
*/
const appendGranularDateRangeValue = (params, fieldName, options) => {
const { start, end, granularity, quickFilter } = options;
if (quickFilter) {
params.append(`filters.${fieldName}.quick-filter`, quickFilter);
}
if (granularity) {
params.append(`filters.${fieldName}.granularity`, granularity);
}
if (start) {
params.append(`filters.${fieldName}.start`, start);
}
if (end) {
params.append(`filters.${fieldName}.end`, end);
}
};
/**
* Resolves default values for a `granulardaterange` filter definition.
*
* Applies precedence rules:
* 1. `defaultQuickFilterValue` (derives start, end, and granularity)
* 2. `defaultGranularity` combined with `defaultValue` (explicit range)
* 3. `defaultGranularity` only
*
* Returns plain resolved values suitable for query construction, or
* `undefined` if the definition does not specify any usable defaults.
*
* @param {components['schemas']['FilterDefinition']} filter
* @return {*} {(GranularDateRangeDefaults | undefined)}
*/
const resolveGranularDateRangeDefaults = (filter) => {
// Case 1: quick filter wins
if (filter.defaultQuickFilterValue) {
const { start, end, granularity } = setDateRangeFromQuickFilterValue(filter.defaultQuickFilterValue);
return {
quickFilter: filter.defaultQuickFilterValue,
granularity,
...(start !== undefined ? { start } : {}),
...(end !== undefined ? { end } : {}),
};
}
// Case 2: granularity + explicit default range
if (filter.defaultGranularity && filter.defaultValue) {
const [start, end] = filter.defaultValue
.split(' - ')
.map(v => v.trim())
.filter(Boolean);
return {
granularity: filter.defaultGranularity,
...(start !== undefined ? { start } : {}),
...(end !== undefined ? { end } : {}),
};
}
// Case 3: granularity only
if (filter.defaultGranularity) {
return {
granularity: filter.defaultGranularity,
};
}
return undefined;
};
var GranularDateRangeInputUtils = {
getFilterFromDefinition,
setValueFromRequest,
setDefaultValue,
setFilterValueFromDefault,
appendGranularDateRangeValue,
resolveGranularDateRangeDefaults,
};
export { appendGranularDateRangeValue, GranularDateRangeInputUtils as default, getFilterFromDefinition, resolveGranularDateRangeDefaults, setDefaultValue, setFilterValueFromDefault, setValueFromRequest };
//# sourceMappingURL=utils.js.map