From a9c0857a841882493fbd0384358586d1e59ef421 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Fri, 8 May 2026 19:01:59 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Fix=20sidebar=20highlight:=20strip?= =?UTF-8?q?=20transient=20keys=20(id/q/awId/...)=20kh=E1=BB=8Fi=20queryMat?= =?UTF-8?q?ches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- fe-admin/src/components/Layout.tsx | 16 +++++++++++----- fe-user/src/components/Layout.tsx | 15 ++++++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/fe-admin/src/components/Layout.tsx b/fe-admin/src/components/Layout.tsx index 45ff110..7aae9f6 100644 --- a/fe-admin/src/components/Layout.tsx +++ b/fe-admin/src/components/Layout.tsx @@ -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). -// Fix bug: /contracts?type=1 và ?type=1&pendingMe=1 cùng highlight vì NavLink -// built-in `end` prop chỉ match pathname, không check query string. +// Transient query keys — không phải "navigation identity", strip trước khi +// compare để menu giữ highlight khi user select row / search / filter. +// 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 { const a = new URLSearchParams(current) const b = new URLSearchParams(target) - const aKeys = [...a.keys()].sort() - const bKeys = [...b.keys()].sort() + const aKeys = [...a.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort() + const bKeys = [...b.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(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 7db7f1a..b51fbae 100644 --- a/fe-user/src/components/Layout.tsx +++ b/fe-user/src/components/Layout.tsx @@ -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). -// Dùng để distinguish /path?type=2 vs /path?type=2&pendingMe=1 — NavLink isActive -// built-in chỉ match pathname, không check query string. +// Transient query keys — không phải "navigation identity", strip trước khi +// compare để menu giữ highlight khi user select row / search / filter. +// 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 { const a = new URLSearchParams(current) const b = new URLSearchParams(target) - const aKeys = [...a.keys()].sort() - const bKeys = [...b.keys()].sort() + const aKeys = [...a.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort() + const bKeys = [...b.keys()].filter(k => !TRANSIENT_QUERY_KEYS.has(k)).sort() if (aKeys.length !== bKeys.length) return false return aKeys.every((k, i) => bKeys[i] === k && a.get(k) === b.get(k)) }