All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m11s
126 lines
4.5 KiB
TypeScript
126 lines
4.5 KiB
TypeScript
import type { DateRange } from './types';
|
|
|
|
export type DateBucket = 'day' | 'week' | 'month';
|
|
|
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
|
|
export const getRangeDayCount = (dateRange: DateRange) => (
|
|
Math.max(1, Math.ceil((dateRange.end.getTime() - dateRange.start.getTime()) / MS_PER_DAY) + 1)
|
|
);
|
|
|
|
export const getAutoDateBucket = (dateRange: DateRange): DateBucket => {
|
|
const days = getRangeDayCount(dateRange);
|
|
if (days > 365) return 'month';
|
|
if (days > 90) return 'week';
|
|
return 'day';
|
|
};
|
|
|
|
export const parseChartDate = (value: string): Date | null => {
|
|
if (!value) return null;
|
|
|
|
const isoMatch = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
if (isoMatch) {
|
|
const [, year, month, day] = isoMatch;
|
|
return new Date(Number(year), Number(month) - 1, Number(day));
|
|
}
|
|
|
|
const localMatch = value.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
|
|
if (localMatch) {
|
|
const [, day, month, year] = localMatch;
|
|
return new Date(Number(year), Number(month) - 1, Number(day));
|
|
}
|
|
|
|
const dashedLocalMatch = value.match(/^(\d{2})-(\d{2})-(\d{4})$/);
|
|
if (dashedLocalMatch) {
|
|
const [, day, month, year] = dashedLocalMatch;
|
|
return new Date(Number(year), Number(month) - 1, Number(day));
|
|
}
|
|
|
|
const date = new Date(value);
|
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
};
|
|
|
|
export const formatChartDateKey = (date: Date) => {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
const startOfWeek = (date: Date) => {
|
|
const nextDate = new Date(date);
|
|
const day = nextDate.getDay();
|
|
const offset = day === 0 ? -6 : 1 - day;
|
|
nextDate.setDate(nextDate.getDate() + offset);
|
|
nextDate.setHours(0, 0, 0, 0);
|
|
return nextDate;
|
|
};
|
|
|
|
export const getDateBucketKey = (value: string, bucket: DateBucket) => {
|
|
const date = parseChartDate(value);
|
|
if (!date) return value;
|
|
if (bucket === 'day') return formatChartDateKey(date);
|
|
if (bucket === 'week') return formatChartDateKey(startOfWeek(date));
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
|
};
|
|
|
|
export const formatDateBucketLabel = (value: string, bucket: DateBucket) => {
|
|
if (bucket === 'month') {
|
|
const match = value.match(/^(\d{4})-(\d{2})$/);
|
|
if (!match) return value;
|
|
const [, year, month] = match;
|
|
return new Intl.DateTimeFormat('pt-BR', { month: 'short', year: '2-digit' }).format(new Date(Number(year), Number(month) - 1, 1));
|
|
}
|
|
|
|
const date = parseChartDate(value);
|
|
if (!date) return value;
|
|
if (bucket === 'week') {
|
|
const endDate = new Date(date);
|
|
endDate.setDate(endDate.getDate() + 6);
|
|
const formatter = new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit' });
|
|
return `${formatter.format(date)}-${formatter.format(endDate)}`;
|
|
}
|
|
|
|
return new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit' }).format(date);
|
|
};
|
|
|
|
export const formatDateBucketLongLabel = (value: string, bucket: DateBucket) => {
|
|
if (bucket === 'month') {
|
|
const match = value.match(/^(\d{4})-(\d{2})$/);
|
|
if (!match) return value;
|
|
const [, year, month] = match;
|
|
return new Intl.DateTimeFormat('pt-BR', { month: 'long', year: 'numeric' }).format(new Date(Number(year), Number(month) - 1, 1));
|
|
}
|
|
|
|
const date = parseChartDate(value);
|
|
if (!date) return value;
|
|
if (bucket === 'week') {
|
|
const endDate = new Date(date);
|
|
endDate.setDate(endDate.getDate() + 6);
|
|
const formatter = new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
return `${formatter.format(date)} - ${formatter.format(endDate)}`;
|
|
}
|
|
|
|
return new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric' }).format(date);
|
|
};
|
|
|
|
export const getMovingAverageWindow = (bucket: DateBucket) => {
|
|
if (bucket === 'day') return 7;
|
|
if (bucket === 'week') return 4;
|
|
return 3;
|
|
};
|
|
|
|
export const averageRecentValues = (values: number[], endIndex: number, windowSize: number) => {
|
|
const startIndex = Math.max(0, endIndex - windowSize + 1);
|
|
const windowValues = values.slice(startIndex, endIndex + 1);
|
|
if (!windowValues.length) return 0;
|
|
return windowValues.reduce((total, value) => total + value, 0) / windowValues.length;
|
|
};
|
|
|
|
export const normalizeUnknownLabel = (value: string, fallback: string) => {
|
|
const normalized = String(value || '').replace(/\s+/g, ' ').trim();
|
|
if (!normalized) return fallback;
|
|
if (/^(0|null|undefined|unknown|n\/a|-|sem nome)$/i.test(normalized)) return fallback;
|
|
return normalized;
|
|
};
|