Files
mtdb_movie/common/resources/client/charts/data/format-report-data.ts
maher 703f50a09d
Some checks failed
Build / run (push) Has been cancelled
first commit
2025-10-29 11:42:25 +01:00

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,
},
);