[CLAUDE] FE-Admin: KHKK go o Ghi chu ca 2 cho, dong nhap con hang muc + gia + Them (FE-only, 0-mig)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m42s

Anh khoanh 3 khoi, ca 3 deu la o Ghi chu:
  - o Ghi chu duoi DONG 1 (trong)
  - o Ghi chu duoi DONG 2 (dang co du lieu "2323223")
  - khoi DONG THEM -> "giu lai cho chon hang muc - so tien - them huy thoi nhe,
    bo luon cho ghi chu luon"
Anh chot lai thao tac: chon hang muc -> gia -> Them -> append phia duoi theo so thu tu.

Da lam:
  - GO o nhap Ghi chu o DONG DA CO (nhanh isDraft) va o DONG THEM. Bang nay gio
    dung 4 cot: STT | Hang muc | Gia | hanh-dong.
  - Don `addNote`/`setAddNote` + `note` khoi payload `addLine` va khoi type+body
    `saveLineField` — sau khi go o nhap thi 0 caller nao truyen nua, giu lai la bien
    gan-ma-khong-doc (dung lop vua don o dot-5 voi `addFor.supplierName`).
  - Va chu-thich o `:556` von viet "Dung khuon o Ghi chu ngay duoi ... 3 truong cua
    mot dong nay CUNG mot ngon ngu" — no tro vao chinh o vua go, de nguyen la de ra
    doc stale.

Thao tac anh mo ta DA DUNG SAN, khong phai sua them: `addLine.onSuccess` goi
`setAddFor(null)` + `invalidate()`, BE sap `OrderBy(CreatedAt).ThenBy(Id)` nen dong
moi roi xuong CUOI bang va STT (= i+1, thu tu hien thi) tu len so ke tiep.

DU LIEU KHONG MAT — da doc BE truoc khi go:
  `UpdateContractSigningPlanLineCommandHandler` gac `if (request.Note != null)` nen
  `note` vang mat la BO QUA, KHONG phai ghi null => sua gia khong xoa ghi chu cu.
  Truong `Note` giu nguyen trong DB (may rong, UI hep). Ghi chu cu VAN hien o nhanh
  chi-doc (`l.note`) cua phieu da duyet — khong giau du lieu, chi bo cho nhap.

Do duoc (truoc/sau + control duong):
  - 🔴 BAY CHUOI DUNG CHUNG, suyt dinh lan thu 4: dem 'Ghi chu (tuy chon)' o dist ra
    2 KHONG PHAI vi go hong — `PeWorkflowPanel.tsx:869` va `HardCopiesPage.tsx:371`
    cung dung chuoi do. Chuoi phan biet cua KHKK la ban CO dau '…' cuoi (placeholder).
  - nguon KHKK 'Ghi chu (tuy chon)…' : @HEAD = 2 -> now = 0.
  - dist ca 2 app 'Ghi chu (tuy chon)…' = 0/0.
  - CONTROL DUONG: ban khong-dau-… (PE + ban cung) van = 2/2 o ca 2 dist => phep do
    dang chay, khong phai 0-vi-thuoc-hong.
  - CONTROL DUONG 2: '— Chon hang muc —' giu nguyen 2 (dong da co + dong them).

Diff CHI 2 file FE, 0 file BE => .NET test khong the bi anh huong, bo qua vong test do
(khai ro). tsc -b + build 2 app sach · eslint file nay exit 0 · SHA256 2 app IDENTICAL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-08-06 22:28:18 +07:00
parent 6a4366859c
commit 99370deb14
2 changed files with 84 additions and 110 deletions

View File

