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 ? ( - <> - ` 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. + ` 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. */} {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 ? ( - <> - ` 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. + ` 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. */} {addLine.isPending ? 'Đang thêm…' : 'Thêm'}