[CLAUDE] FE: PE bang ngan sach muc con thut dong + gach dau dong (phan biet muc co so / khong so)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m39s

Tra Sol + anh Kiet (Zalo, annotate anh do): muc con (khong co so) thut vao + gach dau dong '–' de phan biet voi muc cha co so (1-9). BudgetRow +indent prop (pl-5 + dash span). Ap dung: Block A '– Ngan sach Ban hanh lan dau' + '– Ngan sach V0/hieu chinh tang giam' (con cua Ngan sach full); Block B '– Gia tri ky nay' + '– So sanh voi ngan sach ky nay' + '– So voi NS' + '– So sanh voi Ngan sach full'. Muc 1-9 giu cap cha. FE-only, 2 app SHA256-identical PeDetailTabs. Build PASS x2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-19 14:24:48 +07:00
parent e42d103694
commit e29391ec9e
2 changed files with 26 additions and 10 deletions

View File

@ -1010,7 +1010,7 @@ function VndInlineEdit({
// 1 dòng bảng — label trái | value phải (right-align) | cột 3 (% hoặc ghi chú). // 1 dòng bảng — label trái | value phải (right-align) | cột 3 (% hoặc ghi chú).
// tone: 'brand' = nền brand đậm chữ trắng (dòng tổng) · 'brand-soft' = nền brand-50. // tone: 'brand' = nền brand đậm chữ trắng (dòng tổng) · 'brand-soft' = nền brand-50.
function BudgetRow({ function BudgetRow({
label, sub, value, third, tone, danger, mono = true, label, sub, value, third, tone, danger, mono = true, indent = false,
}: { }: {
label: React.ReactNode label: React.ReactNode
sub?: React.ReactNode sub?: React.ReactNode
@ -1019,6 +1019,8 @@ function BudgetRow({
tone?: 'brand' | 'brand-soft' | 'blue-soft' tone?: 'brand' | 'brand-soft' | 'blue-soft'
danger?: boolean danger?: boolean
mono?: boolean mono?: boolean
/** [S77 Tra Sol] mục con (không có số) → thụt dòng + gạch đầu dòng phân biệt với mục cha có số. */
indent?: boolean
}) { }) {
const toneCls = const toneCls =
tone === 'brand' ? 'bg-[#1F7DC1] text-white font-semibold' tone === 'brand' ? 'bg-[#1F7DC1] text-white font-semibold'
@ -1027,8 +1029,10 @@ function BudgetRow({
: '' : ''
return ( return (
<div className={cn('flex items-start gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]', toneCls)}> <div className={cn('flex items-start gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]', toneCls)}>
<div className="min-w-0 flex-1"> <div className={cn('min-w-0 flex-1', indent && 'pl-5')}>
<div className={cn(tone === 'brand' ? 'text-white' : 'text-slate-700')}>{label}</div> <div className={cn(tone === 'brand' ? 'text-white' : 'text-slate-700')}>
{indent && <span className="mr-1 text-slate-400"></span>}{label}
</div>
{sub && <div className={cn('text-[10px]', tone === 'brand' ? 'text-white/70' : 'text-slate-400')}>{sub}</div>} {sub && <div className={cn('text-[10px]', tone === 'brand' ? 'text-white/70' : 'text-slate-400')}>{sub}</div>}
</div> </div>
<div className={cn( <div className={cn(
@ -1283,7 +1287,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
</tr> </tr>
{/* Ban hành lần đầu */} {/* Ban hành lần đầu */}
<tr> <tr>
<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> <td className="border border-slate-300 px-3 py-1.5 pl-8 text-slate-700"> Ngân sách Ban hành lần đu</td>
<td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300"></td> <td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300"></td>
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top"> <td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
<BudgetCell <BudgetCell
@ -1304,7 +1308,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
</tr> </tr>
{/* V0 / hiệu chỉnh tăng giảm (cho phép ÂM) */} {/* V0 / hiệu chỉnh tăng giảm (cho phép ÂM) */}
<tr> <tr>
<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> <td className="border border-slate-300 px-3 py-1.5 pl-8 text-slate-700"> Ngân sách V0 / hiệu chỉnh tăng giảm</td>
<td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300"></td> <td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300"></td>
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top"> <td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
<BudgetCell <BudgetCell
@ -1405,6 +1409,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
<BudgetRow <BudgetRow
tone="blue-soft" tone="blue-soft"
label="Giá trị kỳ này" label="Giá trị kỳ này"
indent
value={ value={
proposalOver ? ( proposalOver ? (
<span className="inline-block rounded bg-[#C00000] px-2 py-0.5 font-bold text-white">{fmtVnd(row4)}</span> <span className="inline-block rounded bg-[#C00000] px-2 py-0.5 font-bold text-white">{fmtVnd(row4)}</span>
@ -1414,6 +1419,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
<BudgetRow <BudgetRow
tone="blue-soft" tone="blue-soft"
label="So sánh với ngân sách kỳ này" label="So sánh với ngân sách kỳ này"
indent
sub="= 3 4" sub="= 3 4"
value={<span className={cn(cmpPeriod < 0 && 'font-semibold text-red-600')}>{fmtVndSigned(cmpPeriod)}</span>} value={<span className={cn(cmpPeriod < 0 && 'font-semibold text-red-600')}>{fmtVndSigned(cmpPeriod)}</span>}
third={fmtPct(cmpPeriod, row3) ?? undefined} third={fmtPct(cmpPeriod, row3) ?? undefined}
@ -1435,6 +1441,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
/> />
<BudgetRow <BudgetRow
label="So với NS" label="So với NS"
indent
sub="= 5 6" sub="= 5 6"
value={<span className={cn(cmp56 < 0 && 'font-semibold text-red-600')}>{fmtVndSigned(cmp56)}</span>} value={<span className={cn(cmp56 < 0 && 'font-semibold text-red-600')}>{fmtVndSigned(cmp56)}</span>}
third={fmtPct(cmp56, row5) ?? undefined} third={fmtPct(cmp56, row5) ?? undefined}
@ -1486,6 +1493,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
<BudgetRow <BudgetRow
tone="brand-soft" tone="brand-soft"
label="So sánh với Ngân sách full" label="So sánh với Ngân sách full"
indent
sub="= Ngân sách full 9" sub="= Ngân sách full 9"
value={<span className={cn(cmpFull < 0 && 'font-bold text-red-600')}>{fmtVndSigned(cmpFull)}</span>} value={<span className={cn(cmpFull < 0 && 'font-bold text-red-600')}>{fmtVndSigned(cmpFull)}</span>}
third={fmtPct(cmpFull, full) ?? undefined} third={fmtPct(cmpFull, full) ?? undefined}

View File

@ -1010,7 +1010,7 @@ function VndInlineEdit({
// 1 dòng bảng — label trái | value phải (right-align) | cột 3 (% hoặc ghi chú). // 1 dòng bảng — label trái | value phải (right-align) | cột 3 (% hoặc ghi chú).
// tone: 'brand' = nền brand đậm chữ trắng (dòng tổng) · 'brand-soft' = nền brand-50. // tone: 'brand' = nền brand đậm chữ trắng (dòng tổng) · 'brand-soft' = nền brand-50.
function BudgetRow({ function BudgetRow({
label, sub, value, third, tone, danger, mono = true, label, sub, value, third, tone, danger, mono = true, indent = false,
}: { }: {
label: React.ReactNode label: React.ReactNode
sub?: React.ReactNode sub?: React.ReactNode
@ -1019,6 +1019,8 @@ function BudgetRow({
tone?: 'brand' | 'brand-soft' | 'blue-soft' tone?: 'brand' | 'brand-soft' | 'blue-soft'
danger?: boolean danger?: boolean
mono?: boolean mono?: boolean
/** [S77 Tra Sol] mục con (không có số) → thụt dòng + gạch đầu dòng phân biệt với mục cha có số. */
indent?: boolean
}) { }) {
const toneCls = const toneCls =
tone === 'brand' ? 'bg-[#1F7DC1] text-white font-semibold' tone === 'brand' ? 'bg-[#1F7DC1] text-white font-semibold'
@ -1027,8 +1029,10 @@ function BudgetRow({
: '' : ''
return ( return (
<div className={cn('flex items-start gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]', toneCls)}> <div className={cn('flex items-start gap-2 border-b border-slate-100 px-3 py-1.5 text-[13px]', toneCls)}>
<div className="min-w-0 flex-1"> <div className={cn('min-w-0 flex-1', indent && 'pl-5')}>
<div className={cn(tone === 'brand' ? 'text-white' : 'text-slate-700')}>{label}</div> <div className={cn(tone === 'brand' ? 'text-white' : 'text-slate-700')}>
{indent && <span className="mr-1 text-slate-400"></span>}{label}
</div>
{sub && <div className={cn('text-[10px]', tone === 'brand' ? 'text-white/70' : 'text-slate-400')}>{sub}</div>} {sub && <div className={cn('text-[10px]', tone === 'brand' ? 'text-white/70' : 'text-slate-400')}>{sub}</div>}
</div> </div>
<div className={cn( <div className={cn(
@ -1283,7 +1287,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
</tr> </tr>
{/* Ban hành lần đầu */} {/* Ban hành lần đầu */}
<tr> <tr>
<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> <td className="border border-slate-300 px-3 py-1.5 pl-8 text-slate-700"> Ngân sách Ban hành lần đu</td>
<td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300"></td> <td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300"></td>
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top"> <td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
<BudgetCell <BudgetCell
@ -1304,7 +1308,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
</tr> </tr>
{/* V0 / hiệu chỉnh tăng giảm (cho phép ÂM) */} {/* V0 / hiệu chỉnh tăng giảm (cho phép ÂM) */}
<tr> <tr>
<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> <td className="border border-slate-300 px-3 py-1.5 pl-8 text-slate-700"> Ngân sách V0 / hiệu chỉnh tăng giảm</td>
<td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300"></td> <td className="border border-slate-300 px-2 py-1.5 text-right text-slate-300"></td>
<td className="border border-slate-300 px-1.5 py-1.5 text-right align-top"> <td className="border border-slate-300 px-1.5 py-1.5 text-right align-top">
<BudgetCell <BudgetCell
@ -1405,6 +1409,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
<BudgetRow <BudgetRow
tone="blue-soft" tone="blue-soft"
label="Giá trị kỳ này" label="Giá trị kỳ này"
indent
value={ value={
proposalOver ? ( proposalOver ? (
<span className="inline-block rounded bg-[#C00000] px-2 py-0.5 font-bold text-white">{fmtVnd(row4)}</span> <span className="inline-block rounded bg-[#C00000] px-2 py-0.5 font-bold text-white">{fmtVnd(row4)}</span>
@ -1414,6 +1419,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
<BudgetRow <BudgetRow
tone="blue-soft" tone="blue-soft"
label="So sánh với ngân sách kỳ này" label="So sánh với ngân sách kỳ này"
indent
sub="= 3 4" sub="= 3 4"
value={<span className={cn(cmpPeriod < 0 && 'font-semibold text-red-600')}>{fmtVndSigned(cmpPeriod)}</span>} value={<span className={cn(cmpPeriod < 0 && 'font-semibold text-red-600')}>{fmtVndSigned(cmpPeriod)}</span>}
third={fmtPct(cmpPeriod, row3) ?? undefined} third={fmtPct(cmpPeriod, row3) ?? undefined}
@ -1435,6 +1441,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
/> />
<BudgetRow <BudgetRow
label="So với NS" label="So với NS"
indent
sub="= 5 6" sub="= 5 6"
value={<span className={cn(cmp56 < 0 && 'font-semibold text-red-600')}>{fmtVndSigned(cmp56)}</span>} value={<span className={cn(cmp56 < 0 && 'font-semibold text-red-600')}>{fmtVndSigned(cmp56)}</span>}
third={fmtPct(cmp56, row5) ?? undefined} third={fmtPct(cmp56, row5) ?? undefined}
@ -1486,6 +1493,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
<BudgetRow <BudgetRow
tone="brand-soft" tone="brand-soft"
label="So sánh với Ngân sách full" label="So sánh với Ngân sách full"
indent
sub="= Ngân sách full 9" sub="= Ngân sách full 9"
value={<span className={cn(cmpFull < 0 && 'font-bold text-red-600')}>{fmtVndSigned(cmpFull)}</span>} value={<span className={cn(cmpFull < 0 && 'font-bold text-red-600')}>{fmtVndSigned(cmpFull)}</span>}
third={fmtPct(cmpFull, full) ?? undefined} third={fmtPct(cmpFull, full) ?? undefined}