// List + Detail phiếu Duyệt NCC — 3-panel: List | Detail tabs | Workflow + history. // URL params: type (filter A/B), pendingMe (1=inbox), id (selected), q (search). import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useNavigate, useSearchParams } from 'react-router-dom' import { toast } from 'sonner' import { ClipboardCheck, Search, X } from 'lucide-react' import { Input } from '@/components/ui/Input' import { Select } from '@/components/ui/Select' import { EmptyState } from '@/components/EmptyState' import { SlaTimer } from '@/components/SlaTimer' import { api } from '@/lib/api' import { getErrorMessage } from '@/lib/apiError' import { cn } from '@/lib/cn' import type { Paged } from '@/types/master' import { PeDisplayStatus, PeDisplayStatusColor, PeDisplayStatusLabel, PurchaseEvaluationPhase, PurchaseEvaluationTypeLabel, getPeDisplayStatus, type PeDetailBundle, type PeListItem, } from '@/types/purchaseEvaluation' import { PeDetailTabs } from '@/components/pe/PeDetailTabs' import { PeWorkflowPanel } from '@/components/pe/PeWorkflowPanel' export function PurchaseEvaluationsListPage() { const navigate = useNavigate() const qc = useQueryClient() const [sp, setSp] = useSearchParams() const typeFilter = sp.get('type') ? Number(sp.get('type')) : null const pendingMe = sp.get('pendingMe') === '1' const search = sp.get('q') ?? '' const phase = sp.get('phase') ?? '' const approvalWorkflowId = sp.get('awId') ?? '' // Mig 23 — filter quy trình const selectedId = sp.get('id') // Mig 23 — list quy trình duyệt V2 cho dropdown filter (filter theo type screen) const approvalWorkflows = useQuery({ queryKey: ['approval-workflows-v2-filter', typeFilter], queryFn: async () => { if (!typeFilter) return [] const res = await api.get<{ types: { applicableType: number; history: { id: string; code: string; version: number; name: string; isActive: boolean }[] }[] }>( '/approval-workflows-v2', { params: { applicableType: typeFilter } }, ) return res.data.types.find(t => t.applicableType === typeFilter)?.history ?? [] }, enabled: !!typeFilter, }) const list = useQuery({ queryKey: ['pe-list', { typeFilter, pendingMe, search, phase, approvalWorkflowId }], queryFn: async () => { if (pendingMe) { const res = await api.get('/purchase-evaluations/inbox', { params: { type: typeFilter ?? undefined }, }) return { items: res.data, total: res.data.length, page: 1, pageSize: res.data.length } } const res = await api.get>('/purchase-evaluations', { params: { pageSize: 50, search: search || undefined, type: typeFilter ?? undefined, phase: phase || undefined, approvalWorkflowId: approvalWorkflowId || undefined, }, }) return res.data }, }) const detail = useQuery({ queryKey: ['pe-detail', selectedId], queryFn: async () => (await api.get(`/purchase-evaluations/${selectedId}`)).data, enabled: !!selectedId, }) const del = useMutation({ mutationFn: async (id: string) => api.delete(`/purchase-evaluations/${id}`), onSuccess: () => { toast.success('Đã xóa phiếu.') setParam('id', null) qc.invalidateQueries({ queryKey: ['pe-list'] }) }, onError: e => toast.error(getErrorMessage(e)), }) function setParam(key: string, value: string | null) { const next = new URLSearchParams(sp) if (value == null || value === '') next.delete(key) else next.set(key, value) if (key !== 'id') next.delete('page') setSp(next, { replace: key === 'q' }) } function selectRow(id: string) { if (typeof window !== 'undefined' && window.matchMedia('(min-width: 1024px)').matches) { setParam('id', id) } else { navigate(`/purchase-evaluations/${id}`) } } const rows = list.data?.items ?? [] const headerTitle = typeFilter ? (pendingMe ? `${PurchaseEvaluationTypeLabel[typeFilter]} — Chờ duyệt` : PurchaseEvaluationTypeLabel[typeFilter]) : pendingMe ? 'Duyệt NCC — Chờ tôi' : 'Quy trình Duyệt NCC' return (

{headerTitle}

{list.data?.total ?? 0}
{/* Panel 1: List */} {/* Panel 2: Detail tabs */}
{!selectedId && ( )} {selectedId && detail.isLoading &&
Đang tải…
} {selectedId && detail.data && ( setParam('id', null)} onDelete={() => del.mutate(detail.data!.id)} readOnly={true} /> )}
{/* Panel 3: Workflow + history */} {/* Danh sách (pendingMe=false) → readOnly=true → ẩn Chuyển tiếp transition. Duyệt (pendingMe=true) → readOnly=false → cho approver chuyển phase. */}
) } // Fullpage detail route cho mobile (/purchase-evaluations/:id) export function PurchaseEvaluationDetailPage() { const navigate = useNavigate() const id = location.pathname.split('/').pop()! const detail = useQuery({ queryKey: ['pe-detail', id], queryFn: async () => (await api.get(`/purchase-evaluations/${id}`)).data, }) const del = useMutation({ mutationFn: async () => api.delete(`/purchase-evaluations/${id}`), onSuccess: () => { toast.success('Đã xóa.') navigate('/purchase-evaluations') }, }) if (detail.isLoading) return
Đang tải…
if (!detail.data) return
Không tìm thấy phiếu.
return (
navigate('/purchase-evaluations')} onDelete={() => del.mutate()} />
) }