| Nhà cung cấp | @@ -385,16 +415,16 @@ export function KhkkDetailContent({||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| - {l.supplierName ?? '—'} + {orDash(l.supplierName)} {l.contractId && ( ✓ HĐ )} | -+ | {isDraft && !l.contractId ? ( <> | -{formatVnd(l.peReferenceAmount)} | -{formatVnd(l.proposedAmount)} | -{formatVnd(l.approvedAmount)} | -{l.note ?? '—'} | +{moneyOrDash(l.peReferenceAmount)} | +{moneyOrDash(l.proposedAmount)} | +{moneyOrDash(l.approvedAmount)} | +{orDash(l.note)} |
| Loại | @@ -478,19 +516,19 @@ export function KhkkDetailContent({|||||||
|---|---|---|---|---|---|---|---|
| {DOSSIER_KIND_LABELS[d.kind] ?? '—'} | +|||||||
| {orDash(DOSSIER_KIND_LABELS[d.kind])} | {d.name} | {DOSSIER_STATUS_LABELS[d.status]} | -- {d.tvgsName ?? '—'} + | + {orDash(d.tvgsName)} {d.tvgsResultAt && ({formatDateTime(d.tvgsResultAt)})} | -{d.note ?? '—'} | +{orDash(d.note)} | {isDraft && (
@@ -544,7 +582,7 @@ export function KhkkDetailContent({
}
>
{planAttachments.length === 0 ? (
-
Chưa có file đính kèm.
+ Chưa có file đính kèm.
) : (
{plan.maKeHoach ?? '(chưa cấp mã)'}
+ {/* [S175 · B3] Chip nhóm: `text-[9px]` là nấc thứ TƯ của thang chữ (13/11/10/9) —
+ gom về 3 nấc chuẩn 13/11/10. Và `inline` + cỡ chữ khác dòng chứa ⇒ hộp chip cao
+ khác chữ bên cạnh nên tụt baseline; `inline-flex items-center leading-none` ghim
+ nó thẳng hàng thay vì dựa vào baseline của 2 cỡ chữ khác nhau. */}
{plan.approvalGroup != null && (
-
+
N{plan.approvalGroup}
)}
@@ -227,7 +231,7 @@ export function KhkkListPanel({
const shown = group === null ? plans : plans.filter(k => k.approvalGroup === group)
if (shown.length === 0) {
return (
-
+ {/* [S175 · B3] ĐẦU CỘT — trước đây chế-độ CÂY đi thẳng vào thân, không tiêu-đề cũng
+ không số đếm ⇒ cột "cụt đầu": lệch cả với màn Thao tác (`KhkkWorkListPanel` có
+ `Tier` header) LẪN với chính chế-độ phẳng (có dòng đếm ở chân). Mirror đúng khuôn
+ `Tier` để 2 màn KHKK cùng một hình. */}
+ + {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}.`} ) @@ -275,23 +279,42 @@ export function KhkkListPanel({ ))} {flatMode && !deletedView && canPe && ( -+ Đang lọc — danh sách lấy thẳng từ máy chủ (không giới hạn 200 phiếu của cây). )} {!canPe && ( -+ Bạn chưa có quyền xem phiếu Duyệt NCC nên cây toàn trình không dựng được — danh sách kế hoạch ký kết vẫn liệt kê đầy đủ ở dưới. )}
+
+ {flatMode ?
+
{!flatMode && (
diff --git a/fe-admin/src/components/khkk/KhkkWorkListPanel.tsx b/fe-admin/src/components/khkk/KhkkWorkListPanel.tsx
index 58d24ac..e1086e5 100644
--- a/fe-admin/src/components/khkk/KhkkWorkListPanel.tsx
+++ b/fe-admin/src/components/khkk/KhkkWorkListPanel.tsx
@@ -171,7 +171,7 @@ export function KhkkWorkListPanel({
)}
{!candidates.isLoading && !candidates.isError && awaiting.length === 0 && (
- + Mọi phiếu đã duyệt đều đã có kế hoạch ký kết. )} @@ -181,7 +181,7 @@ export function KhkkWorkListPanel({ {projectName} - {items.length} + {items.length}
+ Không còn kế hoạch nào đang xử lý. Phiếu đã duyệt không hiện ở đây — xem tại menu Kế hoạch ký kết HĐ. @@ -287,7 +287,7 @@ export function KhkkWorkListPanel({ sẽ đọc thành "đang cắt bớt" trong khi thật ra là "đang lọc bỏ". Nói đúng việc đang làm, và chỉ nói khi thật sự có cái bị giấu. */} {plans.data && plans.data.items.length > planItems.length && ( -+ Đã ẩn kế hoạch ở trạng thái “Đã duyệt” — xem đủ ở menu Kế hoạch ký kết HĐ. )} diff --git a/fe-admin/src/components/khkk/KhkkWorkflowPanel.tsx b/fe-admin/src/components/khkk/KhkkWorkflowPanel.tsx index 5601048..2c5c900 100644 --- a/fe-admin/src/components/khkk/KhkkWorkflowPanel.tsx +++ b/fe-admin/src/components/khkk/KhkkWorkflowPanel.tsx @@ -33,7 +33,8 @@ import { useMemo, useRef, useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' -import { Check, History, Paperclip, Send, Trash2, Undo2, Workflow } from 'lucide-react' +import { Download, Eye, History, Paperclip, Send, Trash2 } from 'lucide-react' +import { AttachmentPreviewDialog, isPreviewable } from '@/components/pe/AttachmentPreviewDialog' import { Button } from '@/components/ui/Button' import { Dialog } from '@/components/ui/Dialog' import { Label } from '@/components/ui/Label' @@ -104,6 +105,8 @@ export function KhkkWorkflowPanel({ const [pendingFiles, setPendingFiles] = useState
{/* ── 1. Header + meta {code} v{NN} · {name} ───────────────────────────── */}
- Quy trình duyệt+
+
- Quy trình duyệt{plan.workflowCode && ( - +{plan.workflowCode} - {plan.workflowVersion != null && ` v${plan.workflowVersion}`} - {plan.workflowName && · {plan.workflowName}} - + {plan.workflowVersion != null && ` v${String(plan.workflowVersion).padStart(2, '0')}`} + {plan.workflowName && <> · {plan.workflowName}>} + )} - +
- {/* ── 2. Banner kết-thúc-sớm ──────────────────────────────────────────── */}
- {plan.endedByLevelFinalize && (
-
-
- )}
+ {/* ── 2. Banner kết-thúc-sớm ──────────────────────────────────────────── */}
+ {/* [S175 · B1] violet → EMERALD khuôn PE (`:360-377`). PE dành violet RIÊNG cho
+ "⚑ Duyệt thay CEO"; dùng violet cả ở đây là trộn 2 nghĩa vào một màu. */}
+ {plan.endedByLevelFinalize && (
+
- ⚑ Kết thúc tại {plan.finalizeStepName ?? 'cấp được cấu hình'}
- {plan.finalizeLevelName ? ` · ${plan.finalizeLevelName}` : ''}
-
-
- {isApproved
- ? 'Kế hoạch đã duyệt xong ngay tại cấp này — không qua CEO / Ban Giám đốc.'
- : 'Cấp này được cấu hình duyệt là KẾT THÚC — nếu người duyệt tích ô kết thúc thì kế hoạch không qua CEO.'}
-
-
+ {isApproved ? (
+
+ ✅ Kế hoạch đã kết thúc tại{' '}
+ {plan.finalizeStepName ?? 'cấp duyệt'}
+ {plan.finalizeLevelName ? <> · {plan.finalizeLevelName}> : null} — không qua CEO.
+
+ ) : (
+
+ ⚑ Quy trình rút gọn: duyệt đến{' '}
+ {plan.finalizeStepName ?? 'cấp này'}
+ {plan.finalizeLevelName ? <> · {plan.finalizeLevelName}> : null} là KẾT THÚC,
+ không trình CEO. Các cấp trước vẫn duyệt như thường.
+
+ )}
+
+ )}
{/* ── 3. Sơ đồ Bước → Cấp → NV ───────────────────────────────────────── */}
{steps.length > 0 ? (
@@ -317,9 +326,10 @@ export function KhkkWorkflowPanel({
{STATUS_ICON[st]}
Bước {step.order} — {step.name}
- {/* Chip PHÒNG của Bước (BE-4 join `Departments`). */}
+ {/* Chip PHÒNG của Bước (BE-4 join `Departments`).
+ [S175 · B1] viền-xám-nền-trắng → EMERALD đặc khuôn PE (`:406-410`). */}
{step.departmentName && (
-
+
{step.departmentName}
)}
@@ -458,11 +468,11 @@ export function KhkkWorkflowPanel({
}}
title={blockedByLevel ? 'Cấp hiện tại chỉ người được phân công mới thao tác được.' : 'Duyệt cấp hiện tại'}
className={cn(
- 'inline-flex items-center gap-1 rounded border px-2 py-1 text-[11px] font-bold transition',
+ 'rounded border px-2 py-1 text-[11px] font-bold transition',
blockedByLevel ? 'cursor-not-allowed border-slate-200 bg-slate-50 text-slate-400' : 'border-emerald-300 text-emerald-700 hover:bg-emerald-50',
)}
>
- ` +nút `
-
-
-
+
)}
@@ -531,61 +562,63 @@ export function KhkkWorkflowPanel({
{/* [B4/D-11] BE ĐÃ LỌC hàng `decision = Pending` (hành-vi GỬI duyệt) ⇒ số (N) ở đây
CÙNG NGHĨA với (N) của Duyệt NCC. FE KHÔNG dedupe lại; sự kiện "Gửi duyệt" vẫn
còn nguyên ở "Lịch sử thay đổi" bên dưới. */}
- 📎 File đính kèm khi duyệt+File do người duyệt tải lên trong quá trình duyệt. +
{approvalFiles.map(a => (
-
+ {previewAtt && (
+
-
)}
+ {/* [S175 · B1 micro-copy] Nhãn Ô NHẬP theo PE (`PeWorkflowPanel.tsx:869` "Ghi chú
+ (tùy chọn)"). 🔴 CHỈ đổi tên TRƯỜNG — chuỗi "Ý kiến cấp duyệt" ở dòng trợ-giúp
+ bên dưới là TÊN THẬT của mục đích đến trong phiếu (Section 5, Mig 26), đổi nó
+ là trỏ người dùng tới một mục không tồn tại. */}
-
+ {/* [S175 · B1] Bỏ pill đếm → `(n)` trong ngoặc · thẻ `bg-slate-50/60 text-[11px]`
+ → `bg-white p-3 text-sm` · mốc-giờ ĐẨY PHẢI bằng `justify-between`.
+ 🔴 Badge 2-ngả → 3-NGẢ: trước đây MỌI thứ không phải `Reject` đều tô emerald
+ ⇒ "Trả lại" (amber ở PE) bị vẽ xanh như đã-duyệt — đọc ngược nghĩa.
+ `toPhase` đã có sẵn (`khkk.ts:307`) nên phân được TraLai ⟂ TuChoi ⟂ Duyệt. */}
+
+
{/* ── 7. Lịch sử thay đổi ────────────────────────────────────────────── */}
- Lịch sử duyệt ({approvals.length}){approvals.length === 0 ? (Chưa có lượt duyệt nào. ) : ( -
-
{action !== null && (
|