feat: 业务负责人下拉按部门分组显示
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- 部门Tab和客户Tab的业务负责人下拉用optgroup按部门分组
- 顺序:部门名称作为组标题,组内显示该部门的负责人列表
- 部门排序与部门Tab一致(业务一部→二部→...→公务车)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-03-28 23:52:35 +08:00
parent 258def4fdd
commit 66ea340a73

View File

@@ -407,6 +407,7 @@ export default function App() {
// Derived data for dept section
const allManagersList = useMemo(() => deptData.flatMap((d) => d.managers.map((m) => m.manager)).filter((v, i, a) => a.indexOf(v) === i).sort(), [deptData]);
const managersGroupedByDept = useMemo(() => deptData.map((d) => ({ department: d.department, managers: d.managers.map((m) => m.manager) })), [deptData]);
const managerStats = useMemo(() => deptData
.flatMap((d) => d.managers)
.filter((m) => selectedManager === 'All' || m.manager === selectedManager)
@@ -427,6 +428,15 @@ export default function App() {
const uniqueCities = useMemo(() => Array.from(new Set(customerData.map((s) => s.city).filter(Boolean))), [customerData]);
const uniqueCustomerNames = useMemo(() => Array.from(new Set(customerData.map((s) => s.customer).filter(Boolean))), [customerData]);
const uniqueCustomerManagers = useMemo(() => Array.from(new Set(customerData.map((s) => s.manager).filter(Boolean))), [customerData]);
const customerManagersGroupedByDept = useMemo(() => {
const deptMap = new Map<string, Set<string>>();
for (const s of customerData) {
if (!s.manager || !s.department) continue;
if (!deptMap.has(s.department)) deptMap.set(s.department, new Set());
deptMap.get(s.department)!.add(s.manager);
}
return Array.from(deptMap.entries()).map(([dept, mgrs]) => ({ department: dept, managers: Array.from(mgrs) }));
}, [customerData]);
const uniqueInventoryModels = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.model).filter(Boolean))), [inventoryData]);
const uniqueModalPlates = useMemo(() => Array.from(new Set(modalVehicles.map(v => v.plateNumber || v.vin).filter(Boolean))), [modalVehicles]);
const uniqueModalModels = useMemo(() => Array.from(new Set(modalVehicles.map(v => v.model).filter(Boolean))), [modalVehicles]);
@@ -1422,8 +1432,10 @@ 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"
>
<option value="All"></option>
{allManagersList.map(m => (
<option key={m} value={m}>{m}</option>
{managersGroupedByDept.map(g => (
<optgroup key={g.department} label={g.department}>
{g.managers.map(m => <option key={m} value={m}>{m}</option>)}
</optgroup>
))}
</select>
<ChevronDown className="absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none" size={14} />
@@ -2270,7 +2282,11 @@ export default function App() {
<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 }))}>
<option value=""></option>
{uniqueCustomerManagers.map(m => <option key={m} value={m}>{m}</option>)}
{customerManagersGroupedByDept.map(g => (
<optgroup key={g.department} label={g.department}>
{g.managers.map(m => <option key={m} value={m}>{m}</option>)}
</optgroup>
))}
</select>
</div>