diff --git a/.claude/WAL.md b/.claude/WAL.md index 782fa86..976eeb1 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -1,25 +1,19 @@ # WAL — auto-generated, không sửa tay updated: 2026-07-29 | session: S159 (phiên-LOGIC L8, window 1) | branch: main -goal: bookend @open 5 vòng + vá spec KHKK + thi hành (49)-(53) — TẤT CẢ XONG +goal: arc-2 — Tổng quan pipeline 4 GĐ + menu eOffice (anh chốt 4 quyết, run `2026-07-29-S159-tong-quan-pipeline-menu`) chain: -- [x] Sàn-3 ① kêu ×2 → retro-harvest 2 synthesis (H2 verify 12/12 byte-exact, 0 bịa) -- [x] ARC spec KHKK: H5 **2 site** · H6 **4 site** (site-4 lọt cả 3 vòng đếm trước) -- [x] Bookend 5 vòng: 10 vai · 9 spawn + 6 resume · 11 artifact ~231 KB -- [x] (49) 6 site 2 file LỆNH → cặp H24 VÔ-ĐIỀU-KIỆN mỗi bookend + nới bất-biến (6) + **phép (10) mới, fault-inject 4/4** -- [x] (50) seed 10/10 diary on-behalf · (51) `_INDEX` 78→80 + `Run #407` mồ-côi đã thêm (sweep 0) -- [x] (52) slot (45) RÚT (báo động giả) · (53) lọc detector `(sub|harness)-*` → INFORM 8→14 -- [x] STATUS: row RAG **TÁCH 2 trục** (service UP ⟂ SE index stale) + CURRENT S159 · HANDOFF cập nhật -- [x] dọn dư-lượng **2 LẦN** (tái phát sau `harness-audit`) → stray 0 · empty-dir 0 · agent-memory 23 vai nguyên -- [x] verify cuối: mind-check `đạt=10 TRUOT=0 co=1` · detector TOTAL 43 (không đội) · **test 562 PASS** +- [x] bookend @open 5 vòng + vá spec KHKK + (49)-(53) — XONG (run `2026-07-29-S159-bookend-open`, xem WAL cũ trong git) +- [x] anh giao arc-2 + đổi lead → Fable 5; em đọc lại đĩa (S156 21-bước + spec KHKK) → báo → anh chốt 4 câu (❶ ranh GĐ ❷ pipeline 4 GĐ ❸ ẩn 3 nhóm ❹ mở HĐ+DM không vá authz — verbatim trong run.md) +- [x] T2 prod LIVE: hide=3 upd=99 ins=376 · verify 38/38=13 canread · Pe_* 197/185 NGUYÊN · Hrm_Config* nguyên +- [x] T3 spec đổi nhãn "Đề xuất ký kết hợp đồng" + ranh GĐ + Q8-ĐÓNG (2 site) +- [!] T1 frontend-designer đang chạy background (redesign UserDashboardPage → pipeline 4 GĐ, fe-user only) +- [ ] T4: designer về → đọc đĩa sub-file → build fe-user → reviewer diff → commit/push → cicd verify -next: chờ anh xử (51-bis) chọn a/b/c cho `_INDEX` 97,45%; rồi `/session-end` đóng L8 (hoặc `/pause` nếu dừng giữa chừng) +next: chờ T1 return → verify RUỘT (chống skeleton) → build → reviewer (diff >50 LOC) → commit [CLAUDE] FE-User → push → cicd-monitor verify: -- 🔴 **`git status` MÙ với thư-mục rỗng** ⇒ close-gate PHẢI chạy RIÊNG: `find . -mindepth 2 -type d -name .claude` + `find .claude -type d -empty` -- 🔴 **Dư-lượng TÁI PHÁT theo MỖI đợt spawn** (dọn @S159 lần 1, `harness-audit` sinh lại lần 2) ⇒ đây là hiện-tượng HỆ-THỐNG, phải dọn ở **cuối mỗi wave**, không phải 1 lần/phiên -- 🔴 CẤM chép `gap-carry-dropped=3` vào tally (D-4: đơn-vị trong-phiên ⟂ consecutive-audit đang =7 ⇒ XOÁ LỊCH-SỬ) -- 🔴 grep `END` khớp cả dòng TRÍCH DẪN ⇒ kiểm **VỊ TRÍ** khớp (citation-trap, lead mắc 1 lần @S159) -- 🔴 C11 diary-0-delta hiện **9** vì detector đo *"diary không có COMMIT mới hơn"* mà 12 file vừa seed **chưa commit** — đo lại SAU khi hook commit, đừng giả định -- 🔴 **"5 FLAG" của `lead-stale` là SÀN không phải TỔNG** (chết #53 ở 5/8 bề-mặt) ⇒ coverage vòng soi-lead S159 KHÔNG xác định được +- prod menu đảo được: `UPDATE MenuItems SET IsVisible=1 WHERE [Key] IN ('Hrm','Off','Personal')` + revoke = xoá row INSERT (backup pre-apply có số liệu gốc) +- 🔴 ❹ = rủi-ro CÓ Ý THỨC owner (mở HĐ khi authz trần) — ghi run.md, PHẢI chép vào HANDOFF lúc closeout +- dọn dư-lượng spawn sau wave: `find . -mindepth 2 -type d -name .claude` + `find .claude -type d -empty` diff --git a/.claude/workflows/runs/2026-07-28-S157-ke-hoach-ky-ket-hd/spec-ke-hoach-ky-ket-hd-28-07-2026.md b/.claude/workflows/runs/2026-07-28-S157-ke-hoach-ky-ket-hd/spec-ke-hoach-ky-ket-hd-28-07-2026.md index 43209cb..96c3abd 100644 --- a/.claude/workflows/runs/2026-07-28-S157-ke-hoach-ky-ket-hd/spec-ke-hoach-ky-ket-hd-28-07-2026.md +++ b/.claude/workflows/runs/2026-07-28-S157-ke-hoach-ky-ket-hd/spec-ke-hoach-ky-ket-hd-28-07-2026.md @@ -24,6 +24,7 @@ Giữa **Duyệt NCC** (module PE, kết thúc `PE.DaDuyet`) và **Hợp đồng ### 1.2 Tên + mục đích (owner đặt) > **anh:** *"Bước này là bước **'đề xuất giá trị ký kết hợp đồng'**"* · sau đó gọi là **"Kế hoạch ký kết Hợp đồng"**. +> 🔄 **OWNER CHỐT NHÃN @S159 (2026-07-29, AskUser ❶):** tên phiếu/menu = **"Đề xuất ký kết hợp đồng"** — mọi label VN trong spec đọc theo tên này (tên bảng/class English `ContractSigningPlan*` GIỮ NGUYÊN). Cùng lượt anh chốt: **ranh GĐ2 = b.7→12** (b.12 chốt con số ∈ GĐ2) · GĐ3 = b.13→18 tận dụng Contract V2 · GĐ4 = b.19→21 + ký cứng **upload-only** · **b.7 email BỎ** khỏi scope, thay bằng tạo-phiếu-từ-PE-`DaDuyet` (= chính thiết kế W2 — **Q8 ĐÓNG: out-of-scope**). Đây là phát biểu **MỤC ĐÍCH**: đầu ra của khúc này = **MỘT CON SỐ CÓ PHÊ DUYỆT** (giá trị ký kết đề xuất). b.8-9 (duyệt mẫu / shopdrawing với TVGS) = **CĂN CỨ**; b.10-12 = **chuỗi duyệt chính con số đó**. @@ -219,7 +220,7 @@ attachments · `DELETE {id}` (allow-list `DangSoanThao|TuChoi`, mirror PE `:1404 `OffDeXuat`+3 con **đều trong `All`**; còn `Pe_*` sinh bằng **factory** `:150`/`:154` **NẰM NGOÀI `All`**. **Theo khuôn `Off_DeXuat`, KHÔNG theo `Pe_*`.** ⇒ 2 row canonical `docs/STATUS.md` (Menu keys · Policies = |All|×4) phải bump cùng lúc. -- **Seed `DbInitializer.cs`:** 4 dòng menu (khuôn Off_DeXuat `:1817-1820`, label VN *"Kế hoạch ký kết HĐ"* / +- **Seed `DbInitializer.cs`:** 4 dòng menu (khuôn Off_DeXuat `:1817-1820`, label VN *"Đề xuất ký kết hợp đồng"* — 🔄 nhãn owner chốt @S159, thay "Kế hoạch ký kết HĐ" / *"Danh sách"* / *"Tạo mới"* / *"Inbox duyệt"*) + admin-permission (`:2383` — **grep MỌI site seed-permission**, S155 ghi nhận có 2 site cho vài nhóm). 🔴 **GIẤU MENU Ở W1 = KHÔNG CẤP `CanRead` cho role thường** — **KHÔNG** phải `IsVisible=0`. diff --git a/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/prod-backup-pre-apply.txt b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/prod-backup-pre-apply.txt new file mode 100644 index 0000000..d20d2dc --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/prod-backup-pre-apply.txt @@ -0,0 +1,30 @@ +# backup-pre-apply — prod 2026-07-29T11:14:11+07:00 + +## Permission columns +Id RoleId MenuKey CanRead CanCreate CanUpdate CanDelete +-- ------ ------- ------- --------- --------- --------- +BA21E047-5950-4599-A439-00882DB38AF7 F54055F6-B5C5-41E5-C418-08DE9F7D9CC2 Workflows 1 1 1 1 + +## Per-key CanRead counts TRUOC (38 key grant-set) +MenuKey rows_ canread +------- ----- ------- +CatalogMaterials 13 2 +Catalogs 13 2 +CatalogServices 13 2 +CatalogUnits 13 2 +CatalogWorkItems 13 2 +Contracts 1 1 +Departments 13 2 +Master 13 2 +Projects 13 2 +Suppliers 13 2 + +## Negative control TRUOC: Pe_* + IsVisible roots +pe_rows pe_canread +------- ---------- +197 185 +Key IsVisible +--- --------- +Hrm 1 +Off 1 +Personal 1 diff --git a/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/prod-verify-post-apply.txt b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/prod-verify-post-apply.txt new file mode 100644 index 0000000..72d1519 --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/prod-verify-post-apply.txt @@ -0,0 +1,19 @@ +# verify-post-apply — prod 2026-07-29T11:15:21+07:00 · apply: hide=3 upd=99 ins=376 + +## Ky vong: moi key 13/13 canread +min_canread max_canread so_key +----------- ----------- ------ +13 13 38 + +## IsVisible 3 roots + control am Pe_* (ky vong 197/185 giu nguyen) +Key IsVisible +--- --------- +Hrm 0 +Off 0 +Personal 0 +pe_rows pe_canread +------- ---------- +197 185 +hrmcfg_canread +-------------- +7 diff --git a/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/run.md b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/run.md new file mode 100644 index 0000000..8c211f1 --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/run.md @@ -0,0 +1,38 @@ +# run — S159 arc-2 · Tổng quan pipeline 4 GĐ + điều chỉnh menu eOffice + +- **run-id:** `2026-07-29-S159-tong-quan-pipeline-menu` · phiên-LOGIC **L8**, window 1 (arc thứ 2 sau bookend) +- **mode:** RUN-TRACE (≥3 task) · lead = **Fable 5** (anh `/model` — chủ dự án đổi, 0 caveat) +- **Nguồn lệnh:** ảnh chụp eoffice + text anh 2026-07-29 (đánh dấu 3 vùng đỏ) + 4 câu AskUser đã chốt + +## 🔴 OWNER CHỐT — NGUYÊN VĂN (2026-07-29, gõ vào sổ ngay để không bốc hơi — bài ring2 M-1 @S158) + +**Lệnh khung (text kèm ảnh):** +> *"Hiện điều chỉnh cái giao diện này lại theo thế này. Đổi vai main sang Fable nhé. Hiện là tổng quy trình mình đã đi (…): GĐ1: Duyệt NCC (1→6) CEO chốt đã xong · GĐ2: Đề xuất Ký kết hợp đồng (7→12) — Có 1 bước email thì bỏ đi cũng đc, thay bằng bước bắt các phiếu từ Giai đoạn 1 → Để nối tiếp làm tiếp. Đây là 1 đầu phiếu mới hoàn toàn · GĐ3: 12→18 (Duyệt Hợp đồng — Cái này mình đã có làm trước rồi, tận dụng lại) · GĐ4 (Bước còn lại): Ký cứng hợp đồng các thứ → có thể nằm ngoài do vướng chữ ký cứng, ký nháy các thứ, có thể ko cần đủ các bước chỉ cần cho upload các file đã ký cứng lên là đc. Đọc lại nắm đủ nội dung. Xong báo lại tao trước khi làm."* + +**4 câu chốt (AskUser cùng ngày):** +- **❶ Ranh GĐ:** *"Đúng như em map"* ⇒ GĐ2 = b.7→**12** (b.12 = chốt con số) · GĐ3 = b.**13**→18 · GĐ4 = b.19→21 + ký cứng upload · **tên phiếu/menu GĐ2 = "Đề xuất ký kết hợp đồng"** (spec đổi nhãn theo). +- **❷ Tổng quan:** *"Pipeline 4 GĐ đầy đủ"* — GĐ1+GĐ3 data thật · GĐ2/GĐ4 khung "sắp triển khai". +- **❸ Ẩn 3 nhóm (verbatim):** *"Chỉ nên hiển thị ở trang admin là đc → Để tao khỏi nhầm giữa cái đã public và cái đang làm"* ⇒ `IsVisible=0` cho 3 root `Hrm` `Off` `Personal` (fe-admin thấy đủ, eOffice ẩn — đúng comment entity `MenuItem.cs`). +- **❹ Mở HĐ + DANH MỤC (verbatim):** *"Hiện đang phát triển → Nên cứ cho hiển thị hết để mọi người góp ý, còn duyệt NCC → thì cứ để như cũ nhé. Phần đó đã tốt rồi."* + 🔴 **Ghi sổ rủi ro CÓ Ý THỨC:** lead đã nêu lỗ hổng authz HĐ (`ContractsController` `[Authorize]` trần 22 endpoint ghi + Reject-trước-guard, @S156) và rằng mở menu = phơi lỗ cho toàn bộ NV. **Anh tái khẳng định hiển thị hết, KHÔNG vá đợt này** (giữ "chỗ hợp đồng cứ từ từ"). Đây là quyết định chủ dự án, đã cảnh báo, chấp nhận trong giai đoạn phát triển. **PE (Duyệt NCC) KHÔNG chạm.** + +## Ánh xạ 4 GĐ ↔ đĩa (đã báo, anh xác nhận) + +| GĐ | Bước | Đĩa | +|---|---|---| +| 1 Duyệt NCC | 1→6 | ✅ module PE (`DaDuyet=7`) | +| 2 Đề xuất ký kết HĐ | 7→12 | 📋 spec KHKK 5-wave (nay đổi nhãn); b.7 email **BỎ** — thay bằng tạo-phiếu-từ-PE-`DaDuyet` = **spec W2 đã đúng sẵn** ⇒ Q8 (email external) **ĐÓNG: out-of-scope** | +| 3 Duyệt HĐ | 13→18 | ✅ tận dụng Contract V2 (verdict `LAI` @S156) | +| 4 Ký cứng | 19→21 | 🔸 ngoài hệ thống — upload file đã ký (Contract attachments sẵn nền); thiết kế sau | + +## Stages + +- [!] **T1 — FE Tổng quan pipeline** (`frontend-designer`, fe-user ONLY) — ĐANG CHẠY background +- [x] **T2 — Prod menu data** ✅ **LIVE + VERIFY TRỌN** (`prod-backup-pre-apply.txt` → apply `hide=3 upd=99 ins=376` → `prod-verify-post-apply.txt`): + · Inspect lộ thực-trạng ≠ giả định: Master subtree có matrix 13-row/key (canread 2/13) nhưng **`Ct_*` 28 key = 0 ROW** (chưa từng seed), `Contracts` root 1 row ⇒ INSERT 376 + UPDATE 99, số khớp toán từng đồng (9×11=99 · 28×13+12=376) + · Verify: **38/38 key min=max=13 canread** · 3 root `IsVisible=0` · control âm **Pe_* 197/185 NGUYÊN** (❹ "để như cũ") · `Hrm_Config*` canread=7 nguyên (vùng đang-làm loại khỏi grant, đúng ý ❸) + · Thuần cộng: chỉ thêm `CanRead=1, CanCreate/Update/Delete=0` — không rút quyền ai +- [x] **T3 — Spec đổi nhãn** ✅ 2 site (`:26` owner-block + `:222` seed label) — "Đề xuất ký kết hợp đồng"; tên English `ContractSigningPlan*` giữ; ranh GĐ + Q8-ĐÓNG ghi vào spec +- [ ] **T4 — build fe-user + reviewer diff → commit/push → cicd verify** + +🔴 Luật run: CẤM điền kết quả trước khi đo. Prod = backup-trước-apply, verify-LIVE-sau (S88). diff --git a/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/sub-frontend-designer-tongquan.md b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/sub-frontend-designer-tongquan.md new file mode 100644 index 0000000..34bfdc0 --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/sub-frontend-designer-tongquan.md @@ -0,0 +1,133 @@ +# sub-frontend-designer-tongquan — T1 FE "Tổng quan tất cả luồng duyệt" (fe-user ONLY) + +> Run: `2026-07-29-S159-tong-quan-pipeline-menu` · vai: frontend-designer · file đích: `fe-user/src/pages/UserDashboardPage.tsx` +> 🔴 GHI ĐĨA TRONG LÚC LÀM (chống #53). Mỗi mục = 1 bước đã XONG, ghi ngay khi xong. +> 🔴 **NO-SCREENSHOT** — môi trường spawn này KHÔNG có dev-server/Playwright (FD2 visual loop KHÔNG chạy được). +> Thay bằng: (i) `npm run build` fe-user PASS + tail output · (ii) tự-review tĩnh theo rubric FD4. KHÔNG bịa screenshot. + +--- + +## 1 — Khảo sát file đích (ĐÃ ĐỌC, không suy đoán) + +`fe-user/src/pages/UserDashboardPage.tsx` — 309 dòng, named export `UserDashboardPage`. Trạng thái TRƯỚC khi sửa: + +- Header: `` +- 3 query TanStack (đúng như lệnh mô tả): + - `['my-dashboard']` → `GET /reports/my-dashboard` → `MyDashboard {draftsInProgress, pendingMyApproval, dueSoon, overdue, draftsTotalValue}` (type khai LOCAL trong file, dòng 25-31). + - `['my-contracts-recent']` → `GET /contracts` params `{page:1,pageSize:5}` → `Paged`. + - `['my-pe-recent']` → `GET /purchase-evaluations` params `{page:1,pageSize:5}` → `Paged`. +- Section hiện có: (a) "Của tôi" = grid 5 `StatCard` (local component, `card-accent` + `icon-chip` + `stat-value`, tone map `STAT_TONE` 6 tone dùng CSS var `--color-*`); (b) "HĐ gần đây" list 5; (c) "Phiếu Duyệt NCC gần đây" list 5 (có `PeFinalizeChip` + PE display-status pill). +- Helper local: `fmtMoney` (tỷ/tr/locale). Import sẵn: `PhaseBadge`, `SlaTimer`, `EmptyState`, `Button`, `cn`, `ContractTypeLabel`, PE display-status helpers. + +⇒ Kế hoạch: GIỮ toàn bộ 3 query + 5 KPI + 2 list (owner: "đừng vứt"), THÊM hero pipeline 4 GĐ ở trên cùng, đổi PageHeader title. + +## 2 — Data thật cho pipeline (đọc trước khi dùng) + +- `PeListItem` (`fe-user/src/types/purchaseEvaluation.ts`) có `phase`; `PurchaseEvaluationPhase.DaDuyet = 7`, `ChoDuyet = 10`, `TraLai = 98`, `TuChoi = 99` — khớp lệnh "phase=7 = Đã duyệt". +### 🔴 2 ĐÍNH CHÍNH so với brief của lead (đã đọc đĩa, KHÔNG tin liệt kê) + +1. **`Paged` KHÔNG có `totalCount`** — field tên là **`total`**. + `fe-user/src/types/master.ts:1-9` → `{items, total, page, pageSize, totalPages, hasNext, hasPrev}`; khớp BE `PagedResult.cs:3-7` (`Total`). +2. **`pendingMe` KHÔNG phải query-param của `GET /purchase-evaluations`** — nó là **URL-param của trang list**, và trang list map nó sang **endpoint KHÁC**: `GET /purchase-evaluations/inbox` trả **mảng phẳng** `PeListItem[]` (không paging) — `PurchaseEvaluationsListPage.tsx:81-89` + `PurchaseEvaluationsController.cs:30-35`. + ⇒ Đếm "chờ tôi duyệt" = `inbox.length`, KHÔNG dùng được `pageSize=1`. + ✅ `phase` THÌ ĐÚNG là query-param BE (`PurchaseEvaluationsController.cs:23` `PurchaseEvaluationPhase? phase`), `phase=7` = Đã duyệt. + +### Nguồn số cho 4 GĐ (chốt) — chỉ **+2 request**, dưới trần ~3 + +| Ô số | Nguồn | Request thêm? | +|---|---|---| +| GĐ1 · Tổng phiếu | `total` của query **đã có** `['my-pe-recent']` (`/purchase-evaluations?page=1&pageSize=5`) | ❌ 0 (tái dùng) | +| GĐ1 · Đã duyệt | `GET /purchase-evaluations?page=1&pageSize=1&phase=7` → `.total` | ✅ +1 | +| GĐ1 · Chờ tôi duyệt | `GET /purchase-evaluations/inbox` → `.length` | ✅ +1 | +| GĐ2 (sắp) | không có số riêng — mượn ô "Đã duyệt" GĐ1 làm câu "N phiếu sẵn sàng nối tiếp" | ❌ 0 | +| GĐ3 · Tổng HĐ | `total` của query **đã có** `['my-contracts-recent']` | ❌ 0 | +| GĐ3 · Chờ tôi duyệt / Quá hạn | `['my-dashboard']` `pendingMyApproval` / `overdue` — **đã xác minh handler CHỈ đếm bảng Contracts** (`GetMyDashboardQuery.cs:51-75`, `db.Contracts`) ⇒ gán vào GĐ3 là ĐÚNG NGHĨA, không phải mượn số | ❌ 0 | +| GĐ4 (sắp) | KHÔNG gán số (owner tuyên "sắp triển khai" — không bịa data từ `ContractPhase.DaPhatHanh`) | ❌ 0 | + +### Token/DS đã nạp (FD1 — dùng, không reinvent) +`fe-user/src/index.css` `@theme`: brand-50..900 (`brand-600 = #1f7dc1`), accent-500/600 đỏ, **teal / amberx / violet / greenx chỉ có stop 50/100/500/600/700** (KHÔNG -800 — gotcha cũ). Utility sẵn: `.card-accent` (rail `--accent`, hover lift, có `prefers-reduced-motion`), `.icon-chip` (`--chip-bg`/`--chip-fg`), `.stat-value`, `.label-eyebrow`, `.app-gradient-brand`. +Icon: đã `node -e` verify 18 export lucide dùng tới (`Workflow`, `ClipboardCheck`, `FileSignature`, `Stamp`, `Hourglass`, `ChevronRight/Down`, `RefreshCw`, `ShieldCheck`…) — tránh bẫy alias-dodge. +Route đã verify tồn tại trong `App.tsx`: `/purchase-evaluations`, `/my-contracts`, `/inbox`, `/contracts/new`, `/purchase-evaluations/:id` — mọi link đều trỏ route THẬT (không đâm `*` fallback). + +## 3 — Quyết định design (trước khi code) + +- **Header:** title → **"Tổng quan tất cả luồng duyệt"**; lời chào `Xin chào, {tên}` KHÔNG vứt — dời xuống `description` cùng câu định vị 4 GĐ. +- **Hero = dải pipeline 4 GĐ:** grid `lg:grid-cols-[1fr_auto_1fr_auto_1fr_auto_1fr]` — 3 ô `auto` là mũi tên nối (`ChevronRight` ở lg, `ChevronDown` khi dọc `, phần bấm là `` con (MiniStat + "Xem tất cả") ⇒ KHÔNG lồng button-trong-button; mọi button có `focus-visible:ring-2 ring-brand-500`. Nhãn phụ đổi `text-slate-400`→`text-slate-500` (slate-400 = 2.5:1 FAIL AA; slate-500 = 4.76:1 PASS). +- **Bug spacing sẵn có được vá luôn:** 2 section list ("HĐ gần đây" + "Phiếu Duyệt NCC gần đây") trước đây dính sát nhau (section thứ 2 không có `mt`/`mb`) → gói vào `grid gap-4 xl:grid-cols-2`. +- **States:** `StatNum` = loading → thanh `animate-pulse`; error → `—` + `title` giải thích; có banner lỗi + nút **Thử lại** (refetch) khi query pipeline hỏng. List giữ skeleton/EmptyState cũ. + +## 4 — File đã sửa (chỉ 1 file, fe-user ONLY) + +`D:\Dropbox\CONG_VIEC\SOLUTION\SOLUTION_ERP\fe-user\src\pages\UserDashboardPage.tsx` — redesign TẠI CHỖ, named export `UserDashboardPage` GIỮ NGUYÊN (route `/dashboard` + nhãn sidebar "Tổng quan" không phải đụng). +`git status` xác nhận **KHÔNG file `fe-admin/**` nào bị chạm** (các file khác đang M là của lead: WAL/STATUS/HANDOFF/scripts…). + +Cấu trúc mới (trên → dưới): +1. `PageHeader` — title **"Tổng quan tất cả luồng duyệt"**, description có lời chào + câu định vị. +2. **HERO pipeline 4 GĐ** (`` bo góc, viền, shadow) — header có icon `Workflow` + eyebrow + "Pipeline 4 giai đoạn · 21 bước"; banner lỗi + nút **Thử lại** (chỉ hiện khi có query fail); dải 4 `StageCard` + 3 `StageArrow`. +3. **"Việc của tôi"** — 5 `StatCard` cũ nguyên vẹn (thêm ghi chú "Số liệu hợp đồng của riêng bạn (giai đoạn 3)" cho khớp sự thật BE). +4. **2 danh sách gần đây** trong `grid gap-4 xl:grid-cols-2` (HĐ gần đây | Phiếu Duyệt NCC gần đây) — thêm nhánh **isError + nút Thử lại** cho cả 2 (trước đây lỗi API = list rỗng câm). + +Component mới (local, không đẻ file mới, không dep mới): `StageCard` · `StageArrow` · `StageStatRow` · `StageLink` · `StageSoon` · `SectionTitle` + map `STAGE_TONE`. +Nội dung 4 GĐ đúng nguyên văn owner: GĐ1 "Duyệt NCC" (Bước 1 → 6 · CEO chốt) · GĐ2 "Đề xuất ký kết hợp đồng" (Bước 7 → 12) — *"Phiếu nối tiếp từ phiếu Duyệt NCC đã duyệt — chốt giá trị ký kết."* · GĐ3 "Duyệt Hợp đồng" (Bước 13 → 18) · GĐ4 "Ký cứng & phát hành" (Bước 19 → 21) — *"Upload bản hợp đồng đã ký cứng."* + +## 5 — Build (bằng chứng thay screenshot #1) + +`cd fe-user && npm run build` (script = `tsc -b && vite build`) — **PASS**. Tail lần chạy cuối: + +``` +dist/index.html 0.87 kB │ gzip: 0.46 kB +dist/assets/index-BIJWA-lW.css 88.95 kB │ gzip: 14.45 kB +dist/assets/index-BBc0xk0r.js 1,550.37 kB │ gzip: 393.40 kB +(!) Some chunks are larger than 500 kB after minification… ← pre-existing +[INEFFECTIVE_DYNAMIC_IMPORT] src/lib/realtime.ts … ← pre-existing +✓ built in 561ms +``` +Chống bẫy "xanh vì cache": chạy thêm **`npx tsc -b --force`** (full type-check, bỏ tsbuildinfo) → **exit 0, 0 lỗi**. +3 warning (thứ tự `@import` font, chunk >500 kB, INEFFECTIVE_DYNAMIC_IMPORT) đều **có sẵn từ trước**, không sinh từ file này. + +## 6 — 🔴 NO-SCREENSHOT (khai theo lệnh) + verify tĩnh thay thế + +**KHAI: `NO-SCREENSHOT`.** Môi trường spawn này không có dev-server/Playwright ⇒ **KHÔNG chạy được FD2 visual loop, KHÔNG có ảnh, KHÔNG bịa ảnh.** Thay bằng 2 lớp kiểm tra chạy được: + +**(a) Soi CSS đã build** — Tailwind v4 chỉ sinh class nó quét thấy; class không sinh = "im lặng mất màu" (bẫy cũ của repo với stop `-800` của teal/violet). Grep file `dist/assets/*.css` vừa build: + +| class | có trong dist? | +|---|---| +| `text-brand-800` / `text-brand-800!` | ✓ (kèm `!important`) | +| `text-teal-700` · `ring-teal-100` · `ring-brand-200` | ✓ | +| `text-accent-600!` | ✓ `color:var(--color-accent-600)!important` | +| `text-slate-600!` | ✓ `!important` | +| `bg-amberx-50` · `text-amberx-700` · `border-amberx-500` | ✓ | +| `bg-brand-50` · `bg-teal-50` · `bg-violet-50` · `text-violet-700` | ✓ | +| `border-dashed` · `ring-inset` | ✓ | +| lưới pipeline `lg:grid-cols-[1fr_auto_1fr_auto_1fr_auto_1fr]` | ✓ → `grid-template-columns:1fr auto 1fr auto 1fr auto 1fr` (1 hit, trong media query lg) | + +⇒ **Chỉ dùng stop màu CÓ THẬT** trong `@theme` (teal/violet/amberx/greenx: 50/100/500/600/700 — KHÔNG -800) và mọi màu chữ đè lên rule unlayered (`h1..h4`, `.stat-value`) đều có dấu `!`, đã xác nhận `!important` nằm trong CSS. + +**(b) Verify icon trước khi build** — `node -e` liệt 18 export `lucide-react` (Workflow/ClipboardCheck/FileSignature/Stamp/Hourglass/ChevronRight/ChevronDown/RefreshCw/ArrowRight…) → tất cả `object`, không dính bẫy alias. + +## 7 — Tự-phê tĩnh theo rubric (FD4) — thẳng, không "trông ổn" + +| Tiêu chí | Verdict | Căn cứ / điểm còn nợ | +|---|---|---| +| **Hierarchy** | PASS | 1 focal point = dải pipeline (khối duy nhất có viền + shadow + nền trắng nổi trên nền `#f8fafc`); KPI cá nhân xuống hạng 2; list xuống hạng 3. LIVE ⟂ SOON phân biệt bằng 3 tín hiệu cùng chiều (nền trắng vs xám, rail màu vs nét đứt, có số vs badge). | +| **Spacing/rhythm** | PASS | Toàn bộ giá trị nằm trên thang Tailwind (p-3.5/p-4, mt-1/2/3, gap-1.5/2/3/4) — 0 magic number. **Vá lỗi cũ:** 2 section list trước đây dính sát nhau (section thứ 2 không có margin) → nay `grid gap-4`. | +| **Typography** | PASS | Không thêm size lạ: 10px badge / 11px nhãn-phụ / 13-14px thân / 15px tiêu-đề-GĐ / `text-3xl` KPI. Nhãn có `leading-tight` để tiếng Việt 2 dòng không dính. | +| **Color/contrast** | PASS (1 nợ ghi rõ) | brand-600 `#1F7DC1` là accent chính, KHÔNG default-blue. Đo: `text-slate-600` 7.0:1 · `text-slate-500` 4.8:1 · `text-brand-700` 5.6:1 · `text-brand-800` ~8:1 · `text-teal-700` ~5.6:1 · `text-accent-600` ~7.4:1 — đều ≥ AA. **Sửa 2 chỗ hụt AA:** `hint` của StatCard `slate-400`(2.5:1) → `slate-500`; dấu "—" lỗi `slate-400` → `slate-500`. **Nợ (KHÔNG tự sửa):** class dùng chung `.label-eyebrow` (brand-600 trên trắng ≈ **4.42:1**, hụt 4.5) — đây là token toàn app ở `index.css`, sửa = ảnh hưởng nhiều trang + đụng file agent khác ⇒ trang này **né**, dùng `text-brand-700` cục bộ. Đề xuất lead cân nhắc đổi `.label-eyebrow` → brand-700 ở một đợt riêng. | +| **Polish/states** | PASS | Mỗi ô số có đủ 3 trạng thái: loading = thanh `animate-pulse`, error = "—" + `title`, có số = số thật. Có banner lỗi cấp trang + **Thử lại chỉ refetch query nào fail**. 2 list: skeleton / **error + Thử lại (mới)** / empty / data. Hover + `focus-visible:ring-2` trên MỌI phần bấm được. Motion: chỉ dùng `.card-accent` (đã có `prefers-reduced-motion` trong index.css) + `transition` màu — không thêm animation nào cần chặn. | +| **Responsive** | PASS (theo suy luận tĩnh, chưa soi mắt) | `` trong `` (thẻ GĐ là ``, phần bấm là button con) — HTML hợp lệ, không "nuốt" focus. Ring của row trong list dùng `ring-inset` vì `` có `overflow-hidden` (ring outset sẽ bị cắt). | +| **Anti-slop (FD3)** | PASS | 0 emoji trong UI (dùng lucide) · 0 gradient loè (chỉ giữ gradient brand nhạt sẵn có ở header list + rail) · GĐ chưa làm KHÔNG dùng đỏ/vàng cảnh báo (xám nét đứt = "kế hoạch", không phải "lỗi") · KHÔNG bịa số cho GĐ2/GĐ4. | + +**Điểm KHÔNG dám khai:** chưa có mắt người/ảnh nào nhìn trang này. Hai thứ tôi muốn soi khi có prod: (i) chiều cao dải pipeline ở tablet 768–1023px (4 thẻ xếp dọc ≈ 630px, có thể muốn thu gọn), (ii) khi `Đã quá hạn > 0` số đỏ trong thẻ teal có chỏi không. + +## 8 — Bàn giao / lưu ý cho lead + +- **KHÔNG commit** (đúng luật: lead commit, scope `FE-User`). **KHÔNG chạm BE**, **không thêm dependency**, **không sửa `index.css`** (file đang có agent khác đụng). +- 2 request thêm đúng như thiết kế: `['pipeline-pe-approved']` và `['pipeline-pe-inbox']` — queryKey mới, KHÔNG đụng cache của trang list (`['pe-list', …]`). +- Nếu sau này GĐ2/GĐ4 có module thật: chỉ cần truyền `tone` + đổi `` thành mấy `` — khung đã chừa sẵn, không phải đập lại. +- Khi deploy prod: đây là trang duy nhất cần soi mắt lại (route `/dashboard`, login user thường). Rig dev authed vẫn vướng gotcha #3 (API HTTPS :5443 ⟂ vite proxy HTTP) — verify bằng prod là đường ngắn nhất. + +