173 lines
7.1 KiB
TypeScript
173 lines
7.1 KiB
TypeScript
import { Calendar, Fuel, RefreshCw, Truck } from 'lucide-react';
|
|
import type { CustomerType, DateQuickPick } from '../types';
|
|
import type { HydrogenDailyVehicleScope } from '../hydrogen-daily/model';
|
|
import { QUICK_PICK_OPTIONS, type RangeMode } from './model';
|
|
|
|
interface DailyRangeControlsProps {
|
|
pick: RangeMode;
|
|
dateRange: { start: string; end: string };
|
|
customer: CustomerType;
|
|
stations?: { id: number; name: string }[];
|
|
selectedStationId?: number | null;
|
|
vehicleScope?: HydrogenDailyVehicleScope;
|
|
updatedAt?: string | null;
|
|
loading?: boolean;
|
|
onQuickPick: (pick: DateQuickPick) => void;
|
|
onCustomPick: () => void;
|
|
onDateRangeChange: (field: 'start' | 'end', value: string) => void;
|
|
onCustomerChange: (customer: CustomerType) => void;
|
|
onStationChange?: (stationId: number | null) => void;
|
|
onVehicleScopeChange?: (scope: HydrogenDailyVehicleScope) => void;
|
|
onRefresh?: () => void;
|
|
}
|
|
|
|
export default function DailyRangeControls({
|
|
pick,
|
|
dateRange,
|
|
customer,
|
|
stations = [],
|
|
selectedStationId = null,
|
|
vehicleScope,
|
|
updatedAt,
|
|
loading = false,
|
|
onQuickPick,
|
|
onCustomPick,
|
|
onDateRangeChange,
|
|
onCustomerChange,
|
|
onStationChange,
|
|
onVehicleScopeChange,
|
|
onRefresh,
|
|
}: DailyRangeControlsProps) {
|
|
return (
|
|
<section className="rounded-[14px] border border-slate-200/70 bg-white px-3 py-3 shadow-sm md:px-4">
|
|
<div className="flex flex-col gap-3">
|
|
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
<div className="flex rounded-md bg-slate-100 p-0.5">
|
|
{QUICK_PICK_OPTIONS.map(option => (
|
|
<button
|
|
key={option.id}
|
|
type="button"
|
|
onClick={() => onQuickPick(option.id)}
|
|
className={`h-7 rounded px-3 text-[12px] font-medium transition-colors ${
|
|
pick === option.id
|
|
? 'bg-white font-semibold text-blue-600 shadow-sm'
|
|
: 'text-slate-500 hover:text-slate-800'
|
|
}`}
|
|
>
|
|
{option.label}
|
|
</button>
|
|
))}
|
|
<button
|
|
type="button"
|
|
onClick={onCustomPick}
|
|
className={`h-7 rounded px-3 text-[12px] font-medium transition-colors ${
|
|
pick === 'custom'
|
|
? 'bg-white font-semibold text-blue-600 shadow-sm'
|
|
: 'text-slate-500 hover:text-slate-800'
|
|
}`}
|
|
>
|
|
自定义
|
|
</button>
|
|
</div>
|
|
|
|
<DateField label="开始日期" value={dateRange.start} onChange={value => onDateRangeChange('start', value)} />
|
|
<DateField label="结束日期" value={dateRange.end} onChange={value => onDateRangeChange('end', value)} />
|
|
|
|
{onStationChange ? (
|
|
<label className="inline-flex h-8 min-w-[190px] max-w-full items-center gap-2 rounded-md border border-slate-300 bg-white px-2.5 text-slate-600 transition-colors focus-within:border-sky-600 focus-within:ring-2 focus-within:ring-sky-100">
|
|
<Fuel size={13} className="shrink-0 text-slate-400" />
|
|
<select
|
|
aria-label="选择加氢站"
|
|
value={selectedStationId ?? ''}
|
|
onChange={event => onStationChange(event.target.value === '' ? null : Number(event.target.value))}
|
|
className="min-w-0 flex-1 bg-transparent text-[12px] font-semibold text-slate-700 outline-none"
|
|
>
|
|
<option value="">请选择加氢站</option>
|
|
{stations.map((station, index) => (
|
|
<option key={`${station.id}-${station.name}-${index}`} value={station.id}>{station.name}</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
{onVehicleScopeChange && vehicleScope ? (
|
|
<div className="flex rounded-md bg-slate-100 p-0.5" aria-label="车辆归属">
|
|
{([
|
|
['all', '全部车辆'],
|
|
['lingniu', '仅羚牛车辆'],
|
|
['external', '仅外部车辆'],
|
|
] as const).map(([id, label]) => (
|
|
<button
|
|
key={id}
|
|
type="button"
|
|
onClick={() => onVehicleScopeChange(id)}
|
|
className={`inline-flex h-8 items-center gap-1.5 rounded px-2.5 text-[12px] font-medium transition-colors ${
|
|
vehicleScope === id
|
|
? 'bg-white font-semibold text-blue-600 shadow-sm'
|
|
: 'text-slate-500 hover:text-slate-800'
|
|
}`}
|
|
>
|
|
<Truck size={13} />
|
|
{label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="flex rounded-md bg-slate-100 p-0.5">
|
|
{(['lingniu', 'external'] as const).map(option => (
|
|
<button
|
|
key={option}
|
|
type="button"
|
|
onClick={() => onCustomerChange(option)}
|
|
className={`inline-flex h-8 items-center gap-1.5 rounded px-3 text-[12px] font-medium transition-colors ${
|
|
customer === option
|
|
? 'bg-white font-semibold text-blue-600 shadow-sm'
|
|
: 'text-slate-500 hover:text-slate-800'
|
|
}`}
|
|
>
|
|
<Truck size={13} />
|
|
{option === 'external' ? '外部车辆' : '羚牛车辆'}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="ml-auto flex items-center gap-3">
|
|
{updatedAt ? <span className="font-mono text-[11px] text-slate-400">{updatedAt}</span> : null}
|
|
{onRefresh ? (
|
|
<button
|
|
type="button"
|
|
onClick={onRefresh}
|
|
disabled={loading}
|
|
title="刷新数据"
|
|
className="inline-flex h-8 items-center gap-1.5 rounded-md border border-slate-200 bg-white px-3 text-[12px] font-semibold text-slate-600 transition-colors hover:border-blue-200 hover:text-blue-600 disabled:cursor-wait disabled:opacity-60"
|
|
>
|
|
<RefreshCw size={13} className={loading ? 'animate-spin' : ''} />
|
|
刷新
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function DateField({ label, value, onChange }: { label: string; value: string; onChange: (value: string) => void }) {
|
|
return (
|
|
<label className="inline-flex h-8 items-center gap-2 rounded-md border border-slate-300 bg-white px-2.5 transition-colors focus-within:border-sky-600 focus-within:ring-2 focus-within:ring-sky-100">
|
|
<Calendar size={13} className="shrink-0 text-sky-600" />
|
|
<span className="hidden text-[11px] font-medium text-slate-400 sm:inline">{label}</span>
|
|
<input
|
|
type="date"
|
|
aria-label={label}
|
|
value={value}
|
|
onChange={event => onChange(event.target.value)}
|
|
className="h-7 min-w-[112px] bg-transparent font-mono text-[11px] font-semibold text-slate-700 outline-none"
|
|
/>
|
|
</label>
|
|
);
|
|
}
|