From e146b4f16fbc6bedc1bbf8027badec4d2d7cbdf8 Mon Sep 17 00:00:00 2001 From: awei Date: Mon, 27 Jul 2026 22:09:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E8=A9=95=E5=83=B9=E9=99=8D?= =?UTF-8?q?=E5=BA=8F=E6=99=82=E6=B2=92=E8=A9=95=E5=88=86=E7=9A=84=E5=8F=B8?= =?UTF-8?q?=E6=A9=9F=E8=A2=AB=E6=8E=92=E5=88=B0=E6=9C=80=E5=89=8D=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 司機管理頁的「評價」欄升序正確(低分在前、沒評分墊底),但**點第二次切降序後, 沒評分的司機浮到最頂端**:實測得到「新司機, 高分司機, 低分司機」。 機制:antd 做降序的方式是**把 sorter 的比較結果整個反轉**, 所以「沒評分回 1 讓它沉底」的特例也跟著反轉成 -1,變成浮頂。 原本的測試只點一次(升序)就斷言,測不到這條。 修法:用 antd 傳給 sorter 的第三個參數 sortOrder,降序時把特例的正負號跟著翻, 讓「沒評分」在兩個方向都沉底。0 則不代表差,不管升序降序都不該混進名單頭。 驗收:tsc 無誤、oxlint 乾淨、vitest 124 passed(新增 1 案專測降序)。 Co-Authored-By: Claude Opus 4.8 --- src/pages/DriversPage.test.tsx | 27 +++++++++++++++++++++++++++ src/pages/DriversPage.tsx | 14 ++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/pages/DriversPage.test.tsx b/src/pages/DriversPage.test.tsx index 6edd799..2dac6f8 100644 --- a/src/pages/DriversPage.test.tsx +++ b/src/pages/DriversPage.test.tsx @@ -118,3 +118,30 @@ describe('DriversPage 評價欄(B5)', () => { }); }); }); + +describe('DriversPage 評價排序方向', () => { + beforeEach(() => { + setRole('superadmin'); + mockFetchDrivers.mockReset(); + mockFetchDrivers.mockResolvedValue([ + { ID: 1, Name: '高分司機', Phone: '', LineUserID: 'l1', Status: 1, RatingAvg: 4.8, RatingCount: 25 }, + { ID: 2, Name: '低分司機', Phone: '', LineUserID: 'l2', Status: 1, RatingAvg: 2.5, RatingCount: 4 }, + { ID: 3, Name: '新司機', Phone: '', LineUserID: 'l3', Status: 1, RatingAvg: 0, RatingCount: 0 }, + ]); + }); + + // 迴歸:antd 做降序是把比較結果整個反轉,特例不跟著反轉的話沒評分的會浮到最前面。 + it('降序時沒評分的仍排最後(antd 會反轉比較結果)', async () => { + const user = userEvent.setup(); + renderWithProviders(); + await waitFor(() => expect(screen.getByText('高分司機')).toBeInTheDocument()); + + await user.click(screen.getByText('評價')); // 升序 + await user.click(screen.getByText('評價')); // 降序 + + await waitFor(() => { + const names = screen.getAllByRole('row').slice(1).map((r) => r.querySelector('a')?.textContent ?? ''); + expect(names).toEqual(['高分司機', '低分司機', '新司機']); + }); + }); +}); diff --git a/src/pages/DriversPage.tsx b/src/pages/DriversPage.tsx index fbec549..913b9cf 100644 --- a/src/pages/DriversPage.tsx +++ b/src/pages/DriversPage.tsx @@ -2,6 +2,7 @@ import { useMemo, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { App, Button, Card, Empty, Input, Select, Space, Switch, Table, Tag, Tooltip } from 'antd'; import type { ColumnsType } from 'antd/es/table'; +import type { SortOrder } from 'antd/es/table/interface'; import { Link } from 'react-router-dom'; import { StarFilled } from '@ant-design/icons'; @@ -197,11 +198,16 @@ export default function DriversPage() { title: '評價', width: 120, // 可排序:營運要能一眼找出評價最低的司機,那正是這一欄存在的理由。 - // **沒評分的排在最後**(不是最前)——0 則不代表差,不該混進低分名單。 - sorter: (a: Driver, b: Driver) => { + // **沒評分的兩個方向都排最後**(不是最前)——0 則不代表差,不該混進低分名單。 + // + // `sortOrder` 不可省:antd 做降序的方式是**把比較結果整個反轉**, + // 所以「沉底」的特例不跟著反轉的話,降序時沒評分的會被推到最前面 + // (2026-07-27 實測:降序得到「新司機, 高分司機, 低分司機」)。 + sorter: (a: Driver, b: Driver, sortOrder?: SortOrder) => { + const sink = sortOrder === 'descend' ? -1 : 1; // 讓「沒評分」永遠沉底 if (a.RatingCount === 0 && b.RatingCount === 0) return 0; - if (a.RatingCount === 0) return 1; - if (b.RatingCount === 0) return -1; + if (a.RatingCount === 0) return sink; + if (b.RatingCount === 0) return -sink; return a.RatingAvg - b.RatingAvg; }, render: (_: unknown, driver: Driver) =>