[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
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:
@ -70,6 +70,8 @@ Bearer từ `POST api.solutions.com.vn/api/auth/login` → status matrix expecte
|
||||
|
||||
## 📅 Recent activity (FIFO — older → archive/git)
|
||||
|
||||
- **2026-06-17 (S69 recon — Office-module inventory + Hồ sơ-NS CSS-contract, on-disk):** ⭐ **PART A Office:** 21 `Off_*` keys (`MenuKeys.cs:99-121`): root `Off` + DanhBa(card-grid), `Off_PhongHop`{View=cal/Manage=room-CRUD-admin/Book}, `Off_DeXuat`{List/Create/Inbox=Proposal-V2}, `Off_DonTu`{Leave/Ot/Travel}, `Off_DatXe`, `Off_ItTicket`, `Off_ChamCong`(re-parent→Personal S57), `Off_AttendanceReport`(admin). 10 office pages `{fe-admin,fe-user}/src/pages/office/` ALL SHA256-MIRROR except **MyAttendancePage DIFFERS** + AttendanceReportPage ADMIN-ONLY. Routes `App.tsx` user:70-80/admin:88-100; staticMap `Layout.tsx:87-103` (workflow-apps :kind `/workflow-apps/{leave,ot,travel,vehicle}`); menuKeys.ts:45-63. **HIDE-FLAG** `RevokeTemporarilyHiddenModulesAsync` (`DbInitializer.cs:2157-2190` called :2040 LAST) wipes CRUD on `MenuKey.StartsWith("Off")||"Hrm"||==Personal` non-Admin, idempotent. **Golive flip:** remove :2040 call (+ re-add prefix InReviewScope grant). Office already S55-shell polished NOT bare. **PART B Hồ sơ-NS CSS:** layout=3-col flex (`EmployeesListPage.tsx` SHA256-identical x2, 1597 LOC): cây-tổ-chức TRÁI(:178) + NV-list MID(:244) + detail PHẢI = avatar-header `app-gradient-brand`(:643)+`text-white!`(:653)+initials chip bg-white/15 → 5-TAB(:507 Tổng quan/Thân nhân/Trình độ/Kinh nghiệm/Hợp đồng) → `Card`(:1526 left-rail+icon-chip) w/ `Field`(:1572 label uppercase accent-tint + value `font-medium text-brand-800`, empty=`text-slate-300 —`). `ACCENT` map :497-503 Record<5,{chipBg/chipFg/head/rail/labelText}> accent∈{brand,teal,violet,amberx,greenx}, palettes stops 50/100/500/600/700 only no-800→headings -700 (brand -800 OK). Tokens `index.css`: brand-600=#1f7dc1 brand-800=#175685 @theme:5-55, font Be-Vietnam-Pro:53; classes `.app-gradient-brand`(:105 120deg b600→700→800),`.card-accent`(:112),`.icon-chip`(:128 --chip-bg/--chip-fg),`.stat-value`(:140),`.label-eyebrow`(:89). ⚠️ **GOTCHA #66 = `index.css:79-83` `h1,h2,h3,h4{color:#0b1220;font-weight:700}` OUTSIDE @layer** → TW-v4 unlayered wins → heading-tag inside gradient MUST `text-white!`. ⚠️ **CROSS-APP DRIFT:** fe-user=S68 (h1-4 #0b1220/700, label-eyebrow brand-600, 175L); **fe-admin STILL OLD** (h1-4 #0f172a/600, label-eyebrow #64748b slate, 167L) — fe-admin NOT synced S66-68 heading bump → mirror Office to fe-admin needs index.css sync. Tag `[s69, office-inventory, hoso-css-contract, gotcha66, fe-admin-css-drift]`.
|
||||
|
||||
- **[→ git pre-S60]** S60 recon#2 V2-engine-map (ApprovalWorkflow.cs Step/Level Order 1-based per-step; OR-of-N=N rows cùng Order service GroupBy:475; ApproveV2Async:446-634 guard+UPSERT+advance; notify DRAFTER-only:748; skipToFinal F2:561-602 = precedent advance-không-ghi-opinion) · S60 PE Section-3 submit-guard (submit path POST/pe/{id}/transitions→TransitionAsync:38 ROLE-only guard NO data-check; Section-3 mục a/b/c/d map — SUPERSEDED bởi S65ter post-Mig50 Budget-drop; test mirror PurchaseEvaluationWorkflowServiceGuardTests). Full text git.
|
||||
|
||||
- **2026-06-11 (S59 recon — prod test-data wipe + PE tree Hạng mục, prod+on-disk):** ⭐ **Prod:** PE=10 active (1 Nháp + 1 DaDuyet(7) + 8 ChoDuyet(10), MaPhieu A/031-040, ALL WorkItemId NULL) + child 20/10/20/28/138/18/18 (Sup/Det/Quote/Appr/Chg/Att/LvlOp); Contracts=7 ALL `[DEMO]` 05-08 pin V1 (AwId NULL) + Appr15 + details15; Budgets/WorkflowApps/Proposals/Attendances/Meetings ALL 0; Notifications 64. Seq: PE/2026/A=40 B=1; CT=7 demo prefix LastSeq=1. **FK:** PE child CASCADE trừ `Quotes→PE NO_ACTION` (multi-path; Plan R S23 proved single `DELETE FROM PurchaseEvaluations` OK — NO_ACTION check end-of-statement sau cascade Details→Quotes). Contract child ALL CASCADE. PE.ApprovalWorkflowId Restrict → wipe PE trước khi xóa AW QT-DN-V2-001 v1 (inactive, còn 1 PE pin). AW V2=8: 7 ghim KEEP. **Uploads orphan:** purchase-evaluations/ 19 folder vs 10 PE → ~10 orphan từ S23 (file không xóa); contracts/ 1. **Demo gate OK:** SeedDemoContracts/PE TRONG `DemoSeed:Disabled` (DbInitializer:80,131-132) → wipe không resurrect. **Surprise:** Users 55 total / 21 active — 20 user THẬT batch 2026-06-11 06:01 (S58 seed fix ăn; thanh.lethanh NOW EXISTS — stale S57bis mem; chuong.phan typo-domain VẪN active song song twin). **FE tree:** `pe/PurchaseEvaluationsListPage.tsx:138-179` Project>Year(createdAt :150)>Supplier; SHA256 identical 2 app; PeListItem ĐÃ có workItemId/Name (types :116-118, BE Features :514/570/644) → đổi tree FE-only. Tag `[s59-recon, prod-wipe, pe-tree-workitem]`.
|
||||
@ -92,6 +94,7 @@ Bearer từ `POST api.solutions.com.vn/api/auth/login` → status matrix expecte
|
||||
|
||||
- **2026-06-16 (S65bis recon — Employee profile master-detail vs NamGroup, on-disk):** ⭐ **STALE-PREMISE CORRECTION:** fe-user `/employees` KHÔNG list-only — `hrm/EmployeesListPage.tsx` (1201 LOC) ĐÃ master-detail 2-panel (filter sidebar :117 + list table :197 + inline detail :234) với **6 collapsible section** (`<details>` :1157, KHÔNG tab) + 5 satellite inline CRUD (WorkHistory/Education/FamilyRelation/Skill/Document, `setEditing{X}Id`+`adding{X}` mutex pattern 12-ter S35). **fe-admin == fe-user `diff -q` IDENTICAL** (SHA256 same). **Entity gần đủ screenshot:** `Domain/Hrm/EmployeeProfile.cs` (137 LOC) CÓ: DOB/Gender/Ethnicity/Religion/Nationality/Height/Weight(:98-99)/IdCard(số+ngàycấp+nơicấp :52-54)/permanent+temporary addr/phone/personalEmail/code/hireDate/qualification/salary(Base+Total)/bank/4×leave-days. **THIẾU vs screenshot:** (a) BloodType CÓ nhưng "sức khỏe loại" (health-grade A/B/C) KHÔNG; (b) thâm niên = DERIVED từ HireDate (no column); (c) chức danh = `User.Position`/`PositionLevel` (Identity, KHÔNG ở EmployeeProfile) — list/detail JOIN Users (`EmployeeFeatures.cs:467`); (d) "lương BHXH/phụ cấp" tách riêng KHÔNG có (chỉ Base+Total); đơn vị=DepartmentName JOIN. **5 satellite entity + 15 endpoint FULL** (`EmployeesController.cs:75-233` 5 region×Create/Update/Delete; GET detail Include cả 5 :455-459). Skill polymorphic gộp 3 NamGroup table (Computer/Language/Other Kind :69). **GAP THẬT:** (1) **NO org-tree** — `Department.cs` FLAT (Code/Name/ManagerUserId/Note, KHÔNG ParentId), `DepartmentsController` chỉ GET list+byId (NO /tree), KHÔNG endpoint count-per-dept → cây trái + badge phải build CLIENT-side group-by departmentId từ list; (2) **5-tab layout** screenshot = 6-section `<details>` hiện tại (re-skin UI, data đủ); (3) "Hợp đồng lao động" tab = chỉ có `EmployeeDocument` type=LaborContract(5), KHÔNG entity HĐLĐ riêng (3 HĐLĐ table DEFER Plan H2 per `EmployeeProfile.cs:10`). **NamGroup source:** `D:\...\NAMGROUP\SOURCECODE_CÔNG_TY\` find .tsx/.razor = 0 hit (KHÔNG phải React/archived) — RAG `proj_namgroup_main` 0 component; tham khảo layout = screenshot anh gửi, KHÔNG có code mirror trực tiếp. ⇒ **Wire-lại-là-xong:** data + API + satellite CRUD 100% sẵn. **Build mới:** Department.ParentId migration + /tree + count endpoint (nếu muốn org-tree thật thay client-group). **Re-skin:** 6-section→5-tab + avatar header. **No new field bắt buộc** trừ health-grade nếu anh cần. Tag `[s65bis, employee-profile, master-detail-EXISTS, dept-flat-no-tree, stale-list-only-corrected]`.
|
||||
|
||||
- **2026-06-17 (S69 recon — NamGroup "PURO" digital-office layout, CROSS-REPO `D:\...\NAMGROUP\`):** ⭐ **PURO = UI design-language/skin (ref ERP demo.purocorp.vn), KHÔNG phải app riêng** — NamGroup mirror sidebar/typography của nó (comments `InternalLayout.tsx:33,74,109,200,332` "PURO exact spec"). Digital-office sống trong `namgroup.client/` (app NV; admin = config-only). **Shell = `components/layout/InternalLayout.tsx`** (724L): sidebar trái fixed h-screen + `<main flex-1 overflow-auto p-2.5..lg:p-4>` :609 chứa `<Outlet/>`. Sidebar = **`navTree` hardcoded array :76-122** (KHÔNG DB), flat 2-tier group→leaf, 4 group: **"Văn phòng số" :90-100 = 6 leaf** {Danh bạ `/danhba` · Phòng họp `/phonghop` · Đề xuất `/dexuat` · Đơn từ `/dontu` · Đặt xe công `/xecong` · Ticket CNTT `/ticket`}; + Nhân sự(3) + Cá nhân{Chấm công}(1) + Hệ thống(5). Routing = `App.tsx:81-140` flat `<Route element={InternalLayout}>` > `<RouteGuard>` (perm) > index=HomePage. **Landing `/` = `internal/HomePage.tsx`** (296L): grid 2-col (LEFT 2/3 stack 4 WidgetCard: Đề xuất/Nghỉ phép/Bình luận/Truyền-thông · RIGHT 1/3 Công-việc-của-tôi); WidgetCard = gradient-blue header + inline stat-chips + body/EmptyState (shared comp tại :219). **Layout pattern mỗi feature (KHÔNG dùng tab — dùng KpiCard-row + view-toggle):** (a) `<PageHeader>` shared (icon-badge accent + breadcrumb + actions slot, `components/shared/PageHeader.tsx`); (b) **KPI stat-cards clickable filter** (DeXuat :1643 6-card / Ticket :197 5-card — "PURO KPI cards" comment); (c) body = **list-table** (DeXuat/Ticket: `<table>` master + right detail panel grid-cols-3) HOẶC **list↔calendar ViewToggle** (DonTu :683 + XeCong + PhongHop: custom month-grid Sun-Sat, NO FullCalendar — comment :PhongHop "saves install friction"); DanhBa = dept-tree trái + card-grid phải. **Shared comp tái dùng:** PageHeader · DataTable · KpiCard · CrudToolbar · ActionLogList · DatePickerVN · MasterDataPage · DonutChart/MiniBarChart (`components/shared/` 16 file). **Top files mirror:** InternalLayout.tsx(shell+nav) · HomePage.tsx(dashboard) · PageHeader.tsx · DeXuatPage.tsx(1676 KPI+table+detail) · DonTuPage.tsx(1269 +DonTuCalendar.tsx toggle) · XeCongPage/PhongHopPage(month-grid) · TicketPage.tsx(595) · DanhBaPage.tsx(756 tree+grid) · ChamCongPage.tsx(809). ⚠️ Style/màu lấy chỗ khác per task — chỉ structure. SE đã có analog (fe-user `InternalLayout`/office pages) nhưng layout khác (deep-nested vs PURO flat). Tag `[s69, namgroup-puro-recon, digital-office-layout, hardcoded-navtree, kpicard-not-tab, cross-repo]`.
|
||||
- **2026-06-16 (S66 recon — mirror Hồ sơ NS fe-user→fe-admin, on-disk):** ⭐ **VERDICT (B): vá `fe-admin/src/index.css` TRƯỚC rồi cookie-cutter SẠCH.** Copy page thuần = VỠ MÀU. **fe-admin index.css = 86 dòng (chốt `7feb53e`, TRƯỚC redesign S58 `e959f72`/`c98030f`) → THIẾU:** 4 accent palette `teal/amberx/violet/greenx` (mỗi cái 50/100/500/600/700) + 3 utility `.icon-chip`/`.app-gradient-brand`/`.card-accent`/`.stat-value`. **CÓ SẴN:** `--color-brand-50..900` (hex y hệt fe-user, incl brand-800 #175685 :15), `.label-eyebrow` :54, font Be Vietnam Pro :22. ⚠️ heading-weight CẦN CHECK (fe-user S66 `h1-h4 font-weight:700 color:#0b1220`; fe-admin có thể còn 600). **Page fe-user phụ thuộc THẬT:** text-brand-800 ×9, teal/amberx/violet/greenx-50/500/700 ×4 mỗi, icon-chip ×3, app-gradient-brand ×1. **Wiring fe-admin ĐỦ SẴN (0 đụng):** route `/employees`+`/new` `App.tsx:82-83` · `EmployeeCreatePage.tsx` **identical** (diff rỗng) · menu `Hrm_HoSo` `menuKeys.ts:33`+staticMap `Layout.tsx:53`. **Lib parity ✅:** ui/{Input,Select,Textarea,Button} prop-sig **identical** (HTMLAttributes passthrough, content khác chỉ className=chủ ý non-breaking) · EmptyState/cn/api/apiError ✅ · `types/employee.ts` **identical** · Paged `types/master` ✅ · `DepartmentTreeNode` định nghĩa INLINE trong page (:65 mirror BE DepartmentTreeNodeDto, đi theo copy — không cần type file). **KHÔNG khác chủ ý admin/user** — mirror y hệt như S35 (`9616ae2`/`c3cd343` cùng commit 2 app); write Admin-gated ở BE controller. **Cấu trúc:** admin=1200 dòng (S35 cũ, 2-panel + 5 `<details>` + 5 satellite mutex); user=1602 dòng (redesign: cây "SOLUTION COMPANY" đệ quy + list cột trái dọc · detail phải 5 tab accent + avatar gradient). **Scope: 3 file** = index.css (chèn ~40 dòng token+class block từ fe-user :29-51+:100-160) + overwrite EmployeesListPage.tsx (import path KHÔNG chỉnh, đều `@/`) + (tùy chọn heading-700). KHÔNG đụng route/menu/types/primitives/CreatePage. Tag `[s66, mirror-employee-page, accent-token-missing-fe-admin, verdict-B, cookie-cutter-after-css]`.
|
||||
- **2026-06-16 (S65ter recon — Mục E "Link hồ sơ" phiếu PE, on-disk):** ⭐ Anh Kiệt: chèn mục E "Link hồ sơ" NGAY DƯỚI mục D "Bản so sánh". **Render 4 file** (SHA256-identical 2 app): `components/pe/PeDetailTabs.tsx` (detail+edit, 2770 LOC) + `PeWorkspaceCreateView.tsx` (create) × {fe-user,fe-admin}. KHÔNG tabs — 5 `<Section>` dọc, tiêu đề "1./2./3./4." + sub-item chữ thường "a./b./c./d." (label cột trái w-44). **Mục D ∈ Section "3. Đơn vị NCC/TP" = `ChonNccSection`** (`PeDetailTabs.tsx:1302-1375`): a.NCC(:1321) · b.Tổng hợp NS trình ký(:1324 `PeBudgetSummaryTable` — S61 thay Budget) · c.Giá chào thầu(:1326 auto) · **d.Bản so sánh(:1337-1348)** = `GeneralAttachmentsSection`(:2613) upload N FILE filter `supplierId===null` purpose=ComparisonTable(4). **INSERT E: `PeDetailTabs.tsx:1348`** (sau `</div>` mục D, trước paymentTerms :1350); mẫu = block :1337-1348. Create: `PeWorkspaceCreateView.tsx:277` (sau FormRow d). **BE: `PurchaseEvaluation.cs`(:1-72) KHÔNG có field URL** — DiaDiem/MoTa/PaymentTerms semantic khác. 1 link → `string? HoSoLink`(1000)+Mig AddColumn+cmd+DTO+validator; nhiều link → entity con `PurchaseEvaluationLink`+CREATE TABLE+CRUD (nặng). **Attachment KHÔNG reuse URL** — `PurchaseEvaluationAttachmentFeatures.cs:18-55` IFormFile thuần (FileSize>0+ContentType whitelist+IFileStorage). Mục D multi-row → E nên multi-row đối xứng. ⚠️ Surprise: comment :1314 nói "purpose=ComparisonTable hoặc supplier-row null" SAI — filter thực :1315-17 CHỈ `supplierId===null`. Tag `[s65ter, pe-section-e-link, attachment-file-only, insert-1348]`.
|
||||
- **2026-06-16 (S65 recon — public HRM module for all-role, on-disk):** ⭐ **Mục 6 CRITICAL (gotcha #44 family) RESOLVED-FAVORABLE:** `EmployeesController.cs:23-25` = class `[Authorize(Policy="Hrm_HoSo.Read")]` (NOT `Roles="Admin"`) + per-action `Hrm_HoSo.{Create/Update/Delete}` (:45/:54). Policy resolves THROUGH permission matrix (`MenuPermissionHandler.cs:40-52` baseQuery role×menuKey CanRead; Admin-bypass :27) → seed CanRead row = API ALSO unlocked, NO 403. `HrDashboardController.cs:8-11` = `[Authorize]` any-auth only (`/api/hr/dashboard`). GET list = `/api/employees` (:28). ⇒ **seed BE permission ĐỦ, không cần đụng controller.** **Menu keys dưới `Hrm` (prefix THẬT = `Hrm_`):** root `Hrm`="Nhân sự" parent=null Order=28 (`DbInitializer.cs:1805`); `Hrm_Dashboard`="Dashboard NS" parent=Hrm Order=1 (:1850); `Hrm_HoSo`="Hồ sơ Nhân sự" parent=Hrm Order=2 (:1806). Hrm_Config* (6 leaf: LeaveTypes/Holidays/Shifts/OtPolicies/Vehicles/Drivers) parent=**Master** Order=25 (S57 re-parent :1812 — KHÔNG dưới Hrm). **Revoke (Mục 2):** `RevokeTemporarilyHiddenModulesAsync` :2151 — match `StartsWith("Hrm")||StartsWith("Off")||==Personal` AND role!=Admin AND any-flag-true (:2162-67) → set 4 cờ CRUD=false. ⚠️ **THỨ TỰ: gọi CUỐI CÙNG `:2040` trong SeedAsync, SAU grant `:2033`** → revoke THẮNG mọi grant trước nó. Mở Hrm = phải (a) sửa revoke loại trừ Hrm_HoSo/Hrm_Dashboard HOẶC (b) thêm grant SAU :2040. **Pe pattern (Mục 3):** `SeedAllRolesReviewReadPermissionsAsync:2055` — `roleManager.Roles.ToListAsync():2090` loop ALL role × reviewKeys, upsert CanRead (+CanCreate cho Pe_*), additive idempotent (skip-existing non-Pe :2115). **Seed entity (Mục 4):** `Permission`(RoleId,MenuKey,4 CRUD); idempotent = app-level skip per (RoleId,MenuKey); **13 role** `AppRoles.All` (Admin/Drafter/DeptManager/ProjectManager/Procurement/CostControl/Finance/Accounting/Equipment/Director/AuthorizedSigner/HrAdmin/CatalogManager). `Hrm_HoSo`+`Hrm_Dashboard` ĐỀU ∈ `MenuKeys.All:153,160` (khác Pe_* leaf NOT in All). **FE (Mục 5):** menu-tree-API-driven via `GetMyMenuTreeQuery.cs` (`/api/menus/me`); Hrm NOT inherit-root (chỉ 4: Contracts/Workflows/Pe/PeWf :51-59) → MỖI leaf cần CanRead row riêng, NHƯNG root `Hrm` auto-hiện nếu child có access (`HasAccess:96` CanRead OR child). `Layout.tsx:145 USER_HIDDEN_KEYS`={System,Users,Roles,Permissions,Forms,Reports} — KHÔNG chứa Hrm → fe-user auto-render; `staticMap Hrm_HoSo→/employees :75`, `Hrm_Dashboard→/hr/dashboard :104`. NO PermissionGuard per-route fe-user. ⇒ **chỉ seed BE, FE tự hiện.** Tag `[s65-recon, public-hrm, policy-based-authz-not-roles, revoke-runs-last]`.
|
||||
|
||||
Reference in New Issue
Block a user