# sub-reviewer-diff-tongquan — soi đối kháng diff `fe-user/src/pages/UserDashboardPage.tsx` (S159 L8) > Run: `2026-07-29-S159-tong-quan-pipeline-menu` · vai: reviewer (adversarial, READ-only) > Vật soi: `git diff -- fe-user/src/pages/UserDashboardPage.tsx` — 728 dòng (585+/143−), 1 file. > 🔴 Ghi đĩa TRONG LÚC LÀM (chống #53). Mỗi FLAG ghi ngay khi tìm ra, không đợi cuối. > 🔴 Giới hạn đã khai: **KHÔNG live-verify** (route `/dashboard` cần login, FE chưa deploy) ⇒ toàn bộ kết luận là **soi TĨNH** trên diff + đọc file nguồn BE/FE + đọc route/type. Mọi phát biểu về hành vi runtime là **suy luận từ mã**, đã đánh dấu rõ chỗ nào chưa chứng được. --- ## 0 — Xác nhận phạm vi (đã chạm đĩa, không tin lời khai) - `git diff --stat` → **đúng 1 file** `fe-user/src/pages/UserDashboardPage.tsx`, 585+/143−. ✅ khớp lệnh. - `git status --porcelain` → các file M khác (`WAL.md`, `docs/STATUS.md`, `docs/HANDOFF.md`, `scripts/*`, `.claude/commands/*`) **không thuộc vật soi**, đúng như FD khai là của lead. **0 file `fe-admin/**`** bị chạm. ✅ ràng buộc "fe-user ONLY" THOẢ. - Named export `UserDashboardPage` giữ nguyên (dòng context không đổi trong hunk `@@ -113,20 +351,180 @@` phía trên). ✅ - 0 dependency mới (chỉ thêm icon `lucide-react` đã có sẵn). ✅ --- ## A — Những claim của FD tôi đã ĐỘC LẬP kiểm và XÁC NHẬN ĐÚNG (không phải tin lời) | Claim | Kiểm bằng | Kết quả | |---|---|---| | `Paged` có `total`, **không** có `totalCount` | `fe-user/src/types/master.ts:1-9` | ✅ ĐÚNG — lead brief ghi `totalCount` là **SAI**, FD đính chính đúng | | `phase` là query-param BE thật | `PurchaseEvaluationsController.cs:23` `[FromQuery] PurchaseEvaluationPhase? phase` | ✅ | | `PurchaseEvaluationPhase.DaDuyet === 7` | `fe-user/src/types/purchaseEvaluation.ts:28` | ✅ | | `pendingMe` KHÔNG phải query-param BE; `/inbox` trả **mảng phẳng** | `PurchaseEvaluationsController.cs:30-35` trả `List<...>` (không `PagedResult`) | ✅ — dùng `.length` là đúng | | `/reports/my-dashboard` chỉ đếm bảng **Contracts** ⇒ gán GĐ3 đúng nghĩa | `GetMyDashboardQuery.cs` — mọi query đi từ `db.Contracts` | ✅ | | `.icon-chip` có size nội tại 2.25rem ⇒ bỏ `h-9 w-9` là no-op | `index.css:128-137` | ✅ | | `h1..h4` + `.stat-value` là rule **unlayered** ⇒ phải dùng `!` để đè màu | `index.css:79-83` + `:140-146` — cả 2 set `color`, **KHÔNG set `font-size`** | ✅ và FD áp `!` ĐÚNG CHỖ: `text-brand-800!` / `text-brand-700!` / `text-accent-600!` / `text-slate-600!`. `text-base` / `text-[15px]` **không** cần `!` vì 2 rule đó không set `font-size` — tôi đã đọc để bác giả thuyết "chữ phình to". | | Mọi stop màu dùng đều CÓ THẬT trong `@theme` | `index.css:7-51` — brand 50-900 · teal 50/100/500/600/700 · violet · amberx · accent 500/600 | ✅ 0 stop ma (không có `-800` của teal/violet/amberx nào bị dùng) | | `.label-eyebrow` = brand-600 chỉ 4.42:1 | tự tính lại luminance từ `#1f7dc1` | ✅ 4.41 — số FD khai **đo được và đúng**, né sang `text-brand-700` là hợp lý | | `slate-500` = 4.76:1 (FD ghi 4.8) | tự tính từ `#64748b` | ✅ | | `Button` có `size="sm"` + `gap-1.5` sẵn ⇒ bỏ `mr-1` là đúng | `fe-user/src/components/ui/button.tsx` | ✅ | | 1 file, 0 file `fe-admin/**`, 0 dep mới, named export giữ | `git diff --stat` + đọc file | ✅ | **Giả thuyết tôi TỰ ĐẶT RA rồi TỰ BÁC (ghi lại để khỏi ai đào lại):** - *"Số GĐ1 là toàn hệ thống còn GĐ3 là của riêng tôi ⇒ lệch phạm vi, user đọc sai tỷ lệ rơi rụng"* → **BÁC**: cả 2 endpoint đều IDOR-scoped per-user (`PurchaseEvaluationFeatures.cs:596-614` + `ContractFeatures.cs:297-303`). Cùng phạm vi ⇒ so sánh được. - *"`Chờ tôi duyệt` GĐ1 = `inbox.length` sẽ LỚN HƠN số ở màn đích vì màn đích lọc thêm client-side `DaGuiDuyet`"* (`PurchaseEvaluationsListPage.tsx:211-212`) → **BÁC**: BE inbox chỉ trả phase ∈ {ChoPurchasing, ChoDuAn, ChoCCM, ChoCEODuyetPA, ChoCEODuyetNCC, ChoDuyet} (`PurchaseEvaluationFeatures.cs:731-742,762`), tất cả đều map về `DaGuiDuyet` (`purchaseEvaluation.ts:105-110`) ⇒ bộ lọc client là no-op, **2 số khớp nhau**. - *"`?phase=7` / `?pendingMe=1` là URL bịa, màn đích không đọc"* → **BÁC**: `PurchaseEvaluationsListPage.tsx:37,45,95` đọc cả 2. Route `/purchase-evaluations`, `/my-contracts`, `/inbox` đều có thật trong `App.tsx:50,58,59`. - *"queryKey mới đè cache trang list"* → **BÁC**: trang list dùng `['pe-list', {...}]` / `['pe-detail', id]`; key mới là `['pipeline-pe-approved']` / `['pipeline-pe-inbox']` — không giao. - *"bắn request bão"* → **BÁC**: `main.tsx:7-14` `refetchOnWindowFocus:false`, `retry:1`; +2 request đúng trần ≤3. - *"chia 0 / NaN khi rỗng"* → **BÁC**: diff không có phép chia nào; `value ?? 0` chặn undefined. - *"skeleton che cả pipeline"* → **BÁC**: loading là per-row (`StageStatRow`), khung 4 GĐ luôn render. --- ## B — FLAG