b94dc8eb83
* 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>
142 lines
4.9 KiB
TypeScript
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
|
|
};
|
|
}
|