From 4e179d2eca918fbe2ecd55e3c7aff71407b58ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Sun, 1 Mar 2026 12:20:05 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20[CEO=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C]=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EB=AA=A8=EB=8B=AC/=EC=84=B9=EC=85=98?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DashboardSettingsSections, DetailModalSections 분리 - 모달 설정(카드/접대비/복리후생/부가세/월비용) 개선 - 섹션 컴포넌트 최적화 (매출/매입/카드/미출고 등) - mockData, types 정리 Co-Authored-By: Claude Opus 4.6 --- .../business/CEODashboard/CEODashboard.tsx | 44 +- .../business/CEODashboard/components.tsx | 15 +- .../dialogs/DashboardSettingsDialog.tsx | 451 +---------- .../dialogs/DashboardSettingsSections.tsx | 485 ++++++++++++ .../business/CEODashboard/mockData.ts | 55 +- .../cardManagementConfigTransformers.ts | 52 +- .../modalConfigs/cardManagementConfigs.ts | 76 +- .../modalConfigs/entertainmentConfigs.ts | 96 +-- .../modalConfigs/monthlyExpenseConfigs.ts | 166 ++-- .../CEODashboard/modalConfigs/vatConfigs.ts | 82 +- .../modalConfigs/welfareConfigs.ts | 59 +- .../CEODashboard/modals/DetailModal.tsx | 698 +---------------- .../modals/DetailModalSections.tsx | 712 ++++++++++++++++++ .../sections/CardManagementSection.tsx | 12 +- .../sections/EnhancedSections.tsx | 117 ++- .../sections/PurchaseStatusSection.tsx | 35 +- .../sections/SalesStatusSection.tsx | 37 +- .../sections/StatusBoardSection.tsx | 4 +- .../sections/UnshippedSection.tsx | 23 +- src/components/business/CEODashboard/types.ts | 54 +- 20 files changed, 1645 insertions(+), 1628 deletions(-) create mode 100644 src/components/business/CEODashboard/dialogs/DashboardSettingsSections.tsx create mode 100644 src/components/business/CEODashboard/modals/DetailModalSections.tsx diff --git a/src/components/business/CEODashboard/CEODashboard.tsx b/src/components/business/CEODashboard/CEODashboard.tsx index a7588083..daf5fcc0 100644 --- a/src/components/business/CEODashboard/CEODashboard.tsx +++ b/src/components/business/CEODashboard/CEODashboard.tsx @@ -35,12 +35,10 @@ import { DashboardSettingsDialog } from './dialogs/DashboardSettingsDialog'; import { mockData } from './mockData'; import { LazySection } from './LazySection'; import { useCEODashboard, useTodayIssue, useCalendar, useVat, useEntertainment, useWelfare, useWelfareDetail, useMonthlyExpenseDetail } from '@/hooks/useCEODashboard'; -import { useCardManagementModals, type CardManagementCardId } from '@/hooks/useCardManagementModals'; -import type { MonthlyExpenseCardId } from '@/hooks/useCEODashboard'; +import { useCardManagementModals } from '@/hooks/useCardManagementModals'; import { getMonthlyExpenseModalConfig, getCardManagementModalConfig, - getCardManagementModalConfigWithData, getEntertainmentModalConfig, getWelfareModalConfig, getVatModalConfig, @@ -93,19 +91,24 @@ export function CEODashboard() { const data = useMemo(() => ({ ...mockData, // Phase 1 섹션들: API 데이터 우선, 실패 시 mockData fallback - // TODO: 자금현황 카드 변경 (일일일보/매출채권/매입채무/운영자금) - 새 API 구현 후 교체 + // TODO: 자금현황 카드 변경 (일일일보/미수금/미지급금/당월예상지출) - 새 API 구현 후 교체 dailyReport: mockData.dailyReport, - receivable: apiData.receivable.data ?? mockData.receivable, + // TODO: D1.7 카드 구조 변경 - 새 백엔드 API 구현 후 API 데이터로 교체 + // cardManagement: 카드/경조사/상품권/접대비 (기존: 카드/가지급금/법인세/종합세) + // entertainment: 주말심야/기피업종/고액결제/증빙미비 (기존: 매출/한도/잔여한도/사용금액) + // welfare: 비과세초과/사적사용/특정인편중/한도초과 (기존: 한도/잔여한도/사용금액) + // receivable: 누적/당월/거래처/Top3 (기존: 누적/당월/거래처현황) + receivable: mockData.receivable, debtCollection: apiData.debtCollection.data ?? mockData.debtCollection, monthlyExpense: apiData.monthlyExpense.data ?? mockData.monthlyExpense, - cardManagement: apiData.cardManagement.data ?? mockData.cardManagement, + cardManagement: mockData.cardManagement, // Phase 2 섹션들 (API 연동 완료 - 목업 fallback 제거) todayIssue: apiData.statusBoard.data ?? [], todayIssueList: todayIssueData.data?.items ?? [], calendarSchedules: calendarData.data?.items ?? mockData.calendarSchedules, vat: vatData.data ?? mockData.vat, - entertainment: entertainmentData.data ?? mockData.entertainment, - welfare: welfareData.data ?? mockData.welfare, + entertainment: mockData.entertainment, + welfare: mockData.welfare, // 신규 섹션 (API 미구현 - mock 데이터) salesStatus: mockData.salesStatus, purchaseStatus: mockData.purchaseStatus, @@ -204,35 +207,28 @@ export function CEODashboard() { }, []); // 당월 예상 지출 카드 클릭 (개별 카드 클릭 시 상세 모달) - const handleMonthlyExpenseCardClick = useCallback(async (cardId: string) => { - // 1. 먼저 API에서 데이터 fetch 시도 - const apiConfig = await monthlyExpenseDetailData.fetchData(cardId as MonthlyExpenseCardId); - - // 2. API 데이터가 있으면 사용, 없으면 fallback config 사용 - const config = apiConfig ?? getMonthlyExpenseModalConfig(cardId); + // TODO: D1.7 모달 구조 변경 - 새 백엔드 API 구현 후 API 데이터로 교체 + const handleMonthlyExpenseCardClick = useCallback((cardId: string) => { + const config = getMonthlyExpenseModalConfig(cardId); if (config) { setDetailModalConfig(config); setIsDetailModalOpen(true); } - }, [monthlyExpenseDetailData]); + }, []); // 당월 예상 지출 클릭 (deprecated - 개별 카드 클릭으로 대체) const handleMonthlyExpenseClick = useCallback(() => { }, []); - // 카드/가지급금 관리 카드 클릭 (개별 카드 클릭 시 상세 모달) - const handleCardManagementCardClick = useCallback(async (cardId: string) => { - // 1. API에서 데이터 fetch (데이터 직접 반환) - const modalData = await cardManagementModals.fetchModalData(cardId as CardManagementCardId); - - // 2. API 데이터로 config 생성 (데이터 없으면 fallback) - const config = getCardManagementModalConfigWithData(cardId, modalData); - + // 카드/가지급금 관리 카드 클릭 → 모두 가지급금 상세(cm2) 모달 + // 기획서 P52: 카드, 경조사, 상품권, 접대비, 총합계 모두 동일한 가지급금 상세 모달 + const handleCardManagementCardClick = useCallback((cardId: string) => { + const config = getCardManagementModalConfig('cm2'); if (config) { setDetailModalConfig(config); setIsDetailModalOpen(true); } - }, [cardManagementModals]); + }, []); // 접대비 현황 카드 클릭 (개별 카드 클릭 시 상세 모달) const handleEntertainmentCardClick = useCallback((cardId: string) => { diff --git a/src/components/business/CEODashboard/components.tsx b/src/components/business/CEODashboard/components.tsx index 52c4d1c2..d90d29d5 100644 --- a/src/components/business/CEODashboard/components.tsx +++ b/src/components/business/CEODashboard/components.tsx @@ -37,26 +37,15 @@ export const SECTION_THEME_STYLES: Record { +const formatAmount = (amount: number, showUnit = true): string => { const formatted = new Intl.NumberFormat('ko-KR').format(amount); return showUnit ? formatted + '원' : formatted; }; -/** - * 억 단위 포맷 함수 - */ -export const formatBillion = (amount: number): string => { - const billion = amount / 100000000; - if (billion >= 1) { - return billion.toFixed(1) + '억원'; - } - return formatAmount(amount); -}; - /** * USD 달러 포맷 함수 */ -export const formatUSD = (amount: number): string => { +const formatUSD = (amount: number): string => { return '$ ' + new Intl.NumberFormat('en-US').format(amount); }; diff --git a/src/components/business/CEODashboard/dialogs/DashboardSettingsDialog.tsx b/src/components/business/CEODashboard/dialogs/DashboardSettingsDialog.tsx index 96d0b139..cccd950f 100644 --- a/src/components/business/CEODashboard/dialogs/DashboardSettingsDialog.tsx +++ b/src/components/business/CEODashboard/dialogs/DashboardSettingsDialog.tsx @@ -1,10 +1,7 @@ 'use client'; import { useState, useCallback, useEffect } from 'react'; -import { ChevronDown, ChevronUp, GripVertical } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { CurrencyInput } from '@/components/ui/currency-input'; -import { NumberInput } from '@/components/ui/number-input'; import { Dialog, DialogContent, @@ -12,18 +9,6 @@ import { DialogTitle, DialogFooter, } from '@/components/ui/dialog'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from '@/components/ui/collapsible'; import { cn } from '@/lib/utils'; import type { DashboardSettings, @@ -34,21 +19,13 @@ import type { WelfareCalculationType, SectionKey, } from '../types'; -import { DEFAULT_DASHBOARD_SETTINGS, DEFAULT_SECTION_ORDER, SECTION_LABELS } from '../types'; - -// 현황판 항목 라벨 (구 오늘의 이슈) -const STATUS_BOARD_LABELS: Record = { - orders: '수주', - debtCollection: '채권 추심', - safetyStock: '안전 재고', - taxReport: '세금 신고', - newVendor: '신규 업체 등록', - annualLeave: '연차', - lateness: '지각', - absence: '결근', - purchase: '발주', - approvalRequest: '결재 요청', -}; +import { DEFAULT_SECTION_ORDER, SECTION_LABELS } from '../types'; +import { + SectionRow, + StatusBoardItemsList, + EntertainmentContent, + WelfareContent, +} from './DashboardSettingsSections'; interface DashboardSettingsDialogProps { isOpen: boolean; @@ -65,6 +42,7 @@ export function DashboardSettingsDialog({ }: DashboardSettingsDialogProps) { const [localSettings, setLocalSettings] = useState(settings); const [expandedSections, setExpandedSections] = useState>({ + todayIssueList: false, entertainment: false, welfare: false, statusBoard: false, @@ -192,8 +170,8 @@ export function DashboardSettingsDialog({ // 접대비 설정 변경 const handleEntertainmentChange = useCallback( ( - key: 'enabled' | 'limitType' | 'companyType', - value: boolean | EntertainmentLimitType | CompanyType + key: 'enabled' | 'limitType' | 'companyType' | 'highAmountThreshold', + value: boolean | EntertainmentLimitType | CompanyType | number ) => { setLocalSettings((prev) => ({ ...prev, @@ -248,85 +226,6 @@ export function DashboardSettingsDialog({ onClose(); }, [settings, onClose]); - // 커스텀 스위치 (라이트 테마용) - const ToggleSwitch = ({ - checked, - onCheckedChange, - }: { - checked: boolean; - onCheckedChange: (checked: boolean) => void; - }) => ( - - ); - - // 섹션 행 컴포넌트 (라이트 테마) - const SectionRow = ({ - label, - checked, - onCheckedChange, - hasExpand, - isExpanded, - onToggleExpand, - children, - showGrip, - }: { - label: string; - checked: boolean; - onCheckedChange: (checked: boolean) => void; - hasExpand?: boolean; - isExpanded?: boolean; - onToggleExpand?: () => void; - children?: React.ReactNode; - showGrip?: boolean; - }) => ( - -
-
- {showGrip && ( - - )} - {hasExpand && ( - - - - )} - {label} -
- -
- {children && ( - - {children} - - )} -
- ); - // 섹션 렌더링 함수 const renderSection = (key: SectionKey): React.ReactNode => { switch (key) { @@ -336,8 +235,16 @@ export function DashboardSettingsDialog({ label={SECTION_LABELS.todayIssueList} checked={localSettings.todayIssueList} onCheckedChange={handleTodayIssueListToggle} + hasExpand + isExpanded={expandedSections.todayIssueList} + onToggleExpand={() => toggleSection('todayIssueList')} showGrip - /> + > + + ); case 'dailyReport': @@ -361,26 +268,10 @@ export function DashboardSettingsDialog({ onToggleExpand={() => toggleSection('statusBoard')} showGrip > -
- {(Object.keys(STATUS_BOARD_LABELS) as Array).map( - (itemKey) => ( -
- - {STATUS_BOARD_LABELS[itemKey]} - - - handleStatusBoardItemToggle(itemKey, checked) - } - /> -
- ) - )} -
+ ); @@ -415,211 +306,12 @@ export function DashboardSettingsDialog({ onToggleExpand={() => toggleSection('entertainment')} showGrip > -
-
- 접대비 한도 관리 - -
-
- 기업 구분 - -
- {/* 기업 구분 방법 설명 패널 */} - toggleSection('companyTypeInfo')} - > - - - - - {/* ■ 중소기업 판단 기준표 */} -
-
- - 중소기업 판단 기준표 -
- - - - - - - - - - - - - - - - - - - - - - - - - -
조건기준충족 요건
① 매출액업종별 상이업종별 기준 금액 이하
② 자산총액5,000억원미만
③ 독립성소유·경영대기업 계열 아님
-
- - {/* ① 업종별 매출액 기준 */} -
-
- ① 업종별 매출액 기준 (최근 3개년 평균) -
- - - - - - - - - - - - - - - - - - -
업종 분류기준 매출액
제조업1,500억원 이하
건설업1,000억원 이하
운수업1,000억원 이하
도매업1,000억원 이하
소매업600억원 이하
정보통신업600억원 이하
전문서비스업600억원 이하
숙박·음식점업400억원 이하
기타 서비스업400억원 이하
-
- - {/* ② 자산총액 기준 */} -
-
- ② 자산총액 기준 -
- - - - - - - - - - - - - -
구분기준
5,000억원 미만직전 사업연도 말 자산총액
-
- - {/* ③ 독립성 기준 */} -
-
- ③ 독립성 기준 -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
구분내용판정
독립기업아래 항목에 모두 해당하지 않음충족
기업집단 소속공정거래법상 상호출자제한 기업집단 소속미충족
대기업 지분대기업이 발행주식 30% 이상 보유미충족
관계기업 합산관계기업 포함 시 매출액·자산 기준 초과미충족
-
- - {/* ■ 판정 결과 */} -
-
- - 판정 결과 -
- - - - - - - - - - - - - - - - - - - - -
판정조건접대비 기본한도
중소기업①②③ 모두 충족3,600만원
일반법인①②③ 중 하나라도 미충족1,200만원
-
-
-
-
+ toggleSection('companyTypeInfo')} + /> ); @@ -634,87 +326,10 @@ export function DashboardSettingsDialog({ onToggleExpand={() => toggleSection('welfare')} showGrip > -
-
- 복리후생비 한도 관리 - -
-
- 계산 방식 - -
- {localSettings.welfare.calculationType === 'fixed' ? ( -
- 직원당 정해 금액/월 -
- - handleWelfareChange( - 'fixedAmountPerMonth', - value ?? 0 - ) - } - className="w-28 h-8" - /> -
-
- ) : ( -
- 비율 -
- - handleWelfareChange('ratio', value ?? 0) - } - className="w-20 h-8 text-right" - /> - % -
-
- )} -
- 연간 복리후생비총액 -
- - handleWelfareChange('annualTotal', value ?? 0) - } - className="w-32 h-8" - /> -
-
-
+ ); diff --git a/src/components/business/CEODashboard/dialogs/DashboardSettingsSections.tsx b/src/components/business/CEODashboard/dialogs/DashboardSettingsSections.tsx new file mode 100644 index 00000000..11a3d4e2 --- /dev/null +++ b/src/components/business/CEODashboard/dialogs/DashboardSettingsSections.tsx @@ -0,0 +1,485 @@ +'use client'; + +import { ChevronDown, ChevronUp, GripVertical } from 'lucide-react'; +import { CurrencyInput } from '@/components/ui/currency-input'; +import { NumberInput } from '@/components/ui/number-input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/collapsible'; +import { cn } from '@/lib/utils'; +import type { + TodayIssueSettings, + DashboardSettings, + EntertainmentLimitType, + CompanyType, + WelfareLimitType, + WelfareCalculationType, +} from '../types'; + +// ─── 현황판 항목 라벨 ────────────────────────────── +export const STATUS_BOARD_LABELS: Record = { + orders: '수주', + debtCollection: '채권 추심', + safetyStock: '안전 재고', + taxReport: '세금 신고', + newVendor: '신규 업체 등록', + annualLeave: '연차', + vehicle: '차량', + equipment: '장비', + purchase: '발주', + approvalRequest: '결재 요청', + fundStatus: '자금 현황', +}; + +// ─── 커스텀 스위치 ────────────────────────────────── +export function ToggleSwitch({ + checked, + onCheckedChange, +}: { + checked: boolean; + onCheckedChange: (checked: boolean) => void; +}) { + return ( + + ); +} + +// ─── 섹션 행 (Collapsible 래퍼) ───────────────────── +export function SectionRow({ + label, + checked, + onCheckedChange, + hasExpand, + isExpanded, + onToggleExpand, + children, + showGrip, +}: { + label: string; + checked: boolean; + onCheckedChange: (checked: boolean) => void; + hasExpand?: boolean; + isExpanded?: boolean; + onToggleExpand?: () => void; + children?: React.ReactNode; + showGrip?: boolean; +}) { + return ( + +
+
+ {showGrip && ( + + )} + {hasExpand && ( + + + + )} + {label} +
+ +
+ {children && ( + + {children} + + )} +
+ ); +} + +// ─── 현황판 항목 토글 리스트 ──────────────────────── +export function StatusBoardItemsList({ + items, + onToggle, +}: { + items: TodayIssueSettings; + onToggle: (key: keyof TodayIssueSettings, checked: boolean) => void; +}) { + return ( +
+ {(Object.keys(STATUS_BOARD_LABELS) as Array).map( + (itemKey) => ( +
+ + {STATUS_BOARD_LABELS[itemKey]} + + onToggle(itemKey, checked)} + /> +
+ ) + )} +
+ ); +} + +// ─── 기업 구분 방법 설명 패널 ─────────────────────── +function CompanyTypeInfoPanel({ + isExpanded, + onToggle, +}: { + isExpanded: boolean; + onToggle: () => void; +}) { + return ( + + + + + + {/* ■ 중소기업 판단 기준표 */} +
+
+ + 중소기업 판단 기준표 +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
조건기준충족 요건
① 매출액업종별 상이업종별 기준 금액 이하
② 자산총액5,000억원미만
③ 독립성소유·경영대기업 계열 아님
+
+ + {/* ① 업종별 매출액 기준 */} +
+
+ ① 업종별 매출액 기준 (최근 3개년 평균) +
+ + + + + + + + + + + + + + + + + + +
업종 분류기준 매출액
제조업1,500억원 이하
건설업1,000억원 이하
운수업1,000억원 이하
도매업1,000억원 이하
소매업600억원 이하
정보통신업600억원 이하
전문서비스업600억원 이하
숙박·음식점업400억원 이하
기타 서비스업400억원 이하
+
+ + {/* ② 자산총액 기준 */} +
+
+ ② 자산총액 기준 +
+ + + + + + + + + + + + + +
구분기준
5,000억원 미만직전 사업연도 말 자산총액
+
+ + {/* ③ 독립성 기준 */} +
+
+ ③ 독립성 기준 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
구분내용판정
독립기업아래 항목에 모두 해당하지 않음충족
기업집단 소속공정거래법상 상호출자제한 기업집단 소속미충족
대기업 지분대기업이 발행주식 30% 이상 보유미충족
관계기업 합산관계기업 포함 시 매출액·자산 기준 초과미충족
+
+ + {/* ■ 판정 결과 */} +
+
+ + 판정 결과 +
+ + + + + + + + + + + + + + + + + + + + +
판정조건접대비 기본한도
중소기업①②③ 모두 충족3,600만원
일반법인①②③ 중 하나라도 미충족1,200만원
+
+
+
+ ); +} + +// ─── 접대비 설정 콘텐츠 ───────────────────────────── +export function EntertainmentContent({ + entertainment, + onChange, + companyTypeInfoExpanded, + onToggleCompanyTypeInfo, +}: { + entertainment: DashboardSettings['entertainment']; + onChange: ( + key: 'limitType' | 'companyType' | 'highAmountThreshold', + value: EntertainmentLimitType | CompanyType | number, + ) => void; + companyTypeInfoExpanded: boolean; + onToggleCompanyTypeInfo: () => void; +}) { + return ( +
+
+ 접대비 한도 관리 + +
+
+ 기업 구분 + +
+
+ 고액 결제 기준 금액 +
+ onChange('highAmountThreshold', value ?? 0)} + className="w-28 h-8" + /> +
+
+ +
+ ); +} + +// ─── 복리후생비 설정 콘텐츠 ───────────────────────── +export function WelfareContent({ + welfare, + onChange, +}: { + welfare: DashboardSettings['welfare']; + onChange: ( + key: keyof DashboardSettings['welfare'], + value: WelfareLimitType | WelfareCalculationType | number, + ) => void; +}) { + return ( +
+
+ 복리후생비 한도 관리 + +
+
+ 계산 방식 + +
+ {welfare.calculationType === 'fixed' ? ( +
+ 직원당 정해 금액/월 +
+ onChange('fixedAmountPerMonth', value ?? 0)} + className="w-28 h-8" + /> +
+
+ ) : ( +
+ 비율 +
+ onChange('ratio', value ?? 0)} + className="w-20 h-8 text-right" + /> + % +
+
+ )} +
+ 연간 복리후생비 + + ₩ {welfare.annualTotal.toLocaleString()} + +
+
+ 1회 결제 기준 금액 +
+ onChange('singlePaymentThreshold', value ?? 0)} + className="w-32 h-8" + /> +
+
+
+ ); +} diff --git a/src/components/business/CEODashboard/mockData.ts b/src/components/business/CEODashboard/mockData.ts index 8dac19ce..f66e986d 100644 --- a/src/components/business/CEODashboard/mockData.ts +++ b/src/components/business/CEODashboard/mockData.ts @@ -19,9 +19,9 @@ export const mockData: CEODashboardData = { date: '2026년 1월 5일 월요일', cards: [ { id: 'dr1', label: '일일일보', amount: 3050000000, path: '/ko/accounting/daily-report' }, - { id: 'dr2', label: '매출채권 잔액', amount: 3050000000, path: '/ko/accounting/receivables-status' }, - { id: 'dr3', label: '매입채무 잔액', amount: 3050000000 }, - { id: 'dr4', label: '운영자금 잔여', amount: 0, displayValue: '6.2개월' }, + { id: 'dr2', label: '미수금 잔액', amount: 3050000000, path: '/ko/accounting/receivables-status' }, + { id: 'dr3', label: '미지급금 잔액', amount: 3050000000 }, + { id: 'dr4', label: '당월 예상 지출 합계', amount: 350000000 }, ], checkPoints: [ { @@ -91,10 +91,11 @@ export const mockData: CEODashboardData = { cardManagement: { warningBanner: '가지급금 인정이자 4.6%, 법인세 및 연말정산 시 대표자 종합세 가중 주의', cards: [ - { id: 'cm1', label: '카드', amount: 30123000, previousLabel: '미정리 5건 (가지급금 예정)' }, - { id: 'cm2', label: '가지급금', amount: 350000000, previousLabel: '전월 대비 +10.5%' }, - { id: 'cm3', label: '법인세 예상 가중', amount: 3123000, previousLabel: '추가 세금 +10.5%' }, - { id: 'cm4', label: '대표자 종합세 예상 가중', amount: 3123000, previousLabel: '추가 세금 +10.5%' }, + { id: 'cm1', label: '카드', amount: 3123000, previousLabel: '미정리 5건' }, + { id: 'cm2', label: '경조사', amount: 3123000, previousLabel: '미증빙 5건' }, + { id: 'cm3', label: '상품권', amount: 3123000, previousLabel: '미증빙 5건' }, + { id: 'cm4', label: '접대비', amount: 3123000, previousLabel: '미증빙 5건' }, + { id: 'cm_total', label: '총 가지급금 합계', amount: 350000000 }, ], checkPoints: [ { @@ -135,10 +136,10 @@ export const mockData: CEODashboardData = { }, entertainment: { cards: [ - { id: 'et1', label: '매출', amount: 30530000000 }, - { id: 'et2', label: '{1사분기} 접대비 총 한도', amount: 40123000 }, - { id: 'et3', label: '{1사분기} 접대비 잔여한도', amount: 30123000 }, - { id: 'et4', label: '{1사분기} 접대비 사용금액', amount: 10000000 }, + { id: 'et1', label: '주말/심야', amount: 3123000, previousLabel: '미증빙 5건' }, + { id: 'et2', label: '기피업종 (유흥, 귀금속 등)', amount: 3123000, previousLabel: '불인정 5건' }, + { id: 'et3', label: '고액 결제', amount: 3123000, previousLabel: '미증빙 5건' }, + { id: 'et4', label: '증빙 미비', amount: 3123000, previousLabel: '미증빙 5건' }, ], checkPoints: [ { @@ -179,10 +180,10 @@ export const mockData: CEODashboardData = { }, welfare: { cards: [ - { id: 'wf1', label: '당해년도 복리후생비 한도', amount: 30123000 }, - { id: 'wf2', label: '{1사분기} 복리후생비 총 한도', amount: 10123000 }, - { id: 'wf3', label: '{1사분기} 복리후생비 잔여한도', amount: 5123000 }, - { id: 'wf4', label: '{1사분기} 복리후생비 사용금액', amount: 5123000 }, + { id: 'wf1', label: '비과세 한도 초과', amount: 3123000, previousLabel: '5건' }, + { id: 'wf2', label: '사적 사용 의심', amount: 3123000, previousLabel: '5건' }, + { id: 'wf3', label: '특정인 편중', amount: 3123000, previousLabel: '5건' }, + { id: 'wf4', label: '항목별 한도 초과', amount: 3123000, previousLabel: '5건' }, ], checkPoints: [ { @@ -219,28 +220,22 @@ export const mockData: CEODashboardData = { id: 'rv2', label: '당월 미수금', amount: 10123000, - subItems: [ - { label: '매출', value: 60123000 }, - { label: '입금', value: 30000000 }, - ], }, { id: 'rv3', - label: '회사명', - amount: 3123000, + label: '미수금 거래처', + amount: 31, + unit: '건', subItems: [ - { label: '매출', value: 6123000 }, - { label: '입금', value: 3000000 }, + { label: '연체', value: '21건' }, + { label: '악성채권', value: '11건' }, ], }, { id: 'rv4', - label: '회사명', - amount: 2123000, - subItems: [ - { label: '매출', value: 6123000 }, - { label: '입금', value: 3000000 }, - ], + label: '미수금 Top 3', + amount: 0, + displayValue: '상세보기', }, ], checkPoints: [ @@ -268,7 +263,7 @@ export const mockData: CEODashboardData = { { id: 'dc1', label: '누적 악성채권', amount: 350000000, subLabel: '25건' }, { id: 'dc2', label: '추심중', amount: 30123000, subLabel: '12건' }, { id: 'dc3', label: '법적조치', amount: 3123000, subLabel: '3건' }, - { id: 'dc4', label: '회수완료', amount: 280000000, subLabel: '10건' }, + { id: 'dc4', label: '추심종료', amount: 280000000, subLabel: '10건' }, ], checkPoints: [ { diff --git a/src/components/business/CEODashboard/modalConfigs/cardManagementConfigTransformers.ts b/src/components/business/CEODashboard/modalConfigs/cardManagementConfigTransformers.ts index c37e6867..6c7e688c 100644 --- a/src/components/business/CEODashboard/modalConfigs/cardManagementConfigTransformers.ts +++ b/src/components/business/CEODashboard/modalConfigs/cardManagementConfigTransformers.ts @@ -162,16 +162,6 @@ export function transformCm1ModalConfig( ], defaultValue: 'all', }, - { - key: 'sortOrder', - options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '등록순' }, - { value: 'amountDesc', label: '금액 높은순' }, - { value: 'amountAsc', label: '금액 낮은순' }, - ], - defaultValue: 'latest', - }, ], showTotal: true, totalLabel: '합계', @@ -196,46 +186,44 @@ export function transformCm2ModalConfig( // 테이블 데이터 매핑 const tableData = (items || []).map((item) => ({ date: item.loan_date, - target: item.user_name, - category: '-', // API에서 별도 필드 없음 + classification: item.status_label || '카드', + category: '-', amount: item.amount, - status: item.status_label || item.status, content: item.description, })); - // 대상 필터 옵션 동적 생성 - const uniqueTargets = [...new Set((items || []).map((item) => item.user_name))]; - const targetFilterOptions = [ + // 분류 필터 옵션 동적 생성 + const uniqueClassifications = [...new Set(tableData.map((item) => item.classification))]; + const classificationFilterOptions = [ { value: 'all', label: '전체' }, - ...uniqueTargets.map((target) => ({ - value: target, - label: target, + ...uniqueClassifications.map((cls) => ({ + value: cls, + label: cls, })), ]; return { title: '가지급금 상세', summaryCards: [ - { label: '가지급금', value: formatKoreanCurrency(summary.total_outstanding) }, - { label: '인정이자 4.6%', value: summary.recognized_interest, unit: '원' }, - { label: '미설정', value: `${summary.pending_count ?? 0}건` }, + { label: '가지급금 합계', value: formatKoreanCurrency(summary.total_outstanding) }, + { label: '인정비율 4.6%', value: summary.recognized_interest, unit: '원' }, + { label: '미정리/미분류', value: `${summary.pending_count ?? 0}건` }, ], table: { title: '가지급금 관련 내역', columns: [ { key: 'no', label: 'No.', align: 'center' }, - { key: 'date', label: '발생일시', align: 'center' }, - { key: 'target', label: '대상', align: 'center' }, + { key: 'date', label: '발생일', align: 'center' }, + { key: 'classification', label: '분류', align: 'center' }, { key: 'category', label: '구분', align: 'center' }, { key: 'amount', label: '금액', align: 'right', format: 'currency' }, - { key: 'status', label: '상태', align: 'center', highlightValue: '미설정' }, { key: 'content', label: '내용', align: 'left' }, ], data: tableData, filters: [ { - key: 'target', - options: targetFilterOptions, + key: 'classification', + options: classificationFilterOptions, defaultValue: 'all', }, { @@ -247,16 +235,6 @@ export function transformCm2ModalConfig( ], defaultValue: 'all', }, - { - key: 'sortOrder', - options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '등록순' }, - { value: 'amountDesc', label: '금액 높은순' }, - { value: 'amountAsc', label: '금액 낮은순' }, - ], - defaultValue: 'latest', - }, ], showTotal: true, totalLabel: '합계', diff --git a/src/components/business/CEODashboard/modalConfigs/cardManagementConfigs.ts b/src/components/business/CEODashboard/modalConfigs/cardManagementConfigs.ts index 86331fe0..0d9ad10f 100644 --- a/src/components/business/CEODashboard/modalConfigs/cardManagementConfigs.ts +++ b/src/components/business/CEODashboard/modalConfigs/cardManagementConfigs.ts @@ -153,16 +153,6 @@ export function getCardManagementModalConfig(cardId: string): DetailModalConfig ], defaultValue: 'all', }, - { - key: 'sortOrder', - options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '등록순' }, - { value: 'amountDesc', label: '금액 높은순' }, - { value: 'amountAsc', label: '금액 낮은순' }, - ], - defaultValue: 'latest', - }, ], showTotal: true, totalLabel: '합계', @@ -170,60 +160,68 @@ export function getCardManagementModalConfig(cardId: string): DetailModalConfig totalColumnKey: 'amount', }, }, + // P52: 가지급금 상세 cm2: { title: '가지급금 상세', + dateFilter: { + enabled: true, + defaultPreset: '당월', + showSearch: true, + }, summaryCards: [ - { label: '가지급금', value: '4.5억원' }, - { label: '인정이자 4.6%', value: 6000000, unit: '원' }, - { label: '미설정', value: '10건' }, + { label: '가지급금 합계', value: '4.5억원' }, + { label: '가지급금 총액', value: 6000000, unit: '원' }, + { label: '건수', value: '10건' }, ], + reviewCards: { + title: '가지급금 검토 필요', + cards: [ + { label: '카드', amount: 3123000, subLabel: '미정리 5건' }, + { label: '경조사', amount: 3123000, subLabel: '미증빙 5건' }, + { label: '상품권', amount: 3123000, subLabel: '미증빙 5건' }, + { label: '접대비', amount: 3123000, subLabel: '미증빙 5건' }, + ], + }, table: { - title: '가지급금 관련 내역', + title: '가지급금 내역', columns: [ { key: 'no', label: 'No.', align: 'center' }, - { key: 'date', label: '발생일시', align: 'center' }, - { key: 'target', label: '대상', align: 'center' }, + { key: 'date', label: '발생일', align: 'center' }, + { key: 'classification', label: '분류', align: 'center' }, { key: 'category', label: '구분', align: 'center' }, { key: 'amount', label: '금액', align: 'right', format: 'currency' }, - { key: 'status', label: '상태', align: 'center', highlightValue: '미설정' }, - { key: 'content', label: '내용', align: 'left' }, + { key: 'response', label: '대응', align: 'left' }, ], data: [ - { date: '2025-12-12 12:12', target: '홍길동', category: '카드명', amount: 1000000, status: '미설정', content: '미설정' }, - { date: '2025-12-12 12:12', target: '홍길동', category: '카드명', amount: 1000000, status: '접비(미정리)', content: '접대비 불인정' }, - { date: '2025-12-12 12:12', target: '홍길동', category: '계좌명', amount: 1000000, status: '미설정', content: '접대비 불인정' }, - { date: '2025-12-12 12:12', target: '홍길동', category: '계좌명', amount: 1000000, status: '미설정', content: '미설정' }, - { date: '2025-12-12 12:12', target: '홍길동', category: '-', amount: 1000000, status: '미설정', content: '미설정' }, - { date: '2025-12-12 12:12', target: '홍길동', category: '카드명', amount: 1000000, status: '접대비', content: '접대비 불인정' }, - { date: '2025-12-12 12:12', target: '홍길동', category: '카드명', amount: 1000000, status: '-', content: '복리후생비, 주말/심야 카드 사용' }, + { date: '2025-12-12', classification: '카드', category: '카드명', amount: 1000000, response: '미정리' }, + { date: '2025-12-12', classification: '카드', category: '카드명', amount: 1000000, response: '미증빙' }, + { date: '2025-12-12', classification: '경조사', category: '계좌명', amount: 1000000, response: '미증빙' }, + { date: '2025-12-12', classification: '상품권', category: '계좌명', amount: 1000000, response: '미증빙' }, + { date: '2025-12-12', classification: '접대비', category: '카드명', amount: 1000000, response: '주말 카드 사용' }, + { date: '2025-12-12', classification: '접대비', category: '카드명', amount: 1000000, response: '접대비 불인정' }, + { date: '2025-12-12', classification: '카드', category: '카드명', amount: 1000000, response: '불인정 가맹점(귀금속)' }, ], filters: [ { - key: 'target', + key: 'classification', options: [ { value: 'all', label: '전체' }, - { value: '홍길동', label: '홍길동' }, - ], - defaultValue: 'all', - }, - { - key: 'category', - 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: 'all', label: '정렬' }, { value: 'amountDesc', label: '금액 높은순' }, { value: 'amountAsc', label: '금액 낮은순' }, + { value: 'latest', label: '최신순' }, ], - defaultValue: 'latest', + defaultValue: 'all', }, ], showTotal: true, diff --git a/src/components/business/CEODashboard/modalConfigs/entertainmentConfigs.ts b/src/components/business/CEODashboard/modalConfigs/entertainmentConfigs.ts index 0ca155c7..39164a5b 100644 --- a/src/components/business/CEODashboard/modalConfigs/entertainmentConfigs.ts +++ b/src/components/business/CEODashboard/modalConfigs/entertainmentConfigs.ts @@ -5,18 +5,27 @@ import type { DetailModalConfig } from '../types'; */ const entertainmentDetailConfig: DetailModalConfig = { title: '접대비 상세', + dateFilter: { + enabled: true, + defaultPreset: '당월', + showSearch: true, + }, summaryCards: [ // 첫 번째 줄: 당해년도 - { label: '당해년도 접대비 총한도', value: 3123000, unit: '원' }, + { 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: '원' }, + { label: '당해년도 접대비 초과 금액', value: 0, unit: '원' }, ], + reviewCards: { + title: '접대비 검토 필요', + cards: [ + { label: '주말/심야', amount: 3123000, subLabel: '미증빙 5건' }, + { label: '기피업종 (유흥, 귀금속 등)', amount: 3123000, subLabel: '불인정 5건' }, + { label: '고액 결제', amount: 3123000, subLabel: '미증빙 5건' }, + { label: '증빙 미비', amount: 3123000, subLabel: '미증빙 5건' }, + ], + }, barChart: { title: '월별 접대비 사용 추이', data: [ @@ -50,14 +59,14 @@ const entertainmentDetailConfig: DetailModalConfig = { { 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' }, + { key: 'content', 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: '사용용도' }, + { cardName: '카드명', user: '홍길동', useDate: '2025-12-12 12:12', transDate: '가맹점명', amount: 1000000, content: '심야 카드 사용' }, + { cardName: '카드명', user: '홍길동', useDate: '2025-12-12 12:12', transDate: '가맹점명', amount: 1000000, content: '미증빙' }, + { cardName: '카드명', user: '홍길동', useDate: '2025-12-12 12:12', transDate: '가맹점명', amount: 1000000, content: '고액 결제' }, + { cardName: '카드명', user: '김철수', useDate: '2025-10-14 12:12', transDate: '가맹점명', amount: 1000000, content: '불인정 가맹점 (귀금속)' }, + { cardName: '카드명', user: '이영희', useDate: '2025-12-12 12:12', transDate: '가맹점명', amount: 1000000, content: '접대비 불인정' }, ], filters: [ { @@ -71,14 +80,15 @@ const entertainmentDetailConfig: DetailModalConfig = { defaultValue: 'all', }, { - key: 'sortOrder', + key: 'content', options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '등록순' }, - { value: 'amountDesc', label: '금액 높은순' }, - { value: 'amountAsc', label: '금액 낮은순' }, + { value: 'all', label: '전체' }, + { value: '주말/심야', label: '주말/심야' }, + { value: '기피업종', label: '기피업종' }, + { value: '고액 결제', label: '고액 결제' }, + { value: '증빙 미비', label: '증빙 미비' }, ], - defaultValue: 'latest', + defaultValue: 'all', }, ], showTotal: true, @@ -91,24 +101,25 @@ const entertainmentDetailConfig: DetailModalConfig = { { title: '접대비 손금한도 계산 - 기본한도', columns: [ - { key: 'type', label: '구분', align: 'left' }, - { key: 'limit', label: '기본한도', align: 'right' }, + { key: 'type', label: '법인 유형', align: 'left' }, + { key: 'annualLimit', label: '연간 기본한도', align: 'right' }, + { key: 'monthlyLimit', label: '월 환산', align: 'right' }, ], data: [ - { type: '일반법인', limit: '3,600만원 (연 1,200만원)' }, - { type: '중소기업', limit: '5,400만원 (연 3,600만원)' }, + { type: '일반법인', annualLimit: '12,000,000원', monthlyLimit: '1,000,000원' }, + { type: '중소기업', annualLimit: '36,000,000원', monthlyLimit: '3,000,000원' }, ], }, { title: '수입금액별 추가한도', columns: [ - { key: 'range', label: '수입금액', align: 'left' }, - { key: 'rate', label: '적용률', align: 'center' }, + { key: 'range', label: '수입금액 구간', align: 'left' }, + { key: 'formula', label: '추가한도 계산식', align: 'left' }, ], data: [ - { range: '100억원 이하', rate: '0.3%' }, - { range: '100억원 초과 ~ 500억원 이하', rate: '0.2%' }, - { range: '500억원 초과', rate: '0.03%' }, + { range: '100억원 이하', formula: '수입금액 × 0.2%' }, + { range: '100억 초과 ~ 500억 이하', formula: '2,000만원 + (수입금액 - 100억) × 0.1%' }, + { range: '500억원 초과', formula: '6,000만원 + (수입금액 - 500억) × 0.03%' }, ], }, ], @@ -116,18 +127,20 @@ const entertainmentDetailConfig: DetailModalConfig = { calculationCards: { title: '접대비 계산', cards: [ - { label: '기본한도', value: 36000000 }, - { label: '추가한도', value: 91170000, operator: '+' }, - { label: '접대비 손금한도', value: 127170000, operator: '=' }, + { label: '중소기업 연간 기본한도', value: 36000000 }, + { label: '당해년도 수입금액별 추가한도', value: 16000000, operator: '+' }, + { label: '당해년도 접대비 총 한도', value: 52000000, 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 }, + { label: '한도금액', q1: 13000000, q2: 13000000, q3: 13000000, q4: 13000000, total: 52000000 }, + { 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: '' }, ], }, }; @@ -204,16 +217,6 @@ export function getEntertainmentModalConfig(cardId: string): DetailModalConfig | ], defaultValue: 'all', }, - { - key: 'sortOrder', - options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '등록순' }, - { value: 'amountDesc', label: '금액 높은순' }, - { value: 'amountAsc', label: '금액 낮은순' }, - ], - defaultValue: 'latest', - }, ], showTotal: true, totalLabel: '합계', @@ -225,6 +228,11 @@ export function getEntertainmentModalConfig(cardId: string): DetailModalConfig | et_limit: entertainmentDetailConfig, et_remaining: entertainmentDetailConfig, et_used: entertainmentDetailConfig, + // 대시보드 카드 ID (et1~et4) → 접대비 상세 모달 + et1: entertainmentDetailConfig, + et2: entertainmentDetailConfig, + et3: entertainmentDetailConfig, + et4: entertainmentDetailConfig, }; return configs[cardId] || null; diff --git a/src/components/business/CEODashboard/modalConfigs/monthlyExpenseConfigs.ts b/src/components/business/CEODashboard/modalConfigs/monthlyExpenseConfigs.ts index 2921a2ee..170b3ab4 100644 --- a/src/components/business/CEODashboard/modalConfigs/monthlyExpenseConfigs.ts +++ b/src/components/business/CEODashboard/modalConfigs/monthlyExpenseConfigs.ts @@ -1,18 +1,24 @@ import type { DetailModalConfig } from '../types'; /** - * 당월 예상 지출 모달 설정 + * 당월 예상 지출 모달 설정 (D1.7 기획서 P48-51 반영) */ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig | null { const configs: Record = { + // P48: 매입 상세 me1: { - title: '당월 매입 상세', + title: '매입 상세', + dateFilter: { + enabled: true, + defaultPreset: '당월', + showSearch: true, + }, 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 }, ], barChart: { - title: '월별 매입 추이', + title: '매입 추이', data: [ { name: '1월', value: 45000000 }, { name: '2월', value: 52000000 }, @@ -30,8 +36,8 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig title: '자재 유형별 구매 비율', data: [ { name: '원자재', value: 55000000, percentage: 55, color: '#60A5FA' }, - { name: '부자재', value: 35000000, percentage: 35, color: '#34D399' }, - { name: '포장재', value: 10000000, percentage: 10, color: '#FBBF24' }, + { name: '부자재', value: 35000000, percentage: 35, color: '#FBBF24' }, + { name: '포장재', value: 10000000, percentage: 10, color: '#F87171' }, ], }, table: { @@ -41,36 +47,14 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig { key: 'date', label: '매입일', align: 'center', format: 'date' }, { key: 'vendor', label: '거래처', align: 'left' }, { key: 'amount', label: '매입금액', align: 'right', format: 'currency' }, - { key: 'type', label: '매입유형', align: 'center' }, ], 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: '미설정' }, - ], - defaultValue: 'all', - }, - { - key: 'sortOrder', - options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '오래된순' }, - ], - defaultValue: 'latest', - }, + { date: '2025-12-01', vendor: '회사명', amount: 11000000 }, + { date: '2025-12-01', vendor: '회사명', amount: 11000000 }, + { date: '2025-12-01', vendor: '회사명', amount: 11000000 }, + { date: '2025-12-01', vendor: '회사명', amount: 11000000 }, + { date: '2025-12-01', vendor: '회사명', amount: 11000000 }, + { date: '2025-12-01', vendor: '회사명', amount: 11000000 }, ], showTotal: true, totalLabel: '합계', @@ -78,15 +62,21 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig totalColumnKey: 'amount', }, }, + // P49: 카드 상세 me2: { - title: '당월 카드 상세', + title: '카드 상세', + dateFilter: { + enabled: true, + defaultPreset: '당월', + showSearch: true, + }, summaryCards: [ - { label: '당월 카드 사용', value: 6000000, unit: '원' }, - { label: '전월 대비', value: '-12.5%', isComparison: true, isPositive: false }, - { label: '이용건', value: '10건' }, + { label: '카드 사용', value: 6000000, unit: '원' }, + { label: '이전 대비', value: '-12.5%', isComparison: true, isPositive: false }, + { label: '건수', value: '10건' }, ], barChart: { - title: '월별 카드 사용 추이', + title: '카드 사용 추이', data: [ { name: '1월', value: 4500000 }, { name: '2월', value: 5200000 }, @@ -104,8 +94,8 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig title: '사용자별 카드 사용 비율', data: [ { name: '홍길동', value: 55000000, percentage: 55, color: '#60A5FA' }, - { name: '김길동', value: 35000000, percentage: 35, color: '#34D399' }, - { name: '이길동', value: 10000000, percentage: 10, color: '#FBBF24' }, + { name: '김영희', value: 35000000, percentage: 35, color: '#FBBF24' }, + { name: '이정현', value: 10000000, percentage: 10, color: '#F87171' }, ], }, table: { @@ -114,30 +104,16 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig { key: 'no', label: 'No.', align: 'center' }, { key: 'cardName', label: '카드명', align: 'left' }, { key: 'user', label: '사용자', align: 'center' }, - { key: 'date', label: '사용일시', align: 'center', format: 'date' }, + { key: 'date', label: '사용일자', align: 'center', format: 'date' }, { key: 'store', label: '가맹점명', align: 'left' }, { key: 'amount', label: '사용금액', align: 'right', format: 'currency' }, - { key: 'usageType', label: '사용유형', align: 'center', highlightValue: '미설정' }, + { key: 'usageType', label: '계정과목', align: 'center', highlightValue: '미설정' }, ], data: [ - { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1000000, usageType: '복리후생비' }, - { cardName: '카드명', user: '홍길동', date: '2025-12-11 14:30', store: '가맹점명', amount: 1000000, usageType: '접대비' }, - { cardName: '카드명', user: '홍길동', date: '2025-12-10 09:45', store: '가맹점명', amount: 1000000, usageType: '미설정' }, - { cardName: '카드명', user: '홍길동', date: '2025-12-09 18:20', store: '가맹점명', amount: 1000000, usageType: '미설정' }, - { cardName: '카드명', user: '홍길동', date: '2025-12-08 11:15', store: '가맹점명', amount: 1000000, usageType: '미설정' }, - { cardName: '카드명', user: '김길동', date: '2025-12-07 16:40', store: '가맹점명', amount: 5000000, usageType: '교통비' }, - { cardName: '카드명', user: '이길동', date: '2025-12-06 10:30', store: '가맹점명', amount: 1000000, usageType: '소모품비' }, - { cardName: '카드명', user: '홍길동', date: '2025-12-05 13:25', store: '스타벅스', amount: 45000, usageType: '복리후생비' }, - { cardName: '카드명', user: '김길동', date: '2025-12-04 19:50', store: '주유소', amount: 80000, usageType: '교통비' }, - { cardName: '카드명', user: '이길동', date: '2025-12-03 08:10', store: '편의점', amount: 15000, usageType: '미설정' }, - { cardName: '카드명', user: '홍길동', date: '2025-12-02 12:00', store: '식당', amount: 250000, usageType: '접대비' }, - { cardName: '카드명', user: '김길동', date: '2025-12-01 15:35', store: '문구점', amount: 35000, usageType: '소모품비' }, - { cardName: '카드명', user: '홍길동', date: '2025-11-30 17:20', store: '호텔', amount: 350000, usageType: '미설정' }, - { cardName: '카드명', user: '이길동', date: '2025-11-29 09:00', store: '택시', amount: 25000, usageType: '교통비' }, - { cardName: '카드명', user: '김길동', date: '2025-11-28 14:15', store: '커피숍', amount: 32000, usageType: '복리후생비' }, - { cardName: '카드명', user: '홍길동', date: '2025-11-27 11:45', store: '마트', amount: 180000, usageType: '소모품비' }, - { cardName: '카드명', user: '이길동', date: '2025-11-26 16:30', store: '서점', amount: 45000, usageType: '미설정' }, - { cardName: '카드명', user: '김길동', date: '2025-11-25 10:20', store: '식당', amount: 120000, usageType: '접대비' }, + { cardName: '홍길동', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1000000, usageType: '복리후생비' }, + { cardName: '홍길동', user: '홍길동', date: '2025-12-11 14:30', store: '가맹점명', amount: 1000000, usageType: '접대비' }, + { cardName: '홍길동', user: '홍길동', date: '2025-12-10 09:45', store: '가맹점명', amount: 1000000, usageType: '미설정' }, + { cardName: '홍길동', user: '홍길동', date: '2025-12-09 18:20', store: '가맹점명', amount: 1000000, usageType: '미설정' }, ], filters: [ { @@ -145,21 +121,11 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig options: [ { value: 'all', 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: '합계', @@ -167,14 +133,21 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig totalColumnKey: 'amount', }, }, + // P50: 발행어음 상세 me3: { - title: '당월 발행어음 상세', + title: '발행어음 상세', + dateFilter: { + enabled: true, + presets: ['당해년도', '전전월', '전월', '당월', '어제'], + defaultPreset: '당월', + showSearch: true, + }, 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 }, ], barChart: { - title: '월별 발행어음 추이', + title: '발행어음 추이', data: [ { name: '1월', value: 2000000 }, { name: '2월', value: 2500000 }, @@ -188,15 +161,14 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig xAxisKey: 'name', color: '#60A5FA', }, - horizontalBarChart: { - title: '당월 거래처별 발행어음', + pieChart: { + title: '거래처별 발행어음', data: [ - { name: '거래처1', value: 50000000 }, - { name: '거래처2', value: 35000000 }, - { name: '거래처3', value: 20000000 }, - { name: '거래처4', value: 6000000 }, + { name: '거래처1', value: 50000000, percentage: 45, color: '#60A5FA' }, + { name: '거래처2', value: 35000000, percentage: 32, color: '#FBBF24' }, + { name: '거래처3', value: 20000000, percentage: 18, color: '#F87171' }, + { name: '거래처4', value: 6000000, percentage: 5, color: '#34D399' }, ], - color: '#60A5FA', }, table: { title: '일별 발행어음 내역', @@ -215,7 +187,6 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig { vendor: '회사명', issueDate: '2025-12-12', dueDate: '2025-12-12', amount: 1000000, status: '만기임박' }, { vendor: '회사명', issueDate: '2025-12-12', dueDate: '2025-12-12', amount: 1000000, status: '보관중' }, { vendor: '회사명', issueDate: '2025-12-12', dueDate: '2025-12-12', amount: 1000000, status: '만기임박' }, - { vendor: '회사명', issueDate: '2025-12-12', dueDate: '2025-12-12', amount: 105000000, status: '보관중' }, ], filters: [ { @@ -238,16 +209,6 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig ], defaultValue: 'all', }, - { - key: 'sortOrder', - options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '등록순' }, - { value: 'amountDesc', label: '금액 높은순' }, - { value: 'amountAsc', label: '금액 낮은순' }, - ], - defaultValue: 'latest', - }, ], showTotal: true, totalLabel: '합계', @@ -255,6 +216,7 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig totalColumnKey: 'amount', }, }, + // P51: 당월 지출 예상 상세 me4: { title: '당월 지출 예상 상세', summaryCards: [ @@ -278,8 +240,6 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig { paymentDate: '2025-12-12', item: '품의 사유...', amount: 1000000, vendor: '회사명', account: '국민은행 1234' }, { paymentDate: '2025-12-12', item: '거래처명 12월분', amount: 1000000, vendor: '회사명', account: '국민은행 1234' }, { paymentDate: '2025-12-12', item: '품의 사유...', amount: 1000000, vendor: '회사명', account: '국민은행 1234' }, - { paymentDate: '2025-12-12', item: '품의 사유...', amount: 1000000, vendor: '회사명', account: '국민은행 1234' }, - { paymentDate: '2025-12-12', item: '품의 사유...', amount: 1000000, vendor: '회사명', account: '국민은행 1234' }, { paymentDate: '2025-12-12', item: '적요 내용', amount: 1000000, vendor: '회사명', account: '국민은행 1234' }, ], filters: [ @@ -291,14 +251,6 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig ], defaultValue: 'all', }, - { - key: 'sortOrder', - options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '등록순' }, - ], - defaultValue: 'latest', - }, ], showTotal: true, totalLabel: '2025/12 계', @@ -314,4 +266,4 @@ export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig }; return configs[cardId] || null; -} \ No newline at end of file +} diff --git a/src/components/business/CEODashboard/modalConfigs/vatConfigs.ts b/src/components/business/CEODashboard/modalConfigs/vatConfigs.ts index 72c4b26a..56da1940 100644 --- a/src/components/business/CEODashboard/modalConfigs/vatConfigs.ts +++ b/src/components/business/CEODashboard/modalConfigs/vatConfigs.ts @@ -7,29 +7,36 @@ import type { DetailModalConfig } from '../types'; export function getVatModalConfig(): DetailModalConfig { return { title: '예상 납부세액', - summaryCards: [], - // 세액 산출 내역 테이블 + periodSelect: { + enabled: true, + options: [ + { value: '2026-1-expected', label: '2026년 1기 예정' }, + { value: '2025-2-confirmed', label: '2025년 2기 확정' }, + { value: '2025-2-expected', label: '2025년 2기 예정' }, + { value: '2025-1-confirmed', label: '2025년 1기 확정' }, + ], + defaultValue: '2026-1-expected', + }, + summaryCards: [ + { label: '예상매출', value: '30.5억원' }, + { label: '예상매입', value: '20.5억원' }, + { label: '예상 납부세액', value: '1.1억원' }, + ], + // 부가세 요약 테이블 referenceTable: { - title: '2026년 1사분기 세액 산출 내역', + title: '2026년 1기 예정 부가세 요약', columns: [ - { key: 'category', label: '구분', align: 'center' }, - { key: 'amount', label: '금액', align: 'right' }, - { key: 'note', label: '비고', align: 'left' }, + { key: 'category', label: '구분', align: 'left' }, + { key: 'supplyAmount', label: '공급가액', align: 'right' }, + { key: 'taxAmount', label: '세액', align: 'right' }, ], 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: '=' }, + { category: '매출(전자세금계산서)', supplyAmount: '100,000,000', taxAmount: '10,000,000' }, + { category: '매입(전자세금계산서)', supplyAmount: '10,000,000', taxAmount: '1,000,000' }, + { category: '매입(종이세금계산서)', supplyAmount: '10,000,000', taxAmount: '1,000,000' }, + { category: '매입(계산서)', supplyAmount: '10,000,000', taxAmount: '1,000,000' }, + { category: '매입(신용카드)', supplyAmount: '10,000,000', taxAmount: '1,000,000' }, + { category: '납부세액', supplyAmount: '', taxAmount: '6,000,000' }, ], }, // 세금계산서 미발행/미수취 내역 @@ -38,19 +45,17 @@ export function getVatModalConfig(): DetailModalConfig { columns: [ { key: 'no', label: 'No.', align: 'center' }, { key: 'type', label: '구분', align: 'center' }, - { key: 'issueDate', label: '발행일자', align: 'center', format: 'date' }, + { key: 'issueDate', label: '발생일자', align: 'center', format: 'date' }, { key: 'vendor', label: '거래처', align: 'left' }, { key: 'vat', label: '부가세', align: 'right', format: 'currency' }, - { key: 'invoiceStatus', label: '세금계산서 발행', align: 'center' }, + { 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: '미발행' }, + { type: '매출', issueDate: '2025-12-12', vendor: '회사명', vat: 11000000, invoiceStatus: '미발행' }, + { type: '매입', issueDate: '2025-12-12', vendor: '회사명', vat: 11000000, invoiceStatus: '미수취' }, + { type: '매출', issueDate: '2025-12-12', vendor: '회사명', vat: 11000000, invoiceStatus: '미발행' }, + { type: '매입', issueDate: '2025-12-12', vendor: '회사명', vat: 11000000, invoiceStatus: '미수취' }, + { type: '매출', issueDate: '2025-12-12', vendor: '회사명', vat: 11000000, invoiceStatus: '미발행' }, ], filters: [ { @@ -62,25 +67,6 @@ export function getVatModalConfig(): DetailModalConfig { ], 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: '합계', @@ -88,4 +74,4 @@ export function getVatModalConfig(): DetailModalConfig { totalColumnKey: 'vat', }, }; -} \ No newline at end of file +} diff --git a/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts b/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts index bbb24c0a..8089411f 100644 --- a/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts +++ b/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts @@ -45,18 +45,27 @@ export function getWelfareModalConfig(calculationType: 'fixed' | 'ratio'): Detai return { title: '복리후생비 상세', + dateFilter: { + enabled: true, + defaultPreset: '당월', + showSearch: true, + }, 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: '원' }, + { label: '당해년도 복리후생비 총 한도', value: 3123000, unit: '원' }, + { label: '당해년도 복리후생비 잔여한도', value: 6000000, unit: '원' }, + { label: '당해년도 복리후생비 사용금액', value: 6000000, unit: '원' }, + { label: '당해년도 복리후생비 초과 금액', value: 0, unit: '원' }, ], + reviewCards: { + title: '복리후생비 검토 필요', + cards: [ + { label: '비과세 한도 초과', amount: 3123000, subLabel: '5건' }, + { label: '사적 사용 의심', amount: 3123000, subLabel: '5건' }, + { label: '특정인 편중', amount: 3123000, subLabel: '5건' }, + { label: '항목별 한도 초과', amount: 3123000, subLabel: '5건' }, + ], + }, barChart: { title: '월별 복리후생비 사용 추이', data: [ @@ -89,36 +98,34 @@ export function getWelfareModalConfig(calculationType: 'fixed' | 'ratio'): Detai { key: 'date', label: '사용일자', align: 'center', format: 'date' }, { key: 'store', label: '가맹점명', align: 'left' }, { key: 'amount', label: '사용금액', align: 'right', format: 'currency' }, - { key: 'usageType', label: '사용항목', align: 'center' }, + { key: 'content', label: '내용', align: 'left' }, ], 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: '식비' }, + { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1000000, content: '비과세 한도 초과' }, + { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1200000, content: '사적 사용 의심' }, + { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1500000, content: '특정인 편중' }, + { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1300000, content: '항목별 한도 초과' }, + { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 6000000, content: '비과세 한도 초과' }, ], filters: [ { - key: 'usageType', + key: 'user', options: [ { value: 'all', label: '전체' }, - { value: '식비', label: '식비' }, - { value: '건강검진', label: '건강검진' }, - { value: '경조사비', label: '경조사비' }, - { value: '기타', label: '기타' }, + { value: '홍길동', label: '홍길동' }, ], defaultValue: 'all', }, { - key: 'sortOrder', + key: 'content', options: [ - { value: 'latest', label: '최신순' }, - { value: 'oldest', label: '등록순' }, - { value: 'amountDesc', label: '금액 높은순' }, - { value: 'amountAsc', label: '금액 낮은순' }, + { value: 'all', label: '전체' }, + { value: '비과세 한도 초과', label: '비과세 한도 초과' }, + { value: '사적 사용 의심', label: '사적 사용 의심' }, + { value: '특정인 편중', label: '특정인 편중' }, + { value: '항목별 한도 초과', label: '항목별 한도 초과' }, ], - defaultValue: 'latest', + defaultValue: 'all', }, ], showTotal: true, diff --git a/src/components/business/CEODashboard/modals/DetailModal.tsx b/src/components/business/CEODashboard/modals/DetailModal.tsx index 9c2d54c1..acb80a11 100644 --- a/src/components/business/CEODashboard/modals/DetailModal.tsx +++ b/src/components/business/CEODashboard/modals/DetailModal.tsx @@ -1,6 +1,5 @@ 'use client'; -import { useState, useCallback, useMemo } from 'react'; import { X } from 'lucide-react'; import { Dialog, @@ -8,39 +7,22 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - PieChart, - Pie, - Cell, -} from 'recharts'; import { cn } from '@/lib/utils'; -import type { - DetailModalConfig, - SummaryCardData, - BarChartConfig, - PieChartConfig, - HorizontalBarChartConfig, - TableConfig, - TableFilterConfig, - ComparisonSectionConfig, - ReferenceTableConfig, - CalculationCardsConfig, - QuarterlyTableConfig, -} from '../types'; +import type { DetailModalConfig } from '../types'; +import { + DateFilterSection, + PeriodSelectSection, + SummaryCard, + ReviewCardsSection, + BarChartSection, + PieChartSection, + HorizontalBarChartSection, + ComparisonSection, + CalculationCardsSection, + QuarterlyTableSection, + ReferenceTableSection, + TableSection, +} from './DetailModalSections'; interface DetailModalProps { isOpen: boolean; @@ -48,641 +30,6 @@ interface DetailModalProps { config: DetailModalConfig; } -/** - * 금액 포맷 함수 - */ -const formatCurrency = (value: number): string => { - return new Intl.NumberFormat('ko-KR').format(value); -}; - -/** - * 요약 카드 컴포넌트 - 모바일 반응형 지원 - */ -const SummaryCard = ({ data }: { data: SummaryCardData }) => { - const displayValue = typeof data.value === 'number' - ? formatCurrency(data.value) + (data.unit || '원') - : data.value; - - return ( -
-

{data.label}

-

- {data.isComparison && !data.isPositive && typeof data.value === 'string' && !data.value.startsWith('-') ? '-' : ''} - {displayValue} -

-
- ); -}; - -/** - * 막대 차트 컴포넌트 - 모바일 반응형 지원 - */ -const BarChartSection = ({ config }: { config: BarChartConfig }) => { - return ( -
-

{config.title}

-
- - - - - value >= 10000 ? `${value / 10000}만` : value} - width={35} - /> - [formatCurrency(value as number) + '원', '']} - contentStyle={{ fontSize: 12 }} - /> - - - -
-
- ); -}; - -/** - * 도넛 차트 컴포넌트 - 모바일 반응형 지원 - */ -const PieChartSection = ({ config }: { config: PieChartConfig }) => { - return ( -
-

{config.title}

- {/* 도넛 차트 - 중앙 정렬, 모바일 크기 조절 */} -
- - >} - cx={50} - cy={50} - innerRadius={28} - outerRadius={45} - paddingAngle={2} - dataKey="value" - > - {config.data.map((entry, index) => ( - - ))} - - -
- {/* 범례 - 세로 배치 (모바일 최적화) */} -
- {config.data.map((item, index) => ( -
-
-
- {item.name} - {item.percentage}% -
- - {formatCurrency(item.value)}원 - -
- ))} -
-
- ); -}; - -/** - * 가로 막대 차트 컴포넌트 - */ -const HorizontalBarChartSection = ({ config }: { config: HorizontalBarChartConfig }) => { - const maxValue = Math.max(...config.data.map(d => d.value)); - - return ( -
-

{config.title}

-
- {config.data.map((item, index) => ( -
-
- {item.name} - - {formatCurrency(item.value)}원 - -
-
-
-
-
- ))} -
-
- ); -}; - -/** - * VS 비교 섹션 컴포넌트 - */ -const ComparisonSection = ({ config }: { config: ComparisonSectionConfig }) => { - const formatValue = (value: string | number, unit?: string): string => { - if (typeof value === 'number') { - return formatCurrency(value) + (unit || '원'); - } - return value; - }; - - const borderColorClass = { - orange: 'border-orange-400', - blue: 'border-blue-400', - }; - - const titleBgClass = { - orange: 'bg-orange-50', - blue: 'bg-blue-50', - }; - - return ( -
- {/* 왼쪽 박스 */} -
-
- {config.leftBox.title} -
-
- {config.leftBox.items.map((item, index) => ( -
-

{item.label}

-

- {formatValue(item.value, item.unit)} -

-
- ))} -
-
- - {/* VS 영역 */} -
- VS -
-

{config.vsLabel}

-

- {typeof config.vsValue === 'number' - ? formatCurrency(config.vsValue) + '원' - : config.vsValue} -

- {config.vsSubLabel && ( -

{config.vsSubLabel}

- )} - {/* VS 세부 항목 */} - {config.vsBreakdown && config.vsBreakdown.length > 0 && ( -
- {config.vsBreakdown.map((item, index) => ( -
- {item.label} - - {typeof item.value === 'number' - ? formatCurrency(item.value) + (item.unit || '원') - : item.value} - -
- ))} -
- )} -
-
- - {/* 오른쪽 박스 */} -
-
- {config.rightBox.title} -
-
- {config.rightBox.items.map((item, index) => ( -
-

{item.label}

-

- {formatValue(item.value, item.unit)} -

-
- ))} -
-
-
- ); -}; - -/** - * 계산 카드 섹션 컴포넌트 (접대비 계산 등) - */ -const CalculationCardsSection = ({ config }: { config: CalculationCardsConfig }) => { - const isResultCard = (index: number, operator?: string) => { - // '=' 연산자가 있는 카드는 결과 카드로 강조 - return operator === '='; - }; - - return ( -
-
-

{config.title}

- {config.subtitle && ( - {config.subtitle} - )} -
-
- {config.cards.map((card, index) => ( -
- {/* 연산자 표시 (첫 번째 카드 제외) */} - {index > 0 && card.operator && ( - - {card.operator} - - )} - {/* 카드 */} -
-

- {card.label} -

-

- {formatCurrency(card.value)}{card.unit || '원'} -

-
-
- ))} -
-
- ); -}; - -/** - * 분기별 테이블 섹션 컴포넌트 (접대비 현황 등) - 가로 스크롤 지원 - */ -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 ( -
-

{config.title}

-
- - - - - - - - - - - - - {config.rows.map((row, rowIndex) => ( - - - - - - - - - ))} - -
구분1사분기2사분기3사분기4사분기합계
{row.label}{formatValue(row.q1)}{formatValue(row.q2)}{formatValue(row.q3)}{formatValue(row.q4)}{formatValue(row.total)}
-
-
- ); -}; - -/** - * 참조 테이블 컴포넌트 (필터 없는 정보성 테이블) - 가로 스크롤 지원 - */ -const ReferenceTableSection = ({ config }: { config: ReferenceTableConfig }) => { - const getAlignClass = (align?: string): string => { - switch (align) { - case 'center': - return 'text-center'; - case 'right': - return 'text-right'; - default: - return 'text-left'; - } - }; - - return ( -
-

{config.title}

-
- - - - {config.columns.map((column) => ( - - ))} - - - - {config.data.map((row, rowIndex) => ( - - {config.columns.map((column) => ( - - ))} - - ))} - -
- {column.label} -
- {String(row[column.key] ?? '-')} -
-
-
- ); -}; - -/** - * 테이블 컴포넌트 - */ -const TableSection = ({ config }: { config: TableConfig }) => { - const [filters, setFilters] = useState>(() => { - const initial: Record = {}; - config.filters?.forEach((filter) => { - initial[filter.key] = filter.defaultValue; - }); - return initial; - }); - - const handleFilterChange = useCallback((key: string, value: string) => { - setFilters((prev) => ({ ...prev, [key]: value })); - }, []); - - // 필터링된 데이터 - const filteredData = useMemo(() => { - // 데이터가 없는 경우 빈 배열 반환 - if (!config.data || !Array.isArray(config.data)) { - return []; - } - let result = [...config.data]; - - // 각 필터 적용 (sortOrder는 정렬용이므로 제외) - config.filters?.forEach((filter) => { - if (filter.key === 'sortOrder') return; // 정렬 필터는 값 필터링에서 제외 - const filterValue = filters[filter.key]; - if (filterValue && filterValue !== 'all') { - result = result.filter((row) => row[filter.key] === filterValue); - } - }); - - // 정렬 필터 적용 (sortOrder가 있는 경우) - if (filters['sortOrder']) { - const sortOrder = filters['sortOrder']; - result.sort((a, b) => { - // 금액 정렬 - if (sortOrder === 'amountDesc') { - return (b['amount'] as number) - (a['amount'] as number); - } - if (sortOrder === 'amountAsc') { - return (a['amount'] as number) - (b['amount'] as number); - } - // 날짜 정렬 - const dateA = new Date(a['date'] as string).getTime(); - const dateB = new Date(b['date'] as string).getTime(); - return sortOrder === 'latest' ? dateB - dateA : dateA - dateB; - }); - } - - return result; - }, [config.data, config.filters, filters]); - - // 셀 값 포맷팅 - const formatCellValue = (value: unknown, format?: string): string => { - if (value === null || value === undefined) return '-'; - - switch (format) { - case 'currency': - return typeof value === 'number' ? formatCurrency(value) : String(value); - case 'number': - return typeof value === 'number' ? formatCurrency(value) : String(value); - case 'date': - return String(value); - default: - return String(value); - } - }; - - // 셀 정렬 클래스 - const getAlignClass = (align?: string): string => { - switch (align) { - case 'center': - return 'text-center'; - case 'right': - return 'text-right'; - default: - return 'text-left'; - } - }; - - return ( -
- {/* 테이블 헤더 */} -
-
-

{config.title}

- 총 {filteredData.length}건 -
- - {/* 필터 영역 */} - {config.filters && config.filters.length > 0 && ( -
- {config.filters.map((filter) => ( - - ))} -
- )} -
- - {/* 테이블 - 가로 스크롤 지원 */} -
- - - - {config.columns.map((column) => ( - - ))} - - - - {filteredData.map((row, rowIndex) => ( - - {config.columns.map((column) => { - const cellValue = column.key === 'no' - ? rowIndex + 1 - : formatCellValue(row[column.key], column.format); - const isHighlighted = column.highlightValue && String(row[column.key]) === column.highlightValue; - - // highlightColor 클래스 매핑 - const highlightColorClass = column.highlightColor ? { - red: 'text-red-500', - orange: 'text-orange-500', - blue: 'text-blue-500', - green: 'text-green-500', - }[column.highlightColor] : ''; - - return ( - - ); - })} - - ))} - - {/* 합계 행 */} - {config.showTotal && ( - - {config.columns.map((column, colIndex) => ( - - ))} - - )} - -
- {column.label} -
- {cellValue} -
- {column.key === config.totalColumnKey - ? (typeof config.totalValue === 'number' - ? formatCurrency(config.totalValue) - : config.totalValue) - : (colIndex === 0 ? config.totalLabel || '합계' : '')} -
-
- - {/* 하단 다중 합계 섹션 */} - {config.footerSummary && config.footerSummary.length > 0 && ( -
-
- {config.footerSummary.map((item, index) => ( -
- {item.label} - - {typeof item.value === 'number' - ? formatCurrency(item.value) - : item.value} - -
- ))} -
-
- )} -
- ); -}; - -/** - * 상세 모달 공통 컴포넌트 - */ export function DetailModal({ isOpen, onClose, config }: DetailModalProps) { return ( !open && onClose()} > @@ -702,6 +49,16 @@ export function DetailModal({ isOpen, onClose, config }: DetailModalProps) {
+ {/* 기간선택기 영역 */} + {config.dateFilter?.enabled && ( + + )} + + {/* 신고기간 셀렉트 영역 */} + {config.periodSelect?.enabled && ( + + )} + {/* 요약 카드 영역 - 모바일: 세로배치 */} {config.summaryCards.length > 0 && (
)} + {/* 검토 필요 카드 영역 */} + {config.reviewCards && ( + + )} + {/* 차트 영역 */} {(config.barChart || config.pieChart || config.horizontalBarChart) && (
diff --git a/src/components/business/CEODashboard/modals/DetailModalSections.tsx b/src/components/business/CEODashboard/modals/DetailModalSections.tsx new file mode 100644 index 00000000..0e8d773f --- /dev/null +++ b/src/components/business/CEODashboard/modals/DetailModalSections.tsx @@ -0,0 +1,712 @@ +'use client'; + +import { useState, useCallback, useMemo } from 'react'; +import { Search } from 'lucide-react'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Input } from '@/components/ui/input'; +import { DateRangeSelector } from '@/components/molecules/DateRangeSelector'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + PieChart, + Pie, + Cell, +} from 'recharts'; +import { cn } from '@/lib/utils'; +import { formatNumber as formatCurrency } from '@/lib/utils/amount'; +import type { + DateFilterConfig, + PeriodSelectConfig, + SummaryCardData, + BarChartConfig, + PieChartConfig, + HorizontalBarChartConfig, + TableConfig, + ComparisonSectionConfig, + ReferenceTableConfig, + CalculationCardsConfig, + QuarterlyTableConfig, + ReviewCardsConfig, +} from '../types'; + +// ============================================ +// 공통 유틸리티 +// ============================================ +// 필터 섹션 +// ============================================ + +export const DateFilterSection = ({ config }: { config: DateFilterConfig }) => { + const today = new Date(); + const [startDate, setStartDate] = useState(() => { + const d = new Date(today.getFullYear(), today.getMonth(), 1); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; + }); + const [endDate, setEndDate] = useState(() => { + const d = new Date(today.getFullYear(), today.getMonth() + 1, 0); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; + }); + const [searchText, setSearchText] = useState(''); + + return ( +
+ + + setSearchText(e.target.value)} + placeholder="검색" + className="h-8 pl-7 pr-3 text-xs w-[140px]" + /> +
+ ) : undefined + } + /> +
+ ); +}; + +export const PeriodSelectSection = ({ config }: { config: PeriodSelectConfig }) => { + const [selected, setSelected] = useState(config.defaultValue || config.options[0]?.value || ''); + + return ( +
+ 신고기간 + +
+ ); +}; + +// ============================================ +// 카드 섹션 +// ============================================ + +export const SummaryCard = ({ data }: { data: SummaryCardData }) => { + const displayValue = typeof data.value === 'number' + ? formatCurrency(data.value) + (data.unit || '원') + : data.value; + + return ( +
+

{data.label}

+

+ {data.isComparison && !data.isPositive && typeof data.value === 'string' && !data.value.startsWith('-') ? '-' : ''} + {displayValue} +

+
+ ); +}; + +export const ReviewCardsSection = ({ config }: { config: ReviewCardsConfig }) => { + return ( +
+

{config.title}

+
+ {config.cards.map((card, index) => ( +
+

{card.label}

+

+ {formatCurrency(card.amount)}원 +

+

{card.subLabel}

+
+ ))} +
+
+ ); +}; + +export const CalculationCardsSection = ({ config }: { config: CalculationCardsConfig }) => { + const isResultCard = (_index: number, operator?: string) => { + return operator === '='; + }; + + return ( +
+
+

{config.title}

+ {config.subtitle && ( + {config.subtitle} + )} +
+
+ {config.cards.map((card, index) => ( +
+ {index > 0 && card.operator && ( + + {card.operator} + + )} +
+

+ {card.label} +

+

+ {formatCurrency(card.value)}{card.unit || '원'} +

+
+
+ ))} +
+
+ ); +}; + +// ============================================ +// 차트 섹션 +// ============================================ + +export const BarChartSection = ({ config }: { config: BarChartConfig }) => { + return ( +
+

{config.title}

+
+ + + + + value >= 10000 ? `${value / 10000}만` : value} + width={35} + /> + [formatCurrency(value as number) + '원', '']} + contentStyle={{ fontSize: 12 }} + /> + + + +
+
+ ); +}; + +export const PieChartSection = ({ config }: { config: PieChartConfig }) => { + return ( +
+

{config.title}

+
+ + >} + cx={50} + cy={50} + innerRadius={28} + outerRadius={45} + paddingAngle={2} + dataKey="value" + > + {config.data.map((entry, index) => ( + + ))} + + +
+
+ {config.data.map((item, index) => ( +
+
+
+ {item.name} + {item.percentage}% +
+ + {formatCurrency(item.value)}원 + +
+ ))} +
+
+ ); +}; + +export const HorizontalBarChartSection = ({ config }: { config: HorizontalBarChartConfig }) => { + const maxValue = Math.max(...config.data.map(d => d.value)); + + return ( +
+

{config.title}

+
+ {config.data.map((item, index) => ( +
+
+ {item.name} + + {formatCurrency(item.value)}원 + +
+
+
+
+
+ ))} +
+
+ ); +}; + +// ============================================ +// 비교 섹션 +// ============================================ + +export const ComparisonSection = ({ config }: { config: ComparisonSectionConfig }) => { + const formatValue = (value: string | number, unit?: string): string => { + if (typeof value === 'number') { + return formatCurrency(value) + (unit || '원'); + } + return value; + }; + + const borderColorClass = { + orange: 'border-orange-400', + blue: 'border-blue-400', + }; + + const titleBgClass = { + orange: 'bg-orange-50', + blue: 'bg-blue-50', + }; + + return ( +
+ {/* 왼쪽 박스 */} +
+
+ {config.leftBox.title} +
+
+ {config.leftBox.items.map((item, index) => ( +
+

{item.label}

+

+ {formatValue(item.value, item.unit)} +

+
+ ))} +
+
+ + {/* VS 영역 */} +
+ VS +
+

{config.vsLabel}

+

+ {typeof config.vsValue === 'number' + ? formatCurrency(config.vsValue) + '원' + : config.vsValue} +

+ {config.vsSubLabel && ( +

{config.vsSubLabel}

+ )} + {config.vsBreakdown && config.vsBreakdown.length > 0 && ( +
+ {config.vsBreakdown.map((item, index) => ( +
+ {item.label} + + {typeof item.value === 'number' + ? formatCurrency(item.value) + (item.unit || '원') + : item.value} + +
+ ))} +
+ )} +
+
+ + {/* 오른쪽 박스 */} +
+
+ {config.rightBox.title} +
+
+ {config.rightBox.items.map((item, index) => ( +
+

{item.label}

+

+ {formatValue(item.value, item.unit)} +

+
+ ))} +
+
+
+ ); +}; + +// ============================================ +// 테이블 섹션 +// ============================================ + +export 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 ( +
+

