[CLAUDE] App+Infra+FE-Admin: seed master data + MyDashboard widgets
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
Task 1 — Seed master data unblock UAT/demo: - DbInitializer.SeedDepartmentsAsync: 9 departments từ QT-TP-NCC.docx (PM/QS/CCM/PRO/FIN/ACT/EQU/HRA/BOD) — reference data không phải demo. - DbInitializer.SeedDemoMasterDataAsync: 5 demo suppliers (NCC VLXD, NTP Xây dựng, TĐ Hoàng Nam, DV Clean, CĐT Vingroup — covers cả 5 SupplierType) + 3 demo projects (FLOCK01, SkyGarden, Industrial). Chỉ seed nếu tables empty — respect admin's real data khi họ add. Task 2 — Roles CRUD đã có sẵn trong UsersPage (Shield icon button mở dialog gán 12 roles từ AppRoles.cs). Skip. Task 3 — MyDashboard role-specific widgets: - GetMyDashboardQuery (Reports): returns DraftsInProgress (tôi là Drafter + phase soạn thảo), PendingMyApproval (phase eligible role tôi + không phải tôi drafter), DueSoon 24h, Overdue, DraftsTotalValue. - Endpoint GET /api/reports/my-dashboard. - FE MyDashboardRow ở đầu DashboardPage: 4 card hover → navigate. Admin ẩn row nếu tất cả = 0 (ERP noise reduction). 'Đang soạn thảo' + 'Chờ tôi duyệt' clickable → /contracts?filter=... (filter param để wire lần sau; row hiện chưa implement). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -1,11 +1,21 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { FileText, CheckCircle2, AlertTriangle, TrendingUp, Coins } from 'lucide-react'
|
||||
import { FileText, CheckCircle2, AlertTriangle, TrendingUp, Coins, Pencil, Clock, Inbox } from 'lucide-react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { BarChart } from '@/components/BarChart'
|
||||
import { PhaseBadge } from '@/components/PhaseBadge'
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import { api } from '@/lib/api'
|
||||
import type { DashboardStats } from '@/types/reports'
|
||||
|
||||
type MyDashboard = {
|
||||
draftsInProgress: number
|
||||
pendingMyApproval: number
|
||||
dueSoon: number
|
||||
overdue: number
|
||||
draftsTotalValue: number
|
||||
}
|
||||
|
||||
const fmtMoney = (v: number) => {
|
||||
if (v >= 1_000_000_000) return (v / 1_000_000_000).toFixed(1) + ' tỷ'
|
||||
if (v >= 1_000_000) return (v / 1_000_000).toFixed(1) + ' tr'
|
||||
@ -32,6 +42,67 @@ function StatCard({ icon: Icon, label, value, hint, tone = 'default' }: {
|
||||
)
|
||||
}
|
||||
|
||||
function MyDashboardRow() {
|
||||
const navigate = useNavigate()
|
||||
const { user } = useAuth()
|
||||
const q = useQuery({
|
||||
queryKey: ['my-dashboard'],
|
||||
queryFn: async () => (await api.get<MyDashboard>('/reports/my-dashboard')).data,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
const d = q.data
|
||||
if (!d) return null
|
||||
|
||||
// Admin thấy everything nhưng card "Tôi đang soạn thảo" + "Chờ tôi duyệt"
|
||||
// thường = 0 cho admin. Chỉ show row nếu có ít nhất 1 card có giá trị.
|
||||
const anyValue = d.draftsInProgress + d.pendingMyApproval + d.dueSoon + d.overdue > 0
|
||||
if (!anyValue && user?.roles.includes('Admin')) return null
|
||||
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<h2 className="mb-2 text-xs font-semibold uppercase tracking-wider text-slate-500">Của tôi</h2>
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||
<button
|
||||
onClick={() => navigate('/contracts?filter=my-drafts')}
|
||||
className="rounded-xl border border-slate-200 bg-white p-4 text-left shadow-sm transition hover:border-brand-300 hover:shadow"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs font-medium text-slate-500">Đang soạn thảo</div>
|
||||
<Pencil className="h-4 w-4 text-brand-600" />
|
||||
</div>
|
||||
<div className="mt-2 text-2xl font-bold text-slate-900 tabular-nums">{d.draftsInProgress}</div>
|
||||
<div className="mt-0.5 text-xs text-slate-400">{fmtMoney(d.draftsTotalValue)} VND</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigate('/contracts?filter=pending-me')}
|
||||
className="rounded-xl border border-slate-200 bg-white p-4 text-left shadow-sm transition hover:border-brand-300 hover:shadow"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs font-medium text-slate-500">Chờ tôi duyệt</div>
|
||||
<Inbox className="h-4 w-4 text-brand-600" />
|
||||
</div>
|
||||
<div className="mt-2 text-2xl font-bold text-slate-900 tabular-nums">{d.pendingMyApproval}</div>
|
||||
<div className="mt-0.5 text-xs text-slate-400">Click để xem hộp thư</div>
|
||||
</button>
|
||||
<div className="rounded-xl border border-slate-200 bg-white p-4 shadow-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs font-medium text-slate-500">Sắp quá hạn (24h)</div>
|
||||
<Clock className="h-4 w-4 text-amber-600" />
|
||||
</div>
|
||||
<div className="mt-2 text-2xl font-bold text-amber-600 tabular-nums">{d.dueSoon}</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-slate-200 bg-white p-4 shadow-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs font-medium text-slate-500">Đã quá hạn</div>
|
||||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||||
</div>
|
||||
<div className="mt-2 text-2xl font-bold text-red-600 tabular-nums">{d.overdue}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DashboardPage() {
|
||||
const stats = useQuery({
|
||||
queryKey: ['dashboard-stats'],
|
||||
@ -59,6 +130,9 @@ export function DashboardPage() {
|
||||
<div className="p-6">
|
||||
<PageHeader title="Tổng quan" description="Tình hình HĐ toàn hệ thống — cập nhật real-time khi refresh." />
|
||||
|
||||
<MyDashboardRow />
|
||||
|
||||
<h2 className="mb-2 text-xs font-semibold uppercase tracking-wider text-slate-500">Toàn hệ thống</h2>
|
||||
{/* KPI Cards */}
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-5">
|
||||
<StatCard icon={FileText} label="Tổng HĐ" value={d.totalContracts} />
|
||||
|
||||
Reference in New Issue
Block a user