refactor: simplify order sidebar
This commit is contained in:
@@ -207,7 +207,6 @@ export function Dashboard() {
|
|||||||
selectedOrderId={selectedOrder.id}
|
selectedOrderId={selectedOrder.id}
|
||||||
search={search}
|
search={search}
|
||||||
activeOrder={selectedOrder}
|
activeOrder={selectedOrder}
|
||||||
showProjectDossier={orders.length === 1}
|
|
||||||
onSearchChange={setSearch}
|
onSearchChange={setSearch}
|
||||||
onSelectOrder={handleSelectOrder}
|
onSelectOrder={handleSelectOrder}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { renderToStaticMarkup } from 'react-dom/server'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { createMockOrders } from '@/data/mock-data'
|
||||||
|
import { OrderSidebar } from './OrderSidebar'
|
||||||
|
|
||||||
|
describe('OrderSidebar', () => {
|
||||||
|
const sourceOrders = createMockOrders()
|
||||||
|
const activeOrder = sourceOrders[0]
|
||||||
|
|
||||||
|
it('keeps the order list and active BOM dossier without legacy filters or pagination', () => {
|
||||||
|
const markup = renderToStaticMarkup(
|
||||||
|
<OrderSidebar
|
||||||
|
orders={sourceOrders.slice(0, 3)}
|
||||||
|
activeOrder={activeOrder}
|
||||||
|
selectedOrderId={activeOrder.id}
|
||||||
|
search=""
|
||||||
|
onSearchChange={() => undefined}
|
||||||
|
onSelectOrder={() => undefined}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(markup).toContain('订单列表')
|
||||||
|
expect(markup).toContain('项目 BOM 档案')
|
||||||
|
expect(markup).not.toContain('筛选订单')
|
||||||
|
expect(markup).not.toContain('全部状态')
|
||||||
|
expect(markup).not.toContain('全部产线')
|
||||||
|
expect(markup).not.toContain('订单分页')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps the active BOM dossier when the search result is empty', () => {
|
||||||
|
const markup = renderToStaticMarkup(
|
||||||
|
<OrderSidebar
|
||||||
|
orders={[]}
|
||||||
|
activeOrder={activeOrder}
|
||||||
|
selectedOrderId={activeOrder.id}
|
||||||
|
search="missing"
|
||||||
|
onSearchChange={() => undefined}
|
||||||
|
onSelectOrder={() => undefined}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(markup).toContain('没有匹配订单')
|
||||||
|
expect(markup).toContain('修改搜索关键词后重试')
|
||||||
|
expect(markup).toContain('项目 BOM 档案')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
Boxes,
|
Boxes,
|
||||||
ChevronDown,
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
DatabaseZap,
|
DatabaseZap,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Layers3,
|
Layers3,
|
||||||
PackageOpen,
|
PackageOpen,
|
||||||
Search,
|
Search,
|
||||||
ShieldAlert,
|
ShieldAlert,
|
||||||
SlidersHorizontal,
|
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import type { OrderSummary, RiskLevel } from '@/types'
|
import type { OrderSummary, RiskLevel } from '@/types'
|
||||||
import { calculateOrderTreeStats, riskMeta, statusMeta } from '@/lib/production'
|
import { calculateOrderTreeStats, riskMeta, statusMeta } from '@/lib/production'
|
||||||
@@ -21,7 +17,6 @@ import { RingProgress, TechVisual } from './TechVisual'
|
|||||||
interface OrderSidebarProps {
|
interface OrderSidebarProps {
|
||||||
orders: OrderSummary[]
|
orders: OrderSummary[]
|
||||||
activeOrder: OrderSummary
|
activeOrder: OrderSummary
|
||||||
showProjectDossier: boolean
|
|
||||||
selectedOrderId: string
|
selectedOrderId: string
|
||||||
search: string
|
search: string
|
||||||
onSearchChange: (value: string) => void
|
onSearchChange: (value: string) => void
|
||||||
@@ -31,39 +26,24 @@ interface OrderSidebarProps {
|
|||||||
export function OrderSidebar({
|
export function OrderSidebar({
|
||||||
orders,
|
orders,
|
||||||
activeOrder,
|
activeOrder,
|
||||||
showProjectDossier,
|
|
||||||
selectedOrderId,
|
selectedOrderId,
|
||||||
search,
|
search,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
onSelectOrder,
|
onSelectOrder,
|
||||||
}: OrderSidebarProps) {
|
}: OrderSidebarProps) {
|
||||||
return (
|
return (
|
||||||
<aside className={`dashboard-panel sidebar-panel ${showProjectDossier ? 'has-project-dossier' : ''}`}>
|
<aside className="dashboard-panel sidebar-panel has-project-dossier">
|
||||||
<div className="sidebar-heading">
|
<div className="sidebar-heading">
|
||||||
<PanelTitle icon={<Boxes />} title="订单总览" subtitle={`${orders.length} 个订单`} />
|
<PanelTitle icon={<Boxes />} title="订单列表" subtitle={`${orders.length} 个订单`} />
|
||||||
<button type="button" className="icon-tool-button" aria-label="筛选订单">
|
|
||||||
<SlidersHorizontal />
|
|
||||||
<span>筛选</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sidebar-filter-card">
|
<div className="sidebar-filter-card">
|
||||||
<div className="select-row">
|
|
||||||
<button type="button" className="select-chip">
|
|
||||||
<span>全部状态</span>
|
|
||||||
<ChevronDown />
|
|
||||||
</button>
|
|
||||||
<button type="button" className="select-chip">
|
|
||||||
<span>全部产线</span>
|
|
||||||
<ChevronDown />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<label className="search-field">
|
<label className="search-field">
|
||||||
<Search aria-hidden="true" />
|
<Search aria-hidden="true" />
|
||||||
<Input
|
<Input
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(event) => onSearchChange(event.target.value)}
|
onChange={(event) => onSearchChange(event.target.value)}
|
||||||
placeholder="搜索订单号/产品"
|
placeholder="搜索订单号/产品/产线"
|
||||||
aria-label="搜索订单"
|
aria-label="搜索订单"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -113,24 +93,12 @@ export function OrderSidebar({
|
|||||||
<div className="empty-state">
|
<div className="empty-state">
|
||||||
<ShieldAlert aria-hidden="true" />
|
<ShieldAlert aria-hidden="true" />
|
||||||
<strong>没有匹配订单</strong>
|
<strong>没有匹配订单</strong>
|
||||||
<span>调整搜索或筛选条件后重试</span>
|
<span>修改搜索关键词后重试</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showProjectDossier ? <ProjectDossier order={activeOrder} /> : (
|
<ProjectDossier order={activeOrder} />
|
||||||
<div className="sidebar-pagination" aria-label="订单分页">
|
|
||||||
<button type="button" aria-label="上一页"><ChevronLeft /></button>
|
|
||||||
<button type="button" className="is-active">1</button>
|
|
||||||
<button type="button">2</button>
|
|
||||||
<button type="button">3</button>
|
|
||||||
<button type="button">4</button>
|
|
||||||
<button type="button">5</button>
|
|
||||||
<span>...</span>
|
|
||||||
<button type="button">22</button>
|
|
||||||
<button type="button" aria-label="下一页"><ChevronRight /></button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</aside>
|
</aside>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-67
@@ -859,10 +859,7 @@ svg {
|
|||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tool-button,
|
.flow-toolbar button {
|
||||||
.select-chip,
|
|
||||||
.flow-toolbar button,
|
|
||||||
.sidebar-pagination button {
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -880,24 +877,12 @@ svg {
|
|||||||
background 180ms ease;
|
background 180ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tool-button {
|
.flow-toolbar svg {
|
||||||
padding: 0 9px;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-tool-button svg,
|
|
||||||
.select-chip svg,
|
|
||||||
.flow-toolbar svg,
|
|
||||||
.sidebar-pagination svg {
|
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tool-button:hover,
|
.flow-toolbar button:hover {
|
||||||
.select-chip:hover,
|
|
||||||
.flow-toolbar button:hover,
|
|
||||||
.sidebar-pagination button:hover {
|
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
border-color: var(--border-strong);
|
border-color: var(--border-strong);
|
||||||
background:
|
background:
|
||||||
@@ -911,24 +896,10 @@ svg {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-chip {
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 10px;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-field {
|
.search-field {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 7px;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-field svg {
|
.search-field svg {
|
||||||
@@ -1103,36 +1074,6 @@ svg {
|
|||||||
height: 43px;
|
height: 43px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-pagination {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 4px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
min-height: 28px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-pagination button {
|
|
||||||
min-width: 25px;
|
|
||||||
min-height: 25px;
|
|
||||||
padding: 0 6px;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-pagination button.is-active {
|
|
||||||
color: #ecfeff;
|
|
||||||
border-color: var(--cyan);
|
|
||||||
background: linear-gradient(180deg, rgba(47, 148, 255, 0.78), rgba(19, 200, 246, 0.34));
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-pagination span {
|
|
||||||
color: var(--muted);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flow-panel-head {
|
.flow-panel-head {
|
||||||
position: relative;
|
position: relative;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -3229,8 +3170,8 @@ svg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.has-project-dossier .order-list {
|
.has-project-dossier .order-list {
|
||||||
flex: 0 0 auto;
|
flex: 0 1 auto;
|
||||||
max-height: 96px;
|
max-height: 42%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.has-project-dossier .order-row {
|
.has-project-dossier .order-row {
|
||||||
@@ -4067,7 +4008,6 @@ svg {
|
|||||||
.panel-title h2,
|
.panel-title h2,
|
||||||
.mini-panel-head h2,
|
.mini-panel-head h2,
|
||||||
.flow-panel-head h2 { font-size: var(--text-panel); }
|
.flow-panel-head h2 { font-size: var(--text-panel); }
|
||||||
.has-project-dossier .order-list { max-height: 110px; }
|
|
||||||
.has-project-dossier .order-row { height: 100px; }
|
.has-project-dossier .order-row { height: 100px; }
|
||||||
.project-stat-grid > div { min-height: 44px; }
|
.project-stat-grid > div { min-height: 44px; }
|
||||||
}
|
}
|
||||||
@@ -4112,7 +4052,6 @@ svg {
|
|||||||
.mini-panel-head p { font-size: 14px; }
|
.mini-panel-head p { font-size: 14px; }
|
||||||
.sidebar-panel,
|
.sidebar-panel,
|
||||||
.detail-panel { padding: 12px; }
|
.detail-panel { padding: 12px; }
|
||||||
.has-project-dossier .order-list { max-height: 136px; }
|
|
||||||
.has-project-dossier .order-row { height: 122px; }
|
.has-project-dossier .order-row { height: 122px; }
|
||||||
.order-row-main { height: 86px; grid-template-rows: repeat(4, minmax(18px, auto)); }
|
.order-row-main { height: 86px; grid-template-rows: repeat(4, minmax(18px, auto)); }
|
||||||
.order-row-titleline strong { font-size: 15px; }
|
.order-row-titleline strong { font-size: 15px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user