{config.title}

+
+ + + + + + + + + + + + + {config.rows.map((row, rowIndex) => ( + + + + + + + + + ))} + +
구분1사분기2사분기3사분기4사분기합계
{row.label}{formatValue(row.q1)}{formatValue(row.q2)}{formatValue(row.q3)}{formatValue(row.q4)}{formatValue(row.total)}
+
+
+ ); +}; + +export const ReferenceTableSection = ({ config }: { config: ReferenceTableConfig }) => { + const getAlignClass = (align?: string): string => { + switch (align) { + case 'center': return 'text-center'; + case 'right': return 'text-right'; + default: return 'text-left'; + } + }; + + return ( +
+

{config.title}

+
+ + + + {config.columns.map((column) => ( + + ))} + + + + {config.data.map((row, rowIndex) => ( + + {config.columns.map((column) => ( + + ))} + + ))} + +
+ {column.label} +
+ {String(row[column.key] ?? '-')} +
+
+
+ ); +}; + +export const TableSection = ({ config }: { config: TableConfig }) => { + const [filters, setFilters] = useState>(() => { + const initial: Record = {}; + config.filters?.forEach((filter) => { + initial[filter.key] = filter.defaultValue; + }); + return initial; + }); + + const handleFilterChange = useCallback((key: string, value: string) => { + setFilters((prev) => ({ ...prev, [key]: value })); + }, []); + + const filteredData = useMemo(() => { + if (!config.data || !Array.isArray(config.data)) { + return []; + } + let result = [...config.data]; + + config.filters?.forEach((filter) => { + if (filter.key === 'sortOrder') return; + const filterValue = filters[filter.key]; + if (filterValue && filterValue !== 'all') { + result = result.filter((row) => row[filter.key] === filterValue); + } + }); + + if (filters['sortOrder']) { + const sortOrder = filters['sortOrder']; + result.sort((a, b) => { + if (sortOrder === 'amountDesc') { + return (b['amount'] as number) - (a['amount'] as number); + } + if (sortOrder === 'amountAsc') { + return (a['amount'] as number) - (b['amount'] as number); + } + const dateA = new Date(a['date'] as string).getTime(); + const dateB = new Date(b['date'] as string).getTime(); + return sortOrder === 'latest' ? dateB - dateA : dateA - dateB; + }); + } + + return result; + }, [config.data, config.filters, filters]); + + const formatCellValue = (value: unknown, format?: string): string => { + if (value === null || value === undefined) return '-'; + switch (format) { + case 'currency': + case 'number': + return typeof value === 'number' ? formatCurrency(value) : String(value); + default: + return String(value); + } + }; + + const getAlignClass = (align?: string): string => { + switch (align) { + case 'center': return 'text-center'; + case 'right': return 'text-right'; + default: return 'text-left'; + } + }; + + return ( +
+
+
+

{config.title}

+ 총 {filteredData.length}건 +
+ + {config.filters && config.filters.length > 0 && ( +
+ {config.filters.map((filter) => ( + + ))} +
+ )} +
+ +
+ + + + {config.columns.map((column) => ( + + ))} + + + + {filteredData.map((row, rowIndex) => ( + + {config.columns.map((column) => { + const cellValue = column.key === 'no' + ? rowIndex + 1 + : formatCellValue(row[column.key], column.format); + const isHighlighted = column.highlightValue && String(row[column.key]) === column.highlightValue; + + const highlightColorClass = column.highlightColor ? { + red: 'text-red-500', + orange: 'text-orange-500', + blue: 'text-blue-500', + green: 'text-green-500', + }[column.highlightColor] : ''; + + return ( + + ); + })} + + ))} + + {config.showTotal && ( + + {config.columns.map((column, colIndex) => ( + + ))} + + )} + +
+ {column.label} +
+ {cellValue} +
+ {column.key === config.totalColumnKey + ? (typeof config.totalValue === 'number' + ? formatCurrency(config.totalValue) + : config.totalValue) + : (colIndex === 0 ? config.totalLabel || '합계' : '')} +
+
+ + {config.footerSummary && config.footerSummary.length > 0 && ( +
+
+ {config.footerSummary.map((item, index) => ( +
+ {item.label} + + {typeof item.value === 'number' + ? formatCurrency(item.value) + : item.value} + +
+ ))} +
+
+ )} +
+ ); +}; diff --git a/src/components/business/CEODashboard/sections/CardManagementSection.tsx b/src/components/business/CEODashboard/sections/CardManagementSection.tsx index d108fc04..487ccd66 100644 --- a/src/components/business/CEODashboard/sections/CardManagementSection.tsx +++ b/src/components/business/CEODashboard/sections/CardManagementSection.tsx @@ -1,13 +1,13 @@ 'use client'; import { useRouter } from 'next/navigation'; -import { CreditCard, Wallet, Receipt, AlertTriangle } from 'lucide-react'; +import { CreditCard, Wallet, Receipt, AlertTriangle, Gift } from 'lucide-react'; import { AmountCardItem, CheckPointItem, CollapsibleDashboardCard, type SectionColorTheme } from '../components'; import type { CardManagementData } from '../types'; // 카드별 아이콘 매핑 -const CARD_ICONS = [CreditCard, Wallet, Receipt, AlertTriangle]; -const CARD_THEMES: SectionColorTheme[] = ['blue', 'indigo', 'purple', 'orange']; +const CARD_ICONS = [CreditCard, Gift, Receipt, AlertTriangle, Wallet]; +const CARD_THEMES: SectionColorTheme[] = ['blue', 'indigo', 'purple', 'orange', 'blue']; interface CardManagementSectionProps { data: CardManagementData; @@ -28,8 +28,8 @@ export function CardManagementSection({ data, onCardClick }: CardManagementSecti return ( } - title="카드/가지급금 관리" - subtitle="카드 및 가지급금 현황" + title="가지급금 현황" + subtitle="가지급금 관리 현황" > {data.warningBanner && (
@@ -38,7 +38,7 @@ export function CardManagementSection({ data, onCardClick }: CardManagementSecti
)} -
+
{data.cards.map((card, idx) => ( = { '세금 신고': 'taxReport', '신규 업체 등록': 'newVendor', '연차': 'annualLeave', - '지각': 'lateness', - '결근': 'absence', + '차량': 'vehicle', + '장비': 'equipment', '발주': 'purchase', '결재 요청': 'approvalRequest', }; @@ -274,6 +275,20 @@ interface EnhancedMonthlyExpenseSectionProps { onCardClick?: (cardId: string) => void; } +// 당월 예상 지출 카드 설정 +const EXPENSE_CARD_CONFIGS: Array<{ + icon: LucideIcon; + iconBg: string; + bgClass: string; + labelClass: string; + defaultLabel: string; + defaultId: string; +}> = [ + { icon: Receipt, iconBg: '#8b5cf6', bgClass: 'bg-purple-50 border-purple-200 dark:bg-purple-900/30 dark:border-purple-800', labelClass: 'text-purple-700 dark:text-purple-300', defaultLabel: '매입', defaultId: 'me1' }, + { icon: CreditCard, iconBg: '#3b82f6', bgClass: 'bg-blue-50 border-blue-200 dark:bg-blue-900/30 dark:border-blue-800', labelClass: 'text-blue-700 dark:text-blue-300', defaultLabel: '카드', defaultId: 'me2' }, + { icon: Banknote, iconBg: '#f59e0b', bgClass: 'bg-amber-50 border-amber-200 dark:bg-amber-900/30 dark:border-amber-800', labelClass: 'text-amber-700 dark:text-amber-300', defaultLabel: '발행어음', defaultId: 'me3' }, +]; + export function EnhancedMonthlyExpenseSection({ data, onCardClick }: EnhancedMonthlyExpenseSectionProps) { // 총 예상 지출 계산 (API에서 문자열로 올 수 있으므로 Number로 변환) const totalAmount = data.cards.reduce((sum, card) => sum + (Number(card?.amount) || 0), 0); @@ -291,77 +306,35 @@ export function EnhancedMonthlyExpenseSection({ data, onCardClick }: EnhancedMon > {/* 카드 그리드 */}
- {/* 카드 1: 매입 */} -
onCardClick?.(data.cards[0]?.id || 'me1')} - > -
-
- + {EXPENSE_CARD_CONFIGS.map((config, idx) => { + const card = data.cards[idx]; + const CardIcon = config.icon; + return ( +
onCardClick?.(card?.id || config.defaultId)} + > +
+
+ +
+ + {card?.label || config.defaultLabel} + +
+
+ {formatKoreanAmount(card?.amount || 0)} +
+ {card?.previousLabel && ( +
+ + {card.previousLabel} +
+ )}
- - {data.cards[0]?.label || '매입'} - -
-
- {formatKoreanAmount(data.cards[0]?.amount || 0)} -
- {data.cards[0]?.previousLabel && ( -
- - {data.cards[0].previousLabel} -
- )} -
- - {/* 카드 2: 카드 */} -
onCardClick?.(data.cards[1]?.id || 'me2')} - > -
-
- -
- - {data.cards[1]?.label || '카드'} - -
-
- {formatKoreanAmount(data.cards[1]?.amount || 0)} -
- {data.cards[1]?.previousLabel && ( -
- - {data.cards[1].previousLabel} -
- )} -
- - {/* 카드 3: 발행어음 */} -
onCardClick?.(data.cards[2]?.id || 'me3')} - > -
-
- -
- - {data.cards[2]?.label || '발행어음'} - -
-
- {formatKoreanAmount(data.cards[2]?.amount || 0)} -
- {data.cards[2]?.previousLabel && ( -
- - {data.cards[2].previousLabel} -
- )} -
+ ); + })} {/* 카드 4: 총 예상 지출 합계 (강조) */}
{ - if (value >= 100000000) return `${(value / 100000000).toFixed(1)}억`; - if (value >= 10000) return `${(value / 10000).toFixed(0)}만`; - return value.toLocaleString(); -}; export function PurchaseStatusSection({ data }: PurchaseStatusSectionProps) { const [supplierFilter, setSupplierFilter] = useState([]); - const [sortOrder, setSortOrder] = useState('date-desc'); const filteredItems = data.dailyItems - .filter((item) => supplierFilter.length === 0 || supplierFilter.includes(item.supplier)) - .sort((a, b) => { - if (sortOrder === 'date-desc') return b.date.localeCompare(a.date); - if (sortOrder === 'date-asc') return a.date.localeCompare(b.date); - if (sortOrder === 'amount-desc') return b.amount - a.amount; - return a.amount - b.amount; - }); + .filter((item) => supplierFilter.length === 0 || supplierFilter.includes(item.supplier)); const suppliers = [...new Set(data.dailyItems.map((item) => item.supplier))]; @@ -130,7 +112,7 @@ export function PurchaseStatusSection({ data }: PurchaseStatusSectionProps) { - + [formatKoreanAmount(Number(value) || 0), '매입']} /> @@ -189,17 +171,6 @@ export function PurchaseStatusSection({ data }: PurchaseStatusSectionProps) { placeholder="전체 공급처" className="w-full h-8 text-xs" /> -
diff --git a/src/components/business/CEODashboard/sections/SalesStatusSection.tsx b/src/components/business/CEODashboard/sections/SalesStatusSection.tsx index 699eae5b..6290846d 100644 --- a/src/components/business/CEODashboard/sections/SalesStatusSection.tsx +++ b/src/components/business/CEODashboard/sections/SalesStatusSection.tsx @@ -12,14 +12,8 @@ import { DollarSign, } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; import { MultiSelectCombobox } from '@/components/ui/multi-select-combobox'; +import { formatCompactAmount } from '@/lib/utils/amount'; import { BarChart, Bar, @@ -37,24 +31,12 @@ interface SalesStatusSectionProps { data: SalesStatusData; } -const formatAmount = (value: number) => { - if (value >= 100000000) return `${(value / 100000000).toFixed(1)}억`; - if (value >= 10000) return `${(value / 10000).toFixed(0)}만`; - return value.toLocaleString(); -}; export function SalesStatusSection({ data }: SalesStatusSectionProps) { const [clientFilter, setClientFilter] = useState([]); - const [sortOrder, setSortOrder] = useState('date-desc'); const filteredItems = data.dailyItems - .filter((item) => clientFilter.length === 0 || clientFilter.includes(item.client)) - .sort((a, b) => { - if (sortOrder === 'date-desc') return b.date.localeCompare(a.date); - if (sortOrder === 'date-asc') return a.date.localeCompare(b.date); - if (sortOrder === 'amount-desc') return b.amount - a.amount; - return a.amount - b.amount; - }); + .filter((item) => clientFilter.length === 0 || clientFilter.includes(item.client)); const clients = [...new Set(data.dailyItems.map((item) => item.client))]; @@ -143,7 +125,7 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) { - + [formatKoreanAmount(Number(value) || 0), '매출']} /> @@ -158,7 +140,7 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) { - + [formatKoreanAmount(Number(value) || 0), '매출']} @@ -187,17 +169,6 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) { placeholder="전체 거래처" className="w-full h-8 text-xs" /> -
diff --git a/src/components/business/CEODashboard/sections/StatusBoardSection.tsx b/src/components/business/CEODashboard/sections/StatusBoardSection.tsx index 44acfb0e..210bc6e4 100644 --- a/src/components/business/CEODashboard/sections/StatusBoardSection.tsx +++ b/src/components/business/CEODashboard/sections/StatusBoardSection.tsx @@ -13,8 +13,8 @@ const LABEL_TO_SETTING_KEY: Record = { '세금 신고': 'taxReport', '신규 업체 등록': 'newVendor', '연차': 'annualLeave', - '지각': 'lateness', - '결근': 'absence', + '차량': 'vehicle', + '장비': 'equipment', '발주': 'purchase', '결재 요청': 'approvalRequest', }; diff --git a/src/components/business/CEODashboard/sections/UnshippedSection.tsx b/src/components/business/CEODashboard/sections/UnshippedSection.tsx index 01591190..67fe9703 100644 --- a/src/components/business/CEODashboard/sections/UnshippedSection.tsx +++ b/src/components/business/CEODashboard/sections/UnshippedSection.tsx @@ -3,13 +3,6 @@ import { useState } from 'react'; import { PackageX } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; import { MultiSelectCombobox } from '@/components/ui/multi-select-combobox'; import { CollapsibleDashboardCard } from '../components'; import type { UnshippedData } from '../types'; @@ -20,16 +13,11 @@ interface UnshippedSectionProps { export function UnshippedSection({ data }: UnshippedSectionProps) { const [clientFilter, setClientFilter] = useState([]); - const [sortOrder, setSortOrder] = useState('due-asc'); const clients = [...new Set(data.items.map((item) => item.orderClient))]; const filteredItems = data.items - .filter((item) => clientFilter.length === 0 || clientFilter.includes(item.orderClient)) - .sort((a, b) => { - if (sortOrder === 'due-asc') return a.daysLeft - b.daysLeft; - return b.daysLeft - a.daysLeft; - }); + .filter((item) => clientFilter.length === 0 || clientFilter.includes(item.orderClient)); return ( -
diff --git a/src/components/business/CEODashboard/types.ts b/src/components/business/CEODashboard/types.ts index cfffe60e..f0214142 100644 --- a/src/components/business/CEODashboard/types.ts +++ b/src/components/business/CEODashboard/types.ts @@ -396,7 +396,7 @@ export const SECTION_LABELS: Record = { dailyReport: '자금현황', statusBoard: '현황판', monthlyExpense: '당월 예상 지출 내역', - cardManagement: '카드/가지급금 관리', + cardManagement: '가지급금 현황', entertainment: '접대비 현황', welfare: '복리후생비 현황', receivable: '미수금 현황', @@ -422,10 +422,11 @@ export interface TodayIssueSettings { taxReport: boolean; // 세금 신고 newVendor: boolean; // 신규 업체 등록 annualLeave: boolean; // 연차 - lateness: boolean; // 지각 - absence: boolean; // 결근 + vehicle: boolean; // 차량 + equipment: boolean; // 장비 purchase: boolean; // 발주 approvalRequest: boolean; // 결재 요청 + fundStatus: boolean; // 자금 현황 } // 접대비 한도 관리 타입 @@ -445,6 +446,7 @@ export interface EntertainmentSettings { enabled: boolean; limitType: EntertainmentLimitType; companyType: CompanyType; + highAmountThreshold: number; // 고액 결제 기준 금액 } // 복리후생비 설정 @@ -455,6 +457,7 @@ export interface WelfareSettings { fixedAmountPerMonth: number; // 직원당 정해 금액/월 ratio: number; // 연봉 총액 X 비율 (%) annualTotal: number; // 연간 복리후생비총액 + singlePaymentThreshold: number; // 1회 결제 기준 금액 } // 대시보드 전체 설정 @@ -662,10 +665,43 @@ export interface QuarterlyTableConfig { rows: QuarterlyTableRow[]; } +// 검토 필요 카드 아이템 타입 +export interface ReviewCardItem { + label: string; + amount: number; + subLabel: string; // e.g., "미증빙 5건" +} + +// 검토 필요 카드 섹션 설정 타입 +export interface ReviewCardsConfig { + title: string; + cards: ReviewCardItem[]; +} + +// 기간 필터 설정 타입 +export type DateFilterPreset = '당해년도' | '전전월' | '전월' | '당월' | '어제' | '오늘'; + +export interface DateFilterConfig { + enabled: boolean; + presets?: DateFilterPreset[]; // 기간 버튼 목록 (기본: 전체) + defaultPreset?: DateFilterPreset; // 기본 선택 프리셋 + showSearch?: boolean; // 검색 입력창 표시 여부 +} + +// 신고기간 셀렉트 설정 타입 +export interface PeriodSelectConfig { + enabled: boolean; + options: { value: string; label: string }[]; + defaultValue?: string; +} + // 상세 모달 전체 설정 타입 export interface DetailModalConfig { title: string; + dateFilter?: DateFilterConfig; // 기간선택기 + 검색 + periodSelect?: PeriodSelectConfig; // 신고기간 셀렉트 (부가세 등) summaryCards: SummaryCardData[]; + reviewCards?: ReviewCardsConfig; // 검토 필요 카드 섹션 barChart?: BarChartConfig; pieChart?: PieChartConfig; horizontalBarChart?: HorizontalBarChartConfig; // 가로 막대 차트 (도넛 차트 대신 사용) @@ -691,10 +727,11 @@ export const DEFAULT_DASHBOARD_SETTINGS: DashboardSettings = { taxReport: false, newVendor: false, annualLeave: true, - lateness: true, - absence: false, + vehicle: false, + equipment: false, purchase: false, approvalRequest: false, + fundStatus: true, }, }, dailyReport: true, @@ -704,6 +741,7 @@ export const DEFAULT_DASHBOARD_SETTINGS: DashboardSettings = { enabled: true, limitType: 'annual', companyType: 'medium', + highAmountThreshold: 500000, }, welfare: { enabled: true, @@ -712,6 +750,7 @@ export const DEFAULT_DASHBOARD_SETTINGS: DashboardSettings = { fixedAmountPerMonth: 200000, ratio: 20.5, annualTotal: 20000000, + singlePaymentThreshold: 500000, }, receivable: true, debtCollection: true, @@ -737,10 +776,11 @@ export const DEFAULT_DASHBOARD_SETTINGS: DashboardSettings = { taxReport: false, newVendor: false, annualLeave: true, - lateness: true, - absence: false, + vehicle: false, + equipment: false, purchase: false, approvalRequest: false, + fundStatus: true, }, }, }; \ No newline at end of file