chore(WEB): CEO 대시보드 개선 및 모바일 테스트 계획 추가

- CEO 대시보드: 일일보고, 접대비, 복리후생 섹션 개선
- CEO 대시보드: 상세 모달 기능 확장
- 카드거래조회: 기능 및 타입 확장
- 알림설정: 항목 설정 다이얼로그 추가
- 회사정보관리: 컴포넌트 개선
- 모바일 오버플로우 테스트 계획서 추가 (Galaxy Fold 대응)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2026-01-09 16:02:04 +09:00
parent f92393f898
commit e4af3232dd
13 changed files with 1924 additions and 199 deletions

View File

@@ -281,6 +281,7 @@ const mockData: CEODashboardData = {
],
},
],
detailButtonPath: '/accounting/receivables-status',
},
debtCollection: {
cards: [
@@ -955,12 +956,40 @@ export function CEODashboard() {
cm4: {
title: '대표자 종합소득세 예상 가중 상세',
summaryCards: [
{ label: '합계', value: 3123000, unit: '원' },
{ label: '전월 대비', value: '+12.5%', isComparison: true, isPositive: false },
{ label: '대표자 종합세 예상 가중', value: 3123000, unit: '원' },
{ label: '추가 세금', value: '+12.5%', isComparison: true, isPositive: false },
{ label: '가지급금', value: '4.5억원' },
{ label: '인정 이자', value: 6000000, unit: '원' },
{ label: '인정이자 4.6%', value: 6000000, unit: '원' },
],
table: {
comparisonSection: {
leftBox: {
title: '가지급금 인정이자가 반영된 종합소득세',
items: [
{ label: '현재 예상 과세표준 (근로소득+상여)', value: 6000000, unit: '원' },
{ label: '현재 적용 세율', value: '19%' },
{ label: '현재 예상 세액', value: 10000000, unit: '원' },
],
borderColor: 'orange',
},
rightBox: {
title: '가지급금 인정이자가 정리된 종합소득세',
items: [
{ label: '가지급금 정리 시 예상 과세표준 (근로소득+상여)', value: 6000000, unit: '원' },
{ label: '가지급금 정리 시 적용 세율', value: '19%' },
{ label: '가지급금 정리 시 예상 세액', value: 10000000, unit: '원' },
],
borderColor: 'blue',
},
vsLabel: '종합소득세 예상 절감',
vsValue: 3123000,
vsSubLabel: '감소 세금 -12.5%',
vsBreakdown: [
{ label: '종합소득세', value: -2000000, unit: '원' },
{ label: '지방소득세', value: -200000, unit: '원' },
{ label: '4대 보험', value: -1000000, unit: '원' },
],
},
referenceTable: {
title: '종합소득세 과세표준 (2024년 기준)',
columns: [
{ key: 'bracket', label: '과세표준', align: 'left' },
@@ -989,16 +1018,450 @@ export function CEODashboard() {
}
}, []);
// 접대비 클릭
const handleEntertainmentClick = useCallback(() => {
// TODO: 접대비 상세 팝업 열기
console.log('접대비 클릭');
// 접대비 현황 카드 클릭 (개별 카드 클릭 시 상세 모달)
const handleEntertainmentCardClick = useCallback((cardId: string) => {
// 접대비 상세 공통 모달 config (et2, et3, et4 공통)
const entertainmentDetailConfig: DetailModalConfig = {
title: '접대비 상세',
summaryCards: [
// 첫 번째 줄: 당해년도
{ label: '당해년도 접대비 총한도', value: 3123000, unit: '원' },
{ label: '당해년도 접대비 잔여한도', value: 6000000, unit: '원' },
{ label: '당해년도 접대비 사용금액', value: 6000000, unit: '원' },
{ label: '당해년도 접대비 사용잔액', value: 0, unit: '원' },
// 두 번째 줄: 분기별
{ label: '1사분기 접대비 총한도', value: 3123000, unit: '원' },
{ label: '1사분기 접대비 잔여한도', value: 6000000, unit: '원' },
{ label: '1사분기 접대비 사용금액', value: 6000000, unit: '원' },
{ label: '1사분기 접대비 초과금액', value: 6000000, unit: '원' },
],
barChart: {
title: '월별 접대비 사용 추이',
data: [
{ name: '1월', value: 3500000 },
{ name: '2월', value: 4200000 },
{ name: '3월', value: 2300000 },
{ name: '4월', value: 3800000 },
{ name: '5월', value: 4500000 },
{ name: '6월', value: 3200000 },
{ name: '7월', value: 2800000 },
],
dataKey: 'value',
xAxisKey: 'name',
color: '#60A5FA',
},
pieChart: {
title: '사용자별 접대비 사용 비율',
data: [
{ name: '홍길동', value: 15000000, percentage: 53, color: '#60A5FA' },
{ name: '김철수', value: 10000000, percentage: 31, color: '#34D399' },
{ name: '이영희', value: 10000000, percentage: 10, color: '#FBBF24' },
{ name: '기타', value: 2000000, percentage: 6, color: '#F87171' },
],
},
table: {
title: '월별 접대비 사용 내역',
columns: [
{ key: 'no', label: 'No.', align: 'center' },
{ key: 'cardName', label: '카드명', align: 'left' },
{ key: 'user', label: '사용자', align: 'center' },
{ key: 'useDate', label: '사용일시', align: 'center', format: 'date' },
{ key: 'transDate', label: '거래일시', align: 'center', format: 'date' },
{ key: 'amount', label: '사용금액', align: 'right', format: 'currency' },
{ key: 'purpose', label: '사용용도', align: 'left' },
],
data: [
{ cardName: '카드명', user: '홍길동', useDate: '2025-12-12 12:12', transDate: '가맹점명', amount: 1000000, purpose: '사용용도' },
{ cardName: '카드명', user: '홍길동', useDate: '2025-12-12 12:12', transDate: '가맹점명', amount: 1000000, purpose: '사용용도' },
{ cardName: '카드명', user: '홍길동', useDate: '2025-12-12 12:12', transDate: '가맹점명', amount: 1000000, purpose: '사용용도' },
{ cardName: '카드명', user: '홍길동', useDate: '2025-10-14 12:12', transDate: '가맹점명', amount: 1000000, purpose: '사용용도' },
{ cardName: '카드명', user: '홍길동', useDate: '2025-12-12 12:12', transDate: '가맹점명', amount: 1000000, purpose: '사용용도' },
],
filters: [
{
key: 'user',
options: [
{ value: 'all', label: '전체' },
{ value: '홍길동', label: '홍길동' },
{ value: '김철수', label: '김철수' },
{ value: '이영희', label: '이영희' },
],
defaultValue: 'all',
},
{
key: 'sortOrder',
options: [
{ value: 'latest', label: '최신순' },
{ value: 'oldest', label: '등록순' },
{ value: 'amountDesc', label: '금액 높은순' },
{ value: 'amountAsc', label: '금액 낮은순' },
],
defaultValue: 'latest',
},
],
showTotal: true,
totalLabel: '합계',
totalValue: 11000000,
totalColumnKey: 'amount',
},
// 접대비 손금한도 계산 - 기본한도 / 수입금액별 추가한도
referenceTables: [
{
title: '접대비 손금한도 계산 - 기본한도',
columns: [
{ key: 'type', label: '구분', align: 'left' },
{ key: 'limit', label: '기본한도', align: 'right' },
],
data: [
{ type: '일반법인', limit: '3,600만원 (연 1,200만원)' },
{ type: '중소기업', limit: '5,400만원 (연 3,600만원)' },
],
},
{
title: '수입금액별 추가한도',
columns: [
{ key: 'range', label: '수입금액', align: 'left' },
{ key: 'rate', label: '적용률', align: 'center' },
],
data: [
{ range: '100억원 이하', rate: '0.3%' },
{ range: '100억원 초과 ~ 500억원 이하', rate: '0.2%' },
{ range: '500억원 초과', rate: '0.03%' },
],
},
],
// 접대비 계산
calculationCards: {
title: '접대비 계산',
cards: [
{ label: '기본한도', value: 36000000 },
{ label: '추가한도', value: 91170000, operator: '+' },
{ label: '접대비 손금한도', value: 127170000, operator: '=' },
],
},
// 접대비 현황 (분기별)
quarterlyTable: {
title: '접대비 현황',
rows: [
{ label: '접대비 한도', q1: 31792500, q2: 31792500, q3: 31792500, q4: 31792500, total: 127170000 },
{ label: '접대비 사용', q1: 10000000, q2: 0, q3: 0, q4: 0, total: 10000000 },
{ label: '접대비 잔여', q1: 21792500, q2: 31792500, q3: 31792500, q4: 31792500, total: 117170000 },
],
},
};
const cardConfigs: Record<string, DetailModalConfig> = {
et1: {
title: '당해 매출 상세',
summaryCards: [
{ label: '당해년도 매출', value: 600000000, unit: '원' },
{ label: '전년 대비', value: '-12.5%', isComparison: true, isPositive: false },
{ label: '당월 매출', value: 6000000, unit: '원' },
],
barChart: {
title: '월별 매출 추이',
data: [
{ name: '1월', value: 85000000 },
{ name: '2월', value: 92000000 },
{ name: '3월', value: 78000000 },
{ name: '4월', value: 95000000 },
{ name: '5월', value: 88000000 },
{ name: '6월', value: 102000000 },
{ name: '7월', value: 60000000 },
],
dataKey: 'value',
xAxisKey: 'name',
color: '#60A5FA',
},
horizontalBarChart: {
title: '당해년도 거래처별 매출',
data: [
{ name: '(주)세우', value: 120000000 },
{ name: '대한건설', value: 95000000 },
{ name: '삼성테크', value: 78000000 },
{ name: '현대상사', value: 65000000 },
{ name: '기타', value: 42000000 },
],
color: '#60A5FA',
},
table: {
title: '일별 매출 내역',
columns: [
{ key: 'no', label: 'No.', align: 'center' },
{ key: 'date', label: '매출일', align: 'center', format: 'date' },
{ key: 'vendor', label: '거래처', align: 'left' },
{ key: 'amount', label: '매출금액', align: 'right', format: 'currency' },
{ key: 'type', label: '매출유형', align: 'center', highlightValue: '미설정' },
],
data: [
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '상품 매출' },
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '부품 매출' },
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '공사 매출' },
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '미설정' },
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '미설정' },
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '미설정' },
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '상품 매출' },
],
filters: [
{
key: 'type',
options: [
{ value: 'all', label: '전체' },
{ value: '상품 매출', label: '상품 매출' },
{ value: '부품 매출', label: '부품 매출' },
{ value: '공사 매출', label: '공사 매출' },
{ value: '임대 수익', label: '임대 수익' },
{ value: '기타 매출', label: '기타 매출' },
{ value: '미설정', label: '미설정' },
],
defaultValue: 'all',
},
{
key: 'sortOrder',
options: [
{ value: 'latest', label: '최신순' },
{ value: 'oldest', label: '등록순' },
{ value: 'amountDesc', label: '금액 높은순' },
{ value: 'amountAsc', label: '금액 낮은순' },
],
defaultValue: 'latest',
},
],
showTotal: true,
totalLabel: '합계',
totalValue: 111000000,
totalColumnKey: 'amount',
},
},
// et2, et3, et4는 모두 동일한 접대비 상세 모달
et2: entertainmentDetailConfig,
et3: entertainmentDetailConfig,
et4: entertainmentDetailConfig,
};
const config = cardConfigs[cardId];
if (config) {
setDetailModalConfig(config);
setIsDetailModalOpen(true);
}
}, []);
// 부가세 클릭
// 복리후생비 현황 카드 클릭 (모든 카드가 동일한 상세 모달)
const handleWelfareCardClick = useCallback(() => {
// 계산 방식에 따른 조건부 calculationCards 생성
const calculationType = dashboardSettings.welfare.calculationType;
const calculationCards = calculationType === 'fixed'
? {
// 직원당 정액 금액/월 방식
title: '복리후생비 계산',
subtitle: '직원당 정액 금액/월 200,000원',
cards: [
{ label: '직원 수', value: 20, unit: '명' },
{ label: '연간 직원당 월급 금액', value: 2400000, unit: '원', operator: '×' as const },
{ label: '당해년도 복리후생비 총 한도', value: 48000000, unit: '원', operator: '=' as const },
],
}
: {
// 연봉 총액 비율 방식
title: '복리후생비 계산',
subtitle: '연봉 총액 기준 비율 20.5%',
cards: [
{ label: '연봉 총액', value: 1000000000, unit: '원' },
{ label: '비율', value: 20.5, unit: '%', operator: '×' as const },
{ label: '당해년도 복리후생비 총 한도', value: 205000000, unit: '원', operator: '=' as const },
],
};
const config: DetailModalConfig = {
title: '복리후생비 상세',
summaryCards: [
// 1행: 당해년도 기준
{ label: '당해년도 복리후생비 계정', value: 3123000, unit: '원' },
{ label: '당해년도 복리후생비 한도', value: 600000, unit: '원' },
{ label: '당해년도 복리후생비 사용', value: 6000000, unit: '원' },
{ label: '당해년도 잔여한도', value: 0, unit: '원' },
// 2행: 1사분기 기준
{ label: '1사분기 복리후생비 총 한도', value: 3123000, unit: '원' },
{ label: '1사분기 복리후생비 잔여한도', value: 6000000, unit: '원' },
{ label: '1사분기 복리후생비 사용금액', value: 6000000, unit: '원' },
{ label: '1사분기 복리후생비 초과 금액', value: 6000000, unit: '원' },
],
barChart: {
title: '월별 복리후생비 사용 추이',
data: [
{ name: '1월', value: 1500000 },
{ name: '2월', value: 1800000 },
{ name: '3월', value: 2200000 },
{ name: '4월', value: 1900000 },
{ name: '5월', value: 2100000 },
{ name: '6월', value: 1700000 },
],
dataKey: 'value',
xAxisKey: 'name',
color: '#60A5FA',
},
pieChart: {
title: '항목별 사용 비율',
data: [
{ name: '식비', value: 55000000, percentage: 55, color: '#FBBF24' },
{ name: '건강검진', value: 25000000, percentage: 5, color: '#60A5FA' },
{ name: '경조사비', value: 10000000, percentage: 10, color: '#F87171' },
{ name: '기타', value: 10000000, percentage: 30, color: '#34D399' },
],
},
table: {
title: '일별 복리후생비 사용 내역',
columns: [
{ key: 'no', label: 'No.', align: 'center' },
{ key: 'cardName', label: '카드명', align: 'left' },
{ key: 'user', label: '사용자', align: 'center' },
{ key: 'date', label: '사용일자', align: 'center', format: 'date' },
{ key: 'store', label: '가맹점명', align: 'left' },
{ key: 'amount', label: '사용금액', align: 'right', format: 'currency' },
{ key: 'usageType', label: '사용항목', align: 'center' },
],
data: [
{ cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1000000, usageType: '식비' },
{ cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1200000, usageType: '건강검진' },
{ cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1500000, usageType: '경조사비' },
{ cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1300000, usageType: '기타' },
{ cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 6000000, usageType: '식비' },
],
filters: [
{
key: 'usageType',
options: [
{ value: 'all', label: '전체' },
{ value: '식비', label: '식비' },
{ value: '건강검진', label: '건강검진' },
{ value: '경조사비', label: '경조사비' },
{ value: '기타', label: '기타' },
],
defaultValue: 'all',
},
{
key: 'sortOrder',
options: [
{ value: 'latest', label: '최신순' },
{ value: 'oldest', label: '등록순' },
{ value: 'amountDesc', label: '금액 높은순' },
{ value: 'amountAsc', label: '금액 낮은순' },
],
defaultValue: 'latest',
},
],
showTotal: true,
totalLabel: '합계',
totalValue: 11000000,
totalColumnKey: 'amount',
},
// 복리후생비 계산 (조건부 - calculationType에 따라)
calculationCards,
// 복리후생비 현황 (분기별 테이블)
quarterlyTable: {
title: '복리후생비 현황',
rows: [
{ label: '한도금액', q1: 12000000, q2: 12000000, q3: 12000000, q4: 12000000, total: 48000000 },
{ label: '이월금액', q1: 0, q2: '', q3: '', q4: '', total: '' },
{ label: '사용금액', q1: 1000000, q2: '', q3: '', q4: '', total: '' },
{ label: '잔여한도', q1: 11000000, q2: '', q3: '', q4: '', total: '' },
{ label: '초과금액', q1: '', q2: '', q3: '', q4: '', total: '' },
],
},
};
setDetailModalConfig(config);
setIsDetailModalOpen(true);
}, [dashboardSettings.welfare.calculationType]);
// 부가세 클릭 (모든 카드가 동일한 상세 모달)
const handleVatClick = useCallback(() => {
// TODO: 부가세 상세 팝업 열기
console.log('부가세 클릭');
const config: DetailModalConfig = {
title: '예상 납부세액',
summaryCards: [],
// 세액 산출 내역 테이블
referenceTable: {
title: '2026년 1사분기 세액 산출 내역',
columns: [
{ key: 'category', label: '구분', align: 'center' },
{ key: 'amount', label: '금액', align: 'right' },
{ key: 'note', label: '비고', align: 'left' },
],
data: [
{ category: '매출세액', amount: '11,000,000', note: '과세매출 X 10%' },
{ category: '매입세액', amount: '1,000,000', note: '공제대상 매입 X 10%' },
{ category: '경감·공제세액', amount: '0', note: '해당없음' },
],
},
// 예상 납부세액 계산
calculationCards: {
title: '예상 납부세액 계산',
cards: [
{ label: '매출세액', value: 11000000, unit: '원' },
{ label: '매입세액', value: 1000000, unit: '원', operator: '-' },
{ label: '경감·공제세액', value: 0, unit: '원', operator: '-' },
{ label: '예상 납부세액', value: 10000000, unit: '원', operator: '=' },
],
},
// 세금계산서 미발행/미수취 내역
table: {
title: '세금계산서 미발행/미수취 내역',
columns: [
{ key: 'no', label: 'No.', align: 'center' },
{ key: 'type', label: '구분', align: 'center' },
{ key: 'issueDate', label: '발행일자', align: 'center', format: 'date' },
{ key: 'vendor', label: '거래처', align: 'left' },
{ key: 'vat', label: '부가세', align: 'right', format: 'currency' },
{ key: 'invoiceStatus', label: '세금계산서 발행', align: 'center' },
],
data: [
{ type: '매출', issueDate: '2025-12-12', vendor: '거래처1', vat: 11000000, invoiceStatus: '미발행' },
{ type: '매입', issueDate: '2025-12-12', vendor: '거래처2', vat: 11000000, invoiceStatus: '미수취' },
{ type: '매출', issueDate: '2025-12-12', vendor: '거래처3', vat: 11000000, invoiceStatus: '미발행' },
{ type: '매입', issueDate: '2025-12-12', vendor: '거래처4', vat: 11000000, invoiceStatus: '미수취' },
{ type: '매출', issueDate: '2025-12-12', vendor: '거래처5', vat: 11000000, invoiceStatus: '미발행' },
{ type: '매입', issueDate: '2025-12-12', vendor: '거래처6', vat: 11000000, invoiceStatus: '미수취' },
{ type: '매출', issueDate: '2025-12-12', vendor: '거래처7', vat: 11000000, invoiceStatus: '미발행' },
],
filters: [
{
key: 'type',
options: [
{ value: 'all', label: '전체' },
{ value: '매출', label: '매출' },
{ value: '매입', label: '매입' },
],
defaultValue: 'all',
},
{
key: 'invoiceStatus',
options: [
{ value: 'all', label: '전체' },
{ value: '미발행', label: '미발행' },
{ value: '미수취', label: '미수취' },
],
defaultValue: 'all',
},
{
key: 'sortOrder',
options: [
{ value: 'latest', label: '최신순' },
{ value: 'oldest', label: '등록순' },
{ value: 'amountDesc', label: '금액 높은순' },
{ value: 'amountAsc', label: '금액 낮은순' },
],
defaultValue: 'latest',
},
],
showTotal: true,
totalLabel: '합계',
totalValue: 111000000,
totalColumnKey: 'vat',
},
};
setDetailModalConfig(config);
setIsDetailModalOpen(true);
}, []);
// 캘린더 일정 클릭 (기존 일정 수정)
@@ -1119,13 +1582,16 @@ export function CEODashboard() {
{dashboardSettings.entertainment.enabled && (
<EntertainmentSection
data={data.entertainment}
onClick={handleEntertainmentClick}
onCardClick={handleEntertainmentCardClick}
/>
)}
{/* 복리후생비 현황 */}
{dashboardSettings.welfare.enabled && (
<WelfareSection data={data.welfare} />
<WelfareSection
data={data.welfare}
onCardClick={handleWelfareCardClick}
/>
)}
{/* 미수금 현황 */}

View File

@@ -38,6 +38,8 @@ import type {
TableFilterConfig,
ComparisonSectionConfig,
ReferenceTableConfig,
CalculationCardsConfig,
QuarterlyTableConfig,
} from '../types';
interface DetailModalProps {
@@ -245,7 +247,7 @@ const ComparisonSection = ({ config }: { config: ComparisonSectionConfig }) => {
{/* VS 영역 */}
<div className="flex flex-col items-center justify-center px-4">
<span className="text-2xl font-bold text-gray-400 mb-2">VS</span>
<div className="bg-red-50 rounded-lg px-4 py-2 text-center">
<div className="bg-red-50 rounded-lg px-4 py-3 text-center min-w-[180px]">
<p className="text-xs text-gray-600 mb-1">{config.vsLabel}</p>
<p className="text-xl font-bold text-red-500">
{typeof config.vsValue === 'number'
@@ -255,6 +257,21 @@ const ComparisonSection = ({ config }: { config: ComparisonSectionConfig }) => {
{config.vsSubLabel && (
<p className="text-xs text-gray-500 mt-1">{config.vsSubLabel}</p>
)}
{/* VS 세부 항목 */}
{config.vsBreakdown && config.vsBreakdown.length > 0 && (
<div className="mt-2 pt-2 border-t border-red-200 space-y-1">
{config.vsBreakdown.map((item, index) => (
<div key={index} className="flex justify-between text-xs">
<span className="text-gray-600">{item.label}</span>
<span className="font-medium text-gray-700">
{typeof item.value === 'number'
? formatCurrency(item.value) + (item.unit || '원')
: item.value}
</span>
</div>
))}
</div>
)}
</div>
</div>
@@ -284,6 +301,105 @@ const ComparisonSection = ({ config }: { config: ComparisonSectionConfig }) => {
);
};
/**
* 계산 카드 섹션 컴포넌트 (접대비 계산 등)
*/
const CalculationCardsSection = ({ config }: { config: CalculationCardsConfig }) => {
const isResultCard = (index: number, operator?: string) => {
// '=' 연산자가 있는 카드는 결과 카드로 강조
return operator === '=';
};
return (
<div className="mt-6">
<div className="flex items-center gap-2 mb-3">
<h4 className="font-medium text-gray-800">{config.title}</h4>
{config.subtitle && (
<span className="text-sm text-gray-500">{config.subtitle}</span>
)}
</div>
<div className="flex items-center gap-3">
{config.cards.map((card, index) => (
<div key={index} className="flex items-center gap-3">
{/* 연산자 표시 (첫 번째 카드 제외) */}
{index > 0 && card.operator && (
<span className="text-3xl font-bold text-gray-400">
{card.operator}
</span>
)}
{/* 카드 */}
<div className={cn(
"rounded-lg p-5 min-w-[180px] text-center border",
isResultCard(index, card.operator)
? "bg-blue-50 border-blue-200"
: "bg-gray-50 border-gray-200"
)}>
<p className={cn(
"text-sm mb-2",
isResultCard(index, card.operator) ? "text-blue-600" : "text-gray-500"
)}>
{card.label}
</p>
<p className={cn(
"text-2xl font-bold",
isResultCard(index, card.operator) ? "text-blue-700" : "text-gray-900"
)}>
{formatCurrency(card.value)}{card.unit || '원'}
</p>
</div>
</div>
))}
</div>
</div>
);
};
/**
* 분기별 테이블 섹션 컴포넌트 (접대비 현황 등)
*/
const QuarterlyTableSection = ({ config }: { config: QuarterlyTableConfig }) => {
const formatValue = (value: number | string | undefined): string => {
if (value === undefined) return '-';
if (typeof value === 'number') return formatCurrency(value);
return value;
};
return (
<div className="mt-6">
<h4 className="font-medium text-gray-800 mb-3">{config.title}</h4>
<div className="border rounded-lg overflow-hidden">
<table className="w-full">
<thead>
<tr className="bg-gray-100">
<th className="px-4 py-3 text-xs font-medium text-gray-600 text-left"></th>
<th className="px-4 py-3 text-xs font-medium text-gray-600 text-center">1</th>
<th className="px-4 py-3 text-xs font-medium text-gray-600 text-center">2</th>
<th className="px-4 py-3 text-xs font-medium text-gray-600 text-center">3</th>
<th className="px-4 py-3 text-xs font-medium text-gray-600 text-center">4</th>
<th className="px-4 py-3 text-xs font-medium text-gray-600 text-center"></th>
</tr>
</thead>
<tbody>
{config.rows.map((row, rowIndex) => (
<tr
key={rowIndex}
className="border-t border-gray-100 hover:bg-gray-50"
>
<td className="px-4 py-3 text-sm text-gray-700 font-medium">{row.label}</td>
<td className="px-4 py-3 text-sm text-gray-700 text-center">{formatValue(row.q1)}</td>
<td className="px-4 py-3 text-sm text-gray-700 text-center">{formatValue(row.q2)}</td>
<td className="px-4 py-3 text-sm text-gray-700 text-center">{formatValue(row.q3)}</td>
<td className="px-4 py-3 text-sm text-gray-700 text-center">{formatValue(row.q4)}</td>
<td className="px-4 py-3 text-sm text-gray-900 text-center font-medium">{formatValue(row.total)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
/**
* 참조 테이블 컴포넌트 (필터 없는 정보성 테이블)
*/
@@ -612,13 +728,32 @@ export function DetailModal({ isOpen, onClose, config }: DetailModalProps) {
<ComparisonSection config={config.comparisonSection} />
)}
{/* 참조 테이블 영역 */}
{/* 참조 테이블 영역 (단일 - 테이블 위에 표시) */}
{config.referenceTable && (
<ReferenceTableSection config={config.referenceTable} />
)}
{/* 테이블 영역 */}
{/* 계산 카드 섹션 영역 (테이블 위에 표시) */}
{config.calculationCards && (
<CalculationCardsSection config={config.calculationCards} />
)}
{/* 메인 테이블 영역 */}
{config.table && <TableSection key={config.title} config={config.table} />}
{/* 참조 테이블 영역 (다중 - 테이블 아래 표시) */}
{config.referenceTables && config.referenceTables.length > 0 && (
<div className="space-y-4">
{config.referenceTables.map((tableConfig, index) => (
<ReferenceTableSection key={index} config={tableConfig} />
))}
</div>
)}
{/* 분기별 테이블 영역 */}
{config.quarterlyTable && (
<QuarterlyTableSection config={config.quarterlyTable} />
)}
</div>
</DialogContent>
</Dialog>

View File

@@ -11,10 +11,7 @@ interface DailyReportSectionProps {
export function DailyReportSection({ data, onClick }: DailyReportSectionProps) {
return (
<Card
className={onClick ? 'cursor-pointer hover:shadow-md transition-shadow' : ''}
onClick={onClick}
>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<SectionTitle title="일일 일보" badge="info" />
@@ -23,7 +20,7 @@ export function DailyReportSection({ data, onClick }: DailyReportSectionProps) {
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
{data.cards.map((card) => (
<AmountCardItem key={card.id} card={card} />
<AmountCardItem key={card.id} card={card} onClick={onClick} />
))}
</div>

View File

@@ -6,10 +6,10 @@ import type { EntertainmentData } from '../types';
interface EntertainmentSectionProps {
data: EntertainmentData;
onClick?: () => void;
onCardClick?: (cardId: string) => void;
}
export function EntertainmentSection({ data, onClick }: EntertainmentSectionProps) {
export function EntertainmentSection({ data, onCardClick }: EntertainmentSectionProps) {
return (
<Card>
<CardContent className="p-6">
@@ -20,7 +20,7 @@ export function EntertainmentSection({ data, onClick }: EntertainmentSectionProp
<AmountCardItem
key={card.id}
card={card}
onClick={onClick}
onClick={() => onCardClick?.(card.id)}
/>
))}
</div>

View File

@@ -6,9 +6,10 @@ import type { WelfareData } from '../types';
interface WelfareSectionProps {
data: WelfareData;
onCardClick?: (cardId: string) => void;
}
export function WelfareSection({ data }: WelfareSectionProps) {
export function WelfareSection({ data, onCardClick }: WelfareSectionProps) {
return (
<Card>
<CardContent className="p-6">
@@ -16,7 +17,11 @@ export function WelfareSection({ data }: WelfareSectionProps) {
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
{data.cards.map((card) => (
<AmountCardItem key={card.id} card={card} />
<AmountCardItem
key={card.id}
card={card}
onClick={onCardClick ? () => onCardClick(card.id) : undefined}
/>
))}
</div>

View File

@@ -326,6 +326,13 @@ export interface ComparisonBoxConfig {
borderColor: 'orange' | 'blue';
}
// VS 중앙 세부 항목 타입
export interface VsBreakdownItem {
label: string;
value: string | number;
unit?: string;
}
// VS 비교 섹션 설정 타입
export interface ComparisonSectionConfig {
leftBox: ComparisonBoxConfig;
@@ -333,6 +340,7 @@ export interface ComparisonSectionConfig {
vsLabel: string;
vsValue: string | number;
vsSubLabel?: string;
vsBreakdown?: VsBreakdownItem[]; // VS 중앙에 표시할 세부 항목들
}
// 참조 테이블 설정 타입 (필터 없는 정보성 테이블)
@@ -342,6 +350,37 @@ export interface ReferenceTableConfig {
data: Record<string, unknown>[];
}
// 계산 카드 아이템 타입 (접대비 계산 등)
export interface CalculationCardItem {
label: string;
value: number;
unit?: string;
operator?: '+' | '=' | '-' | '×'; // 연산자 표시
}
// 계산 카드 섹션 설정 타입
export interface CalculationCardsConfig {
title: string;
subtitle?: string; // 서브타이틀 (예: "직원당 정액 금액/월 200,000원")
cards: CalculationCardItem[];
}
// 분기별 테이블 행 타입
export interface QuarterlyTableRow {
label: string;
q1?: number | string;
q2?: number | string;
q3?: number | string;
q4?: number | string;
total?: number | string;
}
// 분기별 테이블 설정 타입
export interface QuarterlyTableConfig {
title: string;
rows: QuarterlyTableRow[];
}
// 상세 모달 전체 설정 타입
export interface DetailModalConfig {
title: string;
@@ -351,6 +390,9 @@ export interface DetailModalConfig {
horizontalBarChart?: HorizontalBarChartConfig; // 가로 막대 차트 (도넛 차트 대신 사용)
comparisonSection?: ComparisonSectionConfig; // VS 비교 섹션
referenceTable?: ReferenceTableConfig; // 참조 테이블 (필터 없음)
referenceTables?: ReferenceTableConfig[]; // 다중 참조 테이블
calculationCards?: CalculationCardsConfig; // 계산 카드 섹션
quarterlyTable?: QuarterlyTableConfig; // 분기별 테이블
table?: TableConfig;
}