refactor(ui): polish semi mileage discovery
This commit is contained in:
@@ -122,6 +122,8 @@ test('defaults to seven days and supports today and yesterday shortcuts', async
|
||||
await waitFor(() => expect(mocks.mileageStatistics).toHaveBeenCalled());
|
||||
expect(mocks.mileageStatistics.mock.calls[0][0].get('dateFrom')).toBe(sevenDaysAgo);
|
||||
expect(mocks.mileageStatistics.mock.calls[0][0].get('dateTo')).toBe(today);
|
||||
expect(screen.getByRole('button', { name: '近 7 天' })).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(screen.getByRole('button', { name: '今天' })).toHaveAttribute('aria-pressed', 'false');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '昨天' }));
|
||||
await waitFor(() => {
|
||||
@@ -129,6 +131,7 @@ test('defaults to seven days and supports today and yesterday shortcuts', async
|
||||
expect(params.get('dateFrom')).toBe(yesterday);
|
||||
expect(params.get('dateTo')).toBe(yesterday);
|
||||
});
|
||||
expect(screen.getByRole('button', { name: '昨天' })).toHaveAttribute('aria-pressed', 'true');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '今天' }));
|
||||
await waitFor(() => {
|
||||
@@ -136,6 +139,7 @@ test('defaults to seven days and supports today and yesterday shortcuts', async
|
||||
expect(params.get('dateFrom')).toBe(today);
|
||||
expect(params.get('dateTo')).toBe(today);
|
||||
});
|
||||
expect(screen.getByRole('button', { name: '今天' })).toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
|
||||
test('preserves the exact monitor return after a mileage date shortcut', async () => {
|
||||
@@ -280,7 +284,7 @@ test('lets users disable mileage sources and persists the source priority', asyn
|
||||
expect(screen.getByText('GPS 里程')).toBeInTheDocument();
|
||||
expect(screen.getByText('GPS 里程').closest('.semi-tag')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('仪表盘里程')).toHaveLength(3);
|
||||
expect(screen.getByRole('button', { name: /数据源.*GB32960 优先.*3\/3/ })).toHaveAttribute('title', '当前优先:国标 GB32960');
|
||||
expect(screen.getByRole('button', { name: /数据源策略.*GB32960 优先.*3\/3/ })).toHaveAttribute('title', '当前优先:国标 GB32960');
|
||||
fireEvent.click(screen.getByRole('switch', { name: '禁用 国标 GB32960' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '上移 宇通 MQTT' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '查询' }));
|
||||
|
||||
@@ -59,6 +59,34 @@ function isSameRange(criteria: Pick<Criteria, 'dateFrom' | 'dateTo'>, range: { d
|
||||
return criteria.dateFrom === range.dateFrom && criteria.dateTo === range.dateTo;
|
||||
}
|
||||
|
||||
function MileageRangeShortcuts({
|
||||
value,
|
||||
ranges,
|
||||
onChange
|
||||
}: {
|
||||
value: Pick<Criteria, 'dateFrom' | 'dateTo'>;
|
||||
ranges: Array<{ label: string; range: { dateFrom: string; dateTo: string } }>;
|
||||
onChange: (range: { dateFrom: string; dateTo: string }) => void;
|
||||
}) {
|
||||
return <div className="v2-mileage-ranges">
|
||||
<span>快捷日期</span>
|
||||
<ButtonGroup aria-label="快捷日期范围">
|
||||
{ranges.map(({ label, range }) => {
|
||||
const active = isSameRange(value, range);
|
||||
return <Button
|
||||
key={label}
|
||||
theme={active ? 'solid' : 'light'}
|
||||
htmlType="button"
|
||||
aria-pressed={active}
|
||||
onClick={() => onChange(range)}
|
||||
>
|
||||
{label}
|
||||
</Button>;
|
||||
})}
|
||||
</ButtonGroup>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export function mileageRangeContainsDate(
|
||||
range: { dateFrom: string; dateTo: string },
|
||||
date = localDate()
|
||||
@@ -160,8 +188,18 @@ function SourceStrategy({ value, onChange }: { value: MileageSourceOption[]; onC
|
||||
};
|
||||
|
||||
return <div className="v2-mileage-source-strategy">
|
||||
<Button className="v2-mileage-source-trigger" theme="light" icon={<IconSetting />} aria-haspopup="dialog" aria-expanded={open} aria-controls="v2-mileage-source-strategy" title={`当前优先:${primarySource?.label ?? '未配置'}`} onClick={() => setOpen((current) => !current)}>
|
||||
<span>数据源</span><em>{primarySource?.protocol ?? '未配置'} 优先</em><b>{enabled.length}/3</b>
|
||||
<Button
|
||||
className="v2-mileage-source-trigger"
|
||||
theme="light"
|
||||
icon={<IconSetting />}
|
||||
aria-label={`数据源策略,${primarySource?.protocol ?? '未配置'} 优先,${enabled.length}/3 已启用`}
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={open}
|
||||
aria-controls="v2-mileage-source-strategy"
|
||||
title={`当前优先:${primarySource?.label ?? '未配置'}`}
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
>
|
||||
<span>数据源策略</span><em>{primarySource?.protocol ?? '未配置'} 优先</em><b>{enabled.length}/3</b>
|
||||
</Button>
|
||||
<WorkspaceSideSheet
|
||||
className="v2-mileage-source-sidesheet"
|
||||
@@ -545,6 +583,13 @@ export default function StatisticsPage() {
|
||||
const sevenDayRange = defaultWindow(7);
|
||||
const thirtyDayRange = defaultWindow(30);
|
||||
const ninetyDayRange = defaultWindow(90);
|
||||
const quickRanges = [
|
||||
{ label: '今天', range: todayRange },
|
||||
{ label: '昨天', range: yesterdayRange },
|
||||
{ label: '近 7 天', range: sevenDayRange },
|
||||
{ label: '近 30 天', range: thirtyDayRange },
|
||||
{ label: '近 90 天', range: ninetyDayRange }
|
||||
];
|
||||
const refreshing = statistics.isFetching || mileage.isFetching || fleetVehicles.isFetching;
|
||||
const resultsLoading = fleetVehicles.isLoading || mileage.isLoading || mileage.isPlaceholderData;
|
||||
const exportPercent = exportProgress?.total
|
||||
@@ -653,7 +698,7 @@ export default function StatisticsPage() {
|
||||
</label>
|
||||
<Button className="v2-primary-button v2-mileage-inline-submit" theme="solid" htmlType="submit">查询</Button>
|
||||
<SourceStrategy value={draft.sources} onChange={(sources) => setDraft((current) => ({ ...current, sources }))} />
|
||||
<div className="v2-mileage-ranges"><span>快捷范围</span><ButtonGroup aria-label="快捷日期范围"><Button theme={isSameRange(draft, todayRange) ? 'solid' : 'light'} onClick={() => setRange(todayRange)}>今天</Button><Button theme={isSameRange(draft, yesterdayRange) ? 'solid' : 'light'} onClick={() => setRange(yesterdayRange)}>昨天</Button><Button theme={isSameRange(draft, sevenDayRange) ? 'solid' : 'light'} onClick={() => setRange(sevenDayRange)}>近 7 天</Button><Button theme={isSameRange(draft, thirtyDayRange) ? 'solid' : 'light'} onClick={() => setRange(thirtyDayRange)}>近 30 天</Button><Button theme={isSameRange(draft, ninetyDayRange) ? 'solid' : 'light'} onClick={() => setRange(ninetyDayRange)}>近 90 天</Button></ButtonGroup></div>
|
||||
<MileageRangeShortcuts value={draft} ranges={quickRanges} onChange={setRange} />
|
||||
</form>;
|
||||
const filterValidation = validationError || criteriaError ? <p className="v2-mileage-validation" role="alert">{validationError || criteriaError}</p> : null;
|
||||
|
||||
|
||||
@@ -14551,6 +14551,8 @@
|
||||
flex: 0 0 auto;
|
||||
gap: 0;
|
||||
overflow: visible;
|
||||
container-name: mileage-discovery;
|
||||
container-type: inline-size;
|
||||
border: 1px solid #d8e3ef;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
@@ -14702,6 +14704,27 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-mileage-source-trigger.semi-button .semi-button-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.v2-mileage-source-trigger.semi-button .semi-button-content > span,
|
||||
.v2-mileage-source-trigger.semi-button .semi-button-content > em {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v2-mileage-source-trigger.semi-button .semi-button-content > b {
|
||||
flex: 0 0 auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.v2-mileage-source-trigger.semi-button:hover,
|
||||
.v2-mileage-source-trigger.semi-button[aria-expanded="true"] {
|
||||
border-color: #aec6e4;
|
||||
@@ -14715,7 +14738,10 @@
|
||||
}
|
||||
|
||||
.v2-mileage-ranges {
|
||||
display: flex;
|
||||
min-height: 38px;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 9px;
|
||||
@@ -14723,14 +14749,22 @@
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.v2-mileage-ranges > span {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.v2-mileage-ranges > .semi-button-group {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.v2-mileage-ranges .semi-button {
|
||||
min-height: 30px;
|
||||
min-width: 52px;
|
||||
flex: 1 1 0;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 650;
|
||||
@@ -15003,9 +15037,9 @@
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
@media (min-width: 681px) and (max-width: 1050px) {
|
||||
@container mileage-discovery (min-width: 681px) and (max-width: 1100px) {
|
||||
.v2-mileage-query-panel .v2-mileage-filter {
|
||||
grid-template-columns: minmax(200px, 1.25fr) minmax(220px, 1fr) auto;
|
||||
grid-template-columns: minmax(260px, 1.25fr) minmax(242px, .95fr) auto;
|
||||
grid-template-areas:
|
||||
"vehicle date submit"
|
||||
"source ranges ranges";
|
||||
@@ -15028,8 +15062,35 @@
|
||||
grid-area: source;
|
||||
}
|
||||
|
||||
.v2-mileage-query-panel .v2-mileage-source-trigger.semi-button {
|
||||
width: 100%;
|
||||
min-width: 190px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.v2-mileage-query-panel .v2-mileage-ranges {
|
||||
display: flex;
|
||||
grid-area: ranges;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-self: stretch;
|
||||
}
|
||||
|
||||
.v2-mileage-query-panel .v2-mileage-ranges > span {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
padding-inline: 7px 5px;
|
||||
color: #718196;
|
||||
font-size: 10px;
|
||||
font-weight: 650;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v2-mileage-query-panel .v2-mileage-ranges .semi-button {
|
||||
min-width: 52px;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user