import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import {
Truck, Search, Filter, ChevronDown,
Maximize2, Minimize2, RotateCcw,
ArrowUp, ArrowDown, ChevronsUp,
} from 'lucide-react';
import type { MonitoringVehicle, MonitoringStats, MonitoringFilters } from './types';
import { fetchMonitoring } from './api';
const SearchableSelect = ({
options,
value,
onChange,
placeholder
}: {
options: string[],
value: string,
onChange: (val: string) => void,
placeholder: string
}) => {
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState('');
const filtered = useMemo(() => {
if (!search) return options;
return options.filter(opt => opt.toLowerCase().includes(search.toLowerCase()));
}, [options, search]);
return (
setIsOpen(true)}
onChange={(e) => setSearch(e.target.value)}
onBlur={() => {
// Delay to allow clicking an option
setTimeout(() => setIsOpen(false), 200);
}}
/>
{isOpen && (
{
onChange('All');
setSearch('');
setIsOpen(false);
}}
>
无限制
{filtered.map((opt: string) => (
{
onChange(opt);
setSearch('');
setIsOpen(false);
}}
>
{opt}
))}
{filtered.length === 0 && (
无匹配项
)}
)}
);
};
export default function MonitoringView() {
const [searchTerm, setSearchTerm] = useState('');
const [filterDept, setFilterDept] = useState('All');
const [sortBy, setSortBy] = useState<'today' | 'total'>('today');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const [isFilterOpen, setIsFilterOpen] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
// New filters from image
const [filterPlate, setFilterPlate] = useState('All');
const [filterCustomer, setFilterCustomer] = useState('All');
const [filterProject, setFilterProject] = useState('All');
const [filterEntity, setFilterEntity] = useState('All');
const [filterRegionCode, setFilterRegionCode] = useState('All');
const [filterMileageRange, setFilterMileageRange] = useState({ min: '', max: '' });
const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' });
const [filterDate, setFilterDate] = useState(() => new Date().toISOString().split('T')[0]);
const [vehicles, setVehicles] = useState([]);
const [stats, setStats] = useState({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 });
const [filterOptions, setFilterOptions] = useState({ departments: [], customers: [], plates: [], projects: [], entities: [] });
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [showBackToTop, setShowBackToTop] = useState(false);
const PAGE_SIZE = 50;
const departments = filterOptions.departments;
const plateNumbers = filterOptions.plates;
// 加载首页数据
const loadFirstPage = useCallback(() => {
fetchMonitoring({
sortBy,
sortOrder,
limit: PAGE_SIZE,
page: 1,
search: searchTerm || undefined,
dept: filterDept !== 'All' ? filterDept : undefined,
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
project: filterProject !== 'All' ? filterProject : undefined,
entity: filterEntity !== 'All' ? filterEntity : undefined,
plate: filterPlate !== 'All' ? filterPlate : undefined,
mileageMin: appliedMileageRange.min || undefined,
mileageMax: appliedMileageRange.max || undefined,
date: filterDate || undefined,
}).then(d => {
setVehicles(d.vehicles);
setStats(d.stats);
setFilterOptions(d.filters);
setTotal(d.total);
setPage(1);
setHasMore(d.page < d.totalPages);
}).catch(() => {});
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterPlate, appliedMileageRange, filterDate]);
// 加载更多
const loadMore = useCallback(() => {
if (loadingMore || !hasMore) return;
const nextPage = page + 1;
setLoadingMore(true);
fetchMonitoring({
sortBy,
sortOrder,
limit: PAGE_SIZE,
page: nextPage,
search: searchTerm || undefined,
dept: filterDept !== 'All' ? filterDept : undefined,
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
project: filterProject !== 'All' ? filterProject : undefined,
entity: filterEntity !== 'All' ? filterEntity : undefined,
plate: filterPlate !== 'All' ? filterPlate : undefined,
mileageMin: appliedMileageRange.min || undefined,
mileageMax: appliedMileageRange.max || undefined,
date: filterDate || undefined,
}).then(d => {
setVehicles(prev => [...prev, ...d.vehicles]);
setPage(nextPage);
setHasMore(nextPage < d.totalPages);
}).catch(() => {}).finally(() => setLoadingMore(false));
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterPlate, appliedMileageRange, filterDate, page, loadingMore, hasMore]);
// 筛选/排序变化时重新加载
useEffect(() => {
loadFirstPage();
}, [loadFirstPage]);
// 触底检测:用 IntersectionObserver 监听哨兵元素
const loadMoreRef = useRef(loadMore);
loadMoreRef.current = loadMore;
const sentinelRef = useRef(null);
useEffect(() => {
const sentinel = sentinelRef.current;
if (!sentinel) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
loadMoreRef.current();
}
},
{ rootMargin: '200px' }
);
observer.observe(sentinel);
return () => observer.disconnect();
}, []);
// 回到顶部按钮:用 IntersectionObserver 检测顶部哨兵是否离开视口
const topSentinelRef = useRef(null);
useEffect(() => {
const el = topSentinelRef.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => setShowBackToTop(!entry.isIntersecting),
);
observer.observe(el);
return () => observer.disconnect();
}, []);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
document.documentElement.scrollTop = 0;
};
const filteredVehicles = vehicles;
const toggleFullscreen = () => {
setIsFullscreen(!isFullscreen);
};
// 全屏时禁止背景滚动
useEffect(() => {
if (isFullscreen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => { document.body.style.overflow = ''; };
}, [isFullscreen]);
return (
<>
{/* 顶部哨兵:离开视口时显示回到顶部按钮 */}
{/* Fullscreen Landscape View Overlay */}
{isFullscreen && (
{/* Top bar: title + KPI row + close */}
{/* KPI — single row */}
今日总里程
{Math.round(stats.totalToday).toLocaleString()} km
累计总里程
{Math.round(stats.totalAll).toLocaleString()} km
监控台数
{stats.vehicleCount} 台
平均单车
{(stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)} km
{/* Table Area */}
车辆实时明细数据
最后更新: {new Date().toLocaleTimeString()}
|
车牌号
|
在线状态 |
客户
|
业务部门
|
{
if (sortBy === 'today') {
setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc');
} else {
setSortBy('today');
setSortOrder('desc');
}
}}
>
今日里程
{sortBy === 'today' && (
sortOrder === 'desc' ? :
)}
|
{
if (sortBy === 'total') {
setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc');
} else {
setSortBy('total');
setSortOrder('desc');
}
}}
>
累计里程
{sortBy === 'total' && (
sortOrder === 'desc' ? :
)}
|
状态 |
{filteredVehicles.map((v) => (
| {v.plate} |
{v.isOnline ? '在线' : '离线'}
|
{v.customer} |
{v.department || v.rentStatus || ''} |
{!v.isDataSynced && (
)}
{v.dailyKm?.toLocaleString()} km
{!v.isDataSynced && 未对接}
|
{v.totalKm?.toLocaleString()} km
|
{v.isOnline ? '运行中' : '静止/离线'}
|
))}
)}
{/* Ultra Compact Header - Two Rows */}
{/* Top Row: Title & Sort */}
{/* Bottom Row: Quick Filters & Advanced Filter Icon */}
{/* Expandable Filter Panel */}
{isFilterOpen && (
{/* Date */}
setFilterDate(e.target.value)}
/>
{/* Project */}
{/* Department */}
{/* Entity */}
{/* Region Code */}
{/* Mileage Range */}
)}
{/* Active Filter Tags */}
{(() => {
const tags: { label: string; onClear: () => void }[] = [];
if (filterDept !== 'All') tags.push({ label: `部门: ${filterDept}`, onClear: () => setFilterDept('All') });
if (filterCustomer !== 'All') tags.push({ label: `客户: ${filterCustomer}`, onClear: () => setFilterCustomer('All') });
if (filterProject !== 'All') tags.push({ label: `项目: ${filterProject}`, onClear: () => setFilterProject('All') });
if (filterEntity !== 'All') tags.push({ label: `主体: ${filterEntity}`, onClear: () => setFilterEntity('All') });
if (filterPlate !== 'All') tags.push({ label: `车牌: ${filterPlate}`, onClear: () => setFilterPlate('All') });
if (searchTerm) tags.push({ label: `搜索: ${searchTerm}`, onClear: () => setSearchTerm('') });
if (appliedMileageRange.min) tags.push({ label: `里程≥${appliedMileageRange.min}`, onClear: () => { setFilterMileageRange(prev => ({ ...prev, min: '' })); setAppliedMileageRange(prev => ({ ...prev, min: '' })); } });
if (appliedMileageRange.max) tags.push({ label: `里程≤${appliedMileageRange.max}`, onClear: () => { setFilterMileageRange(prev => ({ ...prev, max: '' })); setAppliedMileageRange(prev => ({ ...prev, max: '' })); } });
if (filterRegionCode !== 'All') tags.push({ label: `地区: ${filterRegionCode}`, onClear: () => setFilterRegionCode('All') });
if (filterDate) tags.push({ label: `日期: ${filterDate}`, onClear: () => setFilterDate('') });
if (tags.length === 0) return null;
const clearAll = () => {
setFilterDept('All'); setFilterCustomer('All'); setFilterProject('All'); setFilterEntity('All');
setFilterPlate('All'); setSearchTerm(''); setFilterRegionCode('All');
setFilterMileageRange({ min: '', max: '' }); setAppliedMileageRange({ min: '', max: '' });
setFilterDate('');
};
return (
{tags.map((tag, i) => (
{tag.label}
))}
);
})()}
{/* Sticky header: KPI + 清单标题 */}
{sortBy === 'today' ? '今日' : '累计'}总里程
{Math.round(sortBy === 'today' ? stats.totalToday : stats.totalAll).toLocaleString()}
km
{sortBy === 'today' && stats.yesterdayTotal > 0 && (() => {
const change = ((stats.totalToday - stats.yesterdayTotal) / stats.yesterdayTotal) * 100;
const isUp = change >= 0;
return {isUp ? '\u2191' : '\u2193'}{Math.abs(change).toFixed(1)}%;
})()}
平均单车
{(stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)}
km/台
监控台数
{stats.vehicleCount}
台
车辆详情清单
{total} 条
{/* Vehicle List */}
{filteredVehicles.map((v) => (
{
navigator.clipboard.writeText(v.plate);
}}
>
{v.plate}
{v.isOnline ? '在线' : '离线'}
{v.department ? v.department.replace('业务', '') : v.rentStatus || ''}
{v.customer || '-'}
{!v.isDataSynced && (
)}
{v.dailyKm?.toLocaleString()} km
{v.totalKm?.toLocaleString()} km
{!v.isDataSynced && (
未同步
)}
))}
{filteredVehicles.length === 0 && !loadingMore && (
)}
{/* 加载更多提示 */}
{loadingMore && (
加载中...
)}
{!hasMore && filteredVehicles.length > 0 && (
已加载全部 {total} 条
)}
{/* 哨兵元素:进入视口时触发加载更多 */}
{/* 回到顶部按钮 */}
{showBackToTop && (
)}
>
);
}