From 99370deb14c5f6fb349b3a094882abf2465cd1e7 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 6 Aug 2026 22:28:18 +0700 Subject: [PATCH] [CLAUDE] FE-Admin: KHKK go o Ghi chu ca 2 cho, dong nhap con hang muc + gia + Them (FE-only, 0-mig) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/components/khkk/KhkkDetailContent.tsx | 97 ++++++++----------- .../src/components/khkk/KhkkDetailContent.tsx | 97 ++++++++----------- 2 files changed, 84 insertions(+), 110 deletions(-) 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'}