[CLAUDE] Domain+App+Infra+Api+FE-Admin+FE-User: S38 G-O4+G-O5+G-O6+G-P1+G-H3 SKELETON full-stack
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m53s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m53s
Phase 10.3-10.4 SKELETON 5 plan combo finish — Mig 39+40 + BE skeleton 7 module + FE 2 app SHA256 IDENTICAL + 11 menu key. UAT visible end-to-end. ⚠️ SKELETON Phase 1 trade-off rõ: - Status flat 5-state WorkflowAppStatus enum share Leave/OT/Travel/Vehicle - ApproveV2 workflow advance DEFER Phase 11 (Drafter Create OK, Approve flow chưa wire) - LevelOpinions per-module DEFER Phase 11 - LeaveBalance calc + Auto-assign + SLA timer DEFER Phase 11 - CodeGen atomic + MaDonTu/MaTicket gen DEFER Phase 11 - Vehicle catalog + Driver catalog DEFER Phase 11 (free text VehicleLicense) - ItTicketComments thread DEFER Phase 11 (free text Resolution field) Mig 39 (em main solo): 5 entity Workflow Apps schema - LeaveRequest (G-O4, FK LeaveType Hrm Mig 35, ApplicableType=5) - OtRequest (G-O4, FK OtPolicy optional, ApplicableType=6) - TravelRequest (G-O4, reuse ApplicableType=4 Proposal) - VehicleBooking (G-O5, free text vehicle, ApplicableType=7) - ItTicket (G-O6, NO workflow V2 — kanban status flow) Mig 40 (em main solo): Attendance entity (G-P1) - GPS lat/long check-in/out + Source enum Web/Mobile/Device - UNIQUE composite (UserId, AttendanceDate) - WorkHours computed simple diff (NO OtPolicy multiplier yet) BE CQRS (em main solo, single mega ~1100 LOC): - WorkflowAppsFeatures.cs 7 region (5 module Create+List + Attendance CheckIn/Out/GetMonth + HrDashboard) - 7 Controller: /api/leave-requests + /ot-requests + /travel-requests + /vehicle-bookings + /it-tickets + /attendances + /hr/dashboard - Class-level [Authorize] any authenticated - 13 endpoint total FE 2 app (em main solo fallback gotcha #53 risk): - types/workflowApps.ts × 2 SHA256 IDENTICAL 77470e182a15de88 (all DTOs + Status badge) - WorkflowAppsListPage.tsx × 2 IDENTICAL 58139d0301a60ddf — generic declarative KIND_CONFIG handles 4 module (Leave/OT/Travel/Vehicle) - ItTicketsPage.tsx × 2 IDENTICAL d3062de2f54c794c — kanban 5 status column - MyAttendancePage.tsx × 2 IDENTICAL 86da48ae147db012 — GPS check-in/out + tháng calendar - HrmDashboardPage.tsx × 2 IDENTICAL d9c6c12a5a8694f8 — 4 KPI card + gender ratio + status breakdown - Pattern 16-bis 9× cumulative (App.tsx +4 routes + menuKeys +8 const + Layout staticMap +7 entry) - 7 amber banner "Skeleton Phase 1 — full feature Phase 11" rõ ràng UAT Menu seed: +11 const + SeedMenuTreeAsync 8 row (Off_DonTu sub-group + 3 leaf + Off_DatXe + Off_ItTicket + Off_ChamCong + Hrm_Dashboard). DbInitializer Sample workflow seed DEFER (workflows V2 already seeded từ S29+S37 reuse — admin clone tạo riêng per ApplicableType=5/6/7). Verify: - dotnet build PASS 0 error 2 pre-existing warning - dotnet test 130/130 PASS baseline preserve - npm build × 2 PASS clean - SHA256 verify 5 file × 2 app all IDENTICAL Plan G-* progress 11/11 ✅ (100% COMPLETE): ✅ G-H1 (S33) + G-O1 (S34) + G-H2 (S35) + G-O2 (S36) + G-O3 (S37) + ✅ G-O4 + G-O5 + G-O6 + G-P1 + G-H3 (S38 skeleton) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
85
fe-admin/src/pages/hrm/HrmDashboardPage.tsx
Normal file
85
fe-admin/src/pages/hrm/HrmDashboardPage.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
// HR Dashboard — Phase 10.4 G-H3 (S38 2026-05-28).
|
||||
// 4 KPI card aggregate + gender ratio + birthdays + new hires.
|
||||
// File MIRROR SHA256 identical fe-user counterpart.
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Cake, TrendingUp, UserCheck, Users } from 'lucide-react'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { api } from '@/lib/api'
|
||||
import { cn } from '@/lib/cn'
|
||||
import type { HrDashboardDto } from '@/types/workflowApps'
|
||||
|
||||
const CARDS = [
|
||||
{ key: 'totalEmployees', label: 'Tổng nhân viên', icon: Users, color: 'bg-blue-50 text-blue-700 border-blue-200' },
|
||||
{ key: 'activeEmployees', label: 'Đang làm việc', icon: UserCheck, color: 'bg-emerald-50 text-emerald-700 border-emerald-200' },
|
||||
{ key: 'birthdaysThisWeek', label: 'Sinh nhật 7 ngày tới', icon: Cake, color: 'bg-amber-50 text-amber-700 border-amber-200' },
|
||||
{ key: 'newHiresThisMonth', label: 'Mới vào tháng này', icon: TrendingUp, color: 'bg-violet-50 text-violet-700 border-violet-200' },
|
||||
] as const
|
||||
|
||||
export function HrmDashboardPage() {
|
||||
const data = useQuery({
|
||||
queryKey: ['hr-dashboard'],
|
||||
queryFn: async () => (await api.get<HrDashboardDto>('/hr/dashboard')).data,
|
||||
})
|
||||
|
||||
const d = data.data
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<PageHeader title="Dashboard Nhân sự" description="KPI tổng quan + sinh nhật + tuyển dụng" />
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{CARDS.map((card) => {
|
||||
const Icon = card.icon
|
||||
const value = d ? (d as any)[card.key] : '—'
|
||||
return (
|
||||
<div key={card.key} className={cn('rounded-lg border p-4', card.color)}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-xs font-medium opacity-80">{card.label}</div>
|
||||
<div className="text-3xl font-bold mt-1 tabular-nums">{data.isLoading ? '—' : value}</div>
|
||||
</div>
|
||||
<Icon className="h-8 w-8 opacity-60" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{d && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h3 className="font-medium text-sm mb-3">Phân bố giới tính</h3>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-muted-foreground">Nam</div>
|
||||
<div className="text-2xl font-bold text-blue-700 tabular-nums">{d.maleCount}</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-muted-foreground">Nữ</div>
|
||||
<div className="text-2xl font-bold text-pink-700 tabular-nums">{d.femaleCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h3 className="font-medium text-sm mb-3">Trạng thái nhân sự</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span>Đang làm việc</span>
|
||||
<span className="font-semibold text-emerald-700 tabular-nums">{d.activeEmployees}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span>Nghỉ phép</span>
|
||||
<span className="font-semibold text-amber-700 tabular-nums">{d.onLeaveEmployees}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span>Đã nghỉ việc</span>
|
||||
<span className="font-semibold text-slate-500 tabular-nums">{d.resignedEmployees}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user