@ -182,7 +182,8 @@ export function KhkkDetailContent({
const [addFor, setAddFor] = useState<{ supplierId: string } | null>(null) const [addFor, setAddFor] = useState<{ supplierId: string } | null>(null)
const [addCatalogId, setAddCatalogId] = useState('') const [addCatalogId, setAddCatalogId] = useState('')
const [addAmount, setAddAmount] = 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 phase = plan.phase as KhkkPhaseValue
const draftPhase = phase === KhkkPhase.DangSoanThao || phase === KhkkPhase.TraLai 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 // 🔴 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). // (`UpdateContractSigningPlanLineCommand` — khuôn bug-class #73/S42).
const saveLineField = useMutation({ 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}`, { api.put(`/contract-signing-plans/${id}/lines/${p.lineId}`, {
proposedAmount: p.proposedAmount, proposedAmount: p.proposedAmount,
note: p.note,
}), }),
onSuccess: () => { toast.success('Đã lưu dòng'); invalidate() }, onSuccess: () => { toast.success('Đã lưu dòng'); invalidate() },
onError: (e) => toast.error(getErrorMessage(e)), onError: (e) => toast.error(getErrorMessage(e)),
}) })
const addLine = useMutation({ 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), api.post(`/contract-signing-plans/${id}/lines`, p),
onSuccess: () => { toast.success('Đã thêm hạng mục'); setAddFor(null); invalidate() }, onSuccess: () => { toast.success('Đã thêm hạng mục'); setAddFor(null); invalidate() },
onError: (e) => toast.error(getErrorMessage(e)), onError: (e) => toast.error(getErrorMessage(e)),
@ -545,45 +550,34 @@ export function KhkkDetailContent({
<td className="px-3 py-2 text-right align-top tabular-nums text-slate-400">{i + 1}</td> <td className="px-3 py-2 text-right align-top tabular-nums text-slate-400">{i + 1}</td>
<td className="px-3 py-2 text-slate-700"> <td className="px-3 py-2 text-slate-700">
{isDraft && !l.contractId ? ( {isDraft && !l.contractId ? (
<> // 🧊 [S178 — anh: "bỏ luôn chỗ ghi chú luôn"] Ô nhập Ghi chú của DÒNG
<select // ĐÃ CÓ đã GỠ. Dòng nháp nay chỉ còn đúng 2 ô anh cần: hạng mục + giá.
value={l.catalogEntryId ?? ''} // 🔴 DỮ LIỆU KHÔNG MẤT: `Note` vẫn nguyên trong DB, và sửa giá KHÔNG
onChange={(e) => e.target.value && assignLine.mutate({ lineId: l.id, catalogEntryId: e.target.value })} // xoá nó — `UpdateContractSigningPlanLineCommandHandler` gác
disabled={assignLine.isPending} // `if (request.Note != null)` nên `note` vắng mặt là BỎ QUA, không phải
aria-label={`Chọn hạng mục cho dòng ${l.id}`} // ghi null. Ghi chú cũ vẫn hiện ở nhánh chỉ-đọc bên dưới (`l.note`).
// [S177 — anh: "Các cái mẫu bỏ đi nhé"] DÒNG ĐÃ CÓ = đọc như DỮ // ⇒ Đây là "máy rộng, UI hẹp": BE giữ trường, UI thôi bày.
// LIỆU, không như form: viền trong suốt + nền trong suốt, khung chỉ <select
// hiện khi rê chuột / vào ô. Đúng khuôn ô Ghi chú ngay dưới (đã ghost value={l.catalogEntryId ?? ''}
// từ S175) ⇒ 3 trường của một dòng nay CÙNG một ngôn ngữ. onChange={(e) => e.target.value && assignLine.mutate({ lineId: l.id, catalogEntryId: e.target.value })}
// 🔴 Chỉ đổi VẺ, KHÔNG đổi hành vi: vẫn `<select>` thật, vẫn sửa disabled={assignLine.isPending}
// được, vẫn tự lưu. Mũi tên dropdown của native select vẫn hiện nên aria-label={`Chọn hạng mục cho dòng ${l.id}`}
// không mất tín-hiệu "ô này bấm được". // [S177 — anh: "Các cái mẫu bỏ đi nhé"] DÒNG ĐÃ CÓ = đọc như DỮ
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" // 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.
<option value=""> Chọn hạng mục </option> // 🔴 Chỉ đổi VẺ, KHÔNG đổi hành vi: vẫn `<select>` thật, vẫn sửa
{l.catalogEntryId && !(catalogOptions.data ?? []).some((c) => c.id === l.catalogEntryId) && ( // được, vẫn tự lưu. Mũi tên dropdown của native select vẫn hiện nên
<option value={l.catalogEntryId}>{l.tenHangMuc ?? '(hạng mục đã gán)'}</option> // 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"
{(catalogOptions.data ?? []).map((c) => ( >
<option key={c.id} value={c.id}>{c.code} {c.tenVi}</option> <option value=""> Chọn hạng mục </option>
))} {l.catalogEntryId && !(catalogOptions.data ?? []).some((c) => c.id === l.catalogEntryId) && (
</select> <option value={l.catalogEntryId}>{l.tenHangMuc ?? '(hạng mục đã gán)'}</option>
{/* 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. */} {(catalogOptions.data ?? []).map((c) => (
<input <option key={c.id} value={c.id}>{c.code} {c.tenVi}</option>
type="text" ))}
maxLength={1000} </select>
defaultValue={l.note ?? ''}
placeholder="Ghi chú (tùy chọn)…"
disabled={saveLineField.isPending}
aria-label="Ghi chú dòng"
onBlur={(e) => {
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"
/>
</>
) : ( ) : (
<> <>
{/* [S177 — anh: "mã hạng mục (Dự án) - mã hạng mục chi tiết"] {/* [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" type="button"
onClick={() => { onClick={() => {
setAddFor({ supplierId: l.supplierId }) setAddFor({ supplierId: l.supplierId })
setAddCatalogId(''); setAddAmount(''); setAddNote('') setAddCatalogId(''); setAddAmount('')
}} }}
title={`Thêm hạng mục cho ${l.supplierName ?? 'NCC này'}`} 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'}`} aria-label={`Thêm hạng mục cho ${l.supplierName ?? 'NCC này'}`}
@ -697,16 +691,10 @@ export function KhkkDetailContent({
<option key={c.id} value={c.id}>{c.code} {c.tenVi}</option> <option key={c.id} value={c.id}>{c.code} {c.tenVi}</option>
))} ))}
</select> </select>
{/* Ghi chú xuống DÒNG PHỤ — y hệt dòng dữ liệu thật ở trên, để hai thứ {/* 🧊 [S178 — anh: "bỏ luôn chỗ ghi chú luôn"] Ô Ghi chú của DÒNG THÊM đã
đọc thành CÙNG một hình dng chứ không phải hai kiểu form. */} GỠ, cùng lượt với ô Ghi chú của dòng đã có ở trên — giữ hai chỗ CÙNG
<input hình dạng vẫn là luật ở đây, chỉ là hình dạng chung nay gọn hơn.
type="text" maxLength={1000} 🔴 Thao tác anh chốt còn đúng 3 nhịp: chọn hạng mục → nhập giá → Thêm. */}
value={addNote}
onChange={(e) => 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"
/>
</td> </td>
<td className="px-3 py-2 align-top"> <td className="px-3 py-2 align-top">
<input <input
@ -727,7 +715,6 @@ export function KhkkDetailContent({
supplierId: addFor.supplierId, supplierId: addFor.supplierId,
catalogEntryId: addCatalogId, catalogEntryId: addCatalogId,
proposedAmount: Number(addAmount), proposedAmount: Number(addAmount),
note: addNote.trim() || undefined,
})} })}
> >
{addLine.isPending ? 'Đang thêm…' : 'Thêm'} {addLine.isPending ? 'Đang thêm…' : 'Thêm'}

