Files
ln-bi/src/modules/hydrogen-heatmap/HydrogenHeatmapDetailPanel.tsx
T
kkfluous 392a36a0ec
ci/woodpecker/push/woodpecker Pipeline was successful
feat: add vehicle and hydrogen heatmaps
2026-07-13 21:44:03 +08:00

86 lines
5.3 KiB
TypeScript

import { ChevronRight, Fuel, X } from 'lucide-react';
import type { HydrogenHeatmapMetric, HydrogenStationRank } from './types';
type Props = {
title: string;
subtitle: string;
kg: number;
refuelCount: number;
vehicleCount: number;
stationCount: number;
stations: HydrogenStationRank[];
metric: HydrogenHeatmapMetric;
selected: boolean;
loading: boolean;
coverageText: string;
onClose: () => void;
onHide: () => void;
};
const integerFormat = new Intl.NumberFormat('zh-CN');
const kgFormat = new Intl.NumberFormat('zh-CN', { maximumFractionDigits: 1 });
function metricLabel(metric: HydrogenHeatmapMetric) {
if (metric === 'refuels') return '按加氢次数排序';
if (metric === 'vehicles') return '按车辆数排序';
return '按加氢量排序';
}
function metricValue(station: HydrogenStationRank, metric: HydrogenHeatmapMetric) {
if (metric === 'kg') return `${kgFormat.format(station.kg)} kg`;
if (metric === 'refuels') return `${integerFormat.format(station.refuelCount)} 次`;
return `${integerFormat.format(station.vehicleCount)} 辆`;
}
export default function HydrogenHeatmapDetailPanel(props: Props) {
return (
<aside className="relative z-20 flex h-full w-[310px] shrink-0 flex-col border-l border-slate-200 bg-white max-lg:absolute max-lg:bottom-0 max-lg:left-0 max-lg:right-0 max-lg:h-[43vh] max-lg:w-full max-lg:rounded-t-2xl max-lg:border-l-0 max-lg:border-t max-lg:shadow-[0_-10px_30px_rgba(15,23,42,0.14)] max-md:h-[50dvh]">
<div className="border-b border-slate-100 px-5 pb-4 pt-5 max-md:px-4 max-md:pb-3 max-md:pt-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="flex items-center gap-2">
<h2 className="truncate text-[17px] font-semibold tracking-tight text-slate-900">{props.title}</h2>
{props.loading ? <i className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-cyan-600 border-t-transparent" /> : null}
</div>
<p className="mt-1 text-[11px] text-slate-400">{props.subtitle}</p>
<p className="mt-1 text-[10px] font-medium text-emerald-600">{props.coverageText}</p>
</div>
<div className="flex shrink-0 items-center gap-1">
{props.selected ? (
<button type="button" onClick={props.onClose} aria-label="关闭加氢区域详情" className="grid h-8 w-8 place-items-center rounded-lg text-slate-400 hover:bg-slate-50 hover:text-slate-700"><X size={18} strokeWidth={1.8} /></button>
) : null}
<button type="button" onClick={props.onHide} aria-label="隐藏加氢详情" title="隐藏详情" className="grid h-8 w-8 place-items-center rounded-lg text-slate-400 transition hover:bg-cyan-50 hover:text-cyan-700 max-md:h-10 max-md:w-10"><ChevronRight size={18} strokeWidth={1.8} /></button>
</div>
</div>
<div className="mt-4 grid grid-cols-2 gap-x-4 gap-y-3 border-t border-slate-100 pt-4 max-md:mt-3 max-md:gap-y-2 max-md:pt-3">
<div><p className="text-[10px] text-slate-400">加氢量</p><p className="mt-0.5 text-[19px] font-semibold tracking-tight text-cyan-700">{kgFormat.format(props.kg)} <small className="text-[10px] font-medium">kg</small></p></div>
<div><p className="text-[10px] text-slate-400">加氢次数</p><p className="mt-0.5 text-[19px] font-semibold tracking-tight text-cyan-700">{integerFormat.format(props.refuelCount)}</p></div>
<div><p className="text-[10px] text-slate-400">车辆数</p><p className="mt-0.5 text-[16px] font-semibold text-slate-700">{integerFormat.format(props.vehicleCount)}</p></div>
<div><p className="text-[10px] text-slate-400">站点数</p><p className="mt-0.5 text-[16px] font-semibold text-slate-700">{integerFormat.format(props.stationCount)}</p></div>
</div>
</div>
<div className="min-h-0 flex-1 overflow-auto px-5 py-4 max-md:px-4 max-md:py-3">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-[13px] font-semibold text-slate-800">加氢站 TOP10</h3>
<span className="text-[10px] text-slate-400">{metricLabel(props.metric)}</span>
</div>
{props.stations.length ? (
<ol className="divide-y divide-slate-100">
{props.stations.map((station, index) => (
<li key={station.stationId} className="grid grid-cols-[24px_minmax(0,1fr)_68px] items-center gap-2 py-2.5">
<span className={`text-[11px] font-medium ${index < 3 ? 'text-cyan-700' : 'text-slate-400'}`}>{index + 1}</span>
<span className="min-w-0"><span className="block truncate text-[12px] font-medium text-slate-700" title={station.stationName}>{station.stationName}</span><span className="mt-0.5 block truncate text-[9px] text-slate-400" title={station.address}>{station.address || '地址未维护'}</span></span>
<span className="text-right text-[10px] tabular-nums text-slate-600">{metricValue(station, props.metric)}</span>
</li>
))}
</ol>
) : (
<div className="grid h-44 place-items-center text-center text-xs text-slate-400"><span><Fuel className="mx-auto mb-2 text-slate-300" size={22} />该范围暂无加氢记录</span></div>
)}
</div>
</aside>
);
}