[CLAUDE] FE: PE ngan sach Block A -> bang luoi <table> vien o giong Excel (anh phan hoi)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m46s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m46s
Anh: "giao dien van chua chia cot dung, muon giong file Excel". Block A dang dung flex + spacing (khong co vien doc chia cot) -> chuyen sang <table border-collapse> vien o day du: header [Khoan muc | Du an | PRO | CCM] + 5 hang (full / ban hanh / hieu chinh / ghi chu PRO / ghi chu CCM). BudgetCell xep doc (input full o + nut Luu duoi) cho vua cot. BudgetNoteRow -> BudgetNoteCell (td colSpan=3). Mirror fe-user/fe-admin identical. FE-only, build 2 app PASS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1084,7 +1084,8 @@ function BudgetCell({ value, editable, allowNegative = false, saving, onSave }:
|
|||||||
}
|
}
|
||||||
const dirty = parse() !== value
|
const dirty = parse() !== value
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-end gap-1">
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
{allowNegative && (
|
{allowNegative && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -1102,18 +1103,21 @@ function BudgetCell({ value, editable, allowNegative = false, saving, onSave }:
|
|||||||
value={text}
|
value={text}
|
||||||
onChange={e => setText(e.target.value.replace(/[^\d.]/g, ''))}
|
onChange={e => setText(e.target.value.replace(/[^\d.]/g, ''))}
|
||||||
placeholder="0"
|
placeholder="0"
|
||||||
className="h-7 min-w-0 px-1.5 text-right font-mono text-[12px]"
|
className="h-7 min-w-0 flex-1 px-1.5 text-right font-mono text-[12px]"
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => onSave(parse())} disabled={!dirty || saving} className="h-7 shrink-0 px-2 text-[11px]">
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button onClick={() => onSave(parse())} disabled={!dirty || saving} className="h-6 px-2 text-[10px]">
|
||||||
{saving ? '…' : 'Lưu'}
|
{saving ? '…' : 'Lưu'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// [S76] Dòng ghi chú phòng (Ghi chú từ PRO / từ CCM) — Textarea editable hoặc text display.
|
// [S76] Ô ghi chú phòng (Ghi chú từ PRO / từ CCM) — Textarea editable hoặc text display.
|
||||||
function BudgetNoteRow({ label, editable, value, setValue, savedValue, saving, onSave }: {
|
// Đặt trong <td colSpan> của bảng ngân sách (không bọc row riêng).
|
||||||
label: string
|
function BudgetNoteCell({ editable, value, setValue, savedValue, saving, onSave }: {
|
||||||
editable: boolean
|
editable: boolean
|
||||||
value: string
|
value: string
|
||||||
setValue: (v: string) => void
|
setValue: (v: string) => void
|
||||||
@ -1121,12 +1125,15 @@ function BudgetNoteRow({ label, editable, value, setValue, savedValue, saving, o
|
|||||||
saving: boolean
|
saving: boolean
|
||||||
onSave: () => void
|
onSave: () => void
|
||||||
}) {
|
}) {
|
||||||
|
if (!editable) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]">
|
<div className="whitespace-pre-wrap px-2 py-1.5 text-[12px] text-slate-600">
|
||||||
<div className="min-w-0 flex-1 text-slate-700">{label}</div>
|
{savedValue || <span className="text-slate-400">—</span>}
|
||||||
<div className="w-72 shrink-0">
|
</div>
|
||||||
{editable ? (
|
)
|
||||||
<div className="space-y-1">
|
}
|
||||||
|
return (
|
||||||
|
<div className="space-y-1 px-2 py-1.5">
|
||||||
<textarea
|
<textarea
|
||||||
value={value}
|
value={value}
|
||||||
onChange={e => setValue(e.target.value)}
|
onChange={e => setValue(e.target.value)}
|
||||||
@ -1140,13 +1147,6 @@ function BudgetNoteRow({ label, editable, value, setValue, savedValue, saving, o
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="whitespace-pre-wrap text-right text-[12px] text-slate-600">
|
|
||||||
{savedValue || <span className="text-slate-400">—</span>}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1252,59 +1252,61 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ===== Block A — NGÂN SÁCH gói thầu: MA TRẬN 3 cột (DỰ ÁN | PRO | CCM) =====
|
{/* ===== Block A — NGÂN SÁCH gói thầu: BẢNG LƯỚI 3 cột (DỰ ÁN | PRO | CCM) =====
|
||||||
[S76 anh Kiệt FDC] Mỗi phòng nhập+điều chỉnh cột của CHÍNH mình (role-gate):
|
[S76 anh Kiệt FDC] <table> viền ô đầy đủ giống file Excel. Mỗi phòng nhập+điều
|
||||||
PRO→cột PRO (canEditPro) · CCM→cột CCM (canEditCcm) · DỰ ÁN hiển thị-only
|
chỉnh cột CHÍNH mình (role-gate): PRO→cột PRO (canEditPro) · CCM→cột CCM
|
||||||
(chưa wire BE — sau có người dự án nhập). Full mỗi cột = ban hành + hiệu chỉnh. */}
|
(canEditCcm) · DỰ ÁN hiển-thị-only (—, sau có người dự án nhập). Full mỗi cột
|
||||||
|
= ban hành + hiệu chỉnh. */}
|
||||||
<BudgetBlockHeader>A. Ngân sách (gói thầu / gói vật tư)</BudgetBlockHeader>
|
<BudgetBlockHeader>A. Ngân sách (gói thầu / gói vật tư)</BudgetBlockHeader>
|
||||||
|
|
||||||
{/* Header 3 cột */}
|
<div className="overflow-x-auto">
|
||||||
<div className="flex items-center gap-2 border-b border-slate-200 bg-slate-50 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
<table className="w-full border-collapse text-[13px]">
|
||||||
<div className="min-w-0 flex-1" />
|
<thead>
|
||||||
<div className="w-36 shrink-0 text-center">Dự án</div>
|
<tr className="bg-slate-100 text-[11px] font-semibold uppercase tracking-wide text-slate-600">
|
||||||
<div className="w-36 shrink-0 text-center">PRO</div>
|
<th className="border border-slate-300 px-3 py-1.5 text-left">Khoản mục</th>
|
||||||
<div className="w-36 shrink-0 text-center">CCM</div>
|
<th className="w-20 border border-slate-300 px-2 py-1.5 text-center">Dự án</th>
|
||||||
</div>
|
<th className="w-40 border border-slate-300 px-2 py-1.5 text-center">PRO</th>
|
||||||
|
<th className="w-40 border border-slate-300 px-2 py-1.5 text-center">CCM</th>
|
||||||
{/* Dòng 1 — Ngân sách (full gói thầu) per cột = ban hành + hiệu chỉnh — brand-soft */}
|
</tr>
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 bg-[#1F7DC1]/10 px-3 py-2 text-[13px]">
|
</thead>
|
||||||
<div className="min-w-0 flex-1 font-semibold text-slate-800">Ngân sách (full gói thầu / gói vật tư)</div>
|
<tbody>
|
||||||
<div className="w-36 shrink-0 text-right text-slate-300">—</div>
|
{/* Ngân sách full (= ban hành + hiệu chỉnh) mỗi cột */}
|
||||||
<div className="w-36 shrink-0 text-right font-mono font-bold tabular-nums text-slate-900">
|
<tr className="bg-[#1F7DC1]/10 font-semibold">
|
||||||
|
<td className="border border-slate-300 px-3 py-2 text-slate-800">Ngân sách (full gói thầu / gói vật tư)</td>
|
||||||
|
<td className="border border-slate-300 px-2 py-2 text-right text-slate-300">—</td>
|
||||||
|
<td className="border border-slate-300 px-2 py-2 text-right font-mono font-bold tabular-nums text-slate-900">
|
||||||
{proHasData ? fmtVnd(proFull) : <span className="text-slate-300">—</span>}
|
{proHasData ? fmtVnd(proFull) : <span className="text-slate-300">—</span>}
|
||||||
</div>
|
</td>
|
||||||
<div className="w-36 shrink-0 text-right font-mono font-bold tabular-nums text-slate-900">
|
<td className="border border-slate-300 px-2 py-2 text-right font-mono font-bold tabular-nums text-slate-900">
|
||||||
{ccmHasData ? fmtVnd(ccmFull) : <span className="text-slate-300">—</span>}
|
{ccmHasData ? fmtVnd(ccmFull) : <span className="text-slate-300">—</span>}
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
{/* Ban hành lần đầu */}
|
||||||
{/* Dòng 2 — Ban hành lần đầu: Dự án (—) | PRO (canEditPro) | CCM (canEditCcm) */}
|
<tr>
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]">
|
<td className="border border-slate-300 px-3 py-1.5 text-slate-700">Ngân sách Ban hành lần đầu</td>
|
||||||
<div className="min-w-0 flex-1 text-slate-700">Ngân sách Ban hành lần đầu</div>
|
<td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300">—</td>
|
||||||
<div className="w-36 shrink-0 text-right text-slate-300">—</div>
|
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
|
||||||
<div className="w-36 shrink-0">
|
|
||||||
<BudgetCell
|
<BudgetCell
|
||||||
value={bs.proInitialAmount}
|
value={bs.proInitialAmount}
|
||||||
editable={bs.canEditPro}
|
editable={bs.canEditPro}
|
||||||
saving={proMut.isPending || peFetching}
|
saving={proMut.isPending || peFetching}
|
||||||
onSave={v => proMut.mutate({ proInitialAmount: v, proAdjustmentAmount: bs.proAdjustmentAmount, proNote: bs.proNote })}
|
onSave={v => proMut.mutate({ proInitialAmount: v, proAdjustmentAmount: bs.proAdjustmentAmount, proNote: bs.proNote })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
<div className="w-36 shrink-0">
|
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
|
||||||
<BudgetCell
|
<BudgetCell
|
||||||
value={bs.initialAmount}
|
value={bs.initialAmount}
|
||||||
editable={bs.canEditCcm}
|
editable={bs.canEditCcm}
|
||||||
saving={ccmMut.isPending || peFetching}
|
saving={ccmMut.isPending || peFetching}
|
||||||
onSave={v => ccmMut.mutate({ initialAmount: v, adjustmentAmount: bs.adjustmentAmount, ccmNote: bs.ccmNote })}
|
onSave={v => ccmMut.mutate({ initialAmount: v, adjustmentAmount: bs.adjustmentAmount, ccmNote: bs.ccmNote })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
{/* V0 / hiệu chỉnh tăng giảm (cho phép ÂM) */}
|
||||||
{/* Dòng 3 — V0 / hiệu chỉnh tăng giảm (cho phép ÂM): Dự án (—) | PRO | CCM */}
|
<tr>
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]">
|
<td className="border border-slate-300 px-3 py-1.5 text-slate-700">Ngân sách V0 / hiệu chỉnh tăng giảm</td>
|
||||||
<div className="min-w-0 flex-1 text-slate-700">Ngân sách V0 / hiệu chỉnh tăng giảm</div>
|
<td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300">—</td>
|
||||||
<div className="w-36 shrink-0 text-right text-slate-300">—</div>
|
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
|
||||||
<div className="w-36 shrink-0">
|
|
||||||
<BudgetCell
|
<BudgetCell
|
||||||
value={bs.proAdjustmentAmount}
|
value={bs.proAdjustmentAmount}
|
||||||
editable={bs.canEditPro}
|
editable={bs.canEditPro}
|
||||||
@ -1312,8 +1314,8 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
saving={proMut.isPending || peFetching}
|
saving={proMut.isPending || peFetching}
|
||||||
onSave={v => proMut.mutate({ proInitialAmount: bs.proInitialAmount, proAdjustmentAmount: v, proNote: bs.proNote })}
|
onSave={v => proMut.mutate({ proInitialAmount: bs.proInitialAmount, proAdjustmentAmount: v, proNote: bs.proNote })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
<div className="w-36 shrink-0">
|
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
|
||||||
<BudgetCell
|
<BudgetCell
|
||||||
value={bs.adjustmentAmount}
|
value={bs.adjustmentAmount}
|
||||||
editable={bs.canEditCcm}
|
editable={bs.canEditCcm}
|
||||||
@ -1321,12 +1323,13 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
saving={ccmMut.isPending || peFetching}
|
saving={ccmMut.isPending || peFetching}
|
||||||
onSave={v => ccmMut.mutate({ initialAmount: bs.initialAmount, adjustmentAmount: v, ccmNote: bs.ccmNote })}
|
onSave={v => ccmMut.mutate({ initialAmount: bs.initialAmount, adjustmentAmount: v, ccmNote: bs.ccmNote })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
{/* Ghi chú từ PRO (span 3 cột giá trị) */}
|
||||||
{/* Ghi chú từ PRO + từ CCM (cuối block A, đúng thứ tự Excel anh Kiệt) */}
|
<tr>
|
||||||
<BudgetNoteRow
|
<td className="border border-slate-300 px-3 py-1.5 align-top text-slate-700">Ghi chú từ PRO</td>
|
||||||
label="Ghi chú từ PRO"
|
<td className="border border-slate-300" colSpan={3}>
|
||||||
|
<BudgetNoteCell
|
||||||
editable={bs.canEditPro}
|
editable={bs.canEditPro}
|
||||||
value={proNoteText}
|
value={proNoteText}
|
||||||
setValue={setProNoteText}
|
setValue={setProNoteText}
|
||||||
@ -1334,8 +1337,13 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
saving={proMut.isPending || peFetching}
|
saving={proMut.isPending || peFetching}
|
||||||
onSave={() => proMut.mutate({ proInitialAmount: bs.proInitialAmount, proAdjustmentAmount: bs.proAdjustmentAmount, proNote: proNoteText || null })}
|
onSave={() => proMut.mutate({ proInitialAmount: bs.proInitialAmount, proAdjustmentAmount: bs.proAdjustmentAmount, proNote: proNoteText || null })}
|
||||||
/>
|
/>
|
||||||
<BudgetNoteRow
|
</td>
|
||||||
label="Ghi chú từ CCM"
|
</tr>
|
||||||
|
{/* Ghi chú từ CCM (span 3 cột giá trị) */}
|
||||||
|
<tr>
|
||||||
|
<td className="border border-slate-300 px-3 py-1.5 align-top text-slate-700">Ghi chú từ CCM</td>
|
||||||
|
<td className="border border-slate-300" colSpan={3}>
|
||||||
|
<BudgetNoteCell
|
||||||
editable={bs.canEditCcm}
|
editable={bs.canEditCcm}
|
||||||
value={ccmNoteText}
|
value={ccmNoteText}
|
||||||
setValue={setCcmNoteText}
|
setValue={setCcmNoteText}
|
||||||
@ -1343,6 +1351,11 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
saving={ccmMut.isPending || peFetching}
|
saving={ccmMut.isPending || peFetching}
|
||||||
onSave={() => ccmMut.mutate({ initialAmount: bs.initialAmount, adjustmentAmount: bs.adjustmentAmount, ccmNote: ccmNoteText || null })}
|
onSave={() => ccmMut.mutate({ initialAmount: bs.initialAmount, adjustmentAmount: bs.adjustmentAmount, ccmNote: ccmNoteText || null })}
|
||||||
/>
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* ===== Block B — THỰC HIỆN ===== */}
|
{/* ===== Block B — THỰC HIỆN ===== */}
|
||||||
<BudgetBlockHeader>B. Thực hiện</BudgetBlockHeader>
|
<BudgetBlockHeader>B. Thực hiện</BudgetBlockHeader>
|
||||||
|
|||||||
@ -1084,7 +1084,8 @@ function BudgetCell({ value, editable, allowNegative = false, saving, onSave }:
|
|||||||
}
|
}
|
||||||
const dirty = parse() !== value
|
const dirty = parse() !== value
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-end gap-1">
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
{allowNegative && (
|
{allowNegative && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -1102,18 +1103,21 @@ function BudgetCell({ value, editable, allowNegative = false, saving, onSave }:
|
|||||||
value={text}
|
value={text}
|
||||||
onChange={e => setText(e.target.value.replace(/[^\d.]/g, ''))}
|
onChange={e => setText(e.target.value.replace(/[^\d.]/g, ''))}
|
||||||
placeholder="0"
|
placeholder="0"
|
||||||
className="h-7 min-w-0 px-1.5 text-right font-mono text-[12px]"
|
className="h-7 min-w-0 flex-1 px-1.5 text-right font-mono text-[12px]"
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => onSave(parse())} disabled={!dirty || saving} className="h-7 shrink-0 px-2 text-[11px]">
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button onClick={() => onSave(parse())} disabled={!dirty || saving} className="h-6 px-2 text-[10px]">
|
||||||
{saving ? '…' : 'Lưu'}
|
{saving ? '…' : 'Lưu'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// [S76] Dòng ghi chú phòng (Ghi chú từ PRO / từ CCM) — Textarea editable hoặc text display.
|
// [S76] Ô ghi chú phòng (Ghi chú từ PRO / từ CCM) — Textarea editable hoặc text display.
|
||||||
function BudgetNoteRow({ label, editable, value, setValue, savedValue, saving, onSave }: {
|
// Đặt trong <td colSpan> của bảng ngân sách (không bọc row riêng).
|
||||||
label: string
|
function BudgetNoteCell({ editable, value, setValue, savedValue, saving, onSave }: {
|
||||||
editable: boolean
|
editable: boolean
|
||||||
value: string
|
value: string
|
||||||
setValue: (v: string) => void
|
setValue: (v: string) => void
|
||||||
@ -1121,12 +1125,15 @@ function BudgetNoteRow({ label, editable, value, setValue, savedValue, saving, o
|
|||||||
saving: boolean
|
saving: boolean
|
||||||
onSave: () => void
|
onSave: () => void
|
||||||
}) {
|
}) {
|
||||||
|
if (!editable) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]">
|
<div className="whitespace-pre-wrap px-2 py-1.5 text-[12px] text-slate-600">
|
||||||
<div className="min-w-0 flex-1 text-slate-700">{label}</div>
|
{savedValue || <span className="text-slate-400">—</span>}
|
||||||
<div className="w-72 shrink-0">
|
</div>
|
||||||
{editable ? (
|
)
|
||||||
<div className="space-y-1">
|
}
|
||||||
|
return (
|
||||||
|
<div className="space-y-1 px-2 py-1.5">
|
||||||
<textarea
|
<textarea
|
||||||
value={value}
|
value={value}
|
||||||
onChange={e => setValue(e.target.value)}
|
onChange={e => setValue(e.target.value)}
|
||||||
@ -1140,13 +1147,6 @@ function BudgetNoteRow({ label, editable, value, setValue, savedValue, saving, o
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="whitespace-pre-wrap text-right text-[12px] text-slate-600">
|
|
||||||
{savedValue || <span className="text-slate-400">—</span>}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1252,59 +1252,61 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ===== Block A — NGÂN SÁCH gói thầu: MA TRẬN 3 cột (DỰ ÁN | PRO | CCM) =====
|
{/* ===== Block A — NGÂN SÁCH gói thầu: BẢNG LƯỚI 3 cột (DỰ ÁN | PRO | CCM) =====
|
||||||
[S76 anh Kiệt FDC] Mỗi phòng nhập+điều chỉnh cột của CHÍNH mình (role-gate):
|
[S76 anh Kiệt FDC] <table> viền ô đầy đủ giống file Excel. Mỗi phòng nhập+điều
|
||||||
PRO→cột PRO (canEditPro) · CCM→cột CCM (canEditCcm) · DỰ ÁN hiển thị-only
|
chỉnh cột CHÍNH mình (role-gate): PRO→cột PRO (canEditPro) · CCM→cột CCM
|
||||||
(chưa wire BE — sau có người dự án nhập). Full mỗi cột = ban hành + hiệu chỉnh. */}
|
(canEditCcm) · DỰ ÁN hiển-thị-only (—, sau có người dự án nhập). Full mỗi cột
|
||||||
|
= ban hành + hiệu chỉnh. */}
|
||||||
<BudgetBlockHeader>A. Ngân sách (gói thầu / gói vật tư)</BudgetBlockHeader>
|
<BudgetBlockHeader>A. Ngân sách (gói thầu / gói vật tư)</BudgetBlockHeader>
|
||||||
|
|
||||||
{/* Header 3 cột */}
|
<div className="overflow-x-auto">
|
||||||
<div className="flex items-center gap-2 border-b border-slate-200 bg-slate-50 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
<table className="w-full border-collapse text-[13px]">
|
||||||
<div className="min-w-0 flex-1" />
|
<thead>
|
||||||
<div className="w-36 shrink-0 text-center">Dự án</div>
|
<tr className="bg-slate-100 text-[11px] font-semibold uppercase tracking-wide text-slate-600">
|
||||||
<div className="w-36 shrink-0 text-center">PRO</div>
|
<th className="border border-slate-300 px-3 py-1.5 text-left">Khoản mục</th>
|
||||||
<div className="w-36 shrink-0 text-center">CCM</div>
|
<th className="w-20 border border-slate-300 px-2 py-1.5 text-center">Dự án</th>
|
||||||
</div>
|
<th className="w-40 border border-slate-300 px-2 py-1.5 text-center">PRO</th>
|
||||||
|
<th className="w-40 border border-slate-300 px-2 py-1.5 text-center">CCM</th>
|
||||||
{/* Dòng 1 — Ngân sách (full gói thầu) per cột = ban hành + hiệu chỉnh — brand-soft */}
|
</tr>
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 bg-[#1F7DC1]/10 px-3 py-2 text-[13px]">
|
</thead>
|
||||||
<div className="min-w-0 flex-1 font-semibold text-slate-800">Ngân sách (full gói thầu / gói vật tư)</div>
|
<tbody>
|
||||||
<div className="w-36 shrink-0 text-right text-slate-300">—</div>
|
{/* Ngân sách full (= ban hành + hiệu chỉnh) mỗi cột */}
|
||||||
<div className="w-36 shrink-0 text-right font-mono font-bold tabular-nums text-slate-900">
|
<tr className="bg-[#1F7DC1]/10 font-semibold">
|
||||||
|
<td className="border border-slate-300 px-3 py-2 text-slate-800">Ngân sách (full gói thầu / gói vật tư)</td>
|
||||||
|
<td className="border border-slate-300 px-2 py-2 text-right text-slate-300">—</td>
|
||||||
|
<td className="border border-slate-300 px-2 py-2 text-right font-mono font-bold tabular-nums text-slate-900">
|
||||||
{proHasData ? fmtVnd(proFull) : <span className="text-slate-300">—</span>}
|
{proHasData ? fmtVnd(proFull) : <span className="text-slate-300">—</span>}
|
||||||
</div>
|
</td>
|
||||||
<div className="w-36 shrink-0 text-right font-mono font-bold tabular-nums text-slate-900">
|
<td className="border border-slate-300 px-2 py-2 text-right font-mono font-bold tabular-nums text-slate-900">
|
||||||
{ccmHasData ? fmtVnd(ccmFull) : <span className="text-slate-300">—</span>}
|
{ccmHasData ? fmtVnd(ccmFull) : <span className="text-slate-300">—</span>}
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
{/* Ban hành lần đầu */}
|
||||||
{/* Dòng 2 — Ban hành lần đầu: Dự án (—) | PRO (canEditPro) | CCM (canEditCcm) */}
|
<tr>
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]">
|
<td className="border border-slate-300 px-3 py-1.5 text-slate-700">Ngân sách Ban hành lần đầu</td>
|
||||||
<div className="min-w-0 flex-1 text-slate-700">Ngân sách Ban hành lần đầu</div>
|
<td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300">—</td>
|
||||||
<div className="w-36 shrink-0 text-right text-slate-300">—</div>
|
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
|
||||||
<div className="w-36 shrink-0">
|
|
||||||
<BudgetCell
|
<BudgetCell
|
||||||
value={bs.proInitialAmount}
|
value={bs.proInitialAmount}
|
||||||
editable={bs.canEditPro}
|
editable={bs.canEditPro}
|
||||||
saving={proMut.isPending || peFetching}
|
saving={proMut.isPending || peFetching}
|
||||||
onSave={v => proMut.mutate({ proInitialAmount: v, proAdjustmentAmount: bs.proAdjustmentAmount, proNote: bs.proNote })}
|
onSave={v => proMut.mutate({ proInitialAmount: v, proAdjustmentAmount: bs.proAdjustmentAmount, proNote: bs.proNote })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
<div className="w-36 shrink-0">
|
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
|
||||||
<BudgetCell
|
<BudgetCell
|
||||||
value={bs.initialAmount}
|
value={bs.initialAmount}
|
||||||
editable={bs.canEditCcm}
|
editable={bs.canEditCcm}
|
||||||
saving={ccmMut.isPending || peFetching}
|
saving={ccmMut.isPending || peFetching}
|
||||||
onSave={v => ccmMut.mutate({ initialAmount: v, adjustmentAmount: bs.adjustmentAmount, ccmNote: bs.ccmNote })}
|
onSave={v => ccmMut.mutate({ initialAmount: v, adjustmentAmount: bs.adjustmentAmount, ccmNote: bs.ccmNote })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
{/* V0 / hiệu chỉnh tăng giảm (cho phép ÂM) */}
|
||||||
{/* Dòng 3 — V0 / hiệu chỉnh tăng giảm (cho phép ÂM): Dự án (—) | PRO | CCM */}
|
<tr>
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]">
|
<td className="border border-slate-300 px-3 py-1.5 text-slate-700">Ngân sách V0 / hiệu chỉnh tăng giảm</td>
|
||||||
<div className="min-w-0 flex-1 text-slate-700">Ngân sách V0 / hiệu chỉnh tăng giảm</div>
|
<td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300">—</td>
|
||||||
<div className="w-36 shrink-0 text-right text-slate-300">—</div>
|
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
|
||||||
<div className="w-36 shrink-0">
|
|
||||||
<BudgetCell
|
<BudgetCell
|
||||||
value={bs.proAdjustmentAmount}
|
value={bs.proAdjustmentAmount}
|
||||||
editable={bs.canEditPro}
|
editable={bs.canEditPro}
|
||||||
@ -1312,8 +1314,8 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
saving={proMut.isPending || peFetching}
|
saving={proMut.isPending || peFetching}
|
||||||
onSave={v => proMut.mutate({ proInitialAmount: bs.proInitialAmount, proAdjustmentAmount: v, proNote: bs.proNote })}
|
onSave={v => proMut.mutate({ proInitialAmount: bs.proInitialAmount, proAdjustmentAmount: v, proNote: bs.proNote })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
<div className="w-36 shrink-0">
|
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
|
||||||
<BudgetCell
|
<BudgetCell
|
||||||
value={bs.adjustmentAmount}
|
value={bs.adjustmentAmount}
|
||||||
editable={bs.canEditCcm}
|
editable={bs.canEditCcm}
|
||||||
@ -1321,12 +1323,13 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
saving={ccmMut.isPending || peFetching}
|
saving={ccmMut.isPending || peFetching}
|
||||||
onSave={v => ccmMut.mutate({ initialAmount: bs.initialAmount, adjustmentAmount: v, ccmNote: bs.ccmNote })}
|
onSave={v => ccmMut.mutate({ initialAmount: bs.initialAmount, adjustmentAmount: v, ccmNote: bs.ccmNote })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
{/* Ghi chú từ PRO (span 3 cột giá trị) */}
|
||||||
{/* Ghi chú từ PRO + từ CCM (cuối block A, đúng thứ tự Excel anh Kiệt) */}
|
<tr>
|
||||||
<BudgetNoteRow
|
<td className="border border-slate-300 px-3 py-1.5 align-top text-slate-700">Ghi chú từ PRO</td>
|
||||||
label="Ghi chú từ PRO"
|
<td className="border border-slate-300" colSpan={3}>
|
||||||
|
<BudgetNoteCell
|
||||||
editable={bs.canEditPro}
|
editable={bs.canEditPro}
|
||||||
value={proNoteText}
|
value={proNoteText}
|
||||||
setValue={setProNoteText}
|
setValue={setProNoteText}
|
||||||
@ -1334,8 +1337,13 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
saving={proMut.isPending || peFetching}
|
saving={proMut.isPending || peFetching}
|
||||||
onSave={() => proMut.mutate({ proInitialAmount: bs.proInitialAmount, proAdjustmentAmount: bs.proAdjustmentAmount, proNote: proNoteText || null })}
|
onSave={() => proMut.mutate({ proInitialAmount: bs.proInitialAmount, proAdjustmentAmount: bs.proAdjustmentAmount, proNote: proNoteText || null })}
|
||||||
/>
|
/>
|
||||||
<BudgetNoteRow
|
</td>
|
||||||
label="Ghi chú từ CCM"
|
</tr>
|
||||||
|
{/* Ghi chú từ CCM (span 3 cột giá trị) */}
|
||||||
|
<tr>
|
||||||
|
<td className="border border-slate-300 px-3 py-1.5 align-top text-slate-700">Ghi chú từ CCM</td>
|
||||||
|
<td className="border border-slate-300" colSpan={3}>
|
||||||
|
<BudgetNoteCell
|
||||||
editable={bs.canEditCcm}
|
editable={bs.canEditCcm}
|
||||||
value={ccmNoteText}
|
value={ccmNoteText}
|
||||||
setValue={setCcmNoteText}
|
setValue={setCcmNoteText}
|
||||||
@ -1343,6 +1351,11 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
saving={ccmMut.isPending || peFetching}
|
saving={ccmMut.isPending || peFetching}
|
||||||
onSave={() => ccmMut.mutate({ initialAmount: bs.initialAmount, adjustmentAmount: bs.adjustmentAmount, ccmNote: ccmNoteText || null })}
|
onSave={() => ccmMut.mutate({ initialAmount: bs.initialAmount, adjustmentAmount: bs.adjustmentAmount, ccmNote: ccmNoteText || null })}
|
||||||
/>
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* ===== Block B — THỰC HIỆN ===== */}
|
{/* ===== Block B — THỰC HIỆN ===== */}
|
||||||
<BudgetBlockHeader>B. Thực hiện</BudgetBlockHeader>
|
<BudgetBlockHeader>B. Thực hiện</BudgetBlockHeader>
|
||||||
|
|||||||
Reference in New Issue
Block a user