feat(platform): sync dashboard customer time window
This commit is contained in:
@@ -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} />,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: '查询车辆' }));
|
||||
|
||||
Reference in New Issue
Block a user