diff --git a/fe-user/src/pages/InboxPage.tsx b/fe-user/src/pages/InboxPage.tsx
index d70e779..ed491b8 100644
--- a/fe-user/src/pages/InboxPage.tsx
+++ b/fe-user/src/pages/InboxPage.tsx
@@ -1,6 +1,7 @@
+import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
-import { Inbox } from 'lucide-react'
+import { Inbox, AlertTriangle, Clock, FileText } from 'lucide-react'
import { PageHeader } from '@/components/PageHeader'
import { DataTable, type Column } from '@/components/DataTable'
import { PhaseBadge } from '@/components/PhaseBadge'
@@ -12,6 +13,30 @@ import { ContractTypeLabel } from '@/types/forms'
const fmtMoney = (v: number) => v.toLocaleString('vi-VN')
+function StatCard({
+ icon: Icon,
+ label,
+ value,
+ tone = 'default',
+}: {
+ icon: React.ComponentType<{ className?: string }>
+ label: string
+ value: React.ReactNode
+ tone?: 'default' | 'warn' | 'danger'
+}) {
+ const toneClass =
+ tone === 'danger' ? 'text-red-600' : tone === 'warn' ? 'text-amber-600' : 'text-brand-600'
+ return (
+
+ )
+}
+
export function InboxPage() {
const navigate = useNavigate()
const { user } = useAuth()
@@ -21,14 +46,58 @@ export function InboxPage() {
queryFn: async () => (await api.get('/contracts/inbox')).data,
})
+ const rows = list.data ?? []
+
+ const stats = useMemo(() => {
+ const now = Date.now()
+ const dayMs = 24 * 60 * 60 * 1000
+ let overdue = 0
+ let dueSoon = 0
+ let totalValue = 0
+ for (const c of rows) {
+ totalValue += c.giaTri ?? 0
+ if (!c.slaDeadline) continue
+ const diff = new Date(c.slaDeadline).getTime() - now
+ if (diff < 0) overdue++
+ else if (diff < dayMs) dueSoon++
+ }
+ return { total: rows.length, overdue, dueSoon, totalValue }
+ }, [rows])
+
const columns: Column[] = [
- { key: 'maHopDong', header: 'Mã HĐ', width: 'w-48', render: c => {c.maHopDong ?? '—'} },
+ {
+ key: 'maHopDong',
+ header: 'Mã HĐ',
+ width: 'w-48',
+ render: c => {c.maHopDong ?? '—'},
+ },
{ key: 'tenHopDong', header: 'Tên HĐ', render: c => c.tenHopDong ?? '—' },
- { key: 'type', header: 'Loại', width: 'w-32', render: c => ContractTypeLabel[c.type] ?? '—' },
- { key: 'phase', header: 'Phase hiện tại', width: 'w-36', render: c => },
+ {
+ key: 'type',
+ header: 'Loại',
+ width: 'w-32',
+ render: c => ContractTypeLabel[c.type] ?? '—',
+ },
+ {
+ key: 'phase',
+ header: 'Phase hiện tại',
+ width: 'w-36',
+ render: c => ,
+ },
{ key: 'supplierName', header: 'NCC', render: c => c.supplierName },
- { key: 'giaTri', header: 'Giá trị', align: 'right', width: 'w-32', render: c => fmtMoney(c.giaTri) },
- { key: 'slaDeadline', header: 'SLA', width: 'w-40', render: c => },
+ {
+ key: 'giaTri',
+ header: 'Giá trị',
+ align: 'right',
+ width: 'w-32',
+ render: c => fmtMoney(c.giaTri),
+ },
+ {
+ key: 'slaDeadline',
+ header: 'SLA',
+ width: 'w-40',
+ render: c => ,
+ },
]
return (
@@ -43,9 +112,16 @@ export function InboxPage() {
description={`HĐ chờ vai trò ${user?.roles.join(', ') ?? ''} xử lý. Click row để xem chi tiết.`}
/>
+
+
+
+
+
+
+
c.id}
isLoading={list.isLoading}
empty="Không có HĐ nào chờ bạn xử lý."