[CLAUDE] FE-User: fix menu sang nham — click "Danh sach" lai highlight "Da duyet"
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m41s

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 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-27 14:36:46 +07:00
parent fc064bea94
commit f4494cfefb
2 changed files with 38 additions and 6 deletions

View File

@ -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, // [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); // 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`. // "Đã 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` if (action === 'Deleted') return `/purchase-evaluations?type=${typeInt}&deleted=1`
} }
// PE workflow admin leaf: PeWf_<Code> → /system/pe-workflows/<code> // PE workflow admin leaf: PeWf_<Code> → /system/pe-workflows/<code>
@ -225,8 +229,17 @@ const TRANSIENT_QUERY_KEYS = new Set(['id', 'q', 'editHeader', 'page', 'phase',
function queryMatches(current: string, target: string): boolean { function queryMatches(current: string, target: string): boolean {
const a = new URLSearchParams(current) const a = new URLSearchParams(current)
const b = new URLSearchParams(target) const b = new URLSearchParams(target)
const aKeys = [...a.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 đó.
const bKeys = [...b.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort() // 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 if (aKeys.length !== bKeys.length) return false
return aKeys.every((k, i) => bKeys[i] === k && a.get(k) === b.get(k)) return aKeys.every((k, i) => bKeys[i] === k && a.get(k) === b.get(k))
} }

View File

@ -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, // [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); // 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`. // "Đã 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` if (action === 'Deleted') return `/purchase-evaluations?type=${typeInt}&deleted=1`
} }
return null return null
@ -277,8 +282,22 @@ const TRANSIENT_QUERY_KEYS = new Set(['id', 'q', 'editHeader', 'page', 'phase',
function queryMatches(current: string, target: string): boolean { function queryMatches(current: string, target: string): boolean {
const a = new URLSearchParams(current) const a = new URLSearchParams(current)
const b = new URLSearchParams(target) 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 if (aKeys.length !== bKeys.length) return false
return aKeys.every((k, i) => bKeys[i] === k && a.get(k) === b.get(k)) return aKeys.every((k, i) => bKeys[i] === k && a.get(k) === b.get(k))
} }