15 KiB
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 buildfe-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:
<PageHeader title={"Xin chào, " + fullName} description="Tổng quan công việc HĐ của bạn — click vào card để xem chi tiết." /> - 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 /contractsparams{page:1,pageSize:5}→Paged<ContractListItem>.['my-pe-recent']→GET /purchase-evaluationsparams{page:1,pageSize:5}→Paged<PeListItem>.
- Section hiện có: (a) "Của tôi" = grid 5
StatCard(local component,card-accent+icon-chip+stat-value, tone mapSTAT_TONE6 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ê)
Paged<T>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 BEPagedResult.cs:3-7(Total).pendingMeKHÔNG phải query-param củaGET /purchase-evaluations— nó là URL-param của trang list, và trang list map nó sang endpoint KHÁC:GET /purchase-evaluations/inboxtrả mảng phẳngPeListItem[](không paging) —PurchaseEvaluationsListPage.tsx:81-89+PurchaseEvaluationsController.cs:30-35. ⇒ Đếm "chờ tôi duyệt" =inbox.length, KHÔNG dùng đượcpageSize=1. ✅phaseTHÌ ĐÚNG là query-param BE (PurchaseEvaluationsController.cs:23PurchaseEvaluationPhase? 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ốngdescriptioncù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 ôautolà mũi tên nối (ChevronRightở lg,ChevronDownkhi dọc <lg),aria-hidden. Mobile = 1 cột dọc, mũi tên xuống. - LIVE (GĐ1 brand / GĐ3 teal): nền trắng +
.card-accentrail màu +.icon-chiptinted + 3 MiniStat bấm được. SOON (GĐ2/GĐ4):border-dashed border-slate-300 bg-slate-50+ badgeHourglass"Sắp triển khai" xám-trung tính (KHÔNG đỏ/vàng cảnh báo → không trông như lỗi) + 1 dòng mô tả owner đưa nguyên văn. - a11y: card LIVE là
<div>, phần bấm là<button>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ụ đổitext-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àogrid gap-4 xl:grid-cols-2. - States:
StatNum= loading → thanhanimate-pulse; error →—+titlegiả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):
PageHeader— title "Tổng quan tất cả luồng duyệt", description có lời chào + câu định vị.- HERO pipeline 4 GĐ (
<section>bo góc, viền, shadow) — header có iconWorkflow+ 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 4StageCard+ 3StageArrow. - "Việc của tôi" — 5
StatCardcũ 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). - 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) | <lg = 1 cột dọc, mũi tên ChevronDown chen giữa (đúng yêu cầu owner). lg = lưới 7 cột 1fr auto ×3. Ở 1280px (trừ sidebar ~240 + p-6) mỗi thẻ ≈ 216px: hàng badge+chip ≈ 122px → vừa; nhãn "Chờ tôi duyệt" 11px ≈ 72px + số ≈ 20px trong 190px → vừa 1 dòng, và nếu xuống dòng vẫn an toàn nhờ leading-tight + min-w-0. h-full + items-stretch giữ 4 thẻ bằng chiều cao dù tiêu đề GĐ2 dài 2 dòng. Không có overflow-x (không đặt min-width cứng). |
| Detail | PASS | tabular-nums cho mọi số → cột phải thẳng hàng. Không lồng <button> trong <button> (thẻ GĐ là <div>, 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ì <section> 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ửaindex.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<StageSoon>thành mấy<StageStatRow>— 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.