import { useQuery } from '@tanstack/react-query' 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' return v.toLocaleString('vi-VN') } function StatCard({ icon: Icon, label, value, hint, tone = 'default' }: { icon: React.ComponentType<{ className?: string }> label: string value: React.ReactNode hint?: string tone?: 'default' | 'warn' | 'good' }) { const toneClass = tone === 'warn' ? 'text-amber-600' : tone === 'good' ? 'text-emerald-600' : 'text-brand-600' return (
{label}
{value}
{hint &&
{hint}
}
) } function MyDashboardRow() { const navigate = useNavigate() const { user } = useAuth() const q = useQuery({ queryKey: ['my-dashboard'], queryFn: async () => (await api.get('/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 (

Của tôi

Sắp quá hạn (24h)
{d.dueSoon}
Đã quá hạn
{d.overdue}
) } export function DashboardPage() { const stats = useQuery({ queryKey: ['dashboard-stats'], queryFn: async () => (await api.get('/reports/dashboard')).data, staleTime: 60_000, }) if (stats.isLoading) { return (
{[1, 2, 3, 4, 5].map(i => (
))}
) } const d = stats.data if (!d) return null return (

Toàn hệ thống

{/* KPI Cards */}
{/* By Phase */}

HĐ theo phase

{d.byPhase.length === 0 &&
Chưa có HĐ nào
} {d.byPhase .slice() .sort((a, b) => b.count - a.count) .map(p => { const total = d.byPhase.reduce((s, x) => s + x.count, 0) || 1 const pct = (p.count / total) * 100 return (
{p.count}
) })}
{/* Monthly value */}

Giá trị HĐ theo tháng (12 tháng gần nhất)

({ label: `${String(m.month).padStart(2, '0')}/${m.year}`, value: m.totalValue, sublabel: `${m.count} HĐ`, }))} formatValue={fmtMoney} />
{/* Top suppliers */}

Top NCC theo số HĐ

({ label: s.supplierName, value: s.count, sublabel: `Tổng ${fmtMoney(s.totalValue)} VND`, }))} />
{/* Top projects */}

Top dự án theo số HĐ

({ label: p.projectName, value: p.count, sublabel: `Tổng ${fmtMoney(p.totalValue)} VND`, }))} />
) }