feat(platform): sync dashboard customer time window

This commit is contained in:
lingniu
2026-07-06 01:27:31 +08:00
parent d89f7f7168
commit af95f69388
3 changed files with 52 additions and 11 deletions

View File

@@ -600,7 +600,7 @@ export default function App() {
};
const pages: Record<PageKey, JSX.Element> = {
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={openQuality} onOpenMap={openMap} onOpenRealtime={openRealtime} onOpenVehicles={openVehicles} onOpenHistory={openHistoryWithFilters} onOpenMileage={openMileageWithFilters} />,
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={openQuality} onOpenMap={openMap} onOpenRealtime={openRealtime} onOpenVehicles={openVehicles} onOpenHistory={openHistoryWithFilters} onOpenMileage={openMileageWithFilters} customerTimeWindow={customerTimeWindow} />,
vehicles: <Vehicles onOpenVehicle={openVehicle} onOpenQuality={openQuality} onOpenMap={openMap} onOpenRealtime={openRealtime} onOpenHistory={openHistoryWithFilters} onOpenMileage={openMileageWithFilters} onFiltersChange={updateVehicleFilters} initialFilters={vehicleFilters} />,
map: <Realtime mode="map" title="车辆实时地图" description="以车辆为中心查看位置分布、在线状态、关注车辆和轨迹入口" onOpenVehicle={openVehicle} onOpenHistory={openHistoryForVehicle} onOpenQuality={openQuality} onFiltersChange={updateMapFilters} initialFilters={realtimeFilters} />,
realtime: <Realtime onOpenVehicle={openVehicle} onOpenHistory={openHistoryForVehicle} onOpenQuality={openQuality} onFiltersChange={updateRealtimeFilters} initialFilters={realtimeFilters} />,

View File

@@ -154,6 +154,16 @@ function dayRangeText(dateFrom?: string, dateTo?: string) {
return days <= 0 ? '同日或倒置时间窗' : `${days} 天窗口`;
}
function naturalDayRangeText(dateFrom?: string, dateTo?: string) {
if (!dateFrom && !dateTo) return '未限定时间';
if (!dateFrom || !dateTo) return '单边时间窗';
const start = new Date(`${dateFrom}T00:00:00+08:00`).getTime();
const end = new Date(`${dateTo}T00:00:00+08:00`).getTime();
if (!Number.isFinite(start) || !Number.isFinite(end)) return '时间格式待确认';
const days = Math.max(0, Math.round((end - start) / 86400000)) + 1;
return days <= 0 ? '同日或倒置时间窗' : `${days} 天窗口`;
}
function priorityIssueVehicleLabel(row: QualityIssueRow) {
const identity = row.vin?.trim() && row.vin !== 'unknown' ? row.vin.trim() : row.phone?.trim();
return [row.plate?.trim(), identity].filter(Boolean).join(' / ') || row.sourceEndpoint || '-';
@@ -261,6 +271,14 @@ function sourceConsistencyAction(row: VehicleCoverageRow, onFilter: (filters: Re
return <Tag color={color}>{label}</Tag>;
}
function defaultTimeMonitorFilters() {
const today = todayDateString();
return {
dateFrom: previousDateString(today),
dateTo: today
};
}
export function Dashboard({
onOpenVehicle,
onOpenQuality,
@@ -268,7 +286,8 @@ export function Dashboard({
onOpenRealtime,
onOpenVehicles,
onOpenHistory,
onOpenMileage
onOpenMileage,
customerTimeWindow
}: {
onOpenVehicle: (vin: string, protocol?: string) => void;
onOpenQuality: (filters?: Record<string, string>) => void;
@@ -277,6 +296,7 @@ export function Dashboard({
onOpenVehicles: (filters?: Record<string, string>) => void;
onOpenHistory: (filters?: Record<string, string>) => void;
onOpenMileage: (filters?: Record<string, string>) => void;
customerTimeWindow?: Record<string, string>;
}) {
const [summary, setSummary] = useState<DashboardSummary | null>(null);
const [serviceSummary, setServiceSummary] = useState<VehicleServiceSummary | null>(null);
@@ -284,13 +304,7 @@ export function Dashboard({
const [locations, setLocations] = useState<VehicleRealtimeRow[]>([]);
const [qualityIssues, setQualityIssues] = useState<QualityIssueRow[]>([]);
const [opsHealth, setOpsHealth] = useState<OpsHealth | null>(null);
const [timeMonitorFilters, setTimeMonitorFilters] = useState<Record<string, string>>(() => {
const today = todayDateString();
return {
dateFrom: previousDateString(today),
dateTo: today
};
});
const [timeMonitorFilters, setTimeMonitorFilters] = useState<Record<string, string>>(defaultTimeMonitorFilters);
const [loading, setLoading] = useState(true);
const [coverageLoading, setCoverageLoading] = useState(false);
const [coverageServiceStatusTitle, setCoverageServiceStatusTitle] = useState('');
@@ -343,6 +357,25 @@ export function Dashboard({
.finally(() => setLoading(false));
}, []);
useEffect(() => {
const dateFrom = customerTimeWindow?.dateFrom?.trim();
const dateTo = customerTimeWindow?.dateTo?.trim();
if (!dateFrom && !dateTo) {
return;
}
setTimeMonitorFilters((previous) => {
const next = {
...previous,
dateFrom: dateFrom ?? '',
dateTo: dateTo ?? ''
};
if (previous.dateFrom === next.dateFrom && previous.dateTo === next.dateTo) {
return previous;
}
return next;
});
}, [customerTimeWindow?.dateFrom, customerTimeWindow?.dateTo]);
const serviceActionQueue = useMemo<Array<{ label: string; count: number; filters: Record<string, string>; detail: string }>>(() => {
const items: Array<{ label: string; count: number; filters: Record<string, string>; detail: string }> = [];
if ((serviceSummary?.noDataVehicles ?? 0) > 0) {
@@ -873,7 +906,14 @@ export function Dashboard({
}
];
const timeMonitorScopeValue = timeMonitorScope();
const timeMonitorWindowLabel = dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo);
const usesCustomerTimeWindow = Boolean(
(customerTimeWindow?.dateFrom || customerTimeWindow?.dateTo)
&& customerTimeWindow?.dateFrom === timeMonitorScopeValue.dateFrom
&& customerTimeWindow?.dateTo === timeMonitorScopeValue.dateTo
);
const timeMonitorWindowLabel = usesCustomerTimeWindow
? naturalDayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)
: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo);
const timeMonitorChecklist = [
{
label: '范围可解释',
@@ -1003,7 +1043,7 @@ export function Dashboard({
{
step: '1',
label: '锁定范围',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
value: timeMonitorWindowLabel,
detail: timeMonitorScopeValue.keyword ? `单车 ${timeMonitorScopeValue.keyword}` : '先确认车辆或车辆池,再应用同一时间窗。',
action: '应用时间窗',
color: timeMonitorHasRange ? 'green' as const : 'orange' as const,

View File

@@ -7324,6 +7324,7 @@ test('topbar customer time window presets support common monitoring ranges', asy
fireEvent.click(screen.getByRole('button', { name: '顶部时间窗 近7天' }));
expect(screen.getByLabelText('顶部时间窗开始')).toHaveValue('2026-06-29');
expect(screen.getByLabelText('顶部时间窗结束')).toHaveValue('2026-07-05');
expect(await screen.findByRole('button', { name: /时间窗服务路径 1 锁定范围 7 天窗口 应用时间窗/ })).toBeInTheDocument();
fireEvent.change(screen.getByPlaceholderText('搜索 VIN / 车牌 / 手机号'), { target: { value: '粤A快捷窗' } });
fireEvent.click(screen.getByRole('button', { name: '查询车辆' }));