Files
ezbookkeeping/src/components/base/DateRangeSelectionBase.ts
T
Sebastian Reategui b94dc8eb83 Feature - Add support for a fiscal year period defined in user settings.
* Add "This fiscal year", "Last fiscal year" as date range options in Transaction Details to filter transactions to those periods
* Add fiscal year ranges to Statistics & Trend Analysis
* Add "fiscal year start date" to user profile settings, allowing the user to select any date of the calendar year as the start of the fiscal year
* Add "fiscal year format" to user profile settings, allowing the user to specify how financial year date labels should appear

Implementation notes:
* The default fiscal year start is January 1 and the default fiscal year display format is "FY 2025"
* Fiscal year start date (month number & day number) are stored together in db as a uint16, high byte & low byte respectively
* February 29 is disallowed as a fiscal year start date, since it is never used as a convention in any country
* Jest is added to the project as a dev dependency, for unit tests in frontend

Signed-off-by: Sebastian Reategui <seb.reategui@gmail.com>
2025-06-07 22:04:47 +08:00

142 lines
4.9 KiB
TypeScript

import { ref, computed } from 'vue';
import { type TimeRangeAndDateType, type PresetDateRange, type UnixTimeRange, DateRange } from '@/core/datetime.ts';
import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
import {
getCurrentUnixTime,
getCurrentYear,
getUnixTime,
getLocalDatetimeFromUnixTime,
getTodayFirstUnixTime,
getDummyUnixTimeForLocalUsage,
getActualUnixTimeForStore,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getDateRangeByDateType
} from '@/lib/datetime.ts';
import { useI18n } from '@/locales/helpers.ts';
import { useUserStore } from '@/stores/user.ts';
export interface CommonDateRangeSelectionProps {
minTime?: number;
maxTime?: number;
title?: string;
hint?: string;
show: boolean;
}
function getDateRangeFromProps(props: CommonDateRangeSelectionProps): { minDate: number; maxDate: number } {
let minDate = getTodayFirstUnixTime();
let maxDate = getCurrentUnixTime();
if (props.minTime) {
minDate = props.minTime;
}
if (props.maxTime) {
maxDate = props.maxTime;
}
return {
minDate,
maxDate
};
}
export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps) {
const { tt, getAllMinWeekdayNames, formatUnixTimeToLongDateTime, isLongDateMonthAfterYear, isLongTime24HourFormat } = useI18n();
const userStore = useUserStore();
const { minDate, maxDate } = getDateRangeFromProps(props);
const yearRange = ref<number[]>([
2000,
getCurrentYear() + 1
]);
const dateRange = ref<Date[]>([
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(minDate, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())),
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(maxDate, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))
]);
const firstDayOfWeek = computed<number>(() => userStore.currentUserFirstDayOfWeek);
const dayNames = computed<string[]>(() => arrangeArrayWithNewStartIndex(getAllMinWeekdayNames(), firstDayOfWeek.value));
const isYearFirst = computed<boolean>(() => isLongDateMonthAfterYear());
const is24Hour = computed<boolean>(() => isLongTime24HourFormat());
const beginDateTime = computed<string>(() => {
const actualBeginUnixTime = getActualUnixTimeForStore(getUnixTime(dateRange.value[0]), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return formatUnixTimeToLongDateTime(actualBeginUnixTime);
});
const endDateTime = computed<string>(() => {
const actualEndUnixTime = getActualUnixTimeForStore(getUnixTime(dateRange.value[1]), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return formatUnixTimeToLongDateTime(actualEndUnixTime);
});
const presetRanges = computed<PresetDateRange[]>(() => {
const presetRanges:PresetDateRange[] = [];
[
DateRange.Today,
DateRange.LastSevenDays,
DateRange.LastThirtyDays,
DateRange.ThisWeek,
DateRange.ThisMonth,
DateRange.ThisYear,
DateRange.LastYear,
DateRange.ThisFiscalYear,
DateRange.LastFiscalYear
].forEach(dateRangeType => {
const dateRange = getDateRangeByDateType(dateRangeType.type, firstDayOfWeek.value, userStore.currentUserFiscalYearStart) as TimeRangeAndDateType;
presetRanges.push({
label: tt(dateRangeType.name),
value: [
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dateRange.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())),
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dateRange.maxTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))
]
});
});
return presetRanges;
});
function getFinalDateRange(): UnixTimeRange | null {
if (!dateRange.value[0] || !dateRange.value[1]) {
return null;
}
const currentMinDate = dateRange.value[0];
const currentMaxDate = dateRange.value[1];
let minUnixTime = getUnixTime(currentMinDate);
let maxUnixTime = getUnixTime(currentMaxDate);
if (minUnixTime < 0 || maxUnixTime < 0) {
throw new Error('Date is too early');
}
minUnixTime = getActualUnixTimeForStore(minUnixTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
maxUnixTime = getActualUnixTimeForStore(maxUnixTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return {
minUnixTime,
maxUnixTime
};
}
return {
// states
yearRange,
dateRange,
// computed states
dayNames,
isYearFirst,
is24Hour,
beginDateTime,
endDateTime,
presetRanges,
// functions
getFinalDateRange
};
}