Merge Profile0928 asset analysis and permission previews into main
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -24,16 +24,14 @@ import {
|
||||
Cell,
|
||||
LabelList,
|
||||
} from 'recharts';
|
||||
import type { SummaryData, TypeSummary, VehicleListItem, DeptGroup, RegionGroup, CustomerStats, RegionalInventoryStats } from './types';
|
||||
import { fetchSummary, fetchByType, fetchVehicleList, fetchWeeklyDetail, fetchDeptStats, fetchRegionStats, fetchCustomerStats, fetchInventoryStats, fetchRegionChart, fetchSubjects, fetchFlowStats, type SubjectOption } from './api';
|
||||
import type { FlowDetailItem, FlowStatsResponse, FlowType, WeeklyDetailItem } from './api';
|
||||
import type { SummaryData, TypeSummary, DeptGroup, RegionGroup, CustomerStats, RegionalInventoryStats } from './types';
|
||||
import { fetchSummary, fetchByType, fetchDeptStats, fetchRegionStats, fetchCustomerStats, fetchInventoryStats, fetchRegionChart, fetchSubjects, fetchFlowStats, type SubjectOption } from './api';
|
||||
import type { FlowDetailItem, FlowStatsResponse, FlowType } from './api';
|
||||
import {
|
||||
buildCustomerPieData,
|
||||
buildVehicleModalRequest,
|
||||
deriveCustomerView,
|
||||
deriveDepartmentView,
|
||||
deriveInventoryView,
|
||||
deriveModalVehicleView,
|
||||
formatLocalDateTime,
|
||||
getRecentFlowRange,
|
||||
selectFlowDetails,
|
||||
@@ -44,8 +42,8 @@ import { SearchSelect } from '../../components/SearchSelect';
|
||||
import { MultiSearchSelect } from '../../components/MultiSearchSelect';
|
||||
import { ErrorState, LoadingState, PageFrame, SkeletonBlock, SurfaceCard } from '../../components/ui/surface';
|
||||
import { AssetsHeader } from './components/AssetsHeader';
|
||||
import { FlowDetailModal } from './components/FlowDetailModal';
|
||||
import { VehicleDetailModal } from './components/VehicleDetailModal';
|
||||
import { FlowAnalysisModal } from './components/FlowAnalysisModal';
|
||||
import { AssetAnalysisModal } from './components/analysis/AssetAnalysisModal';
|
||||
import { OverviewView } from './components/OverviewView';
|
||||
|
||||
|
||||
@@ -67,7 +65,7 @@ export default function AssetsModule() {
|
||||
useEffect(() => {
|
||||
if (!assistantOpen) return;
|
||||
const previousTitle = document.title;
|
||||
document.title = 'AI 问数 - 资产BI';
|
||||
document.title = 'AI问数 - 资产BI';
|
||||
return () => { document.title = previousTitle; };
|
||||
}, [assistantOpen]);
|
||||
const openAssistant = () => {
|
||||
@@ -109,16 +107,14 @@ export default function AssetsModule() {
|
||||
const [expandedModels, setExpandedModels] = useState<Set<string>>(new Set());
|
||||
const [expandedAssetTypes, setExpandedAssetTypes] = useState<Set<string>>(new Set());
|
||||
const [showPlateNumbers, setShowPlateNumbers] = useState<VehicleModalSelection | null>(null);
|
||||
const closeAssetAnalysis = useCallback(() => setShowPlateNumbers(null), []);
|
||||
|
||||
// Data state
|
||||
const [summary, setSummary] = useState<SummaryData | null>(null);
|
||||
const [processedData, setProcessedData] = useState<TypeSummary[]>([]);
|
||||
const [modalVehicles, setModalVehicles] = useState<VehicleListItem[]>([]);
|
||||
const [modalWeeklyDetail, setModalWeeklyDetail] = useState<WeeklyDetailItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [lastUpdate, setLastUpdate] = useState<string>(() => formatLocalDateTime(new Date()));
|
||||
const [modalLoading, setModalLoading] = useState(false);
|
||||
const [flowRange, setFlowRange] = useState(() => getRecentFlowRange());
|
||||
const [flowStats, setFlowStats] = useState<FlowStatsResponse | null>(null);
|
||||
const [flowLoading, setFlowLoading] = useState(false);
|
||||
@@ -163,17 +159,6 @@ export default function AssetsModule() {
|
||||
const [regionChartView, setRegionChartView] = useState<'region' | 'province'>('region');
|
||||
const [regionChartData, setRegionChartData] = useState<{ name: string; value: number }[]>([]);
|
||||
|
||||
// Modal filter state
|
||||
const [modalFilters, setModalFilters] = useState({ plateNumber: '', model: '', brand: '', location: '' });
|
||||
const [isModalFilterExpanded, setIsModalFilterExpanded] = useState(false);
|
||||
|
||||
// Reset modal filters when modal opens
|
||||
useEffect(() => {
|
||||
if (showPlateNumbers) {
|
||||
setModalFilters({ plateNumber: '', model: '', brand: '', location: '' });
|
||||
}
|
||||
}, [showPlateNumbers]);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -262,30 +247,7 @@ export default function AssetsModule() {
|
||||
.catch(() => setRegionChartData([]));
|
||||
}, [regionChartView, selectedSubject]);
|
||||
|
||||
// Load modal vehicles
|
||||
useEffect(() => {
|
||||
if (!showPlateNumbers) {
|
||||
setModalVehicles([]);
|
||||
setModalWeeklyDetail([]);
|
||||
return;
|
||||
}
|
||||
setModalLoading(true);
|
||||
const request = buildVehicleModalRequest(showPlateNumbers, selectedSubject);
|
||||
if (request.kind === 'weekly') {
|
||||
setModalVehicles([]);
|
||||
fetchWeeklyDetail(request.type, { ...request.filters, subject: selectedSubject })
|
||||
.then(setModalWeeklyDetail)
|
||||
.catch(() => setModalWeeklyDetail([]))
|
||||
.finally(() => setModalLoading(false));
|
||||
return;
|
||||
}
|
||||
|
||||
setModalWeeklyDetail([]);
|
||||
fetchVehicleList(request.params)
|
||||
.then(setModalVehicles)
|
||||
.catch(() => setModalVehicles([]))
|
||||
.finally(() => setModalLoading(false));
|
||||
}, [showPlateNumbers, selectedSubject]);
|
||||
// Vehicle records are requested only at the final level of AssetAnalysisModal.
|
||||
|
||||
const allTypesExpanded = processedData.length > 0 && processedData.every((t) => expandedAssetTypes.has(t.type));
|
||||
|
||||
@@ -403,18 +365,6 @@ export default function AssetsModule() {
|
||||
[customerData, deptData, customerFilters],
|
||||
);
|
||||
|
||||
const {
|
||||
plates: uniqueModalPlates,
|
||||
models: uniqueModalModels,
|
||||
brands: uniqueModalBrands,
|
||||
locations: uniqueModalLocations,
|
||||
filteredVehicles: filteredModalVehicles,
|
||||
filteredWeeklyDetails: filteredModalWeeklyDetail,
|
||||
} = useMemo(
|
||||
() => deriveModalVehicleView(modalVehicles, modalWeeklyDetail, modalFilters),
|
||||
[modalVehicles, modalWeeklyDetail, modalFilters],
|
||||
);
|
||||
|
||||
const selectedFlowDetails = useMemo(
|
||||
() => selectFlowDetails(flowStats, selectedFlow),
|
||||
[flowStats, selectedFlow],
|
||||
@@ -461,7 +411,7 @@ export default function AssetsModule() {
|
||||
|
||||
if (assistantOpen) {
|
||||
return (
|
||||
<Suspense fallback={<div role="status" className="min-h-screen bg-[var(--app-bg)] p-6 text-slate-700">正在加载 AI 问数页面…</div>}>
|
||||
<Suspense fallback={<div role="status" className="min-h-screen bg-[var(--app-bg)] p-6 text-slate-700">正在加载AI问数页面…</div>}>
|
||||
<AssetAssistant key={selectedSubject || 'all'} subject={selectedSubject} appearance="light" close={closeAssistant} />
|
||||
</Suspense>
|
||||
);
|
||||
@@ -1445,7 +1395,7 @@ export default function AssetsModule() {
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">业务负责人</label>
|
||||
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={draftCustomerFilters.manager} onChange={(e) => setDraftCustomerFilters(prev => ({ ...prev, manager: e.target.value }))}>
|
||||
<option value="">所有负责人</option>
|
||||
<option value="">所有业务负责人</option>
|
||||
{customerManagersGroupedByDept
|
||||
.filter(g => g.department !== '公务车')
|
||||
.filter(g => !draftCustomerFilters.department || g.department === draftCustomerFilters.department)
|
||||
@@ -1499,7 +1449,7 @@ export default function AssetsModule() {
|
||||
)}
|
||||
{customerFilters.manager && (
|
||||
<span className="px-2 py-0.5 bg-emerald-50 text-emerald-700 rounded text-[10px] flex items-center gap-1">
|
||||
负责人: {customerFilters.manager}
|
||||
业务负责人: {customerFilters.manager}
|
||||
<button onClick={() => setCustomerFilters(prev => ({...prev, manager: ''}))} className="hover:text-red-500 ml-0.5">×</button>
|
||||
</span>
|
||||
)}
|
||||
@@ -1717,28 +1667,17 @@ export default function AssetsModule() {
|
||||
)}
|
||||
|
||||
|
||||
<FlowDetailModal
|
||||
<FlowAnalysisModal
|
||||
selectedFlow={selectedFlow}
|
||||
selectedFlowDetails={selectedFlowDetails}
|
||||
setSelectedFlow={setSelectedFlow}
|
||||
exportFlowDetails={exportFlowDetails}
|
||||
/>
|
||||
|
||||
<VehicleDetailModal
|
||||
<AssetAnalysisModal
|
||||
selection={showPlateNumbers}
|
||||
setSelection={setShowPlateNumbers}
|
||||
isFilterExpanded={isModalFilterExpanded}
|
||||
setIsFilterExpanded={setIsModalFilterExpanded}
|
||||
filters={modalFilters}
|
||||
setFilters={setModalFilters}
|
||||
plates={uniqueModalPlates}
|
||||
models={uniqueModalModels}
|
||||
brands={uniqueModalBrands}
|
||||
locations={uniqueModalLocations}
|
||||
loading={modalLoading}
|
||||
weeklyDetails={modalWeeklyDetail}
|
||||
filteredWeeklyDetails={filteredModalWeeklyDetail}
|
||||
filteredVehicles={filteredModalVehicles}
|
||||
subject={selectedSubject}
|
||||
close={closeAssetAnalysis}
|
||||
/>
|
||||
|
||||
</div>
|
||||
@@ -1746,11 +1685,11 @@ export default function AssetsModule() {
|
||||
ref={assistantTrigger}
|
||||
type="button"
|
||||
onClick={openAssistant}
|
||||
aria-label="进入 AI 问数页面"
|
||||
aria-label="进入AI问数页面"
|
||||
className="fixed bottom-[calc(5rem+env(safe-area-inset-bottom))] right-4 z-[60] inline-flex min-h-12 items-center gap-2 rounded-full bg-gradient-to-br from-blue-600 to-cyan-500 px-4 text-sm font-semibold text-white shadow-lg shadow-blue-200 transition hover:from-blue-700 hover:to-cyan-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 md:bottom-6 md:right-6"
|
||||
>
|
||||
<Sparkles size={20} aria-hidden="true" />
|
||||
AI 问数
|
||||
AI问数
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user