124 lines
3.6 KiB
TypeScript
124 lines
3.6 KiB
TypeScript
import { Fuel, TrendingUp, Truck, Zap } from 'lucide-react';
|
|
import type { HydrogenDailyVehicleScope } from '../model';
|
|
|
|
interface DailyKpiGridProps {
|
|
rangeLabel: string;
|
|
rangeText: string;
|
|
totalKg: number;
|
|
activeDays: number;
|
|
dayCount: number;
|
|
averageKg: number;
|
|
stationCount: number;
|
|
vehicleScope: HydrogenDailyVehicleScope;
|
|
lingniuKg: number;
|
|
externalKg: number;
|
|
selectedStationName?: string;
|
|
}
|
|
|
|
const VEHICLE_LABEL: Record<HydrogenDailyVehicleScope, string> = {
|
|
all: '全部车辆',
|
|
lingniu: '羚牛车辆',
|
|
external: '外部车辆',
|
|
};
|
|
|
|
export function DailyKpiGrid({
|
|
rangeLabel,
|
|
rangeText,
|
|
totalKg,
|
|
activeDays,
|
|
dayCount,
|
|
averageKg,
|
|
stationCount,
|
|
vehicleScope,
|
|
lingniuKg,
|
|
externalKg,
|
|
selectedStationName,
|
|
}: DailyKpiGridProps) {
|
|
return (
|
|
<section className="grid grid-cols-2 gap-2.5 md:grid-cols-4 md:gap-3">
|
|
<KpiCard
|
|
icon={Fuel}
|
|
title={`${rangeLabel}加氢量`}
|
|
value={totalKg.toLocaleString('zh-CN', { maximumFractionDigits: 1 })}
|
|
unit="Kg"
|
|
helper={rangeText}
|
|
tone="blue"
|
|
/>
|
|
<KpiCard
|
|
icon={Truck}
|
|
title="车辆结构"
|
|
value={VEHICLE_LABEL[vehicleScope]}
|
|
helper={vehicleScope === 'all'
|
|
? `羚牛 ${formatKg(lingniuKg)} · 外部 ${formatKg(externalKg)}`
|
|
: `仅统计${VEHICLE_LABEL[vehicleScope]}`}
|
|
tone="green"
|
|
smallValue
|
|
/>
|
|
<KpiCard
|
|
icon={TrendingUp}
|
|
title="有效天数"
|
|
value={activeDays}
|
|
unit="天"
|
|
helper={`区间 ${dayCount} 天 · 日均 ${averageKg.toLocaleString('zh-CN', { maximumFractionDigits: 1 })} Kg`}
|
|
tone="amber"
|
|
/>
|
|
<KpiCard
|
|
icon={Zap}
|
|
title={selectedStationName ? '当前加氢站' : '涉及加氢站'}
|
|
value={selectedStationName ?? stationCount}
|
|
unit={selectedStationName ? undefined : '站'}
|
|
helper={selectedStationName ?? '按区间明细站点去重'}
|
|
tone="purple"
|
|
smallValue={Boolean(selectedStationName)}
|
|
/>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function formatKg(value: number): string {
|
|
return `${value.toLocaleString('zh-CN', { maximumFractionDigits: 1 })} Kg`;
|
|
}
|
|
|
|
function KpiCard({
|
|
icon: Icon,
|
|
title,
|
|
value,
|
|
unit,
|
|
helper,
|
|
tone,
|
|
smallValue = false,
|
|
}: {
|
|
icon: typeof Fuel;
|
|
title: string;
|
|
value: string | number;
|
|
unit?: string;
|
|
helper: string;
|
|
tone: 'blue' | 'green' | 'amber' | 'purple';
|
|
smallValue?: boolean;
|
|
}) {
|
|
const toneClass = {
|
|
blue: 'bg-blue-50 text-blue-600',
|
|
green: 'bg-emerald-50 text-emerald-600',
|
|
amber: 'bg-amber-50 text-amber-600',
|
|
purple: 'bg-violet-50 text-violet-600',
|
|
}[tone];
|
|
|
|
return (
|
|
<article className="min-w-0 rounded-[10px] border border-slate-200/70 bg-white px-3 py-3 shadow-sm md:px-4">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<span className="truncate text-[12px] font-medium text-slate-500">{title}</span>
|
|
<span className={`inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg ${toneClass}`}>
|
|
<Icon size={14} />
|
|
</span>
|
|
</div>
|
|
<div className="mt-1.5 flex min-w-0 items-baseline gap-1 font-mono tabular-nums text-slate-900">
|
|
<strong className={`${smallValue ? 'truncate text-[17px] md:text-[19px]' : 'text-[23px] md:text-[25px]'} leading-tight font-extrabold`}>
|
|
{value}
|
|
</strong>
|
|
{unit ? <span className="shrink-0 text-[11px] font-semibold text-slate-400">{unit}</span> : null}
|
|
</div>
|
|
<p className="mt-1 truncate font-mono text-[10px] font-medium text-slate-400" title={helper}>{helper}</p>
|
|
</article>
|
|
);
|
|
}
|