// Detail content cho 1 phiếu Duyệt NCC. Flat render (no tabs): Thông tin + // NCC + Hạng mục + Báo giá stack vertically trong 1 màn hình. // Duyệt history + Lịch sử thay đổi → moved to Panel 3 (xem PeWorkflowPanel // → PeApprovalsSection + PeHistorySection). import { useRef, useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useNavigate } from 'react-router-dom' import { toast } from 'sonner' import { Check, Paperclip, Pencil, Plus, Trash2, Upload } from 'lucide-react' import { Button } from '@/components/ui/Button' import { Dialog } from '@/components/ui/Dialog' import { Input } from '@/components/ui/Input' import { Label } from '@/components/ui/Label' import { Select } from '@/components/ui/Select' import { api } from '@/lib/api' import { getErrorMessage } from '@/lib/apiError' import { cn } from '@/lib/cn' import { PeAttachmentPurpose, PeAttachmentPurposeLabel, PeDepartmentKind, PeDepartmentKindLabel, PurchaseEvaluationPhase, PurchaseEvaluationPhaseColor, PurchaseEvaluationPhaseLabel, PurchaseEvaluationTypeLabel, type PeAttachment, type PeChangelog, type PeDepartmentOpinion, type PeDetailBundle, type PeDetailRow, type PeQuote, type PeSupplier, } from '@/types/purchaseEvaluation' import { BudgetPhase, type BudgetListItem } from '@/types/budget' import type { Paged, Supplier } from '@/types/master' const fmtMoney = (v: number) => v.toLocaleString('vi-VN') // Main detail content — flat render 3 section không tabs. // Tên giữ PeDetailTabs để không break callsite (rename gây churn). // // `mode` (2026-05-07): // - 'detail' (default): full UX — Section 5 Ý kiến 4PB editable theo readOnly. // Dùng ở leaf "Danh sách" + "Duyệt" (3-panel pages). // - 'workspace': dùng ở leaf "Thao tác" (2-panel workspace). Section 5 LUÔN // disabled (Q5 user — ý kiến nhập khi duyệt, không phải workspace nhập liệu). // Workflow Panel + Approvals + History KHÔNG render trong PeDetailTabs (luôn // ở caller PeWorkflowPanel — workspace caller skip render Panel 3 hoàn toàn). export function PeDetailTabs({ evaluation, onBack, onDelete, readOnly = false, mode = 'detail', autoEditHeader = false, }: { evaluation: PeDetailBundle onBack: () => void onDelete: () => void /** Menu "Duyệt" (pendingMe=1) — ẩn mọi action thêm/sửa/xóa, chỉ xem + duyệt phase. */ readOnly?: boolean /** 'workspace' = Section 5 LUÔN disabled (ý kiến nhập ở leaf Duyệt). */ mode?: 'detail' | 'workspace' /** Auto open Section 1 InfoTab in edit mode khi mount — triggered từ pencil icon Panel 1 */ autoEditHeader?: boolean }) { const navigate = useNavigate() const isDraft = evaluation.phase === PurchaseEvaluationPhase.DangSoanThao const opinionsReadOnly = readOnly || mode === 'workspace' return (

{evaluation.tenGoiThau}

{PurchaseEvaluationPhaseLabel[evaluation.phase]} {readOnly && ( chế độ duyệt )}
{evaluation.maPhieu ?? '—'} · {PurchaseEvaluationTypeLabel[evaluation.type]} · {evaluation.projectName} {evaluation.drafterName && <>·Soạn: {evaluation.drafterName}}
{isDraft && !readOnly && ( <> )}
{/* Section 1 — đúng spec form FO-PHIẾU TRÌNH KÝ CHỌN TP/NCC */}
{mode === 'workspace' && (
Ý kiến + chữ ký nhập khi duyệt phiếu — vào menu “Duyệt” để ký.
)}
) } function Section({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
) } // ===== Section 5 — Ý kiến 4 phòng ban ===== // Render 2x2 grid 4 box (Phê duyệt / CCM / MuaHàng / SM-PM). Mỗi box hiển // thị Opinion text + chữ ký (UserName + SignedAt) nếu đã ký, hoặc form nhập // + 2 button "Lưu" + "Lưu & Ký" khi chưa ký / readOnly=false. function DepartmentOpinionsSection({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolean }) { const KINDS: { kind: number; label: string }[] = [ { kind: PeDepartmentKind.PheDuyet, label: PeDepartmentKindLabel[PeDepartmentKind.PheDuyet] }, { kind: PeDepartmentKind.Ccm, label: PeDepartmentKindLabel[PeDepartmentKind.Ccm] }, { kind: PeDepartmentKind.MuaHang, label: PeDepartmentKindLabel[PeDepartmentKind.MuaHang] }, { kind: PeDepartmentKind.SmPm, label: PeDepartmentKindLabel[PeDepartmentKind.SmPm] }, ] return (
{KINDS.map(k => { const existing = ev.departmentOpinions.find(o => o.kind === k.kind) ?? null return ( ) })}
) } function OpinionBox({ evaluationId, kind, kindLabel, existing, readOnly, }: { evaluationId: string kind: number kindLabel: string existing: PeDepartmentOpinion | null readOnly: boolean }) { const qc = useQueryClient() const [text, setText] = useState(existing?.opinion ?? '') const isSigned = !!existing?.signedAt const save = useMutation({ mutationFn: async (sign: boolean) => api.post(`/purchase-evaluations/${evaluationId}/opinions`, { kind, opinion: text || null, sign, }), onSuccess: () => { toast.success('Đã lưu ý kiến.') qc.invalidateQueries({ queryKey: ['pe-detail', evaluationId] }) }, onError: e => toast.error(getErrorMessage(e)), }) return (

{kindLabel}

{isSigned && ( Đã ký )}
{readOnly ? ( <>
{existing?.opinion ?? — chưa có ý kiến}
{isSigned && (
Ký bởi {existing?.userName ?? '—'} · {new Date(existing!.signedAt!).toLocaleString('vi-VN')}
)} ) : ( <>