diff --git a/src/modules/assets/AssetsModule.tsx b/src/modules/assets/AssetsModule.tsx index 48c38cb..1af81e4 100644 --- a/src/modules/assets/AssetsModule.tsx +++ b/src/modules/assets/AssetsModule.tsx @@ -30,10 +30,11 @@ import { LabelList, } from 'recharts'; import type { SummaryData, TypeSummary, VehicleListItem, DeptGroup, RegionGroup, CustomerStats, RegionalInventoryStats } from './types'; -import { fetchSummary, fetchByType, fetchVehicleList, fetchWeeklyDetail, fetchDeptStats, fetchRegionStats, fetchCustomerStats, fetchInventoryStats, fetchRegionChart } from './api'; +import { fetchSummary, fetchByType, fetchVehicleList, fetchWeeklyDetail, fetchDeptStats, fetchRegionStats, fetchCustomerStats, fetchInventoryStats, fetchRegionChart, fetchSubjects, type SubjectOption } from './api'; import type { WeeklyDetailItem } from './api'; import { SearchSelect } from '../../components/SearchSelect'; import { MultiSearchSelect } from '../../components/MultiSearchSelect'; +import Blur from '../../components/Blur'; // --- Constants --- @@ -57,6 +58,13 @@ export default function AssetsModule() { } }, [activeTab]); const [theme, setTheme] = useState<'soft' | 'minimal' | 'vibrant'>('soft'); + + // 所属公司(归属主体)筛选 —— 影响全页聚合 + const [selectedSubject, setSelectedSubject] = useState(null); + const [subjects, setSubjects] = useState([]); + const [subjectDropdownOpen, setSubjectDropdownOpen] = useState(false); + const [subjectSearch, setSubjectSearch] = useState(''); + const subjectDropdownRef = useRef(null); const [expandedModels, setExpandedModels] = useState>(new Set()); const [expandedAssetTypes, setExpandedAssetTypes] = useState>(new Set()); const [showPlateNumbers, setShowPlateNumbers] = useState<{ @@ -140,12 +148,12 @@ export default function AssetsModule() { setLoading(true); setError(null); const [s, byType, dept, region, cust, inv] = await Promise.all([ - fetchSummary(), - fetchByType(), - fetchDeptStats(), - fetchRegionStats(), - fetchCustomerStats(), - fetchInventoryStats(), + fetchSummary(selectedSubject), + fetchByType(selectedSubject), + fetchDeptStats(selectedSubject), + fetchRegionStats(undefined, selectedSubject), + fetchCustomerStats(selectedSubject), + fetchInventoryStats(selectedSubject), ]); setSummary(s); setProcessedData(byType); @@ -159,7 +167,7 @@ export default function AssetsModule() { } finally { setLoading(false); } - }, []); + }, [selectedSubject]); useEffect(() => { loadData(); @@ -167,22 +175,43 @@ export default function AssetsModule() { return () => clearInterval(interval); }, [loadData]); + // 归属公司列表(仅首次加载,公司集合相对稳定) + useEffect(() => { + fetchSubjects().then(setSubjects).catch(() => setSubjects([])); + }, []); + + // 点击外部关闭归属公司下拉 + useEffect(() => { + if (!subjectDropdownOpen) return; + const handler = (e: MouseEvent) => { + if (subjectDropdownRef.current && !subjectDropdownRef.current.contains(e.target as Node)) { + setSubjectDropdownOpen(false); + } + }; + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [subjectDropdownOpen]); + // Re-fetch region data when filters change useEffect(() => { const hasFilter = regionFilters.customer || regionFilters.city || regionFilters.region; if (hasFilter) { - fetchRegionStats({ customer: regionFilters.customer || undefined, city: regionFilters.city || undefined, region: regionFilters.region || undefined }) - .then(setRegionData).catch(() => {}); + fetchRegionStats( + { customer: regionFilters.customer || undefined, city: regionFilters.city || undefined, region: regionFilters.region || undefined }, + selectedSubject, + ).then(setRegionData).catch(() => {}); } else { // No filters: use data from the main loadData cycle - fetchRegionStats().then(setRegionData).catch(() => {}); + fetchRegionStats(undefined, selectedSubject).then(setRegionData).catch(() => {}); } - }, [regionFilters]); + }, [regionFilters, selectedSubject]); // Fetch region chart data when view changes useEffect(() => { - fetchRegionChart(regionChartView, regionChartView === 'province' ? 5 : 8).then(setRegionChartData).catch(() => setRegionChartData([])); - }, [regionChartView]); + fetchRegionChart(regionChartView, regionChartView === 'province' ? 5 : 8, 'realtime', selectedSubject) + .then(setRegionChartData) + .catch(() => setRegionChartData([])); + }, [regionChartView, selectedSubject]); // Load modal vehicles useEffect(() => { @@ -235,11 +264,11 @@ export default function AssetsModule() { else if (showPlateNumbers.isTrailer === false) params.vehicleType = '其他'; } } - fetchVehicleList(params) + fetchVehicleList({ ...params, subject: selectedSubject }) .then(setModalVehicles) .catch(() => setModalVehicles([])) .finally(() => setModalLoading(false)); - }, [showPlateNumbers]); + }, [showPlateNumbers, selectedSubject]); const allTypesExpanded = processedData.length > 0 && processedData.every((t) => expandedAssetTypes.has(t.type)); @@ -439,9 +468,9 @@ export default function AssetsModule() { const [customerProvinceData, setCustomerProvinceData] = useState<{ name: string; value: number }[]>([]); useEffect(() => { if (customerChartView === 'province') { - fetchRegionChart('province', 5, 'vehicle').then(setCustomerProvinceData).catch(() => setCustomerProvinceData([])); + fetchRegionChart('province', 5, 'vehicle', selectedSubject).then(setCustomerProvinceData).catch(() => setCustomerProvinceData([])); } - }, [customerChartView]); + }, [customerChartView, selectedSubject]); const customerPieData = useMemo(() => { if (customerChartView === 'region') { @@ -512,6 +541,115 @@ export default function AssetsModule() { + {/* 归属公司作用域筛选 (Scope Chip) */} +
+
+ + {subjectDropdownOpen && ( +
+
+
+ + setSubjectSearch(e.target.value)} + placeholder="搜索公司名" + className="w-full h-7 pl-6 pr-2 text-[11px] bg-gray-50 border border-gray-100 rounded focus:outline-none focus:border-blue-300 focus:bg-white" + /> +
+
+
+ +
+ {subjects + .filter((s) => !subjectSearch || s.name.toLowerCase().includes(subjectSearch.toLowerCase())) + .map((s) => { + const active = selectedSubject === s.name; + return ( + + ); + })} + {subjects.filter((s) => !subjectSearch || s.name.toLowerCase().includes(subjectSearch.toLowerCase())).length === 0 && ( +
未找到匹配公司
+ )} +
+
+ )} +
+
+ {/* Tab row */}
{TABS.map(tab => ( @@ -1488,7 +1626,7 @@ export default function AssetsModule() { >
{isManagerExpanded ? : } - {m.manager} + {m.manager}