[CLAUDE] Fix sidebar highlight: strip transient keys (id/q/awId/...) khỏi queryMatches
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m17s

Bug UAT 2026-05-08: ở leaf "Danh sách" /purchase-evaluations?type=1, click
chọn 1 phiếu → URL thành ?type=1&id=abc → leaf bị mất highlight box.

Root cause: queryMatches exact-set equality — `{type}` (target) vs
`{type, id}` (current) length mismatch → no match → leaf unhighlight.

Fix: TRANSIENT_QUERY_KEYS = {id, q, editHeader, page, phase, awId} —
strip trước khi compare. Match dựa trên "navigation identity" only.

Edge case verify (cả 2 case quan trọng đều OK):
- /pe?type=1 click phiếu → ?type=1&id=abc → strip id → match Danh sách ✓
- /pe?type=1&pendingMe=1 → strip nothing → distinct với Danh sách ?type=1 ✓
- /pe?type=1&phase=10 (filter) → strip phase → match Danh sách ✓
- /pe?type=1&pendingMe=1&awId=xyz → strip awId → match Duyệt ✓

Mirror fe-admin + fe-user.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-08 19:01:59 +07:00
parent f77ea3828a
commit a9c0857a84
2 changed files with 21 additions and 10 deletions

View File

@ -160,14 +160,20 @@ function MenuGroup({ node, depth }: { node: MenuNode; depth: number }) {
) )
} }
// So sánh 2 query string dạng key-value set (thứ tự param không quan trọng). // Transient query keys — không phải "navigation identity", strip trước khi
// Fix bug: /contracts?type=1 và ?type=1&pendingMe=1 cùng highlight vì NavLink // compare để menu giữ highlight khi user select row / search / filter.
// built-in `end` prop chỉ match pathname, không check query string. // Ví dụ leaf "Danh sách" `?type=1` vẫn highlight khi user click phiếu →
// `?type=1&id=abc`. Trước đó exact-set match → mất highlight (bug UAT 2026-05-08).
const TRANSIENT_QUERY_KEYS = new Set(['id', 'q', 'editHeader', 'page', 'phase', 'awId'])
// So sánh 2 query string dạng key-value set (thứ tự param không quan trọng,
// transient keys ignored). Fix bug: /contracts?type=1 và ?type=1&pendingMe=1
// cùng highlight vì NavLink built-in `end` prop chỉ match pathname.
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()].sort() const aKeys = [...a.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort()
const bKeys = [...b.keys()].sort() const bKeys = [...b.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(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

@ -192,14 +192,19 @@ function MenuGroup({ node, depth }: { node: MenuNode; depth: number }) {
) )
} }
// So sánh 2 query string dạng key-value set (thứ tự param không quan trọng). // Transient query keys — không phải "navigation identity", strip trước khi
// Dùng để distinguish /path?type=2 vs /path?type=2&pendingMe=1 — NavLink isActive // compare để menu giữ highlight khi user select row / search / filter.
// built-in chỉ match pathname, không check query string. // Ví dụ leaf "Danh sách" `?type=1` vẫn highlight khi user click phiếu →
// `?type=1&id=abc`. Trước đó exact-set match → mất highlight (bug UAT 2026-05-08).
const TRANSIENT_QUERY_KEYS = new Set(['id', 'q', 'editHeader', 'page', 'phase', 'awId'])
// So sánh 2 query string dạng key-value set (thứ tự param không quan trọng,
// transient keys ignored). Dùng để distinguish /path?type=2 vs /path?type=2&pendingMe=1.
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()].sort() const aKeys = [...a.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort()
const bKeys = [...b.keys()].sort() const bKeys = [...b.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(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))
} }