refine Semi UI vehicle workspace

This commit is contained in:
lingniu
2026-07-18 10:00:15 +08:00
parent 04d5992252
commit c1c35f8962
5 changed files with 222 additions and 75 deletions

View File

@@ -181,7 +181,7 @@ export function AppShell() {
</Dropdown> </Dropdown>
</div> </div>
</Header> </Header>
<Content className={`v2-content${section === 'tracks' ? ' is-track-workspace' : ''}`}><Outlet /></Content> <Content key={location.pathname} className={`v2-content${section === 'tracks' ? ' is-track-workspace' : ''}`}><Outlet /></Content>
</Layout> </Layout>
<ContextHelp section={section} visible={helpOpen} onClose={() => setHelpOpen(false)} /> <ContextHelp section={section} visible={helpOpen} onClose={() => setHelpOpen(false)} />
{passwordOpen ? <PasswordDialog onClose={() => setPasswordOpen(false)} onChanged={logout} /> : null} {passwordOpen ? <PasswordDialog onClose={() => setPasswordOpen(false)} onChanged={logout} /> : null}

View File

@@ -172,7 +172,11 @@ test('shows deduplicated Semi vehicle candidates and opens the selected VIN', as
expect(initialQuery!.get('limit')).toBe('10'); expect(initialQuery!.get('limit')).toBe('10');
expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).toBeInTheDocument();
expect(await screen.findByText('本页 1 辆')).toBeInTheDocument(); expect(await screen.findByText('本页 1 辆')).toBeInTheDocument();
expect(screen.getByText('本页在线 1')).toBeInTheDocument();
expect(screen.getByText('多源车辆 0')).toBeInTheDocument();
expect(view.container.querySelector('.v2-vehicle-directory-table.semi-table-wrapper')).toBeInTheDocument(); expect(view.container.querySelector('.v2-vehicle-directory-table.semi-table-wrapper')).toBeInTheDocument();
expect(view.container.querySelector('.v2-vehicle-directory-profile')).toHaveTextContent('测试品牌13800000000');
expect(view.container.querySelector('.v2-vehicle-discovery-shell')).toContainElement(screen.getByLabelText('查车操作'));
expect(screen.getByRole('button', { name: `打开 ${initialRealtime.vin} 车辆档案` })).toBeInTheDocument(); expect(screen.getByRole('button', { name: `打开 ${initialRealtime.vin} 车辆档案` })).toBeInTheDocument();
fireEvent.focus(input); fireEvent.focus(input);
expect(input).toHaveAttribute('aria-expanded', 'true'); expect(input).toHaveAttribute('aria-expanded', 'true');

View File

@@ -4,7 +4,7 @@ import {
IconChevronRight, IconMapPin, IconSearch, IconTickCircle IconChevronRight, IconMapPin, IconSearch, IconTickCircle
} from '@douyinfe/semi-icons'; } from '@douyinfe/semi-icons';
import { Button, Card, Descriptions, Empty, Input, List, Select, SideSheet, Table, Tag } from '@douyinfe/semi-ui'; import { Button, Card, Descriptions, Empty, Input, List, Select, SideSheet, Table, Tag } from '@douyinfe/semi-ui';
import { FormEvent, lazy, Suspense, useEffect, useMemo, useState } from 'react'; import { FormEvent, lazy, Suspense, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { api } from '../../api/client'; import { api } from '../../api/client';
import type { LatestTelemetryResponse, LatestTelemetryValue, QualityIssueRow, VehicleDetail, VehicleRealtimeRow } from '../../api/types'; import type { LatestTelemetryResponse, LatestTelemetryValue, QualityIssueRow, VehicleDetail, VehicleRealtimeRow } from '../../api/types';
@@ -35,11 +35,28 @@ const SINGLE_VEHICLE_REFRESH_MS = 10_000;
const VehicleSearch = lazy(() => import('./VehicleSearchWorkspace')); const VehicleSearch = lazy(() => import('./VehicleSearchWorkspace'));
function resetWorkspaceScroll() { function resetWorkspaceScroll() {
const content = document.querySelector<HTMLElement>('.v2-content'); const targets = [
if (!content) return; document.scrollingElement,
content.scrollTop = 0; document.documentElement,
content.scrollLeft = 0; document.body,
content.scrollTo?.({ top: 0, left: 0, behavior: 'auto' }); document.querySelector<HTMLElement>('.v2-shell'),
document.querySelector<HTMLElement>('.v2-main'),
document.querySelector<HTMLElement>('.v2-content')
];
for (const target of targets) {
if (!(target instanceof HTMLElement)) continue;
target.scrollTop = 0;
target.scrollLeft = 0;
target.scrollTo?.({ top: 0, left: 0, behavior: 'auto' });
}
}
function revealVehicleIdentity(root: HTMLElement | null) {
resetWorkspaceScroll();
if (!root) return;
root.scrollTop = 0;
root.scrollLeft = 0;
root.scrollIntoView?.({ behavior: 'auto', block: 'start', inline: 'nearest' });
} }
function eventTimeLabel(value?: string) { function eventTimeLabel(value?: string) {
@@ -394,6 +411,7 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
const { session } = usePlatformSession(); const { session } = usePlatformSession();
const navigate = useNavigate(); const navigate = useNavigate();
const mobileLayout = useMobileLayout(); const mobileLayout = useMobileLayout();
const recordRootRef = useRef<HTMLDivElement>(null);
const [sourceEvidenceOpen, setSourceEvidenceOpen] = useState(false); const [sourceEvidenceOpen, setSourceEvidenceOpen] = useState(false);
const realtime = liveRealtime ?? detail.realtimeSummary; const realtime = liveRealtime ?? detail.realtimeSummary;
const identity = detail.identity; const identity = detail.identity;
@@ -406,7 +424,33 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
...(hasMenu(session, 'statistics') ? [{ key: 'statistics', label: '里程查询', icon: <IconClock />, to: withMonitorReturn(`/statistics?vins=${encodeURIComponent(detail.vin)}`, monitorReturn), type: 'tertiary' as const }] : []), ...(hasMenu(session, 'statistics') ? [{ key: 'statistics', label: '里程查询', icon: <IconClock />, to: withMonitorReturn(`/statistics?vins=${encodeURIComponent(detail.vin)}`, monitorReturn), type: 'tertiary' as const }] : []),
...(hasMenu(session, 'alerts') ? [{ key: 'alerts', label: '告警事件', icon: <IconAlarm />, to: `/alerts?vin=${encodeURIComponent(detail.vin)}`, type: 'tertiary' as const }] : []) ...(hasMenu(session, 'alerts') ? [{ key: 'alerts', label: '告警事件', icon: <IconAlarm />, to: `/alerts?vin=${encodeURIComponent(detail.vin)}`, type: 'tertiary' as const }] : [])
]; ];
return <div className={`v2-vehicle-record-page${mobileLayout ? ' is-mobile-layout' : ''}`}> useLayoutEffect(() => {
const root = recordRootRef.current;
if (!root) return;
let cancelled = false;
const timers: number[] = [];
const revealIdentity = () => {
if (!cancelled) revealVehicleIdentity(root);
};
const cancelEntryReset = () => {
cancelled = true;
for (const timer of timers) window.clearTimeout(timer);
};
const interactionEvents = ['wheel', 'touchstart', 'pointerdown', 'keydown'] as const;
for (const eventName of interactionEvents) window.addEventListener(eventName, cancelEntryReset, { capture: true, passive: true });
revealIdentity();
const frame = window.requestAnimationFrame(revealIdentity);
// The map and live evidence arrive independently. Keep the identity band
// anchored only during initial settlement, then immediately yield to input.
for (const delay of [80, 180, 360, 720, 1200, 1800, 2400]) timers.push(window.setTimeout(revealIdentity, delay));
return () => {
cancelled = true;
window.cancelAnimationFrame(frame);
for (const timer of timers) window.clearTimeout(timer);
for (const eventName of interactionEvents) window.removeEventListener(eventName, cancelEntryReset, true);
};
}, [detail.vin]);
return <div ref={recordRootRef} className={`v2-vehicle-record-page${mobileLayout ? ' is-mobile-layout' : ''}`}>
<MonitorReturnBar /> <MonitorReturnBar />
<Card className="v2-identity-band v2-record-card v2-live-card v2-live-overview v2-vehicle-command-card" bodyStyle={{ padding: 0 }}> <Card className="v2-identity-band v2-record-card v2-live-card v2-live-overview v2-vehicle-command-card" bodyStyle={{ padding: 0 }}>
<div className="v2-vehicle-command-header"> <div className="v2-vehicle-command-header">

View File

@@ -20,11 +20,20 @@ import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
const VehicleProfileSyncPanel = lazy(() => import('./VehicleProfileSyncPanel')); const VehicleProfileSyncPanel = lazy(() => import('./VehicleProfileSyncPanel'));
function resetWorkspaceScroll() { function resetWorkspaceScroll() {
const content = document.querySelector<HTMLElement>('.v2-content'); const targets = [
if (!content) return; document.scrollingElement,
content.scrollTop = 0; document.documentElement,
content.scrollLeft = 0; document.body,
content.scrollTo?.({ top: 0, left: 0, behavior: 'auto' }); document.querySelector<HTMLElement>('.v2-shell'),
document.querySelector<HTMLElement>('.v2-main'),
document.querySelector<HTMLElement>('.v2-content')
];
for (const target of targets) {
if (!(target instanceof HTMLElement)) continue;
target.scrollTop = 0;
target.scrollLeft = 0;
target.scrollTo?.({ top: 0, left: 0, behavior: 'auto' });
}
} }
function vehicleLastSeenLabel(value?: string) { function vehicleLastSeenLabel(value?: string) {
@@ -61,6 +70,8 @@ export default function VehicleSearchWorkspace() {
const pageVehicles = useMemo(() => options.slice(0, pageSize), [options, pageSize]); const pageVehicles = useMemo(() => options.slice(0, pageSize), [options, pageSize]);
const total = candidates.data?.total ?? 0; const total = candidates.data?.total ?? 0;
const totalPages = Math.max(1, Math.ceil(total / pageSize)); const totalPages = Math.max(1, Math.ceil(total / pageSize));
const onlineOnPage = useMemo(() => pageVehicles.filter((vehicle) => vehicle.online).length, [pageVehicles]);
const multiSourceOnPage = useMemo(() => pageVehicles.filter((vehicle) => vehicle.protocols.length > 1).length, [pageVehicles]);
const administrator = canAdminister(session); const administrator = canAdminister(session);
useEffect(() => () => window.clearTimeout(closeTimerRef.current), []); useEffect(() => () => window.clearTimeout(closeTimerRef.current), []);
useEffect(() => { useEffect(() => {
@@ -79,6 +90,7 @@ export default function VehicleSearchWorkspace() {
const normalized = value.trim(); const normalized = value.trim();
if (!normalized) return; if (!normalized) return;
setCandidatesOpen(false); setCandidatesOpen(false);
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
resetWorkspaceScroll(); resetWorkspaceScroll();
navigate(`/vehicles/${encodeURIComponent(normalized)}`); navigate(`/vehicles/${encodeURIComponent(normalized)}`);
window.queueMicrotask(resetWorkspaceScroll); window.queueMicrotask(resetWorkspaceScroll);
@@ -91,64 +103,70 @@ export default function VehicleSearchWorkspace() {
}; };
const vehicleScope = candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权` : '授权范围'; const vehicleScope = candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权` : '授权范围';
return <section className={`v2-vehicle-search-page${syncOpen ? ' has-sync-panel' : ''}${candidatesOpen ? ' has-candidates' : ''}`}> return <section className={`v2-vehicle-search-page${syncOpen ? ' has-sync-panel' : ''}${candidatesOpen ? ' has-candidates' : ''}`}>
{!mobileLayout || administrator ? <WorkspaceCommandBar <div className="v2-vehicle-discovery-shell">
className={`v2-vehicle-command-bar${mobileLayout ? ' is-mobile-admin' : ''}`} {!mobileLayout || administrator ? <WorkspaceCommandBar
ariaLabel={mobileLayout ? '车辆主档操作' : '查车操作'} className={`v2-vehicle-command-bar${mobileLayout ? ' is-mobile-admin' : ''}`}
title={mobileLayout ? '车辆主档' : '车辆快速定位'} ariaLabel={mobileLayout ? '车辆主档操作' : '查车操作'}
description={mobileLayout ? '批量维护品牌、车型与运营属性' : '车牌、VIN 或手机号快速查车'} title={mobileLayout ? '车辆主档' : '车辆快速定位'}
status={mobileLayout ? undefined : candidates.isPending ? '正在读取授权范围' : vehicleScope} description={mobileLayout ? '批量维护品牌、车型与运营属性' : '车牌、VIN 或手机号快速查车'}
statusColor={candidates.isError ? 'red' : 'blue'} status={mobileLayout ? undefined : candidates.isPending ? '正在读取授权范围' : vehicleScope}
actions={administrator ? <Button theme="light" type="tertiary" icon={mobileLayout ? <IconBox /> : undefined} onClick={() => setSyncOpen((value) => !value)}>{syncOpen ? '收起同步' : mobileLayout ? '主档同步' : '批量同步主档'}</Button> : null} statusColor={candidates.isError ? 'red' : 'blue'}
/> : null} actions={administrator ? <Button theme="light" type="tertiary" icon={mobileLayout ? <IconBox /> : undefined} onClick={() => setSyncOpen((value) => !value)}>{syncOpen ? '收起同步' : mobileLayout ? '主档同步' : '批量同步主档'}</Button> : null}
<WorkspaceFilterPanel /> : null}
className="v2-vehicle-query-panel" <WorkspaceFilterPanel
title={mobileLayout ? '车辆筛选' : '车辆定位'} className="v2-vehicle-query-panel"
description="车牌优先VIN 辅助" title={mobileLayout ? '车辆筛选' : '车辆定位'}
mobileSummary={deferredKeyword ? `正在查找 ${deferredKeyword}` : candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权车辆` : '正在读取授权范围'} description="车牌优先VIN 辅助"
expanded={!filtersCollapsed} mobileSummary={deferredKeyword ? `正在查找 ${deferredKeyword}` : candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权车辆` : '正在读取授权范围'}
status={deferredKeyword ? candidates.isFetching ? '正在查找' : `${options.length} 辆匹配` : vehicleScope} expanded={!filtersCollapsed}
statusColor={deferredKeyword ? 'blue' : 'grey'} status={deferredKeyword ? candidates.isFetching ? '正在查找' : `${options.length} 辆匹配` : vehicleScope}
collapsedLabel="修改" statusColor={deferredKeyword ? 'blue' : 'grey'}
onToggle={() => setFiltersCollapsed((value) => !value)} collapsedLabel="修改"
> onToggle={() => setFiltersCollapsed((value) => !value)}
<form className={`v2-vehicle-search-form${filtersCollapsed ? ' is-mobile-collapsed' : ''}`} onSubmit={submit}> >
<div className={`v2-vehicle-search-picker${candidatesOpen ? ' is-open' : ''}`}> <form className={`v2-vehicle-search-form${filtersCollapsed ? ' is-mobile-collapsed' : ''}`} onSubmit={submit}>
<Input <div className={`v2-vehicle-search-picker${candidatesOpen ? ' is-open' : ''}`}>
aria-label="搜索车辆" <Input
aria-controls="v2-vehicle-search-options" aria-label="搜索车辆"
aria-expanded={candidatesOpen} aria-controls="v2-vehicle-search-options"
prefix={<IconSearch />} aria-expanded={candidatesOpen}
value={keyword} prefix={<IconSearch />}
onChange={(value) => { setKeyword(value); setPage(1); setCandidatesOpen(true); }} value={keyword}
onFocus={openCandidates} onChange={(value) => { setKeyword(value); setPage(1); setCandidatesOpen(true); }}
onBlur={closeCandidates} onFocus={openCandidates}
placeholder="输入车牌 / VIN / 终端手机号" onBlur={closeCandidates}
autoComplete="off" placeholder="输入车牌 / VIN / 终端手机号"
/> autoComplete="off"
</div> />
<Button theme="solid" htmlType="submit" icon={<IconArrowRight />} iconPosition="right"></Button> </div>
{candidatesOpen ? <VehicleCandidateList <Button theme="solid" htmlType="submit" icon={<IconArrowRight />} iconPosition="right"></Button>
id="v2-vehicle-search-options" {candidatesOpen ? <VehicleCandidateList
className="v2-vehicle-search-options" id="v2-vehicle-search-options"
items={options} className="v2-vehicle-search-options"
loading={candidates.isFetching} items={options}
loadingText="正在搜索授权车辆" loading={candidates.isFetching}
error={candidates.isError ? (candidates.error instanceof Error ? candidates.error.message : '车辆候选加载失败') : undefined} loadingText="正在搜索授权车辆"
onRetry={() => candidates.refetch()} error={candidates.isError ? (candidates.error instanceof Error ? candidates.error.message : '车辆候选加载失败') : undefined}
emptyText="没有匹配的授权车辆" onRetry={() => candidates.refetch()}
header="车辆候选" emptyText="没有匹配的授权车辆"
meta="车牌优先 · VIN 辅助" header="车辆候选"
showProtocols meta="车牌优先 · VIN 辅助"
layout={mobileLayout ? 'list' : 'grid'} showProtocols
onSelect={(vehicle) => openVehicle(vehicle.vin)} layout={mobileLayout ? 'list' : 'grid'}
/> : null} onSelect={(vehicle) => openVehicle(vehicle.vin)}
</form> /> : null}
</WorkspaceFilterPanel> </form>
</WorkspaceFilterPanel>
</div>
<Card className="v2-vehicle-recent-card" bodyStyle={{ padding: 0 }}> <Card className="v2-vehicle-recent-card" bodyStyle={{ padding: 0 }}>
<WorkspacePanelHeader <WorkspacePanelHeader
title={deferredKeyword ? '匹配车辆' : mobileLayout ? '授权车辆' : '授权车辆目录'} title={deferredKeyword ? '匹配车辆' : mobileLayout ? '授权车辆' : '授权车辆目录'}
description={deferredKeyword ? '按当前搜索条件展示,可直接进入车辆数字档案' : '按照最新上报时间排列,分页浏览全部授权车辆'} description={deferredKeyword ? '按当前搜索条件展示,可直接进入车辆数字档案' : '按照最新上报时间排列,分页浏览全部授权车辆'}
meta={<Tag color={deferredKeyword ? 'blue' : 'grey'} type="light" size="small">{candidates.data ? `本页 ${formatZhNumber(pageVehicles.length)}` : '授权范围'}</Tag>} meta={<span className="v2-vehicle-directory-summary">
<Tag color={deferredKeyword ? 'blue' : 'grey'} type="light" size="small">{candidates.data ? `本页 ${formatZhNumber(pageVehicles.length)}` : '授权范围'}</Tag>
{candidates.data ? <Tag className="is-secondary" color="green" type="light" size="small">线 {formatZhNumber(onlineOnPage)}</Tag> : null}
{candidates.data ? <Tag className="is-secondary" color="blue" type="light" size="small"> {formatZhNumber(multiSourceOnPage)}</Tag> : null}
</span>}
actions={<Button theme="borderless" type="tertiary" size="small" icon={<IconRefresh />} loading={candidates.isFetching} onClick={() => candidates.refetch()}></Button>} actions={<Button theme="borderless" type="tertiary" size="small" icon={<IconRefresh />} loading={candidates.isFetching} onClick={() => candidates.refetch()}></Button>}
/> />
{candidates.isFetching && !candidates.data {candidates.isFetching && !candidates.data
@@ -186,10 +204,16 @@ export default function VehicleSearchWorkspace() {
width: 220, width: 220,
render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-identity"><strong>{vehicle.plate || '未绑定车牌'}</strong><small>{vehicle.vin}</small></span> render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-identity"><strong>{vehicle.plate || '未绑定车牌'}</strong><small>{vehicle.vin}</small></span>
}, },
{
title: '车辆信息',
dataIndex: 'oem',
width: 260,
render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-profile"><strong>{vehicle.oem || '品牌待维护'}</strong><small>{vehicle.phone || '暂无终端手机号'}</small></span>
},
{ {
title: '实时状态', title: '实时状态',
dataIndex: 'online', dataIndex: 'online',
width: 150, width: 180,
render: (_value: boolean, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-status"><Tag color={vehicle.online ? 'green' : 'grey'} type="light" size="small">{vehicle.online ? '在线' : '离线'}</Tag><small>{vehicleLastSeenLabel(vehicle.lastSeen)}</small></span> render: (_value: boolean, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-status"><Tag color={vehicle.online ? 'green' : 'grey'} type="light" size="small">{vehicle.online ? '在线' : '离线'}</Tag><small>{vehicleLastSeenLabel(vehicle.lastSeen)}</small></span>
}, },
{ {
@@ -200,13 +224,22 @@ export default function VehicleSearchWorkspace() {
{ {
title: '', title: '',
dataIndex: 'vin', dataIndex: 'vin',
width: 104, width: 116,
align: 'right', align: 'right',
render: (vehicleVin: string) => <Button className="v2-vehicle-directory-open" theme="borderless" type="primary" size="small" icon={<IconChevronRight />} iconPosition="right" aria-label={`打开 ${vehicleVin} 车辆档案`} onClick={() => openVehicle(vehicleVin)}></Button> render: (vehicleVin: string) => <Button className="v2-vehicle-directory-open" theme="borderless" type="primary" size="small" icon={<IconChevronRight />} iconPosition="right" aria-label={`打开 ${vehicleVin} 车辆档案`} onClick={(event) => { event.stopPropagation(); openVehicle(vehicleVin); }}></Button>
} }
]} ]}
dataSource={pageVehicles} dataSource={pageVehicles}
rowKey="vin" rowKey="vin"
onRow={(vehicle) => vehicle ? ({
tabIndex: 0,
onClick: () => openVehicle(vehicle.vin),
onKeyDown: (event) => {
if (event.key !== 'Enter' && event.key !== ' ') return;
event.preventDefault();
openVehicle(vehicle.vin);
}
}) : ({})}
pagination={false} pagination={false}
loading={candidates.isFetching} loading={candidates.isFetching}
empty={null} empty={null}

View File

@@ -1983,10 +1983,11 @@
align-content: start; align-content: start;
place-items: start center; place-items: start center;
gap: 12px; gap: 12px;
overflow: auto; overflow: visible;
padding: 16px var(--v2-page-gutter) 28px; padding: 16px var(--v2-page-gutter) 28px;
} }
.v2-vehicle-discovery-shell,
.v2-vehicle-search-page > .v2-page-heading, .v2-vehicle-search-page > .v2-page-heading,
.v2-vehicle-search-page > .v2-mobile-filter-toggle, .v2-vehicle-search-page > .v2-mobile-filter-toggle,
.v2-vehicle-search-page > .v2-vehicle-query-panel, .v2-vehicle-search-page > .v2-vehicle-query-panel,
@@ -1996,6 +1997,32 @@
width: min(1280px, 100%); width: min(1280px, 100%);
} }
.v2-vehicle-discovery-shell {
display: grid;
overflow: visible;
gap: 0;
border: 1px solid #dce4ee;
border-radius: 14px;
background: #fff;
box-shadow: 0 9px 28px rgba(31, 53, 80, .055);
}
.v2-vehicle-discovery-shell > .v2-workspace-command-bar {
width: 100%;
min-height: 50px;
border: 0;
border-bottom: 1px solid #e6ecf3;
border-radius: 14px 14px 0 0;
box-shadow: none;
}
.v2-vehicle-discovery-shell > .v2-vehicle-query-panel.semi-card {
width: 100%;
border: 0;
border-radius: 0 0 14px 14px;
box-shadow: none;
}
.v2-vehicle-query-panel.v2-workspace-filter-panel.semi-card { .v2-vehicle-query-panel.v2-workspace-filter-panel.semi-card {
overflow: visible; overflow: visible;
} }
@@ -2062,27 +2089,35 @@
} }
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row { .v2-vehicle-directory-table .semi-table-tbody > .semi-table-row {
cursor: default; cursor: pointer;
transition: background-color .15s ease; transition: background-color .15s ease;
} }
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row:hover { .v2-vehicle-directory-table .semi-table-tbody > .semi-table-row:hover,
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row:focus-visible {
background: #f7faff; background: #f7faff;
} }
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row:focus-visible {
outline: 2px solid rgba(18, 104, 243, .28);
outline-offset: -2px;
}
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row > .semi-table-row-cell { .v2-vehicle-directory-table .semi-table-tbody > .semi-table-row > .semi-table-row-cell {
height: 48px; height: 48px;
border-bottom-color: #edf1f6; border-bottom-color: #edf1f6;
padding-block: 6px; padding-block: 6px;
} }
.v2-vehicle-directory-identity { .v2-vehicle-directory-identity,
.v2-vehicle-directory-profile {
display: grid; display: grid;
min-width: 0; min-width: 0;
gap: 2px; gap: 2px;
} }
.v2-vehicle-directory-identity strong { .v2-vehicle-directory-identity strong,
.v2-vehicle-directory-profile strong {
overflow: hidden; overflow: hidden;
color: #1b2a3e; color: #1b2a3e;
font-size: 14px; font-size: 14px;
@@ -2091,7 +2126,14 @@
white-space: nowrap; white-space: nowrap;
} }
.v2-vehicle-directory-profile strong {
color: #40536a;
font-size: 11px;
font-weight: 650;
}
.v2-vehicle-directory-identity small, .v2-vehicle-directory-identity small,
.v2-vehicle-directory-profile small,
.v2-vehicle-directory-status small { .v2-vehicle-directory-status small {
overflow: hidden; overflow: hidden;
color: #7f8da1; color: #7f8da1;
@@ -2101,6 +2143,16 @@
white-space: nowrap; white-space: nowrap;
} }
.v2-vehicle-directory-summary {
display: flex;
align-items: center;
gap: 5px;
}
.v2-vehicle-directory-summary > .semi-tag {
margin: 0;
}
.v2-vehicle-directory-status { .v2-vehicle-directory-status {
display: flex; display: flex;
min-width: 0; min-width: 0;
@@ -2148,6 +2200,11 @@
} }
.v2-vehicle-record-page { .v2-vehicle-record-page {
overflow: visible !important;
overflow-anchor: none;
}
.v2-content {
overflow-anchor: none; overflow-anchor: none;
} }
@@ -2236,9 +2293,14 @@
@media (max-width: 700px) { @media (max-width: 700px) {
.v2-vehicle-search-page { .v2-vehicle-search-page {
gap: 8px; gap: 8px;
overflow: visible;
padding: 9px 8px 92px; padding: 9px 8px 92px;
} }
.v2-vehicle-discovery-shell {
display: contents;
}
.v2-vehicle-command-bar.is-mobile-admin { .v2-vehicle-command-bar.is-mobile-admin {
min-height: 50px; min-height: 50px;
border-color: #dce6f2; border-color: #dce6f2;
@@ -2309,6 +2371,10 @@
margin-left: auto; margin-left: auto;
} }
.v2-vehicle-directory-summary > .is-secondary {
display: none;
}
.v2-track-detail-sidesheet .semi-sidesheet-inner { .v2-track-detail-sidesheet .semi-sidesheet-inner {
overflow: hidden; overflow: hidden;
border-radius: 18px 18px 0 0; border-radius: 18px 18px 0 0;