[CLAUDE] Contract: UAT S174 dot-B — be style PE sang KHKK (FE-only, 0-mig)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m37s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m37s
Nguon: spec runs/2026-08-05-S174-khkk-style-audit/spec-khkk-style-dong-bo-05-08-2026.md (ensemble /fable-clone frontend-designer 5 lane). Owner S175: "KHKK theo khuon PE". Mirror 7 CAP file, sha256 khop 7/7. Build XANH ca 2 app, 0 error TS. === B1 panel Quy trinh duyet (KhkkWorkflowPanel) === X2 GO card-accent -> panel TRAN khuon PE (:345). 🔴 GO HAN CLASS, khong de border-0/shadow-none: .card-accent (index.css:112) nam NGOAI @layer nen thang utility Tailwind v4 (gotcha #66). - Tieu de h3 tran + bo icon-chip; so hieu WF padStart(2,'0') => v01 (PE :350). - Banner ket-thuc-som violet -> EMERALD (PE :360-377). PE danh violet RIENG cho "Duyet thay CEO"; dung violet ca 2 cho la tron 2 nghia vao mot mau. - Chip PHONG vien-xam-nen-trang -> emerald dac (PE :406-410). - Nut lucide -> glyph '✓ Duyet' / '← Tra lai' (PE :540); nut Xoa xam -> DO (PE :574). - File dinh kem: +border-t pt-4 +h3 +nut <Eye> xem truoc. Route BE DA CO ContractSigningPlansController.cs:231 (/view, cung hinh PE :317). Nhan "Tai xuong" chuyen vao title= chu KHONG bo — giu chuoi do trong bundle de phep verify prod con control-duong soi duoc. - Lich su duyet: bo pill dem -> (n) trong ngoac; the bg-slate-50/60 -> bg-white p-3 text-sm; moc-gio day phai bang justify-between. 🔴 Badge 2-nga -> 3-NGA: truoc day MOI thu khong phai Reject deu to emerald => "Tra lai" (amber o PE) bi ve xanh nhu da-duyet, doc NGUOC nghia. toPhase da co san (khkk.ts:307). - Lich su thay doi: BO GAP, render thang (PE PeDetailTabs.tsx:787-794). 🔴 BAT BUOC bo luon `enabled: showChangelog` cua useQuery: giu lai thi khoi luon hien nhung query KHONG BAO GIO chay, va no hong duoi dang "Chua co thay doi nao duoc ghi." — tuc trong y het phieu chua tung sua, KHONG phai trong nhu loi. Doi lay +1 request/phieu, dung gia PE dang tra. - Micro-copy: Huy / tuy chon; nhan O NHAP -> "Ghi chu" (PE :869). 🔴 CHI doi ten TRUONG — chuoi "Y kien cap duyet" o dong tro-giup la TEN THAT cua muc dich den trong phieu (Section 5, Mig 26), doi no la tro nguoi dung toi mot muc khong ton tai. - KHONG be chip "NS PRO"/"NS CCM" (2 co do mo quyen sua ngan sach per-goi-thau, khai niem KHKK khong co). GIU moc chu-ky KHKK (cho KHKK tot hon PE). === B2 noi dung phieu (KhkkDetailContent) — 6 P0 === - 2 bang: table-fixed + <colgroup> + min-w-[880px] trong overflow-x-auto. Bang co gian tu-do truoc day de cot tien nhay be rong theo do dai ten NCC => 3 cot so khong thang hang. - O RONG -> em-dash MO (text-slate-300) gom vao helper orDash/moneyOrDash: truoc day em-dash thua-huong dung mau chu that => luot bang thi "—" trong y nhu mot GIA-TRI. moneyOrDash rieng vi formatVnd tra CHUOI "—" nen khong to mo duoc. - empty-block -> text-sm italic text-slate-400 (3 cho). - emoji 🔒 -> icon lucide <Lock>: emoji render khac nhau theo font he dieu-hanh va KHONG an currentColor nen luon lech tong voi chu amber ben canh. - thong nhat text-xs ca hang bang; +hover:bg-slate-50/60 cho <tr>. === B3 panel danh sach (KhkkListPanel + KhkkWorkListPanel + PipelineTreePanel) === - DAU COT: che-do CAY truoc day di thang vao than, khong tieu-de/so dem => cot "cut dau", lech ca voi man Thao tac (Tier header) LAN voi chinh che-do phang (co dem o chan). Mirror khuon Tier. - Gop 2 o tim chong nhau: +prop hideSearch cho PipelineTreePanel (MAC DINH giu nguyen — 4/6 host kia khong truyen, 0 doi hanh vi). O trong cay KHONG bao gio dung toi vi o ngoai di server-side va go vao la chuyen han sang flatMode => cay bien mat. - Thang chu 4 nac (13/11/10/9) -> 3 nac chuan 13/11/10. - 🔴 WCAG: text-slate-400 = 2.56:1 tren nen trang, DUOI nguong AA 4.5:1 — nang len slate-500 (4.76:1) cho 7 dong MANG THONG TIN (6 note/empty-state + 1 so dem). GIU slate-400 cho 2 cho TRANG TRI (icon Search, dau cham phan cach) — WCAG khong doi tuong phan cho noi dung trang tri. - Nut phan trang ky-tu tran ←/→ -> icon ChevronLeft/Right + aria-label: mui ten dang chu doi hinh theo font he dieu-hanh va KHONG co ten cho trinh doc man hinh. - Chip nhom: inline + co chu khac dong chua => hop chip cao khac chu ben canh nen tut baseline; inline-flex items-center leading-none ghim thang hang. === B4 host (KhkkCreatePage) === - Grid 260/320 DA land tu dot A — chi con COMMENT stale ghi 300px, da va. - 🔴 Va comment SAI SU THAT (L3 H-5): comment khai Panel 1 co "nut hanh-dong dinh day" nhung grep -c Button KhkkWorkListPanel = 0 (control-duong PeListPanel = 8). Do la mo ta CUA PE be nguyen sang => tu khai mot thu chua dung. === 2 component DUNG CHUNG — mo rong kieu MAC-DINH-GIU-NGUYEN === AttachmentPreviewDialog +basePath? (mac dinh /purchase-evaluations) => 6 call-site PE KHONG phai sua dong nao (module da golive; bat sua het call-site cu = tu de rui-ro o cho dang chay tot). PipelineTreePanel +hideSearch? y het. Da verify: 0 call-site PE bi dong. === CON NO === 🔴 CHUA soi mat tren prod 375/1366/1440 — ca 5 lane deu NO-SCREENSHOT (dev-rig chan: chi :8080 LISTENING, API :5443 khong co, trang sau ProtectedRoute — gotcha #3). Moi ket luan la DOC MA, khong phai da nhin. Dot C3 (Field to brand cho MOI value) + dot D (O3/O4/O6/O7/O8) chua lam. O3 con cho owner xac nhan cach doc "sum tong" — do la tien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@ -21,7 +21,7 @@
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
ClipboardList, FileCheck, Paperclip, Pencil, Plus, Save, Trash2, Upload, Users,
|
||||
ClipboardList, FileCheck, Lock, Paperclip, Pencil, Plus, Save, Trash2, Upload, Users,
|
||||
} from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { PePipelineStrip } from '@/components/pe/PePipelineStrip'
|
||||
@ -48,6 +48,21 @@ function formatVnd(n: number | null | undefined): string {
|
||||
return n.toLocaleString('vi-VN') + ' đ'
|
||||
}
|
||||
|
||||
// [S175 · B2 · P0] Ô RỖNG phải MỜ HƠN dữ-liệu thật. Trước đây em-dash thừa-hưởng đúng
|
||||
// màu chữ của ô (`text-slate-600/700`) ⇒ lướt bảng thì "—" trông y như một GIÁ-TRỊ.
|
||||
// Gom về một chỗ để các ô rỗng không mỗi nơi một kiểu.
|
||||
const DASH = <span className="text-slate-300">—</span>
|
||||
|
||||
/** Chuỗi có thể rỗng → em-dash mờ. */
|
||||
function orDash(v: string | null | undefined): ReactNode {
|
||||
return v == null || v === '' ? DASH : v
|
||||
}
|
||||
|
||||
/** Tiền: `formatVnd` trả CHUỖI "—" khi rỗng nên không tô mờ được — tách nhánh rỗng ra. */
|
||||
function moneyOrDash(n: number | null | undefined): ReactNode {
|
||||
return n == null ? DASH : formatVnd(n)
|
||||
}
|
||||
|
||||
function formatDateTime(iso: string): string {
|
||||
return new Date(iso).toLocaleString('vi-VN')
|
||||
}
|
||||
@ -362,17 +377,32 @@ export function KhkkDetailContent({
|
||||
chipFg="var(--color-teal-700)"
|
||||
actions={
|
||||
plan.budgetFrozen ? (
|
||||
<span className="inline-flex items-center rounded-md border border-amber-300 bg-amber-100 px-2 py-0.5 text-[11px] font-semibold normal-case tracking-normal text-amber-800">
|
||||
🔒 Giá đã chốt tại thời điểm duyệt
|
||||
// [S175 · B2 · P0] emoji 🔒 → icon lucide: emoji render khác nhau theo font hệ
|
||||
// điều-hành (Windows/macOS/Android ra 3 hình), và nó KHÔNG ăn `currentColor`
|
||||
// nên luôn lệch tông với chữ amber bên cạnh.
|
||||
<span className="inline-flex items-center gap-1 rounded-md border border-amber-300 bg-amber-100 px-2 py-0.5 text-[11px] font-semibold normal-case tracking-normal text-amber-800">
|
||||
<Lock className="h-3 w-3" aria-hidden /> Giá đã chốt tại thời điểm duyệt
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{plan.lines.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">Phiếu nguồn chưa có NCC trúng thầu.</div>
|
||||
<div className="text-sm italic text-slate-400">Phiếu nguồn chưa có NCC trúng thầu.</div>
|
||||
) : (
|
||||
// [S175 · B2 · P0] `table-fixed` + `<colgroup>` + `min-w-[880px]`: bảng co giãn tự-do
|
||||
// trước đây để cột tiền nhảy bề rộng theo độ dài tên NCC ⇒ 3 cột số không thẳng hàng
|
||||
// giữa các phiếu. Chốt bề rộng rồi thì hẹp màn hình sẽ CUỘN NGANG (đúng ý đồ của
|
||||
// `overflow-x-auto` bọc ngoài) thay vì bóp chữ.
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<table className="w-full min-w-[880px] table-fixed text-xs">
|
||||
<colgroup>
|
||||
<col className="w-[22%]" />
|
||||
<col className="w-[22%]" />
|
||||
<col className="w-[14%]" />
|
||||
<col className="w-[14%]" />
|
||||
<col className="w-[14%]" />
|
||||
<col className="w-[14%]" />
|
||||
</colgroup>
|
||||
<thead className="border-b border-slate-200 bg-slate-50/70">
|
||||
<tr>
|
||||
<th className="label-eyebrow px-3 py-2 text-left">Nhà cung cấp</th>
|
||||
@ -385,16 +415,16 @@ export function KhkkDetailContent({
|
||||
</thead>
|
||||
<tbody>
|
||||
{plan.lines.map((l) => (
|
||||
<tr key={l.id} className="border-b border-slate-100 last:border-0">
|
||||
<tr key={l.id} className="border-b border-slate-100 last:border-0 hover:bg-slate-50/60">
|
||||
<td className="px-3 py-2 font-medium text-brand-800">
|
||||
{l.supplierName ?? '—'}
|
||||
{orDash(l.supplierName)}
|
||||
{l.contractId && (
|
||||
<a href={`/contracts/${l.contractId}`} className="ml-2 text-[10px] font-medium text-brand-600 hover:underline" title="Dòng này đã được đưa vào hợp đồng">
|
||||
✓ HĐ
|
||||
</a>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-700">
|
||||
<td className="px-3 py-2 text-slate-700">
|
||||
{isDraft && !l.contractId ? (
|
||||
<>
|
||||
<select
|
||||
@ -426,12 +456,12 @@ export function KhkkDetailContent({
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : l.tenHangMuc ? l.tenHangMuc : <span className="text-slate-300">—</span>}
|
||||
) : orDash(l.tenHangMuc)}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right tabular-nums text-slate-600">{formatVnd(l.peReferenceAmount)}</td>
|
||||
<td className="px-3 py-2 text-right tabular-nums text-brand-800">{formatVnd(l.proposedAmount)}</td>
|
||||
<td className="px-3 py-2 text-right tabular-nums text-emerald-700">{formatVnd(l.approvedAmount)}</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{l.note ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-right tabular-nums text-slate-600">{moneyOrDash(l.peReferenceAmount)}</td>
|
||||
<td className="px-3 py-2 text-right tabular-nums text-brand-800">{moneyOrDash(l.proposedAmount)}</td>
|
||||
<td className="px-3 py-2 text-right tabular-nums text-emerald-700">{moneyOrDash(l.approvedAmount)}</td>
|
||||
<td className="px-3 py-2 text-slate-600">{orDash(l.note)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@ -462,10 +492,18 @@ export function KhkkDetailContent({
|
||||
}
|
||||
>
|
||||
{plan.dossierItems.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">Chưa khai căn cứ nào.</div>
|
||||
<div className="text-sm italic text-slate-400">Chưa khai căn cứ nào.</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<table className="w-full min-w-[880px] table-fixed text-xs">
|
||||
<colgroup>
|
||||
<col className="w-[14%]" />
|
||||
<col className={isDraft ? 'w-[26%]' : 'w-[30%]'} />
|
||||
<col className="w-[14%]" />
|
||||
<col className={isDraft ? 'w-[16%]' : 'w-[20%]'} />
|
||||
<col className={isDraft ? 'w-[18%]' : 'w-[22%]'} />
|
||||
{isDraft && <col className="w-[12%]" />}
|
||||
</colgroup>
|
||||
<thead className="border-b border-slate-200 bg-slate-50/70">
|
||||
<tr>
|
||||
<th className="label-eyebrow px-3 py-2 text-left">Loại</th>
|
||||
@ -478,19 +516,19 @@ export function KhkkDetailContent({
|
||||
</thead>
|
||||
<tbody>
|
||||
{plan.dossierItems.map((d: KhkkDossierItemDto) => (
|
||||
<tr key={d.id} className="border-b border-slate-100 last:border-0">
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{DOSSIER_KIND_LABELS[d.kind] ?? '—'}</td>
|
||||
<tr key={d.id} className="border-b border-slate-100 last:border-0 hover:bg-slate-50/60">
|
||||
<td className="px-3 py-2 text-slate-600">{orDash(DOSSIER_KIND_LABELS[d.kind])}</td>
|
||||
<td className="px-3 py-2 font-medium text-brand-800">{d.name}</td>
|
||||
<td className="px-3 py-2">
|
||||
<span className={cn('inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium', DOSSIER_STATUS_BADGE[d.status])}>
|
||||
{DOSSIER_STATUS_LABELS[d.status]}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">
|
||||
{d.tvgsName ?? '—'}
|
||||
<td className="px-3 py-2 text-slate-600">
|
||||
{orDash(d.tvgsName)}
|
||||
{d.tvgsResultAt && <span className="ml-1 text-[11px] text-slate-400">({formatDateTime(d.tvgsResultAt)})</span>}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{d.note ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-slate-600">{orDash(d.note)}</td>
|
||||
{isDraft && (
|
||||
<td className="px-3 py-2 text-right">
|
||||
<div className="flex justify-end gap-1">
|
||||
@ -544,7 +582,7 @@ export function KhkkDetailContent({
|
||||
}
|
||||
>
|
||||
{planAttachments.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">Chưa có file đính kèm.</div>
|
||||
<div className="text-sm italic text-slate-400">Chưa có file đính kèm.</div>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{planAttachments.map((a) => (
|
||||
|
||||
Reference in New Issue
Block a user