Files
sam-react-prod/src/components/accounting/IncomeStatement/MonthlyView.tsx

288 lines
8.8 KiB
TypeScript

'use client';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { formatNumber } from '@/lib/utils/amount';
import type { IncomeStatementMonthlyData, IncomeStatementSection } from './types';
import { HIGHLIGHT_CODES } from './types';
interface MonthlyViewProps {
data: IncomeStatementMonthlyData;
selectedMonth: string | null; // null = 전체
unitLabel: string;
}
export function MonthlyView({ data, selectedMonth, unitLabel }: MonthlyViewProps) {
const { months, fiscalLabel } = data;
if (selectedMonth) {
// 개별 월 보기
const monthData = months.find((m) => m.month === selectedMonth);
if (!monthData) {
return <div className="text-center py-8 text-muted-foreground text-sm"> .</div>;
}
return (
<SingleMonthView
sections={monthData.sections}
monthLabel={monthData.label}
fiscalLabel={fiscalLabel}
unitLabel={unitLabel}
/>
);
}
// 전체 월 비교 보기 (가로 스크롤)
return (
<AllMonthsView
months={months}
fiscalLabel={fiscalLabel}
unitLabel={unitLabel}
/>
);
}
// 개별 월 보기 — PeriodView와 동일한 2열(금액+소계) 구조
function SingleMonthView({
sections,
monthLabel,
fiscalLabel,
unitLabel,
}: {
sections: IncomeStatementSection[];
monthLabel: string;
fiscalLabel: string;
unitLabel: string;
}) {
return (
<div className="space-y-1">
<div className="rounded-md border overflow-x-auto">
<Table>
<TableHeader>
<TableRow className="bg-green-700 hover:bg-green-700">
<TableHead
rowSpan={2}
className="font-semibold text-xs md:text-sm text-white w-[40%] border-r border-green-600"
>
</TableHead>
<TableHead
colSpan={2}
className="font-semibold text-center text-xs md:text-sm text-white border-b border-green-600"
>
{fiscalLabel} {monthLabel}
</TableHead>
</TableRow>
<TableRow className="bg-green-700 hover:bg-green-700">
<TableHead
colSpan={2}
className="font-semibold text-center text-xs md:text-sm text-white"
>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sections.map((section) => {
const isHighlight = HIGHLIGHT_CODES.includes(section.code);
const highlightClass = isHighlight ? 'bg-green-50' : '';
// 계산 항목 또는 세부과목 없는 항목
if (section.items.length === 0) {
return (
<TableRow key={section.code} className={highlightClass}>
<TableCell className="text-xs md:text-sm font-semibold">
{section.code}. {section.name}
</TableCell>
<TableCell />
<TableCell className={`text-right text-xs md:text-sm whitespace-nowrap ${isHighlight ? 'font-bold' : 'font-semibold'}`}>
{section.currentAmount !== 0 ? formatNumber(section.currentAmount) : ''}
</TableCell>
</TableRow>
);
}
// 세부과목 있는 항목
const lastIdx = section.items.length - 1;
return (
<SingleMonthSectionRows
key={section.code}
section={section}
lastIdx={lastIdx}
/>
);
})}
</TableBody>
</Table>
</div>
<div className="text-xs text-muted-foreground text-right px-1">
(: {unitLabel})
</div>
</div>
);
}
function SingleMonthSectionRows({
section,
lastIdx,
}: {
section: IncomeStatementSection;
lastIdx: number;
}) {
return (
<>
<TableRow>
<TableCell className="text-xs md:text-sm font-semibold">
{section.code}. {section.name}
</TableCell>
<TableCell />
<TableCell />
</TableRow>
{section.items.map((item, idx) => {
const isLast = idx === lastIdx;
return (
<TableRow key={item.code}>
<TableCell className="text-xs md:text-sm pl-8 text-muted-foreground">
{item.name}
</TableCell>
<TableCell className="text-right text-xs md:text-sm whitespace-nowrap">
{item.current !== 0 ? formatNumber(item.current) : ''}
</TableCell>
<TableCell className="text-right text-xs md:text-sm whitespace-nowrap font-semibold">
{isLast ? formatNumber(section.currentAmount) : ''}
</TableCell>
</TableRow>
);
})}
</>
);
}
// 전체 월 비교 보기 (가로 스크롤, 과목 열 sticky)
function AllMonthsView({
months,
fiscalLabel,
unitLabel,
}: {
months: IncomeStatementMonthlyData['months'];
fiscalLabel: string;
unitLabel: string;
}) {
const baseSections = months[0]?.sections || [];
const rows: {
type: 'section' | 'item';
code: string;
name: string;
sectionCode: string;
isHighlight: boolean;
}[] = [];
baseSections.forEach((section) => {
const isHighlight = HIGHLIGHT_CODES.includes(section.code);
rows.push({
type: 'section',
code: section.code,
name: `${section.code}. ${section.name}`,
sectionCode: section.code,
isHighlight,
});
section.items.forEach((item) => {
rows.push({
type: 'item',
code: item.code,
name: item.name,
sectionCode: section.code,
isHighlight: false,
});
});
});
const monthMaps = months.map((m) => {
const sectionMap = new Map<string, number>();
const itemMap = new Map<string, number>();
m.sections.forEach((s) => {
sectionMap.set(s.code, s.currentAmount);
s.items.forEach((it) => {
itemMap.set(`${s.code}-${it.code}`, it.current);
});
});
return { sectionMap, itemMap };
});
return (
<div className="space-y-1">
<div className="rounded-md border overflow-x-auto">
<div style={{ minWidth: `${200 + months.length * 120}px` }}>
<Table>
<TableHeader>
<TableRow>
<TableHead
className="font-semibold text-xs md:text-sm sticky left-0 z-20 bg-background min-w-[200px]"
style={{ boxShadow: '2px 0 4px -2px rgba(0,0,0,0.1)' }}
>
</TableHead>
{months.map((m) => (
<TableHead
key={m.month}
className="font-semibold text-right text-xs md:text-sm min-w-[110px] whitespace-nowrap"
>
{m.label}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{rows.map((row) => {
const isSection = row.type === 'section';
const highlightClass = row.isHighlight ? 'bg-green-50' : '';
const sectionBg = isSection && !row.isHighlight ? 'bg-muted/30' : '';
return (
<TableRow
key={`${row.sectionCode}-${row.code}`}
className={`${highlightClass} ${sectionBg}`.trim() || undefined}
>
<TableCell
className={`text-xs md:text-sm sticky left-0 z-10 bg-inherit ${
isSection ? 'font-semibold' : 'pl-8 text-muted-foreground'
}`}
style={{ boxShadow: '2px 0 4px -2px rgba(0,0,0,0.1)' }}
>
{row.name}
</TableCell>
{months.map((m, mi) => {
const amount = isSection
? monthMaps[mi].sectionMap.get(row.sectionCode) ?? 0
: monthMaps[mi].itemMap.get(`${row.sectionCode}-${row.code}`) ?? 0;
return (
<TableCell
key={m.month}
className={`text-right text-xs md:text-sm whitespace-nowrap ${
isSection ? 'font-semibold' : ''
}`}
>
{formatNumber(amount)}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</div>
</div>
<div className="text-xs text-muted-foreground text-right px-1">
(: {unitLabel})
</div>
</div>
);
}