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)) }