[CLAUDE] FE: Văn phòng số foundation — shared PageHeader/KpiCard/WidgetCard + Dashboard landing (PURO · CSS Hồ sơ NS) + sync fe-admin index.css + menu Off_Dashboard
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m41s

- 3 shared component PageHeader/KpiCard/WidgetCard ×2 app SHA256-identical; tái dùng token Hồ sơ NS (.app-gradient-brand/.card-accent/.icon-chip/.stat-value/.label-eyebrow + accent palette teal/violet/amberx/greenx); gotcha #66 text-white! trên gradient header.
- OfficeDashboardPage 2-cột widget kiểu PURO HomePage: Đề xuất/Đơn từ/Ticket/Phòng họp hôm nay + "Công việc của tôi" + Thao tác nhanh. Reuse query endpoint sẵn có (shared TanStack cache, KHÔNG BE/API mới), đếm client-side, loading/error/empty mỗi widget.
- Sync fe-admin/src/index.css ← fe-user (đóng drift S66-S68: heading 600→700, ink #0f172a→#0b1220, label-eyebrow slate→brand-600 + rule gotcha #66). Nay 2 app đồng bộ.
- Menu key Off_Dashboard (MenuKeys.cs const + All[] + DbInitializer seed Order 0 dưới Off) — no migration, idempotent. GIỮ ẨN non-Admin (RevokeTemporarilyHiddenModules StartsWith Off). Chưa golive.
- Wire 4-place ×2 app: App.tsx route /office/dashboard + menuKeys.ts + Layout staticMap.
- Fix KpiCard activeBorder -300 → -500 (accent palette chỉ 50/100/500/600/700 — chống "vỡ màu im lặng" Tailwind v4: border-teal-300 rơi default Tailwind, border-amberx-300 drop hẳn).
- Build PASS x2 (fe-user index-DrxDysO7 / fe-admin index-TbkadgKd) + dotnet slnx 0/0. reviewer PASS 0 blocker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-17 09:24:17 +07:00
parent 764fe7024b
commit a8bbdaeeea
21 changed files with 1821 additions and 23 deletions

View File

@ -28,6 +28,12 @@
- Fallback khi stack chưa chạy: static component preview / screenshot `/login`**KHÔNG bỏ soi** (FD2 cấm ship-unseen).
## Component inventory (built/verified — chống reinvent)
- **3 shared UI cho Văn phòng số / E-Office (S69, 2026-06-17) — PURO-style + HRM visual language. ALL build-PASS 0 TS:**
- `fe-user/src/components/ui/PageHeader.tsx`**richer page header** (eyebrow/title/subtitle/icon/accent/actions/breadcrumb). ⚠️ KHÁC `@/components/PageHeader` (constrained {title,description,actions}) — module path `@/components/ui/PageHeader` riêng, KHÔNG collision. icon-chip accent-tinted + title `text-xl font-bold` accent-head. Local `ACCENT` map {chipBg,chipFg,head} (brand=text-brand-800, rest -700). Title trên nền SÁNG → KHÔNG cần `text-white!`.
- `fe-user/src/components/ui/KpiCard.tsx`**clickable stat card = FILTER chip** (PURO: row KpiCards thay tabs). icon-chip + `.stat-value text-2xl` accent + `.label-eyebrow`. active = `bg-{x}-50` + `border-{x}-300` + `ring-{x}-500`. a11y FULL: `onClick``role=button`+`tabIndex=0`+Enter/Space (`e.key===' '`)+`aria-pressed=active`+focus-visible ring; no-onClick = inert div. hover `-translate-y-0.5` + `motion-reduce:transform-none`.
- `fe-user/src/components/ui/WidgetCard.tsx`**dashboard widget container** (PURO HomePage). Wrap `.card-accent` (rail via inline `--accent`). Header: brand=`.app-gradient-brand text-white` · non-brand=tinted `bg-{x}-50` bar. **gotcha 66 APPLIED:** gradient `<h3>` title = **`text-white!`** (bang) — plain text-white thua unlayered h1-h4 rule. Props: title/icon/accent/stats[]/onExpand/onRefresh/children/empty/emptyText. `stats[]` = clickable StatChip row (mỗi chip a11y button khi có onClick). empty → muted icon-chip + emptyText. Header IconButton (RefreshCw/Maximize2) contrast-adapt gradient↔tinted, aria-label.
- **3 đều:** NAMED export · `import type` (verbatimModuleSyntax) · `cn` from `@/lib/cn` · lucide-react · accent palettes stop 50/100/500/600/700 ONLY (no -800) → head/value -700 (brand -800 OK) · icon-chip recolor `['--chip-bg' as string]`/`['--chip-fg' as string]` inline (pattern từ HRM Card). NO new npm dep. Build `tsc -b && vite` PASS 0 TS, 24.87s (warning @import-order + chunk-size = pre-existing). fe-admin NOT mirrored (separate pass nếu cần). FD2 authed-screenshot SKIP (components chưa wired vào page nào — pure library; visual verify khi page tiêu thụ chúng).
- `fe-user/src/pages/office/OfficeDashboardPage.tsx`**E-Office landing dashboard (S69, 2026-06-17) — PURO HomePage, COMPOSES the 3 shared ui widgets. build-PASS 0 TS, 434ms.** Layout: `ui/PageHeader` (eyebrow "Văn phòng số" / title "Bảng điều khiển" / icon LayoutDashboard / accent brand) on top → `grid grid-cols-1 lg:grid-cols-3 gap-5`: LEFT `lg:col-span-2` = stack of 4 `WidgetCard` (Đề xuất brand / Đơn từ teal / Ticket CNTT violet / Phòng họp hôm nay amberx — each body = row of 3 `KpiCard` filter-chips except Phòng họp = 1 KpiCard + next-4 booking peek list) · RIGHT `lg:col-span-1` = "Công việc của tôi" WidgetCard (brand-50 hero count `myTodo` + 3 clickable `MetricRow`) + "Thao tác nhanh" `.card-accent` panel (3 Button primary/secondary/outline). Stacks 1-col <lg. **Hooks REUSED (verbatim queryKey+endpoint = shared TanStack cache, NO new API, NO new BE):** proposals `['proposals',{…}]``GET /proposals` (+ separate `inboxOnly:true` query for "Cần duyệt" = needs-my-action signal) · đơn-từ `['/leave-requests'|'/ot-requests'|'/travel-requests',{page:1}]`those 3 endpoints, merged+`countByStatus` client-side · tickets `['it-tickets']``GET /it-tickets` · meetings `['meeting-bookings',{…}]``GET /meeting-bookings` windowed to today (local-midnight→+1d ISO). **Counts ALL client-side** (`countByStatus` reducer; status enums from `@/types/proposal` + `@/types/workflowApps`). **States graceful per-widget:** isError`WidgetError` (retry btn, accent-500 chip) · isLoading`WidgetSkeleton` (3 pulse bars, `motion-reduce:animate-none`) · emptyWidgetCard `empty` prop. NEVER blocks page. **Routing question:** only `/proposals/new` exists as real create route quick-actions "Tạo đề xuất"→`/proposals/new`, "Tạo đơn"→`/workflow-apps/leave` (đơn-từ landing, NO standalone /new), "Tạo ticket"→`/it-tickets` (ticket landing) every link hits an EXISTING route (no `*` fallback dead-link). onExpand per widget its real route. a11y: KpiCard/MetricRow clickable = role=button+Enter/Space+focus-ring; reduced-motion honored. Routing/menu wiring NOT done (next agent's job page NOT imported in App.tsx yet). fe-admin NOT mirrored. FD2 authed-screenshot SKIP (ProtectedRoute + gotcha #3 rig blocks authed; visual verify via deploy).
- `fe-user/src/pages/LoginPage.tsx` login (public, no auth). Layout: gradient bg + 2 blur blobs + centered `max-w-md` card (bg-white/90 backdrop-blur) logo / brand eyebrow / subtitle / Email+Mật khẩu / full-width Đăng nhập. Uses ui/{Button,Input,Label}. Solid baseline; nearly identical in fe-admin (mirror candidate).
- `fe-user/src/pages/hrm/EmployeesListPage.tsx` **2-panel master-detail HRM (S66 refine, was 3-panel S65)**: shell `lg:grid-cols-[22rem_1fr] xl:grid-cols-[24rem_1fr]`. **CỘT TRÁI** = `<div flex flex-col gap-4>` chứa Org-tree (TRÊN, `lg:max-h-[44%] lg:shrink-0`, cuộn riêng) + List+filter (DƯỚI, `flex-1`, cuộn riêng). **CỘT PHẢI** = Detail 5-tab (flex-1, rộng). <lg 1-col (treelistdetail) + mobile tree-toggle `treeOpenMobile` (tree `hidden``flex`). Org tree = recursive `TreeNode` consume `GET /api/departments/tree` (DepartmentTreeNode {id,code,name,parentId,directEmployeeCount,totalEmployeeCount,children}); gốc "SOLUTION COMPANY" (`companyOpen`) `pickDept(null)`=all; `CountBadge` (totalEmployeeCount, active=brand-600 fill) `deptId` URL param. Detail = avatar header (`.app-gradient-brand` + initials-in-rounded-2xl) + 5-tab nav (Tổng quan/Thân nhân/Trình độ/Kinh nghiệm/Hợp đồng) count pills + brand underline. **Accent system (S66 việc 2+3):** `type Accent='brand'|'teal'|'violet'|'amberx'|'greenx'` + `ACCENT` map (chipBg/chipFg/head/rail/labelText). `Card` nhận `accent` prop `.icon-chip` tinted (`--chip-bg`/`--chip-fg` inline) + heading `text-{x}-700` + left rail pseudo `before:content-[''] before:w-1 before:bg-{x}-500` (clip qua overflow-hidden). `Field` label = `text-{x}-700` uppercase semibold (was slate-400), value = `font-medium text-slate-900` (was slate-800). OverviewTab: 1 accent/card (Thông tin chung=brand, Sức khoẻ/Lương=greenx, Liên hệ/Ngân hàng=teal, Giấy tờ/Đoàn thể=violet, Công việc=amberx). Tab-body sections: family=violet, đào tạo=teal, kỹ năng=greenx, công tác=amberx, hợp đồng=brand. **accent palettes chỉ có stop 50/100/500/600/700 — KHÔNG -800** head dùng -700 (else Tailwind v4 silent no-class). Reusable: `Avatar`(hash 5 gradients + dim), `CountBadge`, `Card`(+accent), `Field`(+accent/mono/icon/full). **ALL 5 satellite CRUD + 15 satellite api endpoint (+ top-level del + 3 reads) + 3 query keys preserved verbatim** (grep+tsc verified, layout/style-only). fe-admin NOT mirrored (separate pass).
@ -37,6 +43,7 @@
- Minor noted (NOT fixed, out of bounded scope): 2 `blur-3xl` blobs barely visible at 1440 = render cost ~0 payoff; eyebrow `tracking-[0.2em]` heavy. Candidates if login redesign requested.
## Activity log
- **S69 (2026-06-17) OfficeDashboardPage.tsx fe-user E-Office landing, PURO HomePage, COMPOSES 3 shared widgets:** built `pages/office/OfficeDashboardPage.tsx` (~400 LOC) over EXISTING data hooks of 4 modules. Read-first: 3 ui widgets (exact prop sigs) + 4 source pages (ProposalsListPage/WorkflowAppsListPage/ItTicketsPage/MeetingCalendarPage) to harvest queryKey+endpoint + types + App.tsx routes. **Reused hooks verbatim** shared TanStack cache, ZERO new API/BE: `GET /proposals` (+inboxOnly query for needs-my-action) · `/leave|ot|travel-requests` (merged, countByStatus client-side) · `/it-tickets` · `/meeting-bookings` (today window). Layout = PageHeader(brand) + `lg:grid-cols-3` [LEFT col-span-2 = 4 WidgetCards w/ KpiCard filter-chip bodies | RIGHT col-span-1 = "Công việc của tôi" hero+MetricRows + "Thao tác nhanh" 3 buttons], 1-col <lg. **Routing insight (verified App.tsx):** đơn-từ + ticket have NO standalone `/new` route (creation in-page) quick-actions point at landings `/workflow-apps/leave` + `/it-tickets` (only `/proposals/new` is a real create route) so no link hits the `*` "chưa build" fallback. Per-widget graceful states: WidgetError(retry)/WidgetSkeleton(pulse, motion-reduce)/empty never blocks. a11y full (role=button+Enter/Space+focus-ring on clickables, reduced-motion). `npm run build` (tsc -b strict + vite v8) **PASS 0 TS err, 434ms** (only pre-existing @import-order + chunk-size + INEFFECTIVE_DYNAMIC_IMPORT warnings none from new file). Routing/menu NOT wired (next agent; page not yet in App.tsx). fe-admin NOT mirrored. FD2 authed-screenshot SKIP (ProtectedRoute + rig gotcha #3 verify via deploy). Tag [s69, office-dashboard, compose-3-widgets, reuse-hooks-shared-cache, no-new-be, routing-existing-only, build-pass].
- **S66 (2026-06-16) HRM Hồ Nhân sự fe-user REFINE từ eoffice LIVE (3 việc) layout 3-cột2-cột + màu detail:** anh góp ý sau khi xem prod. **Việc 1 (layout):** 3-cột-ngang `[tree 244 | list 352 | detail 1fr]` **2-cột** `lg:grid-cols-[22rem_1fr] xl:[24rem_1fr]`: CỘT TRÁI = `<div flex flex-col gap-4>` ôm tree (TRÊN, `lg:max-h-[44%] lg:shrink-0`, overflow-auto) + list+filter (DƯỚI, `flex-1`, overflow-auto) mỗi panel cuộn độc lập; CỘT PHẢI = detail (flex-1, rộng hơn nhiều, đỡ chật). <lg vẫn 1-col treelistdetail + giữ nguyên `treeOpenMobile` toggle. **Việc 2+3 (màu detail):** thêm `ACCENT` map 5 tone (brand/teal/violet/amberx/greenx) `Card` prop `accent` icon-chip nền nhạt (`--chip-bg/--chip-fg`) + heading `text-{x}-700` + rail trái pseudo-element; `Field` label uppercase `text-{x}-700` semibold (was slate-400 đơn điệu) + value `font-medium text-slate-900`; mỗi card/section gán 1 accent màu nhưng tinh tế, brand #1F7DC1 + Be Vietnam Pro + avatar gradient brand GIỮ. **Strategy chống truncation #53 = ONE atomic `Write` cả file** (1556 LOC) emit change-list + build status SỚM. **2 self-caught bug TRƯỚC build:** (1) `text-{teal,violet,greenx}-800` accent palettes KHÔNG stop -800 (chỉ 50/100/500/600/700) Tailwind v4 silent no-class đổi head sang -700 (all AA on white); (2) rail pseudo thiếu `before:content-['']` ::before không render box thêm. `npm run build` (tsc -b strict + vite v8) **PASS 0 TS err, 495ms** (warning @import-order + chunk-size = pre-existing, không phải mình). **5 satellite CRUD + 15 satellite api endpoint + top-level del + 3 reads + 3 query keys (employees-list/employee-detail/departments-tree-hrm) + cây SOLUTION COMPANY + 5 tab + search/filter preserved VERBATIM** (grep: 15 satellite api.post/put/delete + 3 queryKey + 5 form fns; tsc type-checks mọi payload shape = wiring bất biến). FD2 authed-screenshot SKIPPED per task instruction + gotcha #3 (rig chặn authed ProtectedRoute; anh xem qua deploy) structural verify thay thế. fe-admin + BE NOT touched, no commit (em main commits). Tag [s66, hrm-2col-refine, eoffice-ref, accent-system, atomic-write-antitrunc, crud-preserved, build-pass, tailwind-v4-stop-gotcha].
- **S65 (2026-06-16) HRM Hồ Nhân sự fe-user 3-panel master-detail NamGroup-ref:** RESTRUCTURE `EmployeesListPage.tsx` (1201→~1140 LOC) 6 `<details>` [Org tree | List | Detail 5-tab]. **Strategy chống truncation #53 = ONE atomic `Write` (cả file)** thay piecemeal Edit (atomic Write either fully-lands or errors, KHÔNG half-break) emit change-list TRƯỚC build DID BOTH Part A (avatar header+5 tab+sectiontab redistribution) + Part B (org tree panel) trong 1 pass, không phải defer B. Org tree consume `/departments/tree` verified BE-side (DepartmentFeatures.cs DepartmentTreeNodeDto, controller `[HttpGet("tree")]`, class-Authorize only). Foundation màu mới DÙNG: `.app-gradient-brand` header / `.icon-chip` / accent palette teal/violet/amberx/greenx (avatar tones) brand #1F7DC1 + Be Vietnam Pro KEPT. **5 satellite CRUD + 16 api endpoint + query keys preserved VERBATIM** (grep-verified: 16 api.post/put/delete identical payload shape, 5 form fns intact). `npm run build` (tsc -b strict + vite) **PASS 0 TS err, 6.13s**. 1 self-caught bug: typo garbage token `网络Placeholder` trong lucide import (mojibake autocomplete) removed, all 21 icons valid (node-checked). FD2 authed-screenshot SKIPPED per explicit task instruction + gotcha #3 (rig blocks authed; anh xem qua deploy) did static structural verify instead (grep endpoint/key preservation). fe-admin NOT touched (mirror = separate pass), no commit. Tag [s65, hrm-3panel, namgroup-ref, atomic-write-antitrunc, crud-preserved, build-pass].
- **S58 (2026-06-11) fe-user redesign theo UI/UX guide AI_INFRA canonical KEEP brand [em main proxy truncated #53 giữa FD2 screenshot, 2nd consecutive]:** Mirror design-system fe-admin S55 14 file fe-user (index.css heading-ladder+.label-eyebrow / 6 ui primitives Button gần SHA-identical fe-admin chỉ khác comment / 6 shell DataTable+RowActions-additive·Layout-brand-left-rail·TopBar·PageHeader·PhaseBadge-ring·EmptyState / LoginPage polish). Rubric mới = guide 13 mục `D:\Dropbox\CONG_VIEC\AI_INFRA\docs\reference\ui-ux-design-guide.md` (density 14px/h32-34/radius-8/thead-sticky/action-luôn-hiện/no-font-bold). BRAND KEPT: #1F7DC1 + Be Vietnam Pro + slate (guide cho plug hue riêng). Chết NGAY TRƯỚC with_server.py screenshot /login em main recover: build ×2 PASS 0 TS + diff-review key-stability từng file + ship `e959f72`; authed visual qua deploy prod (rig-gotcha #3 standing). LESSON: 2 lần liên tiếp truncate CÙNG điểm (sau khi sửa xong, lúc bắt đầu FD2 rig) lần sau EMIT file-list verdict TRƯỚC khi vào screenshot loop. Tag [s58, fe-user-redesign, guide-aiinfra, keep-brand, truncated-53-proxy].