From f4494cfefb67fef30e791d80af9ee46ff275b6e6 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Mon, 27 Jul 2026 14:36:46 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20FE-User:=20fix=20menu=20sang=20nham?= =?UTF-8?q?=20=E2=80=94=20click=20"Danh=20sach"=20lai=20highlight=20"Da=20?= =?UTF-8?q?duyet"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug UAT anh bao 2026-07-27 (anh chup man hinh): bam muc "Danh sach" thi menu lai to sang muc "Da duyet". Goc: `phase` vua la BO LOC trang thai tren trang danh sach (nen nam trong TRANSIENT_QUERY_KEYS tu fix 2026-05-08 — de user loc/chon dong khong mat highlight), vua VUA-MOI tro thanh DANH TINH dieu huong cua muc "Da duyet" (`?type=N&phase=7`, dot 2 hom nay). `queryMatches` strip `phase` khoi CA HAI ve => "Danh sach" ([type]) va "Da duyet" ([type] sau khi mat phase) thanh KHONG PHAN BIET DUOC => URL `?type=1` khop ca hai, muc render sau thang phan hien thi. Cung mot tham so khong the vua la thu-bi-bo-qua vua la thu-dinh-danh. 2 sua, ca 2 app (Layout khong phai file mirror byte-identical nen sua rieng tung ben): - `queryMatches`: transient chi duoc bo qua khi MENU DICH KHONG tu ghim key do (dich ghim => so khop NGHIEM; khong ghim => giu hanh vi cu). - Route "Da duyet" them `&view=approved` = khoa DINH DANH rieng (trang bo qua no, chi `phase=7` loc that). Can thiet vi URL muc "Da duyet" TRUNG HET URL ma user tu loc trang thai tren Danh sach => khong the phan biet bang URL neu thieu khoa nay. Kiem 4 ca duong di (khong chi ca anh bao): ?type=1 -> Danh sach SANG, Da duyet TAT ?type=1&phase=7&view=approved -> Da duyet SANG, Danh sach TAT ?type=1&phase=7 (user tu loc) -> Danh sach VAN SANG (giu fix 2026-05-08) ?type=1&deleted=1 -> Da xoa SANG, con lai TAT tsc 0 loi x2 · npm build PASS x2. Co-Authored-By: Claude Opus 5 --- fe-admin/src/components/Layout.tsx | 19 ++++++++++++++++--- fe-user/src/components/Layout.tsx | 25 ++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/fe-admin/src/components/Layout.tsx b/fe-admin/src/components/Layout.tsx index dbd36e6..144b46c 100644 --- a/fe-admin/src/components/Layout.tsx +++ b/fe-admin/src/components/Layout.tsx @@ -127,7 +127,11 @@ function resolvePath(key: string): string | null { // [S155 đợt 2] 2 mục MỚI — CẢ HAI tái dùng `PurchaseEvaluationsListPage`, 0 route mới, // 0 page mới. "Đã duyệt" chỉ là filter phase sẵn có (`DaDuyet = 7`, đã thông 3 tầng); // "Đã xóa" là view riêng trong cùng page, gọi endpoint `/purchase-evaluations/deleted`. - if (action === 'Approved') return `/purchase-evaluations?type=${typeInt}&phase=7` + // 🔴 `view=approved` = KHOÁ ĐỊNH DANH (trang bỏ qua, chỉ `phase=7` lọc thật). URL mục + // "Đã duyệt" trùng hệt URL user tự lọc trạng thái trên Danh sách ⇒ menu sáng nhầm + // (bug UAT 2026-07-27). `phase` thuộc TRANSIENT nên không làm danh tính được. + // Mirror `fe-user/src/components/Layout.tsx` — cùng bản vá. + if (action === 'Approved') return `/purchase-evaluations?type=${typeInt}&phase=7&view=approved` if (action === 'Deleted') return `/purchase-evaluations?type=${typeInt}&deleted=1` } // PE workflow admin leaf: PeWf_ → /system/pe-workflows/ @@ -225,8 +229,17 @@ const TRANSIENT_QUERY_KEYS = new Set(['id', 'q', 'editHeader', 'page', 'phase', function queryMatches(current: string, target: string): boolean { const a = new URLSearchParams(current) const b = new URLSearchParams(target) - const aKeys = [...a.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort() - const bKeys = [...b.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort() + // 🔴 [S155 đợt 2] Transient chỉ được bỏ qua khi MENU ĐÍCH KHÔNG tự ghim key đó. + // Bug UAT 2026-07-27: click "Danh sách" mà menu sáng "Đã duyệt". `phase` vừa là + // BỘ LỌC trạng thái (nên nằm trong TRANSIENT — fix 2026-05-08) vừa vừa-mới thành + // DANH TÍNH điều hướng của "Đã duyệt" (`?type=N&phase=7`). Strip cả 2 vế ⇒ 2 mục + // KHÔNG PHÂN BIỆT ĐƯỢC. Quyền quyết thuộc về ĐÍCH: ghim ⇒ so nghiêm, không ghim ⇒ + // giữ hành vi cũ. (Mirror `fe-user/src/components/Layout.tsx` — cùng bản vá.) + const pinnedByTarget = new Set(b.keys()) + const ignorable = (k: string) => TRANSIENT_QUERY_KEYS.has(k) && !pinnedByTarget.has(k) + + const aKeys = [...a.keys()].filter(k => !ignorable(k)).sort() + const bKeys = [...b.keys()].filter(k => !ignorable(k)).sort() if (aKeys.length !== bKeys.length) return false return aKeys.every((k, i) => bKeys[i] === k && a.get(k) === b.get(k)) } diff --git a/fe-user/src/components/Layout.tsx b/fe-user/src/components/Layout.tsx index 41fe922..43669e6 100644 --- a/fe-user/src/components/Layout.tsx +++ b/fe-user/src/components/Layout.tsx @@ -140,7 +140,12 @@ function resolvePath(key: string): string | null { // [S155 đợt 2] 2 mục MỚI — CẢ HAI tái dùng `PurchaseEvaluationsListPage`, 0 route mới, // 0 page mới. "Đã duyệt" chỉ là filter phase sẵn có (`DaDuyet = 7`, đã thông 3 tầng); // "Đã xóa" là view riêng trong cùng page, gọi endpoint `/purchase-evaluations/deleted`. - if (action === 'Approved') return `/purchase-evaluations?type=${typeInt}&phase=7` + // 🔴 `view=approved` là KHOÁ ĐỊNH DANH, KHÔNG phải bộ lọc — trang bỏ qua nó, chỉ + // `phase=7` mới thật sự lọc. Vì sao phải có: URL của mục "Đã duyệt" TRÙNG HỆT URL + // mà user tự lọc trạng thái "Đã duyệt" trên trang Danh sách ⇒ không phân biệt được + // bằng URL ⇒ menu sáng nhầm (bug UAT anh báo 2026-07-27). `phase` nằm trong + // TRANSIENT (là bộ lọc) nên KHÔNG dùng làm danh tính được; `view` thì không. + if (action === 'Approved') return `/purchase-evaluations?type=${typeInt}&phase=7&view=approved` if (action === 'Deleted') return `/purchase-evaluations?type=${typeInt}&deleted=1` } return null @@ -277,8 +282,22 @@ const TRANSIENT_QUERY_KEYS = new Set(['id', 'q', 'editHeader', 'page', 'phase', function queryMatches(current: string, target: string): boolean { const a = new URLSearchParams(current) const b = new URLSearchParams(target) - const aKeys = [...a.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort() - const bKeys = [...b.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort() + + // 🔴 [S155 đợt 2] Transient chỉ được bỏ qua khi MENU ĐÍCH KHÔNG tự ghim key đó. + // Bug UAT 2026-07-27 (anh báo): click "Danh sách" mà menu sáng "Đã duyệt". + // Nguyên nhân: `phase` vừa là BỘ LỌC trạng thái trên trang danh sách (nên nằm + // trong TRANSIENT — fix 2026-05-08), vừa vừa-mới trở thành DANH TÍNH điều hướng + // của mục "Đã duyệt" (`?type=N&phase=7`, đợt 2). Strip cả 2 vế ⇒ "Danh sách" + // (`[type]`) và "Đã duyệt" (`[type]` sau khi mất `phase`) thành KHÔNG PHÂN BIỆT + // ĐƯỢC ⇒ URL `?type=1` khớp cả hai, mục render sau thắng. + // Cùng một tham số không thể vừa là thứ-bị-bỏ-qua vừa là thứ-định-danh; nên + // quyền quyết định thuộc về ĐÍCH: đích ghim `phase` ⇒ so khớp NGHIÊM; đích không + // ghim ⇒ giữ nguyên hành vi cũ (lọc/chọn dòng không làm mất highlight). + const pinnedByTarget = new Set(b.keys()) + const ignorable = (k: string) => TRANSIENT_QUERY_KEYS.has(k) && !pinnedByTarget.has(k) + + const aKeys = [...a.keys()].filter(k => !ignorable(k)).sort() + const bKeys = [...b.keys()].filter(k => !ignorable(k)).sort() if (aKeys.length !== bKeys.length) return false return aKeys.every((k, i) => bKeys[i] === k && a.get(k) === b.get(k)) }