refactor: modularize application domains
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { ArrowDown, ArrowUp, Download, Filter, Maximize2, RefreshCw, RotateCcw } from 'lucide-react';
|
||||
import PlateMultiSelect from '../../PlateMultiSelect';
|
||||
import type { MileageSourceGroup, MonitoringFilters } from '../../types';
|
||||
import { getRangePreset, type RangePreset } from '../date-range';
|
||||
import BatchMultiSelect from './BatchMultiSelect';
|
||||
import SearchableSelect from './SearchableSelect';
|
||||
import SourcePriorityControl from './SourcePriorityControl';
|
||||
|
||||
type SortBy = 'today' | 'total' | 'statisticTime';
|
||||
type SortOrder = 'asc' | 'desc';
|
||||
|
||||
export default function MonitoringToolbar({
|
||||
onToggleFullscreen,
|
||||
onDownload,
|
||||
exporting,
|
||||
lastRefreshedAt,
|
||||
onManualRefresh,
|
||||
manualRefreshing,
|
||||
sortBy,
|
||||
onSortByChange,
|
||||
sortOrder,
|
||||
onSortOrderChange,
|
||||
sourcePriority,
|
||||
onSourcePriorityChange,
|
||||
filterOptions,
|
||||
filterTargetNames,
|
||||
onFilterTargetNamesChange,
|
||||
filterRegion,
|
||||
onFilterRegionChange,
|
||||
plateNumbers,
|
||||
filterPlates,
|
||||
onFilterPlatesChange,
|
||||
isFilterOpen,
|
||||
onFilterOpenChange,
|
||||
searchTerm,
|
||||
filterDept,
|
||||
filterCustomer,
|
||||
filterRentStatus,
|
||||
filterProject,
|
||||
filterBrands,
|
||||
rangeStart,
|
||||
rangeEnd,
|
||||
onApplyRangePreset,
|
||||
rangeLabel,
|
||||
}: {
|
||||
onToggleFullscreen: () => void;
|
||||
onDownload: () => void;
|
||||
exporting: boolean;
|
||||
lastRefreshedAt: Date | null;
|
||||
onManualRefresh: () => void;
|
||||
manualRefreshing: boolean;
|
||||
sortBy: SortBy;
|
||||
onSortByChange: Dispatch<SetStateAction<SortBy>>;
|
||||
sortOrder: SortOrder;
|
||||
onSortOrderChange: Dispatch<SetStateAction<SortOrder>>;
|
||||
sourcePriority: MileageSourceGroup[];
|
||||
onSourcePriorityChange: (value: MileageSourceGroup[]) => void;
|
||||
filterOptions: MonitoringFilters;
|
||||
filterTargetNames: string[];
|
||||
onFilterTargetNamesChange: (value: string[]) => void;
|
||||
filterRegion: string;
|
||||
onFilterRegionChange: (value: string) => void;
|
||||
plateNumbers: string[];
|
||||
filterPlates: string[];
|
||||
onFilterPlatesChange: (value: string[]) => void;
|
||||
isFilterOpen: boolean;
|
||||
onFilterOpenChange: (value: boolean) => void;
|
||||
searchTerm: string;
|
||||
filterDept: string;
|
||||
filterCustomer: string;
|
||||
filterRentStatus: string;
|
||||
filterProject: string;
|
||||
filterBrands: string[];
|
||||
rangeStart: string;
|
||||
rangeEnd: string;
|
||||
onApplyRangePreset: (preset: RangePreset) => void;
|
||||
rangeLabel: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3 rounded-2xl border border-gray-100 bg-white p-3 shadow-sm">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div className="h-8 w-1 flex-shrink-0 rounded-full bg-blue-600" />
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="truncate text-lg font-black leading-none text-slate-900">里程看板</h1>
|
||||
<button onClick={onToggleFullscreen} className="p-1 text-slate-300 transition-colors hover:text-blue-600" title="全屏视图">
|
||||
<Maximize2 size={14} />
|
||||
</button>
|
||||
<button onClick={onDownload} disabled={exporting} className="p-1 text-slate-300 transition-colors hover:text-blue-600 disabled:text-slate-200" title="下载当前筛选结果">
|
||||
{exporting ? <RotateCcw size={14} className="animate-spin" /> : <Download size={14} />}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-1.5">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-blue-500" />
|
||||
<span className="text-[9px] font-bold uppercase tracking-tight text-slate-400">数据监控 • OneOS 实时查询</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 items-center gap-1.5">
|
||||
<span className="hidden text-[9px] font-bold tabular-nums text-slate-400 sm:inline">
|
||||
{lastRefreshedAt ? lastRefreshedAt.toLocaleTimeString('zh-CN', { hour12: false }) : '--:--:--'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onManualRefresh}
|
||||
disabled={manualRefreshing}
|
||||
className="flex h-7 items-center gap-1 whitespace-nowrap rounded-lg border border-blue-100 bg-blue-50 px-2 text-[9px] font-bold text-blue-600 disabled:cursor-wait disabled:opacity-60"
|
||||
title="局部刷新当前筛选数据"
|
||||
>
|
||||
<RefreshCw size={11} className={manualRefreshing ? 'animate-spin' : ''} />
|
||||
<span>{manualRefreshing ? '刷新中' : '刷新数据'}</span>
|
||||
</button>
|
||||
<div className="flex items-center gap-1 rounded-lg bg-slate-100 p-0.5">
|
||||
<button onClick={() => onSortByChange('today')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'today' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}>区间</button>
|
||||
<button onClick={() => onSortByChange('total')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'total' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}>累计</button>
|
||||
<button onClick={() => onSortByChange('statisticTime')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'statisticTime' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}>统计时间</button>
|
||||
<button
|
||||
onClick={() => onSortOrderChange(sortOrder === 'desc' ? 'asc' : 'desc')}
|
||||
className="rounded-md p-1 text-blue-600 transition-all hover:bg-white"
|
||||
title={sortOrder === 'desc' ? '当前倒序,点击切换正序' : '当前正序,点击切换倒序'}
|
||||
>
|
||||
{sortOrder === 'desc' ? <ArrowDown size={12} /> : <ArrowUp size={12} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SourcePriorityControl priority={sourcePriority} onChange={onSourcePriorityChange} />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="grid flex-1 grid-cols-3 gap-1.5">
|
||||
<BatchMultiSelect options={filterOptions.targetNames} selected={filterTargetNames} onChange={onFilterTargetNamesChange} placeholder="批次型号" />
|
||||
<SearchableSelect options={filterOptions.regions} value={filterRegion} onChange={onFilterRegionChange} placeholder="运营区域" />
|
||||
<PlateMultiSelect allPlates={plateNumbers} selected={filterPlates} onChange={onFilterPlatesChange} placeholder="按车牌" />
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onFilterOpenChange(!isFilterOpen)}
|
||||
className={`flex-shrink-0 rounded-lg border p-1.5 transition-all ${isFilterOpen || searchTerm || filterDept !== 'All' || filterCustomer !== 'All' || filterRentStatus !== 'All' || filterPlates.length > 0 || filterProject !== 'All' || filterTargetNames.length > 0 || filterBrands.length > 0 ? 'border-blue-100 bg-blue-50 text-blue-600' : 'border-transparent bg-slate-50 text-slate-400'}`}
|
||||
title="高级筛选"
|
||||
>
|
||||
<Filter size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 overflow-x-auto no-scrollbar">
|
||||
{([
|
||||
['today', '今天'],
|
||||
['yesterday', '昨天'],
|
||||
['thisWeek', '本周'],
|
||||
['thisMonth', '本月'],
|
||||
['last15', '近15天'],
|
||||
] as Array<[RangePreset, string]>).map(([preset, label]) => {
|
||||
const range = getRangePreset(preset);
|
||||
const active = rangeStart === range.start && rangeEnd === range.end;
|
||||
return (
|
||||
<button
|
||||
key={preset}
|
||||
type="button"
|
||||
onClick={() => onApplyRangePreset(preset)}
|
||||
className={`shrink-0 rounded-lg border px-2.5 py-1.5 text-[10px] font-black transition-all ${active ? 'border-blue-200 bg-blue-50 text-blue-600 shadow-sm' : 'border-slate-100 bg-slate-50 text-slate-500 hover:border-blue-100 hover:bg-blue-50 hover:text-blue-600'}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<span className="ml-auto shrink-0 rounded-lg bg-slate-50 px-2 py-1 text-[10px] font-bold text-slate-400">{rangeLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user