diff --git a/fe-admin/src/components/khkk/KhkkDetailContent.tsx b/fe-admin/src/components/khkk/KhkkDetailContent.tsx
index dcf9d04..41c066a 100644
--- a/fe-admin/src/components/khkk/KhkkDetailContent.tsx
+++ b/fe-admin/src/components/khkk/KhkkDetailContent.tsx
@@ -182,7 +182,8 @@ export function KhkkDetailContent({
const [addFor, setAddFor] = useState<{ supplierId: string } | null>(null)
const [addCatalogId, setAddCatalogId] = useState('')
const [addAmount, setAddAmount] = useState('')
- const [addNote, setAddNote] = useState('')
+ // 🧊 [S178] `addNote` ĐÃ GỠ cùng ô nhập Ghi chú — giữ lại sẽ là biến gán-mà-không-đọc,
+ // đúng lớp vừa dọn ở đợt-5 (`addFor.supplierName`).
const phase = plan.phase as KhkkPhaseValue
const draftPhase = phase === KhkkPhase.DangSoanThao || phase === KhkkPhase.TraLai
@@ -217,17 +218,21 @@ export function KhkkDetailContent({
// 🔴 Body null-safe: chỉ gửi field ĐANG sửa; field không gửi thì BE giữ nguyên
// (`UpdateContractSigningPlanLineCommand` — khuôn bug-class #73/S42).
const saveLineField = useMutation({
- mutationFn: async (p: { lineId: string; proposedAmount?: number; note?: string }) =>
+ // 🧊 [S178] Bỏ `note` khỏi type + body: sau khi gỡ ô nhập thì 0 caller nào truyền nữa.
+ // 🔴 KHÔNG đổi hành vi — `note: undefined` vốn đã bị JSON bỏ qua, và BE gác
+ // `if (request.Note != null)` nên vắng mặt = BỎ QUA, không phải ghi null. Trường `Note`
+ // của BE giữ NGUYÊN (máy rộng), chỉ FE thôi gửi.
+ mutationFn: async (p: { lineId: string; proposedAmount?: number }) =>
api.put(`/contract-signing-plans/${id}/lines/${p.lineId}`, {
proposedAmount: p.proposedAmount,
- note: p.note,
}),
onSuccess: () => { toast.success('Đã lưu dòng'); invalidate() },
onError: (e) => toast.error(getErrorMessage(e)),
})
const addLine = useMutation({
- mutationFn: async (p: { supplierId: string; catalogEntryId: string; proposedAmount: number; note?: string }) =>
+ // 🧊 [S178] Bỏ `note` — cùng lý lẽ với `saveLineField` ngay trên.
+ mutationFn: async (p: { supplierId: string; catalogEntryId: string; proposedAmount: number }) =>
api.post(`/contract-signing-plans/${id}/lines`, p),
onSuccess: () => { toast.success('Đã thêm hạng mục'); setAddFor(null); invalidate() },
onError: (e) => toast.error(getErrorMessage(e)),
@@ -545,45 +550,34 @@ export function KhkkDetailContent({
{i + 1}
{isDraft && !l.contractId ? (
- <>
-
{addLine.isPending ? 'Đang thêm…' : 'Thêm'}
diff --git a/fe-user/src/components/khkk/KhkkDetailContent.tsx b/fe-user/src/components/khkk/KhkkDetailContent.tsx
index dcf9d04..41c066a 100644
--- a/fe-user/src/components/khkk/KhkkDetailContent.tsx
+++ b/fe-user/src/components/khkk/KhkkDetailContent.tsx
@@ -182,7 +182,8 @@ export function KhkkDetailContent({
const [addFor, setAddFor] = useState<{ supplierId: string } | null>(null)
const [addCatalogId, setAddCatalogId] = useState('')
const [addAmount, setAddAmount] = useState('')
- const [addNote, setAddNote] = useState('')
+ // 🧊 [S178] `addNote` ĐÃ GỠ cùng ô nhập Ghi chú — giữ lại sẽ là biến gán-mà-không-đọc,
+ // đúng lớp vừa dọn ở đợt-5 (`addFor.supplierName`).
const phase = plan.phase as KhkkPhaseValue
const draftPhase = phase === KhkkPhase.DangSoanThao || phase === KhkkPhase.TraLai
@@ -217,17 +218,21 @@ export function KhkkDetailContent({
// 🔴 Body null-safe: chỉ gửi field ĐANG sửa; field không gửi thì BE giữ nguyên
// (`UpdateContractSigningPlanLineCommand` — khuôn bug-class #73/S42).
const saveLineField = useMutation({
- mutationFn: async (p: { lineId: string; proposedAmount?: number; note?: string }) =>
+ // 🧊 [S178] Bỏ `note` khỏi type + body: sau khi gỡ ô nhập thì 0 caller nào truyền nữa.
+ // 🔴 KHÔNG đổi hành vi — `note: undefined` vốn đã bị JSON bỏ qua, và BE gác
+ // `if (request.Note != null)` nên vắng mặt = BỎ QUA, không phải ghi null. Trường `Note`
+ // của BE giữ NGUYÊN (máy rộng), chỉ FE thôi gửi.
+ mutationFn: async (p: { lineId: string; proposedAmount?: number }) =>
api.put(`/contract-signing-plans/${id}/lines/${p.lineId}`, {
proposedAmount: p.proposedAmount,
- note: p.note,
}),
onSuccess: () => { toast.success('Đã lưu dòng'); invalidate() },
onError: (e) => toast.error(getErrorMessage(e)),
})
const addLine = useMutation({
- mutationFn: async (p: { supplierId: string; catalogEntryId: string; proposedAmount: number; note?: string }) =>
+ // 🧊 [S178] Bỏ `note` — cùng lý lẽ với `saveLineField` ngay trên.
+ mutationFn: async (p: { supplierId: string; catalogEntryId: string; proposedAmount: number }) =>
api.post(`/contract-signing-plans/${id}/lines`, p),
onSuccess: () => { toast.success('Đã thêm hạng mục'); setAddFor(null); invalidate() },
onError: (e) => toast.error(getErrorMessage(e)),
@@ -545,45 +550,34 @@ export function KhkkDetailContent({
{i + 1}
{isDraft && !l.contractId ? (
- <>
- e.target.value && assignLine.mutate({ lineId: l.id, catalogEntryId: e.target.value })}
- disabled={assignLine.isPending}
- aria-label={`Chọn hạng mục cho dòng ${l.id}`}
- // [S177 — anh: "Các cái mẫu bỏ đi nhé"] DÒNG ĐÃ CÓ = đọc như DỮ
- // LIỆU, không như form: viền trong suốt + nền trong suốt, khung chỉ
- // hiện khi rê chuột / vào ô. Đúng khuôn ô Ghi chú ngay dưới (đã ghost
- // từ S175) ⇒ 3 trường của một dòng nay CÙNG một ngôn ngữ.
- // 🔴 Chỉ đổi VẺ, KHÔNG đổi hành vi: vẫn `` thật, vẫn sửa
- // được, vẫn tự lưu. Mũi tên dropdown của native select vẫn hiện nên
- // không mất tín-hiệu "ô này bấm được".
- className="h-8 w-full rounded border border-transparent bg-transparent px-2 text-xs text-slate-700 hover:border-slate-200 focus-visible:border-brand-500 focus-visible:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/30"
- >
-
- {l.catalogEntryId && !(catalogOptions.data ?? []).some((c) => c.id === l.catalogEntryId) && (
-
- )}
- {(catalogOptions.data ?? []).map((c) => (
-
- ))}
-
- {/* Ghi chú xuống DÒNG PHỤ thay vì chiếm 1 cột — anh chốt bảng chỉ
- còn "hạng mục + giá", nhưng ghi chú thì anh vẫn muốn nhập được. */}
- {
- const v = e.target.value.trim()
- if (v !== (l.note ?? '')) saveLineField.mutate({ lineId: l.id, note: v })
- }}
- className="mt-1 h-7 w-full rounded border border-transparent bg-transparent px-2 text-[11px] text-slate-500 hover:border-slate-200 focus-visible:border-brand-500 focus-visible:bg-white focus-visible:outline-none"
- />
- >
+ // 🧊 [S178 — anh: "bỏ luôn chỗ ghi chú luôn"] Ô nhập Ghi chú của DÒNG
+ // ĐÃ CÓ đã GỠ. Dòng nháp nay chỉ còn đúng 2 ô anh cần: hạng mục + giá.
+ // 🔴 DỮ LIỆU KHÔNG MẤT: `Note` vẫn nguyên trong DB, và sửa giá KHÔNG
+ // xoá nó — `UpdateContractSigningPlanLineCommandHandler` gác
+ // `if (request.Note != null)` nên `note` vắng mặt là BỎ QUA, không phải
+ // ghi null. Ghi chú cũ vẫn hiện ở nhánh chỉ-đọc bên dưới (`l.note`).
+ // ⇒ Đây là "máy rộng, UI hẹp": BE giữ trường, UI thôi bày.
+ e.target.value && assignLine.mutate({ lineId: l.id, catalogEntryId: e.target.value })}
+ disabled={assignLine.isPending}
+ aria-label={`Chọn hạng mục cho dòng ${l.id}`}
+ // [S177 — anh: "Các cái mẫu bỏ đi nhé"] DÒNG ĐÃ CÓ = đọc như DỮ
+ // LIỆU, không như form: viền trong suốt + nền trong suốt, khung chỉ
+ // hiện khi rê chuột / vào ô — cùng ngôn ngữ với ô giá bên phải.
+ // 🔴 Chỉ đổi VẺ, KHÔNG đổi hành vi: vẫn `` thật, vẫn sửa
+ // được, vẫn tự lưu. Mũi tên dropdown của native select vẫn hiện nên
+ // không mất tín-hiệu "ô này bấm được".
+ className="h-8 w-full rounded border border-transparent bg-transparent px-2 text-xs text-slate-700 hover:border-slate-200 focus-visible:border-brand-500 focus-visible:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/30"
+ >
+
+ {l.catalogEntryId && !(catalogOptions.data ?? []).some((c) => c.id === l.catalogEntryId) && (
+
+ )}
+ {(catalogOptions.data ?? []).map((c) => (
+
+ ))}
+
) : (
<>
{/* [S177 — anh: "mã hạng mục (Dự án) - mã hạng mục chi tiết"]
@@ -632,7 +626,7 @@ export function KhkkDetailContent({
type="button"
onClick={() => {
setAddFor({ supplierId: l.supplierId })
- setAddCatalogId(''); setAddAmount(''); setAddNote('')
+ setAddCatalogId(''); setAddAmount('')
}}
title={`Thêm hạng mục cho ${l.supplierName ?? 'NCC này'}`}
aria-label={`Thêm hạng mục cho ${l.supplierName ?? 'NCC này'}`}
@@ -697,16 +691,10 @@ export function KhkkDetailContent({
))}
- {/* Ghi chú xuống DÒNG PHỤ — y hệt dòng dữ liệu thật ở trên, để hai thứ
- đọc thành CÙNG một hình dạng chứ không phải hai kiểu form. */}
- setAddNote(e.target.value)}
- placeholder="Ghi chú (tùy chọn)…"
- aria-label="Ghi chú của dòng mới"
- className="mt-1 h-7 w-full rounded border border-transparent bg-transparent px-2 text-[11px] text-slate-500 hover:border-slate-200 focus-visible:border-brand-500 focus-visible:bg-white focus-visible:outline-none"
- />
+ {/* 🧊 [S178 — anh: "bỏ luôn chỗ ghi chú luôn"] Ô Ghi chú của DÒNG THÊM đã
+ GỠ, cùng lượt với ô Ghi chú của dòng đã có ở trên — giữ hai chỗ CÙNG
+ hình dạng vẫn là luật ở đây, chỉ là hình dạng chung nay gọn hơn.
+ 🔴 Thao tác anh chốt còn đúng 3 nhịp: chọn hạng mục → nhập giá → Thêm. */}