166 lines
4.4 KiB
TypeScript
Executable File
166 lines
4.4 KiB
TypeScript
Executable File
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<FormattedDatasetItem, 'value'>;
|
|
|
|
export function formatReportData(
|
|
report: ReportMetric<DatasetItem> | 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<T extends ChartType = ChartType>(
|
|
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<FormattedDatasetItem, 'value'> {
|
|
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,
|
|
},
|
|
);
|