diff --git a/fe-user/src/pages/InboxPage.tsx b/fe-user/src/pages/InboxPage.tsx index 5ee4a38..2eb6b74 100644 --- a/fe-user/src/pages/InboxPage.tsx +++ b/fe-user/src/pages/InboxPage.tsx @@ -7,7 +7,7 @@ import { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { useNavigate, useSearchParams } from 'react-router-dom' -import { Inbox, AlertTriangle, Clock, FileText, Search, X } from 'lucide-react' +import { Inbox, AlertTriangle, Clock, FileText, Search, X, ClipboardList } from 'lucide-react' import { ContractDetailContent } from '@/components/contracts/ContractDetailContent' import { WorkflowHistoryPanel } from '@/components/contracts/WorkflowHistoryPanel' import { PhaseBadge } from '@/components/PhaseBadge' @@ -19,6 +19,12 @@ import { api } from '@/lib/api' import { cn } from '@/lib/cn' import type { ContractDetail, ContractListItem } from '@/types/contracts' import { ContractTypeLabel } from '@/types/forms' +import { + PurchaseEvaluationPhaseColor, + PurchaseEvaluationPhaseLabel, + PurchaseEvaluationTypeLabel, + type PeListItem, +} from '@/types/purchaseEvaluation' const fmtMoney = (v: number) => v.toLocaleString('vi-VN') @@ -62,6 +68,14 @@ export function InboxPage() { queryFn: async () => (await api.get('/contracts/inbox')).data, }) + // PE inbox — phiếu Duyệt NCC chờ user xử lý (Optional polish §6.B HANDOFF). + // Click row → navigate /purchase-evaluations/:id thay vì inline detail (đỡ + // duplicate ContractDetailContent component cho PE entity khác shape). + const peList = useQuery({ + queryKey: ['pe-inbox'], + queryFn: async () => (await api.get('/purchase-evaluations/inbox')).data, + }) + const detail = useQuery({ queryKey: ['contract', selectedId], queryFn: async () => (await api.get(`/contracts/${selectedId}`)).data, @@ -84,6 +98,19 @@ export function InboxPage() { return items }, [allRows, typeFilter, search]) + const peRows = useMemo(() => { + let items = peList.data ?? [] + if (search.trim()) { + const q = search.toLowerCase() + items = items.filter(p => + (p.maPhieu ?? '').toLowerCase().includes(q) || + (p.tenGoiThau ?? '').toLowerCase().includes(q) || + (p.projectName ?? '').toLowerCase().includes(q), + ) + } + return items + }, [peList.data, search]) + const stats = useMemo(() => { const now = Date.now() const dayMs = 24 * 60 * 60 * 1000 @@ -97,8 +124,15 @@ export function InboxPage() { if (diff < 0) overdue++ else if (diff < dayMs) dueSoon++ } - return { total: rows.length, overdue, dueSoon, totalValue } - }, [rows]) + // PE rows tham gia stats overdue/dueSoon (KHÔNG totalValue — PE không có giá trị HĐ) + for (const p of peRows) { + if (!p.slaDeadline) continue + const diff = new Date(p.slaDeadline).getTime() - now + if (diff < 0) overdue++ + else if (diff < dayMs) dueSoon++ + } + return { total: rows.length + peRows.length, overdue, dueSoon, totalValue } + }, [rows, peRows]) function selectContract(id: string) { if (typeof window !== 'undefined' && window.matchMedia('(min-width: 1024px)').matches) { @@ -171,7 +205,7 @@ export function InboxPage() { ))} )} - {!list.isLoading && rows.length === 0 && ( + {!list.isLoading && rows.length === 0 && peRows.length === 0 && (
)} -
    - {rows.map(c => { - const overdue = c.slaDeadline && new Date(c.slaDeadline).getTime() < Date.now() - return ( -
  • - -
  • - ) - })} -
+ + + ) + })} + + + )} + + {/* Section 2 — Phiếu Duyệt NCC chờ tôi (PE) */} + {peRows.length > 0 && ( + <> +
+ + Phiếu Duyệt NCC ({peRows.length}) +
+
    + {peRows.map(p => { + const overdue = p.slaDeadline && new Date(p.slaDeadline).getTime() < Date.now() + return ( +
  • + +
  • + ) + })} +
+ + )}