diff --git a/fe-admin/src/components/khkk/KhkkDetailContent.tsx b/fe-admin/src/components/khkk/KhkkDetailContent.tsx
new file mode 100644
index 0000000..072aa4f
--- /dev/null
+++ b/fe-admin/src/components/khkk/KhkkDetailContent.tsx
@@ -0,0 +1,709 @@
+// [S171 KHKK 3-panel] PANEL 2 (giữa) — nội-dung phiếu "Kế hoạch ký kết HĐ" (GĐ2).
+// File MIRROR SHA256 identical với fe-admin counterpart.
+//
+// 🔴 Khối ý-kiến cấp duyệt (Section 5) là bản thứ 3 của cùng một hình (PE
+// `PeDetailTabs.tsx:705-774` · HĐ `components/contracts/WorkflowHistoryPanel.tsx`).
+// Gộp được KHI VÀ CHỈ KHI BE 3 module thống-nhất shape `status` precompute — trước đó
+// gộp chỉ đẻ ra `if (module === …)`.
+//
+// Bố-cục mirror Duyệt NCC: dải 4 giai-đoạn trên cùng, rồi các Section ĐÁNH SỐ nối nhau
+// bằng `divide-y`, ý-kiến cấp duyệt nằm DƯỚI CÙNG (Ảnh 3 khoanh đỏ).
+// 1. Thông tin kế hoạch 4. File đính kèm
+// 2. Giá đề xuất theo NCC 5. Ý kiến cấp duyệt
+// 3. Căn cứ hồ sơ (b.8-9)
+// 🔸 KHÁC PE ở NỘI DUNG (miễn-trừ P2-3, có lý-do nghiệp-vụ): GĐ2 không có báo-giá / so-sánh
+// NCC (đã chốt ở GĐ1) mà có căn-cứ hồ-sơ TVGS. Giống nhau ở KHUNG, không ở chữ.
+//
+// [P2-7] `readOnly` là prop TỪ HOST — host quyết `phiếu ≠ Nháp ∨ user ≠ người soạn`.
+// Không dựng workspace riêng cho việc này.
+// [R-8] KHKK KHÔNG có phiếu V1-legacy (module sinh 2026-07-29) ⇒ KHÔNG port nhánh fallback
+// V1 của PE (`PeDetailTabs.tsx:434-436`) — đó là code chết ngay ngày đầu.
+import { useState, type ReactNode } from 'react'
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
+import {
+ ClipboardList, FileCheck, MessageSquare, Paperclip, Pencil, Plus, Save, Trash2, Upload, Users,
+} from 'lucide-react'
+import { toast } from 'sonner'
+import { PePipelineStrip } from '@/components/pe/PePipelineStrip'
+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 { Textarea } from '@/components/ui/Textarea'
+import { api } from '@/lib/api'
+import { getErrorMessage } from '@/lib/apiError'
+import { cn } from '@/lib/cn'
+import {
+ DOSSIER_KIND_LABELS, DOSSIER_STATUS_BADGE, DOSSIER_STATUS_LABELS,
+ DossierItemKind, DossierItemStatus,
+ KHKK_APPROVAL_GROUP_LABELS, KHKK_PHASE_BADGE, KHKK_PHASE_LABELS,
+ KhkkAttachmentPurpose, KhkkPhase,
+ type DossierItemKindValue, type DossierItemStatusValue, type KhkkAttachmentDto,
+ type KhkkDetailDto, type KhkkDossierItemDto, type KhkkPhaseValue,
+ type UpsertKhkkDossierItemInput,
+} from '@/types/khkk'
+
+function formatVnd(n: number | null | undefined): string {
+ if (n === null || n === undefined) return '—'
+ return n.toLocaleString('vi-VN') + ' đ'
+}
+
+function formatDateTime(iso: string): string {
+ return new Date(iso).toLocaleString('vi-VN')
+}
+
+function formatSize(b: number): string {
+ return b > 1024 * 1024 ? `${(b / 1024 / 1024).toFixed(1)} MB` : `${Math.round(b / 1024)} KB`
+}
+
+/** Khung Section ĐÁNH SỐ — mirror khuôn Section của màn Duyệt NCC (đánh số + divide-y). */
+function Section({
+ title, icon, accent, head, chipBg, chipFg, count, actions, children,
+}: {
+ title: string
+ icon: ReactNode
+ accent: string
+ head: string
+ chipBg: string
+ chipFg: string
+ count?: number
+ actions?: ReactNode
+ children: ReactNode
+}) {
+ return (
+
+ {title}
+
+ {count != null && (
+
+ {count}
+
+ )}
+ {actions && {actions}}
+
| Nhà cung cấp | +Hạng mục | +Giá tham chiếu | +Giá đề xuất | +Giá duyệt | +Ghi chú | +
|---|---|---|---|---|---|
| + {l.supplierName ?? '—'} + {l.contractId && ( + + ✓ HĐ + + )} + | +
+ {isDraft && !l.contractId ? (
+ <>
+
+ {catalogOptions.isError && (
+
+ Không tải được danh mục hạng mục —{' '}
+
+
+ )}
+ {catalogOptions.isSuccess && (catalogOptions.data?.length ?? 0) === 0 && (
+
+ Nhóm duyệt N{plan.approvalGroup} chưa có hạng mục đang hiệu lực — liên hệ quản trị danh mục.
+
+ )}
+ >
+ ) : l.tenHangMuc ? l.tenHangMuc : —}
+ |
+ {formatVnd(l.peReferenceAmount)} | +{formatVnd(l.proposedAmount)} | +{formatVnd(l.approvedAmount)} | +{l.note ?? '—'} | +
| Loại | +Tên căn cứ | +Trạng thái | +TVGS | +Ghi chú | + {isDraft &&Thao tác | } +
|---|---|---|---|---|---|
| {DOSSIER_KIND_LABELS[d.kind] ?? '—'} | +{d.name} | ++ + {DOSSIER_STATUS_LABELS[d.status]} + + | ++ {d.tvgsName ?? '—'} + {d.tvgsResultAt && ({formatDateTime(d.tvgsResultAt)})} + | +{d.note ?? '—'} | + {isDraft && ( +
+
+
+
+
+ |
+ )}
+
Bước này chưa cấu hình cấp duyệt.
+ )} + {step.levels.map((lv) => { + const signed = opinions.find(o => o.approvalWorkflowLevelId === lv.id) + return ( ++ {group === null ? 'Gói này chưa có kế hoạch ký kết.' : `Gói này chưa có phiếu nhóm N${group}.`} +
+ ) + } + return ( +Chưa có lượt duyệt nào.
+ ) : ( +Đang tải…
} + {changelogs.isError &&Không tải được lịch sử thay đổi.
} + {changelogs.data && changelogs.data.length === 0 && ( +Chưa có thay đổi nào được ghi.
+ )} +