// Panel 3 — workflow timeline + transition buttons + approval history + changelog. // Pulls nextPhases từ BE bundle (single source of truth). 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 { ApprovalDecision, ApprovalStage, BudgetPhase, BudgetPhaseColor, BudgetPhaseLabel, type BudgetDepartmentApproval, type BudgetDetailBundle, } from '@/types/budget' import { BudgetApprovalsSection, BudgetHistorySection } from './BudgetDetailTabs' export function BudgetWorkflowPanel({ budget }: { budget: BudgetDetailBundle }) { const [target, setTarget] = useState(null) const [comment, setComment] = useState('') const qc = useQueryClient() // 2-stage dept approvals (Migration 16) — fetch riêng để FE render timeline. const { data: deptApprovals = [] } = useQuery({ queryKey: ['budget-dept-approvals', budget.id], queryFn: async () => (await api.get(`/budgets/${budget.id}/department-approvals`)).data, }) const transition = useMutation({ mutationFn: async () => api.post(`/budgets/${budget.id}/transitions`, { targetPhase: target, decision: target === BudgetPhase.TuChoi ? ApprovalDecision.Reject : ApprovalDecision.Approve, comment: comment || null, }), onSuccess: () => { toast.success('Đã chuyển phase.') qc.invalidateQueries({ queryKey: ['budget-detail', budget.id] }) qc.invalidateQueries({ queryKey: ['budget-list'] }) qc.invalidateQueries({ queryKey: ['budget-changelog', budget.id] }) qc.invalidateQueries({ queryKey: ['budget-dept-approvals', budget.id] }) setTarget(null) setComment('') }, onError: e => toast.error(getErrorMessage(e)), }) const next = budget.workflow.nextPhases return (

Quy trình

{budget.workflow.policyDescription}

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