refactoring dashboard

This commit is contained in:
Emily
2025-01-23 17:34:43 +01:00
parent afeaac1b0d
commit e4bdf7e4c3
112 changed files with 2345 additions and 12532 deletions

View File

@@ -0,0 +1,224 @@
import dayjs from 'dayjs';
import * as fns from 'date-fns';
export type Slice = keyof typeof slicesData;
const slicesData = {
hour: {},
day: {},
week: {},
month: {},
year: {}
}
const startOfFunctions: { [key in Slice]: (date: Date) => Date } = {
hour: fns.startOfHour,
day: fns.startOfDay,
week: fns.startOfWeek,
month: fns.startOfMonth,
year: fns.startOfYear
};
const endOfFunctions: { [key in Slice]: (date: Date) => Date } = {
hour: fns.endOfHour,
day: fns.endOfDay,
week: fns.endOfWeek,
month: fns.endOfMonth,
year: fns.endOfYear
};
class DateService {
public slicesData = slicesData;
getChartLabelFromISO(iso: string, offset: number, slice: Slice) {
const date = new Date(new Date(iso).getTime() + offset * 1000 * 60);
if (slice === 'hour') return fns.format(date, 'HH:mm');
if (slice === 'day') return fns.format(date, 'dd/MM');
if (slice === 'week') return fns.format(date, 'dd/MM');
if (slice === 'month') return fns.format(date, 'MMMM');
if (slice === 'year') return fns.format(date, 'YYYY');
return iso;
}
canUseSlice(from: string | number | Date, to: string | number | Date, slice: Slice) {
const daysDiff = fns.differenceInDays(
new Date(new Date(to).getTime() + 1000),
new Date(from)
);
return this.canUseSliceFromDays(daysDiff, slice);
}
canUseSliceFromDays(days: number, slice: Slice): [false, string] | [true, number] {
// 3 Days
if (slice === 'hour' && (days > 3)) return [false, 'Date gap too big for this slice'];
// 3 Weeks
if (slice === 'day' && (days > 31)) return [false, 'Date gap too big for this slice'];
// 3 Years
if (slice === 'month' && (days > 365 * 3)) return [false, 'Date gap too big for this slice'];
// 2 days
if (slice === 'day' && (days < 2)) return [false, 'Date gap too small for this slice'];
// 2 month
if (slice === 'month' && (days < 31 * 2)) return [false, 'Date gap too small for this slice'];
return [true, days]
}
startOfSlice(date: Date, slice: Slice) {
const fn = startOfFunctions[slice];
if (!fn) throw Error(`startOfFunction of slice ${slice} not found`);
return fn(date);
}
endOfSlice(date: Date, slice: Slice) {
const fn = endOfFunctions[slice];
if (!fn) throw Error(`endOfFunction of slice ${slice} not found`);
return fn(date);
}
getGranularityData(slice: Slice, dateField: string) {
const dateFromParts: Record<string, any> = {};
let granularity;
switch (slice) {
case 'hour':
dateFromParts.hour = { $hour: { date: dateField } }
granularity = granularity || 'hour';
case 'day':
dateFromParts.day = { $dayOfMonth: { date: dateField } }
granularity = granularity || 'day';
case 'month':
dateFromParts.month = { $month: { date: dateField } }
granularity = granularity || 'month';
case 'year':
dateFromParts.year = { $year: { date: dateField } }
granularity = granularity || 'year';
}
return { dateFromParts, granularity }
}
/**
* @deprecated interal to generateDateSlices
*/
prepareDateRange(from: string, to: string, slice: Slice) {
let fromDate = dayjs(from).minute(0).second(0).millisecond(0);
let toDate = dayjs(to).minute(0).second(0).millisecond(0);
switch (slice) {
case 'day':
fromDate = fromDate.hour(0);
toDate = toDate.hour(0);
break;
case 'hour':
break;
}
return {
from: fromDate.toDate(),
to: toDate.toDate()
}
}
/**
* @deprecated interal to generateDateSlices
*/
createBetweenDates(from: string, to: string, slice: Slice) {
let start = dayjs(from);
const end = dayjs(to);
const filledDates: dayjs.Dayjs[] = [];
while (start.isBefore(end) || start.isSame(end)) {
filledDates.push(start);
start = start.add(1, slice);
}
return { dates: filledDates, from, to };
}
/**
* @deprecated use generateDateSlices
*/
fillDates(dates: string[], slice: Slice) {
const allDates: dayjs.Dayjs[] = [];
const firstDate = dayjs(dates.at(0));
const lastDate = dayjs(dates.at(-1));
let currentDate = firstDate.clone();
allDates.push(currentDate);
while (currentDate.isBefore(lastDate, slice)) {
currentDate = currentDate.add(1, slice);
allDates.push(currentDate);
}
return allDates;
}
/**
* @deprecated use mergeDates
*/
mergeFilledDates<T extends Record<string, any>, K extends keyof T>(dates: dayjs.Dayjs[], items: T[], dateField: K, slice: Slice, fillData: Omit<T, K>) {
const result = new Array<T>();
for (const date of dates) {
const item = items.find(e => dayjs(e[dateField]).isSame(date, slice));
result.push(item ?? { ...fillData, [dateField]: date.format() } as T);
}
return result;
}
generateDateSlices(slice: Slice, fromDate: Date, toDate: Date) {
const slices: Date[] = [];
let currentDate = fromDate;
const addFunctions: { [key in Slice]: any } = { hour: fns.addHours, day: fns.addDays, week: fns.addWeeks, month: fns.addMonths, year: fns.addYears };
const addFunction = addFunctions[slice];
if (!addFunction) { throw new Error(`Invalid slice: ${slice}`); }
while (fns.isBefore(currentDate, toDate) || currentDate.getTime() === toDate.getTime()) {
slices.push(currentDate);
currentDate = addFunction(currentDate, 1);
}
return slices;
}
isSameDayUTC(a: Date, b: Date) {
return a.getUTCFullYear() === b.getUTCFullYear() && a.getUTCMonth() === b.getUTCMonth() && a.getUTCDate() === b.getUTCDate();
}
mergeDates(timeline: { _id: string, count: number }[], allDates: Date[], slice: Slice) {
const result: { _id: string, count: number }[] = [];
const isSames: { [key in Slice]: any } = { hour: fns.isSameHour, day: this.isSameDayUTC, week: fns.isSameWeek, month: fns.isSameMonth, year: fns.isSameYear, }
const isSame = isSames[slice];
if (!isSame) {
throw new Error(`Invalid slice: ${slice}`);
}
for (const date of allDates) {
result.push({ _id: date.toISOString(), count: 0 });
for (const element of timeline) {
const elementDate = new Date(element._id);
if (isSame(elementDate, date)) {
const existingEntry = result.find(item => isSame(date, new Date(item._id)));
if (!existingEntry) throw new Error('THIS CANNOT HAPPEN');
existingEntry.count += element.count;
}
}
}
return result;
}
}
const dateServiceInstance = new DateService();
export default dateServiceInstance;