fix: 筛选面板改为确认后才搜索、隐藏公务车分类
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 三个筛选面板(库存/区域/客户)改为draft状态模式: 打开时复制当前筛选到draft,面板内操作draft, 点确认才应用到实际筛选状态 - 移除点击外部关闭(只能通过确认按钮关闭) - 业务负责人下拉隐藏"公务车"分组(部门Tab和客户Tab都已处理) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
77
src/App.tsx
77
src/App.tsx
@@ -161,11 +161,13 @@ export default function App() {
|
|||||||
const [expandedRegionCities, setExpandedRegionCities] = useState<Set<string>>(new Set());
|
const [expandedRegionCities, setExpandedRegionCities] = useState<Set<string>>(new Set());
|
||||||
const [regionFilters, setRegionFilters] = useState({ region: '', city: '', customer: '' });
|
const [regionFilters, setRegionFilters] = useState({ region: '', city: '', customer: '' });
|
||||||
const [isRegionFilterOpen, setIsRegionFilterOpen] = useState(false);
|
const [isRegionFilterOpen, setIsRegionFilterOpen] = useState(false);
|
||||||
|
const [draftRegionFilters, setDraftRegionFilters] = useState({ region: '', city: '', customer: '' });
|
||||||
|
|
||||||
// Customer section state
|
// Customer section state
|
||||||
const [expandedCustomers, setExpandedCustomers] = useState<Set<string>>(new Set());
|
const [expandedCustomers, setExpandedCustomers] = useState<Set<string>>(new Set());
|
||||||
const [customerFilters, setCustomerFilters] = useState({ customer: '', brand: '', department: '', manager: '', region: '' });
|
const [customerFilters, setCustomerFilters] = useState({ customer: '', brand: '', department: '', manager: '', region: '' });
|
||||||
const [isCustomerFilterOpen, setIsCustomerFilterOpen] = useState(false);
|
const [isCustomerFilterOpen, setIsCustomerFilterOpen] = useState(false);
|
||||||
|
const [draftCustomerFilters, setDraftCustomerFilters] = useState({ customer: '', brand: '', department: '', manager: '', region: '' });
|
||||||
|
|
||||||
// Inventory statistics section state
|
// Inventory statistics section state
|
||||||
const [inventoryData, setInventoryData] = useState<RegionalInventoryStats[]>([]);
|
const [inventoryData, setInventoryData] = useState<RegionalInventoryStats[]>([]);
|
||||||
@@ -174,6 +176,7 @@ export default function App() {
|
|||||||
const [expandedInventoryTypes, setExpandedInventoryTypes] = useState<Set<string>>(new Set(['4.5T普货']));
|
const [expandedInventoryTypes, setExpandedInventoryTypes] = useState<Set<string>>(new Set(['4.5T普货']));
|
||||||
const [inventoryFilters, setInventoryFilters] = useState({ region: '', city: '', brand: '', type: '', model: '' });
|
const [inventoryFilters, setInventoryFilters] = useState({ region: '', city: '', brand: '', type: '', model: '' });
|
||||||
const [isInventoryFilterOpen, setIsInventoryFilterOpen] = useState(false);
|
const [isInventoryFilterOpen, setIsInventoryFilterOpen] = useState(false);
|
||||||
|
const [draftInventoryFilters, setDraftInventoryFilters] = useState({ region: '', city: '', brand: '', type: '', model: '' });
|
||||||
|
|
||||||
// Chart view states
|
// Chart view states
|
||||||
const [customerChartView, setCustomerChartView] = useState<'region' | 'city'>('region');
|
const [customerChartView, setCustomerChartView] = useState<'region' | 'city'>('region');
|
||||||
@@ -377,9 +380,10 @@ export default function App() {
|
|||||||
return types.sort((a, b) => INVENTORY_TYPE_ORDER_LIST.indexOf(a) - INVENTORY_TYPE_ORDER_LIST.indexOf(b));
|
return types.sort((a, b) => INVENTORY_TYPE_ORDER_LIST.indexOf(a) - INVENTORY_TYPE_ORDER_LIST.indexOf(b));
|
||||||
}, [inventoryData]);
|
}, [inventoryData]);
|
||||||
const uniqueInventoryModelsForType = useMemo(() => {
|
const uniqueInventoryModelsForType = useMemo(() => {
|
||||||
const source = inventoryFilters.type ? inventoryData.filter((s) => s.type === inventoryFilters.type) : inventoryData;
|
const typeFilter = isInventoryFilterOpen ? draftInventoryFilters.type : inventoryFilters.type;
|
||||||
|
const source = typeFilter ? inventoryData.filter((s) => s.type === typeFilter) : inventoryData;
|
||||||
return Array.from(new Set(source.map((s) => s.model).filter(Boolean)));
|
return Array.from(new Set(source.map((s) => s.model).filter(Boolean)));
|
||||||
}, [inventoryData, inventoryFilters.type]);
|
}, [inventoryData, inventoryFilters.type, draftInventoryFilters.type, isInventoryFilterOpen]);
|
||||||
|
|
||||||
const inventoryByRegion = useMemo(() => {
|
const inventoryByRegion = useMemo(() => {
|
||||||
const result: Record<string, Record<string, RegionalInventoryStats[]>> = {};
|
const result: Record<string, Record<string, RegionalInventoryStats[]>> = {};
|
||||||
@@ -1023,7 +1027,7 @@ export default function App() {
|
|||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsInventoryFilterOpen(!isInventoryFilterOpen)}
|
onClick={() => { if (!isInventoryFilterOpen) setDraftInventoryFilters({...inventoryFilters}); setIsInventoryFilterOpen(!isInventoryFilterOpen); }}
|
||||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold transition-all ${
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold transition-all ${
|
||||||
isInventoryFilterOpen || (inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.type || inventoryFilters.model)
|
isInventoryFilterOpen || (inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.type || inventoryFilters.model)
|
||||||
? 'bg-blue-600 text-white shadow-md'
|
? 'bg-blue-600 text-white shadow-md'
|
||||||
@@ -1040,7 +1044,7 @@ export default function App() {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isInventoryFilterOpen && (
|
{isInventoryFilterOpen && (
|
||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-40" onClick={() => setIsInventoryFilterOpen(false)} />
|
<div className="fixed inset-0 z-40" />
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
@@ -1049,52 +1053,47 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-xs font-bold text-slate-800">库存统计 - 数据筛选</h3>
|
<h3 className="text-xs font-bold text-slate-800">库存统计 - 数据筛选</h3>
|
||||||
<button
|
<button onClick={() => setDraftInventoryFilters({ region: '', city: '', brand: '', type: '', model: '' })} className="text-[10px] text-blue-500 hover:underline">重置所有</button>
|
||||||
onClick={() => setInventoryFilters({ region: '', city: '', brand: '', type: '', model: '' })}
|
|
||||||
className="text-[10px] text-blue-500 hover:underline"
|
|
||||||
>
|
|
||||||
重置所有
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3 text-left">
|
<div className="space-y-3 text-left">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-slate-400 block mb-1">区域</label>
|
<label className="text-[10px] text-slate-400 block mb-1">区域</label>
|
||||||
<select value={inventoryFilters.region} onChange={(e) => setInventoryFilters({...inventoryFilters, region: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
<select value={draftInventoryFilters.region} onChange={(e) => setDraftInventoryFilters({...draftInventoryFilters, region: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
||||||
<option value="">全部区域</option>
|
<option value="">全部区域</option>
|
||||||
{uniqueInventoryRegions.map(r => <option key={r} value={r}>{r}</option>)}
|
{uniqueInventoryRegions.map(r => <option key={r} value={r}>{r}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-slate-400 block mb-1">城市</label>
|
<label className="text-[10px] text-slate-400 block mb-1">城市</label>
|
||||||
<select value={inventoryFilters.city} onChange={(e) => setInventoryFilters({...inventoryFilters, city: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
<select value={draftInventoryFilters.city} onChange={(e) => setDraftInventoryFilters({...draftInventoryFilters, city: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
||||||
<option value="">全部城市</option>
|
<option value="">全部城市</option>
|
||||||
{uniqueInventoryCities.map(c => <option key={c} value={c}>{c}</option>)}
|
{uniqueInventoryCities.map(c => <option key={c} value={c}>{c}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-slate-400 block mb-1">品牌</label>
|
<label className="text-[10px] text-slate-400 block mb-1">品牌</label>
|
||||||
<select value={inventoryFilters.brand} onChange={(e) => setInventoryFilters({...inventoryFilters, brand: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
<select value={draftInventoryFilters.brand} onChange={(e) => setDraftInventoryFilters({...draftInventoryFilters, brand: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
||||||
<option value="">全部品牌</option>
|
<option value="">全部品牌</option>
|
||||||
{uniqueInventoryBrands.map(b => <option key={b} value={b}>{b}</option>)}
|
{uniqueInventoryBrands.map(b => <option key={b} value={b}>{b}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-slate-400 block mb-1">车型</label>
|
<label className="text-[10px] text-slate-400 block mb-1">车型</label>
|
||||||
<select value={inventoryFilters.type} onChange={(e) => setInventoryFilters({...inventoryFilters, type: e.target.value, model: ''})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
<select value={draftInventoryFilters.type} onChange={(e) => setDraftInventoryFilters({...draftInventoryFilters, type: e.target.value, model: ''})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
||||||
<option value="">全部车型</option>
|
<option value="">全部车型</option>
|
||||||
{uniqueInventoryTypes.map(t => <option key={t} value={t}>{t}</option>)}
|
{uniqueInventoryTypes.map(t => <option key={t} value={t}>{t}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-slate-400 block mb-1">批次</label>
|
<label className="text-[10px] text-slate-400 block mb-1">批次</label>
|
||||||
<select value={inventoryFilters.model} onChange={(e) => setInventoryFilters({...inventoryFilters, model: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
<select value={draftInventoryFilters.model} onChange={(e) => setDraftInventoryFilters({...draftInventoryFilters, model: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
|
||||||
<option value="">全部批次</option>
|
<option value="">全部批次</option>
|
||||||
{uniqueInventoryModelsForType.map(m => <option key={m} value={m}>{m}</option>)}
|
{uniqueInventoryModelsForType.map(m => <option key={m} value={m}>{m}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => setIsInventoryFilterOpen(false)} className="w-full mt-4 py-2 bg-blue-600 text-white rounded-lg text-xs font-bold hover:bg-blue-700 transition-colors">确认</button>
|
<button onClick={() => { setInventoryFilters({...draftInventoryFilters}); setIsInventoryFilterOpen(false); }} className="w-full mt-4 py-2 bg-blue-600 text-white rounded-lg text-xs font-bold hover:bg-blue-700 transition-colors">确认</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -1447,7 +1446,7 @@ export default function App() {
|
|||||||
className="w-full pl-9 pr-8 py-1.5 bg-white border border-gray-200 rounded-lg text-xs focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm appearance-none cursor-pointer font-bold text-gray-700"
|
className="w-full pl-9 pr-8 py-1.5 bg-white border border-gray-200 rounded-lg text-xs focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm appearance-none cursor-pointer font-bold text-gray-700"
|
||||||
>
|
>
|
||||||
<option value="All">所有业务负责人</option>
|
<option value="All">所有业务负责人</option>
|
||||||
{managersGroupedByDept.map(g => (
|
{managersGroupedByDept.filter(g => g.department !== '公务车').map(g => (
|
||||||
<optgroup key={g.department} label={g.department}>
|
<optgroup key={g.department} label={g.department}>
|
||||||
{g.managers.map(m => <option key={m} value={m}>{m}</option>)}
|
{g.managers.map(m => <option key={m} value={m}>{m}</option>)}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
@@ -1954,7 +1953,7 @@ export default function App() {
|
|||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsRegionFilterOpen(!isRegionFilterOpen)}
|
onClick={() => { if (!isRegionFilterOpen) setDraftRegionFilters({...regionFilters}); setIsRegionFilterOpen(!isRegionFilterOpen); }}
|
||||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
|
||||||
isRegionFilterOpen || Object.values(regionFilters).some(v => v !== '')
|
isRegionFilterOpen || Object.values(regionFilters).some(v => v !== '')
|
||||||
? 'bg-slate-200 text-slate-900 shadow-sm'
|
? 'bg-slate-200 text-slate-900 shadow-sm'
|
||||||
@@ -1971,7 +1970,7 @@ export default function App() {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isRegionFilterOpen && (
|
{isRegionFilterOpen && (
|
||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-40" onClick={() => setIsRegionFilterOpen(false)} />
|
<div className="fixed inset-0 z-40" />
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
@@ -1980,38 +1979,33 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-sm font-bold text-gray-900">区域筛选</h3>
|
<h3 className="text-sm font-bold text-gray-900">区域筛选</h3>
|
||||||
<button
|
<button onClick={() => setDraftRegionFilters({ region: '', city: '', customer: '' })} className="text-[10px] text-slate-600 hover:text-slate-700 font-medium">重置所有</button>
|
||||||
onClick={() => setRegionFilters({ region: '', city: '', customer: '' })}
|
|
||||||
className="text-[10px] text-slate-600 hover:text-slate-700 font-medium"
|
|
||||||
>
|
|
||||||
重置所有
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">客户名称</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">客户名称</label>
|
||||||
<SearchSelect value={regionFilters.customer} onChange={(v) => setRegionFilters(prev => ({ ...prev, customer: v }))} options={uniqueCustomerNames} placeholder="所有客户" className="text-xs py-2 px-2" />
|
<SearchSelect value={draftRegionFilters.customer} onChange={(v) => setDraftRegionFilters(prev => ({ ...prev, customer: v }))} options={uniqueCustomerNames} placeholder="所有客户" className="text-xs py-2 px-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">区域</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">区域</label>
|
||||||
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all cursor-pointer shadow-sm" value={regionFilters.region} onChange={(e) => setRegionFilters(prev => ({ ...prev, region: e.target.value }))}>
|
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all cursor-pointer shadow-sm" value={draftRegionFilters.region} onChange={(e) => setDraftRegionFilters(prev => ({ ...prev, region: e.target.value }))}>
|
||||||
<option value="">所有区域</option>
|
<option value="">所有区域</option>
|
||||||
{uniqueRegions.map(r => <option key={r} value={r}>{r}</option>)}
|
{uniqueRegions.map(r => <option key={r} value={r}>{r}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">城市</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">城市</label>
|
||||||
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all cursor-pointer shadow-sm" value={regionFilters.city} onChange={(e) => setRegionFilters(prev => ({ ...prev, city: e.target.value }))}>
|
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all cursor-pointer shadow-sm" value={draftRegionFilters.city} onChange={(e) => setDraftRegionFilters(prev => ({ ...prev, city: e.target.value }))}>
|
||||||
<option value="">所有城市</option>
|
<option value="">所有城市</option>
|
||||||
{uniqueCities.map(c => <option key={c} value={c}>{c}</option>)}
|
{uniqueCities.map(c => <option key={c} value={c}>{c}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => setIsRegionFilterOpen(false)} className="w-full mt-4 py-2 bg-slate-800 text-white rounded-lg text-xs font-bold hover:bg-slate-900 transition-colors">确认</button>
|
<button onClick={() => { setRegionFilters({...draftRegionFilters}); setIsRegionFilterOpen(false); }} className="w-full mt-4 py-2 bg-slate-800 text-white rounded-lg text-xs font-bold hover:bg-slate-900 transition-colors">确认</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -2248,7 +2242,7 @@ export default function App() {
|
|||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsCustomerFilterOpen(!isCustomerFilterOpen)}
|
onClick={() => { if (!isCustomerFilterOpen) setDraftCustomerFilters({...customerFilters}); setIsCustomerFilterOpen(!isCustomerFilterOpen); }}
|
||||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
|
||||||
isCustomerFilterOpen || Object.values(customerFilters).some(v => v !== '')
|
isCustomerFilterOpen || Object.values(customerFilters).some(v => v !== '')
|
||||||
? 'bg-emerald-400 text-emerald-900 shadow-lg shadow-emerald-900/20'
|
? 'bg-emerald-400 text-emerald-900 shadow-lg shadow-emerald-900/20'
|
||||||
@@ -2266,7 +2260,7 @@ export default function App() {
|
|||||||
{isCustomerFilterOpen && (
|
{isCustomerFilterOpen && (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div className="fixed inset-0 z-40" onClick={() => setIsCustomerFilterOpen(false)} />
|
<div className="fixed inset-0 z-40" />
|
||||||
|
|
||||||
{/* Popover Content */}
|
{/* Popover Content */}
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -2278,26 +2272,21 @@ export default function App() {
|
|||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-sm font-bold text-gray-900">数据筛选</h3>
|
<h3 className="text-sm font-bold text-gray-900">数据筛选</h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button onClick={() => setDraftCustomerFilters({ customer: '', brand: '', department: '', manager: '', region: '' })} className="text-[10px] text-emerald-600 hover:text-emerald-700 font-medium">重置所有</button>
|
||||||
onClick={() => setCustomerFilters({ customer: '', brand: '', department: '', manager: '', region: '' })}
|
|
||||||
className="text-[10px] text-emerald-600 hover:text-emerald-700 font-medium"
|
|
||||||
>
|
|
||||||
重置所有
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">客户名称</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">客户名称</label>
|
||||||
<SearchSelect value={customerFilters.customer} onChange={(v) => setCustomerFilters(prev => ({ ...prev, customer: v }))} options={uniqueCustomerNames} placeholder="所有客户" className="text-xs py-2 px-2" />
|
<SearchSelect value={draftCustomerFilters.customer} onChange={(v) => setDraftCustomerFilters(prev => ({ ...prev, customer: v }))} options={uniqueCustomerNames} placeholder="所有客户" className="text-xs py-2 px-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">业务负责人</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">业务负责人</label>
|
||||||
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={customerFilters.manager} onChange={(e) => setCustomerFilters(prev => ({ ...prev, manager: e.target.value }))}>
|
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={draftCustomerFilters.manager} onChange={(e) => setDraftCustomerFilters(prev => ({ ...prev, manager: e.target.value }))}>
|
||||||
<option value="">所有负责人</option>
|
<option value="">所有负责人</option>
|
||||||
{customerManagersGroupedByDept.map(g => (
|
{customerManagersGroupedByDept.filter(g => g.department !== '公务车').map(g => (
|
||||||
<optgroup key={g.department} label={g.department}>
|
<optgroup key={g.department} label={g.department}>
|
||||||
{g.managers.map(m => <option key={m} value={m}>{m}</option>)}
|
{g.managers.map(m => <option key={m} value={m}>{m}</option>)}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
@@ -2308,28 +2297,28 @@ export default function App() {
|
|||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">品牌</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">品牌</label>
|
||||||
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={customerFilters.brand} onChange={(e) => setCustomerFilters(prev => ({ ...prev, brand: e.target.value }))}>
|
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={draftCustomerFilters.brand} onChange={(e) => setDraftCustomerFilters(prev => ({ ...prev, brand: e.target.value }))}>
|
||||||
<option value="">所有品牌</option>
|
<option value="">所有品牌</option>
|
||||||
{uniqueBrands.map(b => <option key={b} value={b}>{b}</option>)}
|
{uniqueBrands.map(b => <option key={b} value={b}>{b}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">部门</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">部门</label>
|
||||||
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={customerFilters.department} onChange={(e) => setCustomerFilters(prev => ({ ...prev, department: e.target.value }))}>
|
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={draftCustomerFilters.department} onChange={(e) => setDraftCustomerFilters(prev => ({ ...prev, department: e.target.value }))}>
|
||||||
<option value="">所有部门</option>
|
<option value="">所有部门</option>
|
||||||
{uniqueDepts.map(d => <option key={d} value={d}>{d}</option>)}
|
{uniqueDepts.map(d => <option key={d} value={d}>{d}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">区域</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">区域</label>
|
||||||
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={customerFilters.region} onChange={(e) => setCustomerFilters(prev => ({ ...prev, region: e.target.value }))}>
|
<select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={draftCustomerFilters.region} onChange={(e) => setDraftCustomerFilters(prev => ({ ...prev, region: e.target.value }))}>
|
||||||
<option value="">所有区域</option>
|
<option value="">所有区域</option>
|
||||||
{uniqueRegions.map(r => <option key={r} value={r}>{r}</option>)}
|
{uniqueRegions.map(r => <option key={r} value={r}>{r}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => setIsCustomerFilterOpen(false)} className="w-full mt-4 py-2 bg-emerald-600 text-white rounded-lg text-xs font-bold hover:bg-emerald-700 transition-colors">确认</button>
|
<button onClick={() => { setCustomerFilters({...draftCustomerFilters}); setIsCustomerFilterOpen(false); }} className="w-full mt-4 py-2 bg-emerald-600 text-white rounded-lg text-xs font-bold hover:bg-emerald-700 transition-colors">确认</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user