// Panel 3: workflow timeline + transition buttons + approval history + changelog. // Pulls nextPhases từ BE bundle (single source of truth) → render per-phase // action button. Approvals + History moved here from PeDetailTabs (2 section // dưới cùng) để Panel 2 tập trung hiển thị nội dung phiếu (Info + NCC + Items). import { useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' import { Dialog } from '@/components/ui/Dialog' import { Button } from '@/components/ui/Button' import { Label } from '@/components/ui/Label' import { Textarea } from '@/components/ui/Textarea' import { api } from '@/lib/api' import { getErrorMessage } from '@/lib/apiError' import { cn } from '@/lib/cn' import { ApprovalStage, PurchaseEvaluationPhase, PurchaseEvaluationPhaseColor, PurchaseEvaluationPhaseLabel, type PeDepartmentApproval, type PeDetailBundle, } from '@/types/purchaseEvaluation' import { PeApprovalsSection, PeHistorySection } from './PeDetailTabs' export function PeWorkflowPanel({ evaluation }: { evaluation: PeDetailBundle }) { const [target, setTarget] = useState(null) const [comment, setComment] = useState('') const qc = useQueryClient() // 2-stage dept approvals (Migration 16) — fetch riêng để FE Workflow Panel // hiển thị progress per phase × dept (Stage Review NV / Confirm TPB). const { data: deptApprovals = [] } = useQuery({ queryKey: ['pe-dept-approvals', evaluation.id], queryFn: async () => { const r = await api.get(`/purchase-evaluations/${evaluation.id}/department-approvals`) return r.data }, }) const transition = useMutation({ mutationFn: async () => api.post(`/purchase-evaluations/${evaluation.id}/transitions`, { targetPhase: target, decision: target === PurchaseEvaluationPhase.TuChoi ? 2 : 1, comment: comment || null, }), onSuccess: () => { toast.success('Đã chuyển phase.') qc.invalidateQueries({ queryKey: ['pe-detail', evaluation.id] }) qc.invalidateQueries({ queryKey: ['pe-list'] }) qc.invalidateQueries({ queryKey: ['pe-dept-approvals', evaluation.id] }) setTarget(null) setComment('') }, onError: e => toast.error(getErrorMessage(e)), }) const next = evaluation.workflow.nextPhases return (

Quy trình

{evaluation.workflow.policyDescription}

    {evaluation.workflow.activePhases .filter(p => p !== PurchaseEvaluationPhase.TuChoi) .map(p => { const isCurrent = evaluation.phase === p const isPast = isPastPhase(evaluation.phase, p, evaluation.workflow.activePhases) return (
  1. {p} {PurchaseEvaluationPhaseLabel[p]} {isCurrent && ● hiện tại} {isPast && }
  2. ) })}
{next.length > 0 && (
{next.map(p => ( ))}
)} {target !== null && ( setTarget(null)} title={`Chuyển → ${PurchaseEvaluationPhaseLabel[target]}`} footer={<> } >