import {DateFormatter, parseAbsoluteToLocal} from '@internationalized/date'; import memoize from 'nano-memoize'; import {ChartType} from 'chart.js'; import { FormattedDatasetItem, FormattedReportData, } from './formatted-dataset-item'; import { DatasetItem, RangedDatasetGranularity, ReportMetric, } from '../../admin/analytics/report-metric'; import {shallowEqual} from '../../utils/shallow-equal'; interface Options { localeCode: string; shareFirstDatasetLabels?: boolean; } type FormattedDatasetLabels = Omit; export function formatReportData( report: ReportMetric | undefined, {localeCode = 'en', shareFirstDatasetLabels = true}: Options, ): FormattedReportData { if (!report) return {datasets: []}; const firstDatasetLabels: FormattedDatasetLabels[] = []; return { ...report, datasets: report.datasets.map((dataset, datasetIndex) => { const data = dataset.data.map((datasetItem, itemIndex) => { let label: FormattedDatasetLabels; // when there are multiple datasets, we'll need to use labels from the first dataset, so charts are // overlapped over one another, otherwise they will be side by side, if labels in all datasets are not identical. if (datasetIndex === 0 || !shareFirstDatasetLabels) { label = generateDatasetLabels( datasetItem, report.granularity, localeCode, ); firstDatasetLabels[itemIndex] = label; } else { label = firstDatasetLabels[itemIndex]; } return { ...label, value: datasetItem.value, }; }); return {...dataset, data}; }), }; } function generateDatasetLabels( datum: DatasetItem, granularity: RangedDatasetGranularity | undefined, locale: string, ): FormattedDatasetLabels { if (datum.label) { return {label: datum.label}; } if (!datum.date) { return {label: ''}; } return generateTimeLabels(datum, granularity, locale); } function generateTimeLabels( {date: isoDate, endDate: isoEndDate}: DatasetItem, granularity: RangedDatasetGranularity | undefined = 'day', locale: string, ): Omit { const date = parseAbsoluteToLocal(isoDate!).toDate(); const endDate = isoEndDate ? parseAbsoluteToLocal(isoEndDate).toDate() : null; switch (granularity) { case 'minute': return { label: getFormatter(locale, { second: '2-digit', }).format(date), tooltipTitle: getFormatter(locale, { day: '2-digit', hour: 'numeric', minute: 'numeric', second: '2-digit', }).format(date), }; case 'hour': return { label: getFormatter(locale, { hour: 'numeric', minute: 'numeric', }).format(date), tooltipTitle: getFormatter(locale, { month: 'short', day: '2-digit', hour: 'numeric', minute: 'numeric', }).format(date), }; case 'day': return { label: getFormatter(locale, { day: '2-digit', weekday: 'short', }).format(date), tooltipTitle: getFormatter(locale, { day: '2-digit', weekday: 'short', month: 'short', }).format(date), }; case 'week': return { label: getFormatter(locale, { month: 'short', day: '2-digit', }).format(date), tooltipTitle: getFormatter(locale, { day: '2-digit', month: 'long', year: 'numeric', }).formatRange(date, endDate as Date), }; case 'month': return { label: getFormatter(locale, { month: 'short', year: 'numeric', }).format(date), tooltipTitle: getFormatter(locale, { month: 'long', year: 'numeric', }).format(date), }; case 'year': return { label: getFormatter(locale, { year: 'numeric', }).format(date), tooltipTitle: getFormatter(locale, { year: 'numeric', }).format(date), }; } } const getFormatter = memoize( (locale, options: Intl.DateTimeFormatOptions) => { return new DateFormatter(locale, options); }, { equals: (a, b) => { return shallowEqual(a, b); }, callTimeout: undefined as any, }, );