331 lines
9.7 KiB
Markdown
331 lines
9.7 KiB
Markdown
|
|
# CEO 대시보드 리팩토링 계획
|
||
|
|
|
||
|
|
> 작성일: 2026-01-10
|
||
|
|
> 대상 파일: `src/components/business/CEODashboard/`
|
||
|
|
> 목표: 파일 분리 + 모바일(344px) 대응
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. 현재 상태 분석
|
||
|
|
|
||
|
|
### 1.1 파일 구조
|
||
|
|
|
||
|
|
```
|
||
|
|
CEODashboard/
|
||
|
|
├── CEODashboard.tsx # 1,648줄 ⚠️ 분리 필요
|
||
|
|
├── components.tsx # 312줄 ✅ 적정
|
||
|
|
├── types.ts # ~100줄 ✅ 적정
|
||
|
|
├── sections/
|
||
|
|
│ ├── index.ts
|
||
|
|
│ ├── TodayIssueSection.tsx # 73줄 ✅
|
||
|
|
│ ├── DailyReportSection.tsx # 37줄 ✅
|
||
|
|
│ ├── MonthlyExpenseSection.tsx # 38줄 ✅
|
||
|
|
│ ├── CardManagementSection.tsx # ~50줄 ✅
|
||
|
|
│ ├── EntertainmentSection.tsx # ~50줄 ✅
|
||
|
|
│ ├── WelfareSection.tsx # ~50줄 ✅
|
||
|
|
│ ├── ReceivableSection.tsx # ~50줄 ✅
|
||
|
|
│ ├── DebtCollectionSection.tsx # ~50줄 ✅
|
||
|
|
│ ├── VatSection.tsx # ~50줄 ✅
|
||
|
|
│ └── CalendarSection.tsx # ~100줄 ✅
|
||
|
|
├── modals/
|
||
|
|
│ ├── ScheduleDetailModal.tsx # ~200줄 ✅
|
||
|
|
│ └── DetailModal.tsx # ~300줄 ✅
|
||
|
|
└── dialogs/
|
||
|
|
└── DashboardSettingsDialog.tsx # ~200줄 ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.2 CEODashboard.tsx 내부 분석 (1,648줄)
|
||
|
|
|
||
|
|
| 줄 범위 | 내용 | 줄 수 | 분리 대상 |
|
||
|
|
|---------|------|-------|----------|
|
||
|
|
| 1-26 | imports | 26 | - |
|
||
|
|
| 27-370 | mockData 객체 | **344** | ✅ 분리 |
|
||
|
|
| 371-748 | handleMonthlyExpenseCardClick (모달 config) | **378** | ✅ 분리 |
|
||
|
|
| 749-1019 | handleCardManagementCardClick (모달 config) | **271** | ✅ 분리 |
|
||
|
|
| 1020-1247 | handleEntertainmentCardClick (모달 config) | **228** | ✅ 분리 |
|
||
|
|
| 1248-1375 | handleWelfareCardClick (모달 config) | **128** | ✅ 분리 |
|
||
|
|
| 1376-1465 | handleVatClick (모달 config) | **90** | ✅ 분리 |
|
||
|
|
| 1466-1509 | 캘린더 관련 핸들러 | 44 | - |
|
||
|
|
| 1510-1648 | 컴포넌트 렌더링 | 139 | - |
|
||
|
|
|
||
|
|
**분리 대상 총합**: ~1,439줄 (87%)
|
||
|
|
**분리 후 예상**: ~210줄
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 분리 계획
|
||
|
|
|
||
|
|
### 2.1 목표 구조
|
||
|
|
|
||
|
|
```
|
||
|
|
CEODashboard/
|
||
|
|
├── CEODashboard.tsx # ~250줄 (컴포넌트 + 핸들러)
|
||
|
|
├── components.tsx # 312줄 (유지)
|
||
|
|
├── types.ts # ~100줄 (유지)
|
||
|
|
├── mockData.ts # 🆕 ~350줄 (목데이터)
|
||
|
|
├── modalConfigs/ # 🆕 모달 설정 분리
|
||
|
|
│ ├── index.ts
|
||
|
|
│ ├── monthlyExpenseConfigs.ts # ~380줄
|
||
|
|
│ ├── cardManagementConfigs.ts # ~280줄
|
||
|
|
│ ├── entertainmentConfigs.ts # ~230줄
|
||
|
|
│ ├── welfareConfigs.ts # ~130줄
|
||
|
|
│ └── vatConfigs.ts # ~100줄
|
||
|
|
├── sections/ # (유지)
|
||
|
|
├── modals/ # (유지)
|
||
|
|
└── dialogs/ # (유지)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 분리 파일 상세
|
||
|
|
|
||
|
|
#### A. mockData.ts (신규)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// mockData.ts
|
||
|
|
import type { CEODashboardData } from './types';
|
||
|
|
|
||
|
|
export const mockData: CEODashboardData = {
|
||
|
|
todayIssue: [...],
|
||
|
|
dailyReport: {...},
|
||
|
|
monthlyExpense: {...},
|
||
|
|
cardManagement: {...},
|
||
|
|
entertainment: {...},
|
||
|
|
welfare: {...},
|
||
|
|
receivable: {...},
|
||
|
|
debtCollection: {...},
|
||
|
|
vat: {...},
|
||
|
|
calendarSchedules: [...],
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
#### B. modalConfigs/index.ts (신규)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// modalConfigs/index.ts
|
||
|
|
export { getMonthlyExpenseModalConfig } from './monthlyExpenseConfigs';
|
||
|
|
export { getCardManagementModalConfig } from './cardManagementConfigs';
|
||
|
|
export { getEntertainmentModalConfig } from './entertainmentConfigs';
|
||
|
|
export { getWelfareModalConfig } from './welfareConfigs';
|
||
|
|
export { getVatModalConfig } from './vatConfigs';
|
||
|
|
```
|
||
|
|
|
||
|
|
#### C. 개별 모달 config 파일 예시
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// modalConfigs/monthlyExpenseConfigs.ts
|
||
|
|
import type { DetailModalConfig } from '../types';
|
||
|
|
|
||
|
|
export function getMonthlyExpenseModalConfig(cardId: string): DetailModalConfig | null {
|
||
|
|
const configs: Record<string, DetailModalConfig> = {
|
||
|
|
me1: { title: '당월 매입 상세', ... },
|
||
|
|
me2: { title: '당월 카드 상세', ... },
|
||
|
|
me3: { title: '당월 발행어음 상세', ... },
|
||
|
|
me4: { title: '당월 지출 예상 상세', ... },
|
||
|
|
};
|
||
|
|
return configs[cardId] || null;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### D. CEODashboard.tsx (리팩토링 후)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// CEODashboard.tsx (리팩토링 후 ~250줄)
|
||
|
|
import { mockData } from './mockData';
|
||
|
|
import {
|
||
|
|
getMonthlyExpenseModalConfig,
|
||
|
|
getCardManagementModalConfig,
|
||
|
|
getEntertainmentModalConfig,
|
||
|
|
getWelfareModalConfig,
|
||
|
|
getVatModalConfig,
|
||
|
|
} from './modalConfigs';
|
||
|
|
|
||
|
|
export function CEODashboard() {
|
||
|
|
// 상태 관리
|
||
|
|
const [data] = useState<CEODashboardData>(mockData);
|
||
|
|
const [detailModalConfig, setDetailModalConfig] = useState<DetailModalConfig | null>(null);
|
||
|
|
// ...
|
||
|
|
|
||
|
|
// 간소화된 핸들러
|
||
|
|
const handleMonthlyExpenseCardClick = useCallback((cardId: string) => {
|
||
|
|
const config = getMonthlyExpenseModalConfig(cardId);
|
||
|
|
if (config) {
|
||
|
|
setDetailModalConfig(config);
|
||
|
|
setIsDetailModalOpen(true);
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// 렌더링
|
||
|
|
return (...);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 모바일 대응 계획
|
||
|
|
|
||
|
|
### 3.1 적용 대상 컴포넌트
|
||
|
|
|
||
|
|
| 컴포넌트 | 현재 상태 | 변경 필요 |
|
||
|
|
|----------|----------|----------|
|
||
|
|
| TodayIssueSection | `grid-cols-2 md:grid-cols-4` | ✅ `grid-cols-1 xs:grid-cols-2 md:grid-cols-4` |
|
||
|
|
| DailyReportSection | `grid-cols-2 md:grid-cols-4` | ✅ 동일 |
|
||
|
|
| MonthlyExpenseSection | `grid-cols-2 md:grid-cols-4` | ✅ 동일 |
|
||
|
|
| CardManagementSection | `grid-cols-2 md:grid-cols-4` | ✅ 동일 |
|
||
|
|
| EntertainmentSection | `grid-cols-2 md:grid-cols-4` | ✅ 동일 |
|
||
|
|
| WelfareSection | `grid-cols-2 md:grid-cols-4` | ✅ 동일 |
|
||
|
|
| ReceivableSection | `grid-cols-2 md:grid-cols-4` | ✅ 동일 |
|
||
|
|
| DebtCollectionSection | `grid-cols-2 md:grid-cols-4` | ✅ 동일 |
|
||
|
|
| VatSection | `grid-cols-2 md:grid-cols-4` | ✅ 동일 |
|
||
|
|
| AmountCardItem (공통) | 고정 텍스트 크기 | ✅ 반응형 텍스트 |
|
||
|
|
| IssueCardItem (공통) | 고정 텍스트 크기 | ✅ 반응형 텍스트 |
|
||
|
|
| PageHeader | 가로 배치 | ✅ 세로/가로 반응형 |
|
||
|
|
|
||
|
|
### 3.2 components.tsx 변경 사항
|
||
|
|
|
||
|
|
#### AmountCardItem
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// Before
|
||
|
|
<p className="text-2xl md:text-3xl font-bold">
|
||
|
|
{formatCardAmount(card.amount)}
|
||
|
|
</p>
|
||
|
|
|
||
|
|
// After
|
||
|
|
<p className="text-lg xs:text-xl md:text-2xl lg:text-3xl font-bold truncate">
|
||
|
|
{formatCardAmount(card.amount)}
|
||
|
|
</p>
|
||
|
|
<p className="text-xs xs:text-sm font-medium mb-1 xs:mb-2 break-keep">
|
||
|
|
{card.label}
|
||
|
|
</p>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### IssueCardItem
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// Before
|
||
|
|
<p className="text-2xl md:text-3xl font-bold">
|
||
|
|
{typeof count === 'number' ? `${count}건` : count}
|
||
|
|
</p>
|
||
|
|
|
||
|
|
// After
|
||
|
|
<p className="text-lg xs:text-xl md:text-2xl lg:text-3xl font-bold">
|
||
|
|
{typeof count === 'number' ? `${count}건` : count}
|
||
|
|
</p>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.3 섹션 공통 변경
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// Before (모든 섹션)
|
||
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
|
|
|
||
|
|
// After
|
||
|
|
<div className="grid grid-cols-1 xs:grid-cols-2 md:grid-cols-4 gap-3 xs:gap-4">
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.4 CardContent 패딩
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// Before
|
||
|
|
<CardContent className="p-6">
|
||
|
|
|
||
|
|
// After
|
||
|
|
<CardContent className="p-3 xs:p-4 md:p-6">
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 실행 계획
|
||
|
|
|
||
|
|
### Phase 1: 파일 분리 (예상 30분)
|
||
|
|
|
||
|
|
- [ ] **1.1** `mockData.ts` 생성 및 데이터 이동
|
||
|
|
- [ ] **1.2** `modalConfigs/` 폴더 생성
|
||
|
|
- [ ] **1.3** `monthlyExpenseConfigs.ts` 생성
|
||
|
|
- [ ] **1.4** `cardManagementConfigs.ts` 생성
|
||
|
|
- [ ] **1.5** `entertainmentConfigs.ts` 생성
|
||
|
|
- [ ] **1.6** `welfareConfigs.ts` 생성
|
||
|
|
- [ ] **1.7** `vatConfigs.ts` 생성
|
||
|
|
- [ ] **1.8** `modalConfigs/index.ts` 생성
|
||
|
|
- [ ] **1.9** `CEODashboard.tsx` 리팩토링
|
||
|
|
- [ ] **1.10** import 정리 및 동작 확인
|
||
|
|
|
||
|
|
### Phase 2: 모바일 대응 (예상 30분)
|
||
|
|
|
||
|
|
- [ ] **2.1** `components.tsx` - AmountCardItem 반응형 적용
|
||
|
|
- [ ] **2.2** `components.tsx` - IssueCardItem 반응형 적용
|
||
|
|
- [ ] **2.3** `sections/*.tsx` - 그리드 반응형 적용 (일괄)
|
||
|
|
- [ ] **2.4** `sections/*.tsx` - CardContent 패딩 반응형 적용
|
||
|
|
- [ ] **2.5** PageHeader 반응형 확인
|
||
|
|
- [ ] **2.6** 344px 테스트 및 미세 조정
|
||
|
|
|
||
|
|
### Phase 3: 검증 (예상 15분)
|
||
|
|
|
||
|
|
- [ ] **3.1** 빌드 확인 요청
|
||
|
|
- [ ] **3.2** 데스크탑(1280px) 동작 확인
|
||
|
|
- [ ] **3.3** 태블릿(768px) 동작 확인
|
||
|
|
- [ ] **3.4** 모바일(375px) 동작 확인
|
||
|
|
- [ ] **3.5** Galaxy Fold(344px) 동작 확인
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. 예상 결과
|
||
|
|
|
||
|
|
### 5.1 파일 크기 변화
|
||
|
|
|
||
|
|
| 파일 | Before | After |
|
||
|
|
|------|--------|-------|
|
||
|
|
| CEODashboard.tsx | 1,648줄 | ~250줄 |
|
||
|
|
| mockData.ts | - | ~350줄 |
|
||
|
|
| modalConfigs/*.ts | - | ~1,100줄 (5개 파일) |
|
||
|
|
|
||
|
|
### 5.2 장점
|
||
|
|
|
||
|
|
1. **유지보수성**: 각 파일이 단일 책임 원칙 준수
|
||
|
|
2. **재사용성**: 모달 config를 다른 곳에서 재사용 가능
|
||
|
|
3. **확장성**: 새 모달 추가 시 별도 파일로 분리
|
||
|
|
4. **가독성**: 핵심 로직만 CEODashboard.tsx에 유지
|
||
|
|
5. **API 전환 용이**: mockData.ts만 교체하면 됨
|
||
|
|
|
||
|
|
### 5.3 모바일 개선 효과
|
||
|
|
|
||
|
|
| 항목 | Before (344px) | After (344px) |
|
||
|
|
|------|----------------|---------------|
|
||
|
|
| 카드 배치 | 2열 (160px/카드) | 1열 (320px/카드) |
|
||
|
|
| 금액 표시 | 잘림 가능 | 완전 표시 |
|
||
|
|
| 라벨 표시 | 잘림 가능 | 줄바꿈/truncate |
|
||
|
|
| 패딩 | 과다 (24px) | 적정 (12px) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. 참고 문서
|
||
|
|
|
||
|
|
- **모바일 대응 가이드**: `claudedocs/guides/[GUIDE] mobile-responsive-patterns.md`
|
||
|
|
- **기존 테스트 계획**: `claudedocs/[PLAN] mobile-overflow-testing.md`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. 의사결정 사항
|
||
|
|
|
||
|
|
### Q1: mockData를 별도 파일로?
|
||
|
|
- **결정**: ✅ 분리
|
||
|
|
- **이유**: 향후 API 연동 시 교체 용이
|
||
|
|
|
||
|
|
### Q2: 모달 config를 폴더로?
|
||
|
|
- **결정**: ✅ 폴더로 분리
|
||
|
|
- **이유**: 각 config가 100줄 이상, 단일 파일은 여전히 큼
|
||
|
|
|
||
|
|
### Q3: 모바일에서 1열 vs 2열?
|
||
|
|
- **결정**: 344px 이하 1열, 375px 이상 2열
|
||
|
|
- **이유**: Galaxy Fold 160px 카드는 너무 좁음
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. 시작 조건
|
||
|
|
|
||
|
|
- [x] 계획서 작성 완료
|
||
|
|
- [x] 모바일 가이드 작성 완료
|
||
|
|
- [ ] 사용자 승인
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
> **다음 단계**: 계획 승인 후 Phase 1 (파일 분리) 시작
|