86 lines
5.3 KiB
TypeScript
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>
|
|
);
|
|
}
|