refactor: polish semi mileage workspace
This commit is contained in:
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
import { afterEach, expect, test, vi } from 'vitest';
|
import { afterEach, expect, test, vi } from 'vitest';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import StatisticsPage from './StatisticsPage';
|
import StatisticsPage, { mileageRangeContainsDate } from './StatisticsPage';
|
||||||
import { buildMonitorPath, withMonitorReturn } from '../routing/monitorContext';
|
import { buildMonitorPath, withMonitorReturn } from '../routing/monitorContext';
|
||||||
import { ROUTER_FUTURE } from '../routing/routerConfig';
|
import { ROUTER_FUTURE } from '../routing/routerConfig';
|
||||||
|
|
||||||
@@ -37,6 +37,13 @@ function prepareData() {
|
|||||||
mocks.vehicleCoverage.mockResolvedValue({ items: [{ vin: 'LTEST000000000001', plate: '粤A12345' }], total: 1, limit: 20, offset: 0 });
|
mocks.vehicleCoverage.mockResolvedValue({ items: [{ vin: 'LTEST000000000001', plate: '粤A12345' }], total: 1, limit: 20, offset: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('refreshes only ranges that include the current business date', () => {
|
||||||
|
expect(mileageRangeContainsDate({ dateFrom: '2026-07-13', dateTo: '2026-07-19' }, '2026-07-19')).toBe(true);
|
||||||
|
expect(mileageRangeContainsDate({ dateFrom: '2026-07-19', dateTo: '2026-07-19' }, '2026-07-19')).toBe(true);
|
||||||
|
expect(mileageRangeContainsDate({ dateFrom: '2026-07-13', dateTo: '2026-07-18' }, '2026-07-19')).toBe(false);
|
||||||
|
expect(mileageRangeContainsDate({ dateFrom: '2026-07-20', dateTo: '2026-07-20' }, '2026-07-19')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test('renders only the desktop matrix with dates as columns and a period total', async () => {
|
test('renders only the desktop matrix with dates as columns and a period total', async () => {
|
||||||
prepareData();
|
prepareData();
|
||||||
const view = renderPage('/statistics?vins=LTEST000000000001&dateFrom=2026-07-13&dateTo=2026-07-14');
|
const view = renderPage('/statistics?vins=LTEST000000000001&dateFrom=2026-07-13&dateTo=2026-07-14');
|
||||||
@@ -53,6 +60,10 @@ test('renders only the desktop matrix with dates as columns and a period total',
|
|||||||
expect(screen.getByLabelText('里程查询工作区')).toHaveTextContent('GB32960 优先');
|
expect(screen.getByLabelText('里程查询工作区')).toHaveTextContent('GB32960 优先');
|
||||||
expect(screen.getByRole('heading', { name: '里程范围', level: 5 })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: '里程范围', level: 5 })).toBeInTheDocument();
|
||||||
expect(screen.getByText('车牌、自然日与里程数据源策略')).toBeInTheDocument();
|
expect(screen.getByText('车牌、自然日与里程数据源策略')).toBeInTheDocument();
|
||||||
|
expect(view.container.querySelector('.v2-mileage-date-range.semi-datepicker')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('group', { name: '快捷日期范围' })).toHaveClass('semi-button-group');
|
||||||
|
expect(screen.getByPlaceholderText('开始日期')).toHaveValue('2026-07-13');
|
||||||
|
expect(screen.getByPlaceholderText('结束日期')).toHaveValue('2026-07-14');
|
||||||
expect(screen.getAllByText('1 辆 · 2 天').some((element) => element.closest('.semi-tag'))).toBe(true);
|
expect(screen.getAllByText('1 辆 · 2 天').some((element) => element.closest('.semi-tag'))).toBe(true);
|
||||||
expect(screen.queryByText('当日起始里程')).not.toBeInTheDocument();
|
expect(screen.queryByText('当日起始里程')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('数据来源')).not.toBeInTheDocument();
|
expect(screen.queryByText('数据来源')).not.toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IconArrowDown, IconArrowUp, IconClose, IconDownload, IconInfoCircle, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
import { IconArrowDown, IconArrowUp, IconClose, IconDownload, IconInfoCircle, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
||||||
import { Button, Card, Empty, Input, Spin, Switch, Table, Tag } from '@douyinfe/semi-ui';
|
import { Button, ButtonGroup, Card, DatePicker, Empty, Input, Progress, Spin, Switch, Table, Tag } from '@douyinfe/semi-ui';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { type CSSProperties, FormEvent, type KeyboardEvent, memo, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
import { type CSSProperties, FormEvent, type KeyboardEvent, memo, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
@@ -30,6 +30,7 @@ const PAGE_SIZE = 20;
|
|||||||
const EXPORT_VEHICLE_PAGE_SIZE = 2_000;
|
const EXPORT_VEHICLE_PAGE_SIZE = 2_000;
|
||||||
const EXPORT_VIN_BATCH_SIZE = 50;
|
const EXPORT_VIN_BATCH_SIZE = 50;
|
||||||
const MAX_MILEAGE_RANGE_DAYS = 366;
|
const MAX_MILEAGE_RANGE_DAYS = 366;
|
||||||
|
const CURRENT_DAY_REFRESH_MS = 60_000;
|
||||||
|
|
||||||
type VehicleOption = Pick<VehicleRow, 'vin' | 'plate'>;
|
type VehicleOption = Pick<VehicleRow, 'vin' | 'plate'>;
|
||||||
type MileageProtocol = 'GB32960' | 'JT808' | 'YUTONG_MQTT';
|
type MileageProtocol = 'GB32960' | 'JT808' | 'YUTONG_MQTT';
|
||||||
@@ -58,6 +59,13 @@ function isSameRange(criteria: Pick<Criteria, 'dateFrom' | 'dateTo'>, range: { d
|
|||||||
return criteria.dateFrom === range.dateFrom && criteria.dateTo === range.dateTo;
|
return criteria.dateFrom === range.dateFrom && criteria.dateTo === range.dateTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mileageRangeContainsDate(
|
||||||
|
range: { dateFrom: string; dateTo: string },
|
||||||
|
date = localDate()
|
||||||
|
) {
|
||||||
|
return Boolean(range.dateFrom && range.dateTo && range.dateFrom <= date && range.dateTo >= date);
|
||||||
|
}
|
||||||
|
|
||||||
function formatKm(value?: number) {
|
function formatKm(value?: number) {
|
||||||
if (value == null || !Number.isFinite(value)) return '—';
|
if (value == null || !Number.isFinite(value)) return '—';
|
||||||
return formatZhNumber(value, 1);
|
return formatZhNumber(value, 1);
|
||||||
@@ -77,6 +85,20 @@ function mileageDateRangeError(criteria: Pick<Criteria, 'dateFrom' | 'dateTo'>)
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function datePickerRange(dateValue?: Date | Date[] | string | string[], formattedValue?: string | string[] | Date | Date[]) {
|
||||||
|
const values = Array.isArray(formattedValue) && formattedValue.length === 2
|
||||||
|
? formattedValue
|
||||||
|
: Array.isArray(dateValue) && dateValue.length === 2 ? dateValue : [];
|
||||||
|
if (values.length !== 2) return;
|
||||||
|
const normalized = values.map((value) => {
|
||||||
|
if (value instanceof Date) return localDate(value);
|
||||||
|
if (typeof value === 'number') return localDate(new Date(value));
|
||||||
|
return String(value).slice(0, 10);
|
||||||
|
});
|
||||||
|
if (normalized.some((value) => !/^\d{4}-\d{2}-\d{2}$/.test(value))) return;
|
||||||
|
return { dateFrom: normalized[0], dateTo: normalized[1] };
|
||||||
|
}
|
||||||
|
|
||||||
function mileageParams(criteria: Criteria, offset = 0) {
|
function mileageParams(criteria: Criteria, offset = 0) {
|
||||||
const params = new URLSearchParams({ dateFrom: criteria.dateFrom, dateTo: criteria.dateTo });
|
const params = new URLSearchParams({ dateFrom: criteria.dateFrom, dateTo: criteria.dateTo });
|
||||||
if (criteria.vehicles.length) params.set('vins', criteria.vehicles.map((vehicle) => vehicle.vin).join(','));
|
if (criteria.vehicles.length) params.set('vins', criteria.vehicles.map((vehicle) => vehicle.vin).join(','));
|
||||||
@@ -441,8 +463,27 @@ export default function StatisticsPage() {
|
|||||||
const statisticsScope = statisticsParams.toString();
|
const statisticsScope = statisticsParams.toString();
|
||||||
const rowsCriteria = useMemo(() => ({ ...criteria, vehicles: displayVehicles }), [criteria, displayVehicles]);
|
const rowsCriteria = useMemo(() => ({ ...criteria, vehicles: displayVehicles }), [criteria, displayVehicles]);
|
||||||
const rowsParams = useMemo(() => mileageParams(rowsCriteria, 0), [rowsCriteria]);
|
const rowsParams = useMemo(() => mileageParams(rowsCriteria, 0), [rowsCriteria]);
|
||||||
const statistics = useQuery({ queryKey: ['mileage-statistics', statisticsScope], queryFn: ({ signal }) => api.mileageStatistics(statisticsParams, signal), enabled: !criteriaError, staleTime: 60_000, gcTime: QUERY_MEMORY.summaryGcTime });
|
const currentDayRange = !criteriaError && mileageRangeContainsDate(criteria);
|
||||||
const mileage = useQuery<Page<DailyMileageRow>>({ queryKey: ['daily-mileage-query', statisticsScope, rowsParams.toString()], queryFn: ({ signal }) => api.dailyMileage(rowsParams, signal), enabled: !criteriaError && displayVehicles.length > 0, staleTime: 60_000, gcTime: QUERY_MEMORY.highVolumeGcTime, placeholderData: retainPreviousPageWithinScope<Page<DailyMileageRow>>(statisticsScope, 2) });
|
const currentDayRefreshInterval = currentDayRange ? CURRENT_DAY_REFRESH_MS : false;
|
||||||
|
const statistics = useQuery({
|
||||||
|
queryKey: ['mileage-statistics', statisticsScope],
|
||||||
|
queryFn: ({ signal }) => api.mileageStatistics(statisticsParams, signal),
|
||||||
|
enabled: !criteriaError,
|
||||||
|
staleTime: CURRENT_DAY_REFRESH_MS,
|
||||||
|
gcTime: QUERY_MEMORY.summaryGcTime,
|
||||||
|
refetchInterval: currentDayRefreshInterval,
|
||||||
|
refetchIntervalInBackground: false
|
||||||
|
});
|
||||||
|
const mileage = useQuery<Page<DailyMileageRow>>({
|
||||||
|
queryKey: ['daily-mileage-query', statisticsScope, rowsParams.toString()],
|
||||||
|
queryFn: ({ signal }) => api.dailyMileage(rowsParams, signal),
|
||||||
|
enabled: !criteriaError && displayVehicles.length > 0,
|
||||||
|
staleTime: CURRENT_DAY_REFRESH_MS,
|
||||||
|
gcTime: QUERY_MEMORY.highVolumeGcTime,
|
||||||
|
placeholderData: retainPreviousPageWithinScope<Page<DailyMileageRow>>(statisticsScope, 2),
|
||||||
|
refetchInterval: currentDayRefreshInterval,
|
||||||
|
refetchIntervalInBackground: false
|
||||||
|
});
|
||||||
const dates = useMemo(() => criteriaError ? [] : rangeDates(criteria.dateFrom, criteria.dateTo), [criteria.dateFrom, criteria.dateTo, criteriaError]);
|
const dates = useMemo(() => criteriaError ? [] : rangeDates(criteria.dateFrom, criteria.dateTo), [criteria.dateFrom, criteria.dateTo, criteriaError]);
|
||||||
const mileageByVin = useMemo(() => {
|
const mileageByVin = useMemo(() => {
|
||||||
const index = new Map<string, { plate: string; days: Map<string, number>; sources: Map<string, string> }>();
|
const index = new Map<string, { plate: string; days: Map<string, number>; sources: Map<string, string> }>();
|
||||||
@@ -592,11 +633,27 @@ export default function StatisticsPage() {
|
|||||||
const rangeSummary = criteria.vehicles.length ? `已选 ${criteria.vehicles.length} 辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)} 天` : `全部车辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)} 天`;
|
const rangeSummary = criteria.vehicles.length ? `已选 ${criteria.vehicles.length} 辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)} 天` : `全部车辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)} 天`;
|
||||||
const filterForm = <form className="v2-mileage-filter" onSubmit={submit}>
|
const filterForm = <form className="v2-mileage-filter" onSubmit={submit}>
|
||||||
<VehicleMultiSelect value={draft.vehicles} onChange={(vehicles) => setDraft((current) => ({ ...current, vehicles }))} />
|
<VehicleMultiSelect value={draft.vehicles} onChange={(vehicles) => setDraft((current) => ({ ...current, vehicles }))} />
|
||||||
<label><span>开始日期</span><Input aria-label="开始日期" type="date" value={draft.dateFrom} max={draft.dateTo} onChange={(value) => setDraft((current) => ({ ...current, dateFrom: value }))} /></label>
|
<label className="v2-mileage-date-field">
|
||||||
<label><span>结束日期</span><Input aria-label="结束日期" type="date" value={draft.dateTo} min={draft.dateFrom} onChange={(value) => setDraft((current) => ({ ...current, dateTo: value }))} /></label>
|
<span id="v2-mileage-date-range-label">日期范围</span>
|
||||||
|
<DatePicker
|
||||||
|
className="v2-mileage-date-range"
|
||||||
|
type="dateRange"
|
||||||
|
density="compact"
|
||||||
|
format="yyyy-MM-dd"
|
||||||
|
value={[draft.dateFrom, draft.dateTo]}
|
||||||
|
placeholder={['开始日期', '结束日期']}
|
||||||
|
showClear={false}
|
||||||
|
onChangeWithDateFirst
|
||||||
|
aria-labelledby="v2-mileage-date-range-label"
|
||||||
|
onChange={(value, formattedValue) => {
|
||||||
|
const range = datePickerRange(value, formattedValue);
|
||||||
|
if (range) setDraft((current) => ({ ...current, ...range }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<Button className="v2-primary-button v2-mileage-inline-submit" theme="solid" htmlType="submit">查询</Button>
|
<Button className="v2-primary-button v2-mileage-inline-submit" theme="solid" htmlType="submit">查询</Button>
|
||||||
<SourceStrategy value={draft.sources} onChange={(sources) => setDraft((current) => ({ ...current, sources }))} />
|
<SourceStrategy value={draft.sources} onChange={(sources) => setDraft((current) => ({ ...current, sources }))} />
|
||||||
<div className="v2-mileage-ranges"><span>快捷范围</span><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></div>
|
<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>
|
||||||
</form>;
|
</form>;
|
||||||
const filterValidation = validationError || criteriaError ? <p className="v2-mileage-validation" role="alert">{validationError || criteriaError}</p> : null;
|
const filterValidation = validationError || criteriaError ? <p className="v2-mileage-validation" role="alert">{validationError || criteriaError}</p> : null;
|
||||||
|
|
||||||
@@ -668,7 +725,9 @@ export default function StatisticsPage() {
|
|||||||
/>
|
/>
|
||||||
{exportProgress ? <div className="v2-mileage-export-progress" role="progressbar" aria-label={exportProgress.label} aria-valuemin={0} aria-valuemax={100} aria-valuenow={exportPercent}>
|
{exportProgress ? <div className="v2-mileage-export-progress" role="progressbar" aria-label={exportProgress.label} aria-valuemin={0} aria-valuemax={100} aria-valuenow={exportPercent}>
|
||||||
<span><strong>{exportProgress.label}</strong><small>{exportPercent == null ? '处理中' : `${exportPercent}%`}</small></span>
|
<span><strong>{exportProgress.label}</strong><small>{exportPercent == null ? '处理中' : `${exportPercent}%`}</small></span>
|
||||||
<i className={exportPercent == null ? 'is-indeterminate' : ''}><b style={exportPercent == null ? undefined : { width: `${exportPercent}%` }} /></i>
|
{exportPercent == null
|
||||||
|
? <Spin className="v2-mileage-export-spinner" size="small" />
|
||||||
|
: <Progress className="v2-mileage-export-bar" percent={exportPercent} showInfo={false} size="small" strokeLinecap="round" aria-label={`${exportProgress.label} ${exportPercent}%`} />}
|
||||||
</div> : null}
|
</div> : null}
|
||||||
{!resultsLoading && displayVehicles.length ? <MileageMatrixGuide /> : null}
|
{!resultsLoading && displayVehicles.length ? <MileageMatrixGuide /> : null}
|
||||||
{resultsLoading ? <PanelLoading className="v2-mileage-loading" title="正在查询里程" description="新筛选范围返回前不会展示上一范围的数据。" /> : displayVehicles.length ? <MileageTable rows={matrixRows} dates={dates} scrollRef={tableScrollRef} /> : null}
|
{resultsLoading ? <PanelLoading className="v2-mileage-loading" title="正在查询里程" description="新筛选范围返回前不会展示上一范围的数据。" /> : displayVehicles.length ? <MileageTable rows={matrixRows} dates={dates} scrollRef={tableScrollRef} /> : null}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
border-bottom: 1px solid var(--v2-border);
|
border-bottom: 1px solid var(--v2-border);
|
||||||
background: linear-gradient(180deg, #fff 0%, #fbfcfe 100%);
|
background: #fff;
|
||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6978,7 +6978,7 @@
|
|||||||
gap: 9px;
|
gap: 9px;
|
||||||
border-bottom: 1px solid #e6ebf2;
|
border-bottom: 1px solid #e6ebf2;
|
||||||
border-radius: 12px 12px 0 0;
|
border-radius: 12px 12px 0 0;
|
||||||
background: linear-gradient(100deg, #f8fbff 0%, #f5f8fc 58%, #fbfcfe 100%);
|
background: #f8fafc;
|
||||||
padding: 7px 12px;
|
padding: 7px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9982,7 +9982,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1480px;
|
max-width: 1480px;
|
||||||
min-height: 52px;
|
min-height: 48px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -9992,7 +9992,7 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 7px 9px 7px 14px;
|
padding: 7px 9px 7px 14px;
|
||||||
box-shadow: 0 6px 20px rgba(31, 50, 76, .035);
|
box-shadow: 0 4px 14px rgba(31, 50, 76, .03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-command-copy {
|
.v2-workspace-command-copy {
|
||||||
@@ -10642,8 +10642,8 @@
|
|||||||
/* Final cascade layer for the shared command hierarchy. */
|
/* Final cascade layer for the shared command hierarchy. */
|
||||||
.v2-workspace-command-bar {
|
.v2-workspace-command-bar {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 52px;
|
height: 48px;
|
||||||
min-height: 52px;
|
min-height: 48px;
|
||||||
padding-block: 5px;
|
padding-block: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14541,8 +14541,8 @@
|
|||||||
* operations workspaces.
|
* operations workspaces.
|
||||||
*/
|
*/
|
||||||
.v2-mileage-page {
|
.v2-mileage-page {
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
padding: 14px 18px 16px;
|
padding: 12px 16px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-discovery-shell {
|
.v2-mileage-discovery-shell {
|
||||||
@@ -14552,9 +14552,9 @@
|
|||||||
gap: 0;
|
gap: 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
border: 1px solid #d8e3ef;
|
border: 1px solid #d8e3ef;
|
||||||
border-radius: 14px;
|
border-radius: 12px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 9px 28px rgba(31, 53, 80, .055);
|
box-shadow: 0 7px 22px rgba(31, 53, 80, .045);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-discovery-shell > .v2-mileage-command-bar {
|
.v2-mileage-discovery-shell > .v2-mileage-command-bar {
|
||||||
@@ -14564,7 +14564,7 @@
|
|||||||
min-height: 46px;
|
min-height: 46px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid #e3eaf2;
|
border-bottom: 1px solid #e3eaf2;
|
||||||
border-radius: 14px 14px 0 0;
|
border-radius: 12px 12px 0 0;
|
||||||
padding-inline: 13px 10px;
|
padding-inline: 13px 10px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
@@ -14579,7 +14579,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0 0 14px 14px;
|
border-radius: 0 0 12px 12px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14590,23 +14590,67 @@
|
|||||||
.v2-mileage-query-panel .v2-workspace-filter-heading {
|
.v2-mileage-query-panel .v2-workspace-filter-heading {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
border-bottom-color: #e3eaf2;
|
border-bottom-color: #e3eaf2;
|
||||||
background: linear-gradient(100deg, #f7faff 0%, #f9fbfd 58%, #fff 100%);
|
background: #f8fafc;
|
||||||
padding-inline: 13px;
|
padding-inline: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-query-panel .v2-mileage-filter {
|
.v2-mileage-query-panel .v2-mileage-filter {
|
||||||
gap: 10px;
|
grid-template-columns: minmax(220px, 1.15fr) minmax(224px, .95fr) auto auto minmax(228px, auto);
|
||||||
|
align-items: end;
|
||||||
|
gap: 8px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 11px 12px 12px;
|
padding: 8px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-filter > label {
|
.v2-mileage-filter > label {
|
||||||
gap: 5px;
|
gap: 4px;
|
||||||
color: #607187;
|
color: #607187;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-mileage-filter > label > span:first-child {
|
||||||
|
min-height: 14px;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-query-panel .v2-mileage-vehicle-field > small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-date-range.semi-datepicker {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-date-range .semi-datepicker-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-date-range .semi-input-wrapper {
|
||||||
|
min-height: 38px;
|
||||||
|
border-color: #d8e2ed;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fbfcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-date-range .semi-input-wrapper:hover {
|
||||||
|
border-color: #b9c9dc;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-date-range .semi-input-wrapper-focus {
|
||||||
|
border-color: #75a5ed;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, .09);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-date-range .semi-input {
|
||||||
|
font-size: 12px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-mileage-multiselect,
|
.v2-mileage-multiselect,
|
||||||
.v2-mileage-filter > label > .semi-input-wrapper {
|
.v2-mileage-filter > label > .semi-input-wrapper {
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
@@ -14644,6 +14688,11 @@
|
|||||||
box-shadow: 0 4px 12px rgba(18, 104, 223, .16);
|
box-shadow: 0 4px 12px rgba(18, 104, 223, .16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-mileage-inline-submit.semi-button,
|
||||||
|
.v2-mileage-source-strategy {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-mileage-source-trigger.semi-button {
|
.v2-mileage-source-trigger.semi-button {
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
border-color: #d8e3ef;
|
border-color: #d8e3ef;
|
||||||
@@ -14674,6 +14723,12 @@
|
|||||||
padding: 3px;
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-mileage-ranges > .semi-button-group {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-mileage-ranges .semi-button {
|
.v2-mileage-ranges .semi-button {
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -14741,9 +14796,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-results > .semi-card-body > .v2-workspace-panel-header {
|
.v2-mileage-results > .semi-card-body > .v2-workspace-panel-header {
|
||||||
min-height: 50px;
|
min-height: 46px;
|
||||||
border-bottom-color: #e2e9f1;
|
border-bottom-color: #e2e9f1;
|
||||||
background: linear-gradient(90deg, #fff 0%, #fbfcfe 100%);
|
background: #fff;
|
||||||
padding-inline: 14px;
|
padding-inline: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14772,7 +14827,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
border-bottom: 1px solid #e2e9f1;
|
border-bottom: 1px solid #e2e9f1;
|
||||||
background: linear-gradient(90deg, #f9fbfe 0%, #fff 100%);
|
background: #f8fafc;
|
||||||
padding: 5px 14px;
|
padding: 5px 14px;
|
||||||
color: #6c7d92;
|
color: #6c7d92;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -14897,11 +14952,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-results > .semi-card-body > footer {
|
.v2-mileage-results > .semi-card-body > footer {
|
||||||
min-height: 40px;
|
min-height: 36px;
|
||||||
border-top-color: #e3eaf2;
|
border-top-color: #e3eaf2;
|
||||||
background: #fbfcfe;
|
background: #fbfcfe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-mileage-export-progress {
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 7px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-export-progress > .v2-mileage-export-bar {
|
||||||
|
min-width: 160px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-export-progress > .v2-mileage-export-spinner {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-mileage-evidence {
|
.v2-mileage-evidence {
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -14936,11 +15005,31 @@
|
|||||||
|
|
||||||
@media (min-width: 681px) and (max-width: 1050px) {
|
@media (min-width: 681px) and (max-width: 1050px) {
|
||||||
.v2-mileage-query-panel .v2-mileage-filter {
|
.v2-mileage-query-panel .v2-mileage-filter {
|
||||||
|
grid-template-columns: minmax(200px, 1.25fr) minmax(220px, 1fr) auto;
|
||||||
|
grid-template-areas:
|
||||||
|
"vehicle date submit"
|
||||||
|
"source ranges ranges";
|
||||||
row-gap: 10px;
|
row-gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-summary > .semi-card-body {
|
.v2-mileage-query-panel .v2-mileage-vehicle-field {
|
||||||
grid-template-columns: minmax(285px, 1fr) minmax(0, 2fr);
|
grid-area: vehicle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-query-panel .v2-mileage-date-field {
|
||||||
|
grid-area: date;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-query-panel .v2-mileage-filter > .v2-primary-button {
|
||||||
|
grid-area: submit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-query-panel .v2-mileage-source-strategy {
|
||||||
|
grid-area: source;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-query-panel .v2-mileage-ranges {
|
||||||
|
grid-area: ranges;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14970,6 +15059,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-filter > label > .semi-input-wrapper,
|
.v2-mileage-filter > label > .semi-input-wrapper,
|
||||||
|
.v2-mileage-date-range .semi-input-wrapper,
|
||||||
.v2-mileage-multiselect,
|
.v2-mileage-multiselect,
|
||||||
.v2-mileage-source-trigger.semi-button,
|
.v2-mileage-source-trigger.semi-button,
|
||||||
.v2-mileage-filter > .v2-primary-button.semi-button {
|
.v2-mileage-filter > .v2-primary-button.semi-button {
|
||||||
@@ -14981,6 +15071,30 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-mileage-query-panel .v2-mileage-vehicle-field > small {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-date-range.semi-datepicker,
|
||||||
|
.v2-mileage-date-range .semi-datepicker-input,
|
||||||
|
.v2-mileage-date-range .semi-input-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-ranges > .semi-button-group {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-ranges > .semi-button-group::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-ranges > .semi-button-group > .semi-button {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-mileage-summary-secondary .v2-mileage-summary-card strong {
|
.v2-mileage-summary-secondary .v2-mileage-summary-card strong {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
@@ -15645,7 +15759,7 @@
|
|||||||
|
|
||||||
.v2-workspace-metric-rail.is-queue > .semi-card-body {
|
.v2-workspace-metric-rail.is-queue > .semi-card-body {
|
||||||
display: grid;
|
display: grid;
|
||||||
min-height: 58px;
|
min-height: 54px;
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15662,9 +15776,9 @@
|
|||||||
.v2-workspace-metric-list > span {
|
.v2-workspace-metric-list > span {
|
||||||
display: grid;
|
display: grid;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 58px;
|
min-height: 54px;
|
||||||
grid-template-columns: auto minmax(0, 1fr);
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
grid-template-rows: auto auto;
|
grid-template-rows: 14px 24px;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
padding: 7px 13px;
|
padding: 7px 13px;
|
||||||
@@ -15681,7 +15795,7 @@
|
|||||||
.v2-workspace-metric-action.semi-button {
|
.v2-workspace-metric-action.semi-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 58px;
|
min-height: 54px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
@@ -15700,9 +15814,9 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 58px;
|
min-height: 54px;
|
||||||
grid-template-columns: auto minmax(0, 1fr);
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
grid-template-rows: auto auto;
|
grid-template-rows: 14px 24px;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
padding: 7px 13px;
|
padding: 7px 13px;
|
||||||
@@ -15717,6 +15831,7 @@
|
|||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span > strong,
|
.v2-workspace-metric-list > span > strong,
|
||||||
@@ -15727,6 +15842,7 @@
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span > em,
|
.v2-workspace-metric-list > span > em,
|
||||||
@@ -15741,6 +15857,7 @@
|
|||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-secondary > strong,
|
.v2-workspace-metric-list > span.is-secondary > strong,
|
||||||
@@ -15749,8 +15866,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-primary {
|
.v2-workspace-metric-list > span.is-primary {
|
||||||
background: linear-gradient(145deg, #edf5ff 0%, #f8fbff 100%);
|
background: #f5f9ff;
|
||||||
box-shadow: inset 0 -3px #1f6feb;
|
box-shadow: inset 3px 0 #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-primary > :is(small, strong),
|
.v2-workspace-metric-list > span.is-primary > :is(small, strong),
|
||||||
@@ -15759,8 +15876,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-danger {
|
.v2-workspace-metric-list > span.is-danger {
|
||||||
background: linear-gradient(145deg, #fff 0%, #fff8f7 100%);
|
background: #fff8f7;
|
||||||
box-shadow: inset 0 -3px #e2554f;
|
box-shadow: inset 3px 0 #e2554f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-danger > :is(small, strong),
|
.v2-workspace-metric-list > span.is-danger > :is(small, strong),
|
||||||
@@ -15784,21 +15901,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-action.is-danger {
|
.v2-workspace-metric-list > span.is-action.is-danger {
|
||||||
background: linear-gradient(145deg, #fff 0%, #fff8f7 100%);
|
background: #fff8f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-action.is-warning {
|
.v2-workspace-metric-list > span.is-action.is-warning {
|
||||||
background: linear-gradient(145deg, #fff 0%, #fffcf5 100%);
|
background: #fffcf5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-action.is-active {
|
.v2-workspace-metric-list > span.is-action.is-active {
|
||||||
background: linear-gradient(145deg, #edf5ff 0%, #f8fbff 100%);
|
background: #f5f9ff;
|
||||||
box-shadow: inset 0 -3px #1f6feb;
|
box-shadow: inset 3px 0 #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-action.is-active.is-danger {
|
.v2-workspace-metric-list > span.is-action.is-active.is-danger {
|
||||||
background: linear-gradient(145deg, #fff1ef 0%, #fff8f7 100%);
|
background: #fff4f2;
|
||||||
box-shadow: inset 0 -3px #e2554f;
|
box-shadow: inset 3px 0 #e2554f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-context {
|
.v2-workspace-metric-context {
|
||||||
@@ -23245,7 +23362,7 @@
|
|||||||
.v2-mileage-summary.v2-workspace-metric-rail.is-queue > .semi-card-body {
|
.v2-mileage-summary.v2-workspace-metric-rail.is-queue > .semi-card-body {
|
||||||
display: grid;
|
display: grid;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: 58px;
|
min-height: 54px;
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
@@ -23256,7 +23373,12 @@
|
|||||||
|
|
||||||
.v2-mileage-summary .v2-workspace-metric-list > span {
|
.v2-mileage-summary .v2-workspace-metric-list > span {
|
||||||
display: grid;
|
display: grid;
|
||||||
min-height: 58px;
|
min-height: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-mileage-summary .v2-workspace-metric-list > span > :is(small, strong, em),
|
||||||
|
.v2-mileage-summary .v2-workspace-metric-action .semi-button-content > :is(small, strong, em) {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-mileage-summary-unit {
|
.v2-mileage-summary-unit {
|
||||||
@@ -23290,6 +23412,10 @@
|
|||||||
.v2-mileage-summary .v2-workspace-metric-list > span {
|
.v2-mileage-summary .v2-workspace-metric-list > span {
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-mileage-summary .v2-workspace-metric-list > span.is-secondary {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Reference in New Issue
Block a user