View File

@ -182,7 +182,8 @@ export function KhkkDetailContent({
const [addFor, setAddFor] = useState<{ supplierId: string } | null>(null) const [addFor, setAddFor] = useState<{ supplierId: string } | null>(null)
const [addCatalogId, setAddCatalogId] = useState('') const [addCatalogId, setAddCatalogId] = useState('')
const [addAmount, setAddAmount] = 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 phase = plan.phase as KhkkPhaseValue
const draftPhase = phase === KhkkPhase.DangSoanThao || phase === KhkkPhase.TraLai 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 // 🔴 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). // (`UpdateContractSigningPlanLineCommand` — khuôn bug-class #73/S42).
const saveLineField = useMutation({ 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}`, { api.put(`/contract-signing-plans/${id}/lines/${p.lineId}`, {
proposedAmount: p.proposedAmount, proposedAmount: p.proposedAmount,
note: p.note,
}), }),
onSuccess: () => { toast.success('Đã lưu dòng'); invalidate() }, onSuccess: () => { toast.success('Đã lưu dòng'); invalidate() },
onError: (e) => toast.error(getErrorMessage(e)), onError: (e) => toast.error(getErrorMessage(e)),
}) })
const addLine = useMutation({ 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), api.post(`/contract-signing-plans/${id}/lines`, p),
onSuccess: () => { toast.success('Đã thêm hạng mục'); setAddFor(null); invalidate() }, onSuccess: () => { toast.success('Đã thêm hạng mục'); setAddFor(null); invalidate() },
onError: (e) => toast.error(getErrorMessage(e)), onError: (e) => toast.error(getErrorMessage(e)),
@ -545,45 +550,34 @@ export function KhkkDetailContent({
<td className="px-3 py-2 text-right align-top tabular-nums text-slate-400">{i + 1}</td> <td className="px-3 py-2 text-right align-top tabular-nums text-slate-400">{i + 1}</td>
<td className="px-3 py-2 text-slate-700"> <td className="px-3 py-2 text-slate-700">
{isDraft && !l.contractId ? ( {isDraft && !l.contractId ? (
<> // 🧊 [S178 — anh: "bỏ luôn chỗ ghi chú luôn"] Ô nhập Ghi chú của DÒNG
<select // ĐÃ CÓ đã GỠ. Dòng nháp nay chỉ còn đúng 2 ô anh cần: hạng mục + giá.
value={l.catalogEntryId ?? ''} // 🔴 DỮ LIỆU KHÔNG MẤT: `Note` vẫn nguyên trong DB, và sửa giá KHÔNG
onChange={(e) => e.target.value && assignLine.mutate({ lineId: l.id, catalogEntryId: e.target.value })} // xoá nó — `UpdateContractSigningPlanLineCommandHandler` gác
disabled={assignLine.isPending} // `if (request.Note != null)` nên `note` vắng mặt là BỎ QUA, không phải
aria-label={`Chọn hạng mục cho dòng ${l.id}`} // ghi null. Ghi chú cũ vẫn hiện ở nhánh chỉ-đọc bên dưới (`l.note`).
// [S177 — anh: "Các cái mẫu bỏ đi nhé"] DÒNG ĐÃ CÓ = đọc như DỮ // ⇒ Đây là "máy rộng, UI hẹp": BE giữ trường, UI thôi bày.
// LIỆU, không như form: viền trong suốt + nền trong suốt, khung chỉ <select
// hiện khi rê chuột / vào ô. Đúng khuôn ô Ghi chú ngay dưới (đã ghost value={l.catalogEntryId ?? ''}
// từ S175) ⇒ 3 trường của một dòng nay CÙNG một ngôn ngữ. onChange={(e) => e.target.value && assignLine.mutate({ lineId: l.id, catalogEntryId: e.target.value })}
// 🔴 Chỉ đổi VẺ, KHÔNG đổi hành vi: vẫn `<select>` thật, vẫn sửa disabled={assignLine.isPending}
// được, vẫn tự lưu. Mũi tên dropdown của native select vẫn hiện nên aria-label={`Chọn hạng mục cho dòng ${l.id}`}
// không mất tín-hiệu "ô này bấm được". // [S177 — anh: "Các cái mẫu bỏ đi nhé"] DÒNG ĐÃ CÓ = đọc như DỮ
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" // 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.
<option value=""> Chọn hạng mục </option> // 🔴 Chỉ đổi VẺ, KHÔNG đổi hành vi: vẫn `<select>` thật, vẫn sửa
{l.catalogEntryId && !(catalogOptions.data ?? []).some((c) => c.id === l.catalogEntryId) && ( // được, vẫn tự lưu. Mũi tên dropdown của native select vẫn hiện nên
<option value={l.catalogEntryId}>{l.tenHangMuc ?? '(hạng mục đã gán)'}</option> // 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"
{(catalogOptions.data ?? []).map((c) => ( >
<option key={c.id} value={c.id}>{c.code} {c.tenVi}</option> <option value=""> Chọn hạng mục </option>
))} {l.catalogEntryId && !(catalogOptions.data ?? []).some((c) => c.id === l.catalogEntryId) && (
</select> <option value={l.catalogEntryId}>{l.tenHangMuc ?? '(hạng mục đã gán)'}</option>
{/* 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. */} {(catalogOptions.data ?? []).map((c) => (
<input <option key={c.id} value={c.id}>{c.code} {c.tenVi}</option>
type="text" ))}
maxLength={1000} </select>
defaultValue={l.note ?? ''}
placeholder="Ghi chú (tùy chọn)…"
disabled={saveLineField.isPending}
aria-label="Ghi chú dòng"
onBlur={(e) => {
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"
/>
</>
) : ( ) : (
<> <>
{/* [S177 — anh: "mã hạng mục (Dự án) - mã hạng mục chi tiết"] {/* [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" type="button"
onClick={() => { onClick={() => {
setAddFor({ supplierId: l.supplierId }) setAddFor({ supplierId: l.supplierId })
setAddCatalogId(''); setAddAmount(''); setAddNote('') setAddCatalogId(''); setAddAmount('')
}} }}
title={`Thêm hạng mục cho ${l.supplierName ?? 'NCC này'}`} 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'}`} aria-label={`Thêm hạng mục cho ${l.supplierName ?? 'NCC này'}`}
@ -697,16 +691,10 @@ export function KhkkDetailContent({
<option key={c.id} value={c.id}>{c.code} {c.tenVi}</option> <option key={c.id} value={c.id}>{c.code} {c.tenVi}</option>
))} ))}
</select> </select>
{/* Ghi chú xuống DÒNG PHỤ — y hệt dòng dữ liệu thật ở trên, để hai thứ {/* 🧊 [S178 — anh: "bỏ luôn chỗ ghi chú luôn"] Ô Ghi chú của DÒNG THÊM đã
đọc thành CÙNG một hình dng chứ không phải hai kiểu form. */} GỠ, cùng lượt với ô Ghi chú của dòng đã có ở trên — giữ hai chỗ CÙNG
<input hình dạng vẫn là luật ở đây, chỉ là hình dạng chung nay gọn hơn.
type="text" maxLength={1000} 🔴 Thao tác anh chốt còn đúng 3 nhịp: chọn hạng mục → nhập giá → Thêm. */}
value={addNote}
onChange={(e) => 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"
/>
</td> </td>
<td className="px-3 py-2 align-top"> <td className="px-3 py-2 align-top">
<input <input
@ -727,7 +715,6 @@ export function KhkkDetailContent({
supplierId: addFor.supplierId, supplierId: addFor.supplierId,
catalogEntryId: addCatalogId, catalogEntryId: addCatalogId,
proposedAmount: Number(addAmount), proposedAmount: Number(addAmount),
note: addNote.trim() || undefined,
})} })}
> >
{addLine.isPending ? 'Đang thêm…' : 'Thêm'} {addLine.isPending ? 'Đang thêm…' : 'Thêm'}