Files
ln-bi/src/modules/mileage/drill-context.ts
T

118 lines
4.0 KiB
TypeScript

import type { MileageSourceGroup } from './types';
export type MileageDrillLevel = 'overview' | 'breakdown' | 'vehicle';
export type MileageBreakdownDimension =
| 'department'
| 'region'
| 'customer'
| 'vehicle-model'
| 'assessment-target';
export interface MileageDrillContext {
level: MileageDrillLevel;
targetId?: number;
dimension?: MileageBreakdownDimension;
dimensionValue?: string;
plate?: string;
startDate?: string;
endDate?: string;
sourcePriority: MileageSourceGroup[];
}
interface LocationParts {
pathname: string;
search: string;
hash: string;
}
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
const BREAKDOWN_DIMENSIONS = new Set<MileageBreakdownDimension>([
'department',
'region',
'customer',
'vehicle-model',
'assessment-target',
]);
function validDate(value: string | null): string | undefined {
if (!value || !DATE_PATTERN.test(value)) return undefined;
const timestamp = Date.parse(`${value}T00:00:00+08:00`);
return Number.isFinite(timestamp) ? value : undefined;
}
function parseSourcePriority(value: string | null): MileageSourceGroup[] {
if (!value) return ['instrument'];
const sources = value
.split(',')
.filter((source): source is MileageSourceGroup => source === 'instrument' || source === 'gps');
return Array.from(new Set(sources)).slice(0, 2).length > 0
? Array.from(new Set(sources)).slice(0, 2)
: ['instrument'];
}
export function parseMileageDrillContext(search: string): MileageDrillContext {
const params = new URLSearchParams(search);
const rawTargetId = Number(params.get('mileageTarget'));
const targetId = Number.isInteger(rawTargetId) && rawTargetId > 0 ? rawTargetId : undefined;
const plate = params.get('mileagePlate')?.trim() || undefined;
const rawDimension = params.get('mileageDimension');
const dimension = rawDimension && BREAKDOWN_DIMENSIONS.has(rawDimension as MileageBreakdownDimension)
? rawDimension as MileageBreakdownDimension
: undefined;
const dimensionValue = params.get('mileageValue')?.trim() || undefined;
const requestedLevel = params.get('mileageLevel');
let level: MileageDrillLevel = 'overview';
if (requestedLevel === 'vehicle' && plate) level = 'vehicle';
if (requestedLevel === 'breakdown' && dimension && dimensionValue) level = 'breakdown';
const context: MileageDrillContext = {
level,
sourcePriority: parseSourcePriority(params.get('mileageSource')),
};
if (targetId) context.targetId = targetId;
if (level === 'breakdown') {
context.dimension = dimension;
context.dimensionValue = dimensionValue;
}
if (level === 'vehicle') context.plate = plate;
const startDate = validDate(params.get('mileageStart'));
const endDate = validDate(params.get('mileageEnd'));
if (startDate) context.startDate = startDate;
if (endDate) context.endDate = endDate;
return context;
}
export function buildMileageDrillUrl(
location: LocationParts,
context: MileageDrillContext,
): string {
const params = new URLSearchParams(location.search);
for (const key of [
'mileageLevel',
'mileageTarget',
'mileageDimension',
'mileageValue',
'mileagePlate',
'mileageStart',
'mileageEnd',
'mileageSource',
]) {
params.delete(key);
}
if (context.level !== 'overview') params.set('mileageLevel', context.level);
if (context.targetId) params.set('mileageTarget', String(context.targetId));
if (context.level === 'breakdown' && context.dimension && context.dimensionValue) {
params.set('mileageDimension', context.dimension);
params.set('mileageValue', context.dimensionValue);
}
if (context.level === 'vehicle' && context.plate) params.set('mileagePlate', context.plate);
if (context.startDate) params.set('mileageStart', context.startDate);
if (context.endDate) params.set('mileageEnd', context.endDate);
if (context.sourcePriority.length > 0) params.set('mileageSource', context.sourcePriority.join(','));
const query = params.toString();
return `${location.pathname}${query ? `?${query}` : ''}${location.hash}`;
}