122 lines
9.1 KiB
TypeScript
122 lines
9.1 KiB
TypeScript
import { useId, useRef, useState } from 'react';
|
||
import { ArrowUpRight, Boxes, ChevronDown, ChevronsDownUp, ChevronsUpDown, Filter, MapPin, SearchX, Truck, X } from 'lucide-react';
|
||
import { InventoryDesktopTable } from './InventoryDesktopTable';
|
||
import { InventoryMobileList } from './InventoryMobileList';
|
||
import { inventoryGroups, inventorySelection, inventoryTotal } from './inventory-presentation';
|
||
import type { InventoryFilters } from '../../model';
|
||
import type { InventoryStatisticsSectionProps } from './types';
|
||
import './inventory-statistics.css';
|
||
|
||
const emptyFilters: InventoryFilters = { region: '', city: '', brand: '', type: '', model: '' };
|
||
const filterLabels: Record<keyof InventoryFilters, string> = { region: '区域', city: '城市', brand: '品牌', type: '车型', model: '型号 / 批次' };
|
||
|
||
export function InventoryStatisticsSection(props: InventoryStatisticsSectionProps) {
|
||
const {
|
||
inventoryFilters: filters, setInventoryFilters: setFilters,
|
||
draftInventoryFilters: draft, setDraftInventoryFilters: setDraft,
|
||
isInventoryFilterOpen: filterOpen, setIsInventoryFilterOpen: setFilterOpen,
|
||
filteredInventoryStats: rows, inventoryTab: tab, setInventoryTab: setTab,
|
||
setVehicleSelection: select,
|
||
} = props;
|
||
const id = useId();
|
||
const filterTrigger = useRef<HTMLButtonElement>(null);
|
||
const [sort, setSort] = useState<'default' | 'quantity'>('default');
|
||
const total = inventoryTotal(rows);
|
||
const abnormal = rows.reduce((sum, row) => sum + (row.abnormal || 0), 0);
|
||
const pending = rows.reduce((sum, row) => sum + (row.pending || 0), 0);
|
||
const activeFilters = (Object.keys(filters) as (keyof InventoryFilters)[]).filter(key => filters[key]);
|
||
const groups = inventoryGroups(tab === 'region' ? props.inventoryByRegion : props.inventoryByModel, sort, tab);
|
||
const expanded = tab === 'region' ? props.expandedInventoryRegions : props.expandedInventoryTypes;
|
||
const toggle = tab === 'region' ? props.toggleInventoryRegion : props.toggleInventoryType;
|
||
const allExpanded = groups.length > 0 && groups.every(group => expanded.has(group.name));
|
||
const closeFilters = () => { setFilterOpen(false); filterTrigger.current?.focus(); };
|
||
const resetFilters = () => { setFilters({ ...emptyFilters }); setDraft({ ...emptyFilters }); };
|
||
const options = {
|
||
region: props.uniqueInventoryRegions, city: props.uniqueInventoryCities,
|
||
brand: props.uniqueInventoryBrands, type: props.uniqueInventoryTypes,
|
||
model: props.uniqueInventoryModelsForType,
|
||
};
|
||
const listProps = { groups, tab, expanded, toggle, total, filters, select };
|
||
|
||
return (
|
||
<section data-testid="inventory-statistics" className="inventory-statistics" aria-labelledby={`${id}-heading`}>
|
||
<header className="inventory-heading">
|
||
<div className="inventory-title">
|
||
<span className="inventory-title-icon"><Boxes size={20} aria-hidden="true" /></span>
|
||
<div><h2 id={`${id}-heading`}>库存统计</h2><p>按区域、车型核对库存分布</p></div>
|
||
</div>
|
||
<button ref={filterTrigger} className="inventory-control" aria-expanded={filterOpen} aria-controls={`${id}-filters`}
|
||
onClick={() => { if (!filterOpen) setDraft({ ...filters }); setFilterOpen(!filterOpen); }}>
|
||
<Filter size={16} aria-hidden="true" /> 筛选{activeFilters.length > 0 && <span className="inventory-filter-count">{activeFilters.length}</span>}
|
||
<ChevronDown size={14} aria-hidden="true" />
|
||
</button>
|
||
</header>
|
||
|
||
<div className="inventory-metrics" data-testid="inventory-status-summary">
|
||
<button className="inventory-metric inventory-metric-primary" disabled={!total}
|
||
onClick={() => select(inventorySelection(filters, activeFilters.length ? '库存统计 · 筛选结果' : '库存统计 · 全部库存'))}>
|
||
<span>{activeFilters.length ? '筛选后库存' : '库存总数'}<ArrowUpRight size={16} aria-hidden="true" /></span>
|
||
<strong>{total.toLocaleString()}<small>台</small></strong>
|
||
</button>
|
||
<button className="inventory-metric inventory-metric-pending" disabled={!pending}
|
||
onClick={() => select(inventorySelection(filters, '库存统计 · 待交车', { category: 'Pending' }))}>
|
||
<span>其中待交车<ArrowUpRight size={16} aria-hidden="true" /></span>
|
||
<strong>{pending.toLocaleString()}<small>台</small></strong>
|
||
</button>
|
||
<button className="inventory-metric inventory-metric-warning" disabled={!abnormal}
|
||
onClick={() => select(inventorySelection(filters, '库存统计 · 异动', { category: 'Abnormal' }))}>
|
||
<span>其中异动<ArrowUpRight size={16} aria-hidden="true" /></span>
|
||
<strong>{abnormal.toLocaleString()}<small>台</small></strong>
|
||
</button>
|
||
</div>
|
||
<div className="inventory-summary-note"><span>{new Set(rows.map(row => row.region)).size} 个区域 · {new Set(rows.map(row => row.city)).size} 个城市</span><span>待交车、异动已包含在库存总数中</span></div>
|
||
|
||
{filterOpen && <form id={`${id}-filters`} className="inventory-filters" aria-label="库存筛选"
|
||
onKeyDown={event => { if (event.key === 'Escape') { event.preventDefault(); closeFilters(); } }}
|
||
onSubmit={event => { event.preventDefault(); setFilters({ ...draft }); closeFilters(); }}>
|
||
<div className="inventory-filter-heading"><strong>筛选库存</strong><button type="button" className="inventory-icon-button" aria-label="关闭库存筛选" onClick={closeFilters}><X size={18} aria-hidden="true" /></button></div>
|
||
<div className="inventory-filter-fields">
|
||
{(Object.keys(filterLabels) as (keyof InventoryFilters)[]).map(key => <label key={key}>
|
||
{filterLabels[key]}
|
||
<select value={draft[key]} onChange={event => setDraft({ ...draft, [key]: event.target.value, ...(key === 'type' ? { model: '' } : {}), ...(key === 'region' ? { city: '' } : {}) })}>
|
||
<option value="">全部{filterLabels[key]}</option>
|
||
{options[key].filter(Boolean).map(value => <option key={value} value={value}>{value}</option>)}
|
||
</select>
|
||
</label>)}
|
||
</div>
|
||
<div className="inventory-filter-actions">
|
||
<button type="button" className="inventory-text-button" onClick={() => setDraft({ ...emptyFilters })}>重置条件</button>
|
||
<button type="button" className="inventory-control" onClick={closeFilters}>取消</button>
|
||
<button type="submit" className="inventory-control inventory-control-primary">应用筛选</button>
|
||
</div>
|
||
</form>}
|
||
|
||
{activeFilters.length > 0 && <div className="inventory-active-filters" aria-label="已应用的库存筛选">
|
||
<span>已筛选</span>
|
||
{activeFilters.map(key => <button key={key} onClick={() => setFilters({ ...filters, [key]: '', ...(key === 'type' ? { model: '' } : {}), ...(key === 'region' ? { city: '' } : {}) })}
|
||
aria-label={`移除${filterLabels[key]}筛选:${filters[key]}`}>
|
||
{filterLabels[key]}:{filters[key]}<X size={13} aria-hidden="true" />
|
||
</button>)}
|
||
<button className="inventory-clear" onClick={resetFilters}>清除全部</button>
|
||
</div>}
|
||
|
||
<div className="inventory-toolbar">
|
||
<div className="inventory-view-switch" role="group" aria-label="库存分组方式">
|
||
<button aria-pressed={tab === 'region'} onClick={() => setTab('region')}><MapPin size={15} aria-hidden="true" />按区域</button>
|
||
<button aria-pressed={tab === 'model'} onClick={() => setTab('model')}><Truck size={15} aria-hidden="true" />按车型</button>
|
||
</div>
|
||
<div className="inventory-toolbar-actions">
|
||
<label className="inventory-sort"><span className="sr-only">库存分组排序</span><select value={sort} onChange={event => setSort(event.target.value as typeof sort)}><option value="default">默认排序</option><option value="quantity">库存从多到少</option></select></label>
|
||
<button className="inventory-text-button inventory-expand-all" disabled={!groups.length} onClick={() => groups.forEach(group => { if (expanded.has(group.name) === allExpanded) toggle(group.name); })}>
|
||
{allExpanded ? <ChevronsDownUp size={16} aria-hidden="true" /> : <ChevronsUpDown size={16} aria-hidden="true" />}{allExpanded ? '收起全部' : '展开全部'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{groups.length ? <><InventoryDesktopTable {...listProps} /><InventoryMobileList key={JSON.stringify([tab, filters])} {...listProps} /></> :
|
||
<div className="inventory-empty"><SearchX size={32} aria-hidden="true" /><h3>{activeFilters.length ? '没有符合条件的库存' : '暂无库存数据'}</h3><p>{activeFilters.length ? '试试减少筛选条件,或清除筛选查看全部库存。' : '当前所属公司范围内暂无库存记录。'}</p>{activeFilters.length > 0 && <button className="inventory-control" onClick={resetFilters}>清除筛选</button>}</div>}
|
||
<footer className="inventory-footer"><span role="status" aria-live="polite">共 {groups.length} 个{tab === 'region' ? '区域' : '车型'} · {rows.length} 条记录 · {total.toLocaleString()} 台库存</span><span>占比基于当前筛选结果 · 单位:台</span></footer>
|
||
</section>
|
||
);
|
||
}
|