# 프로젝트 헬스 개선 계획서

> 작성일: 2025-12-19
> 최종 업데이트: 2025-12-20
> 목적: 프로젝트 구조, 성능, 안정성 개선

---

## 현황 요약

| 영역 | 상태 | 핵심 이슈 |
|------|------|----------|
| 빌드 설정 | ✅ 해결됨 | ~~98개 타입 에러 무시~~ → 0개, ~~API 키 노출~~ → 서버 사이드 이동 |
| 상태관리 | 🟡 개선 필요 | ItemMasterContext 과부하 (13개 상태) |
| Next.js 활용 | 🔴 심각 | 259개 'use client', Server Component 미활용 |
| 디자인 일관성 | ✅ 완료 | ~~다크모드 일부 미완성~~ → 다크모드 스타일 완성 (2025-12-20) |

---

## Phase 1: 긴급 (이번 주)

### 1.1 TypeScript 에러 해결 + ignoreBuildErrors 제거

**현재 상태:**
```typescript
// next.config.ts
typescript: { ignoreBuildErrors: true }  // 98개 에러 숨김
eslint: { ignoreDuringBuilds: true }
```

**작업 내용:**

#### Step 1: 타입 에러 카테고리 분류
| 카테고리 | 개수 | 예시 |
|---------|------|------|
| 모델 타입 불일치 | ~26개 | Employee에 `concurrentPosition` 없음 |
| Props 미스매치 | ~35개 | IntegratedListTemplateV2 props 변경 |
| 배열 타입 불일치 | ~9개 | PricingListItem 타입 정의 |
| 기타 | ~28개 | - |

#### Step 2: 수정 순서
1. [ ] `src/types/` 폴더의 모델 타입 정의 업데이트
2. [ ] `IntegratedListTemplateV2` Props 인터페이스 정리
3. [ ] 페이지별 타입 에러 수정
4. [ ] `ignoreBuildErrors: false` 변경
5. [ ] `npm run build` 성공 확인

**예상 소요:** 2-3시간

**위험도:** 🔴 높음 (빌드 실패 가능)

---

### 1.2 API 키 서버 사이드 이동

**현재 상태:**
```env
# .env.local
NEXT_PUBLIC_API_KEY=42Jfwc6EaR...      # 브라우저에서 노출됨!
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=...   # 브라우저에서 노출됨!
```

**문제점:**
- `NEXT_PUBLIC_` 접두사 → 클라이언트 번들에 포함
- 브라우저 개발자도구에서 확인 가능
- API 남용/해킹 위험

**작업 내용:**

#### Step 1: 환경변수 이름 변경
```env
# .env.local (수정 후)
API_KEY=42Jfwc6EaR...                    # 서버만 접근
GOOGLE_MAPS_API_KEY=AIzaSyAS3bA...       # 서버만 접근

# 클라이언트에서 필요한 공개 정보만
NEXT_PUBLIC_API_BASE_URL=https://api.example.com
```

#### Step 2: 서버 사이드 프록시 확인
```typescript
// src/app/api/proxy/[...path]/route.ts
// 이미 구현됨 - API_KEY를 서버에서 주입
const response = await fetch(url, {
  headers: {
    'Authorization': `Bearer ${process.env.API_KEY}`,  // 서버에서만 접근
  },
});
```

#### Step 3: Google Maps 처리
```typescript
// 옵션 A: 서버 사이드 렌더링
// 옵션 B: API 라우트로 프록시
// 옵션 C: Maps Embed API 사용 (키 제한 설정)
```

**예상 소요:** 30분-1시간

**위험도:** 🟡 중간 (dev 서버 재시작 필요)

---

### 1.3 ThemeContext SSR 수정

**현재 상태:**
```typescript
// src/contexts/ThemeContext.tsx
const [theme, setThemeState] = useState<Theme>("light");

useEffect(() => {
  const savedTheme = localStorage.getItem("theme");  // SSR에서 에러 가능
  if (savedTheme) {
    setThemeState(savedTheme);
  }
}, []);
```

**문제점:**
- 서버에서 `localStorage` 접근 시 에러
- Hydration mismatch 발생 가능

**작업 내용:**

#### 수정 코드
```typescript
// src/contexts/ThemeContext.tsx (수정 후)
const [theme, setThemeState] = useState<Theme>(() => {
  // SSR 안전 체크
  if (typeof window === 'undefined') return 'light';

  const savedTheme = localStorage.getItem('theme');
  return (savedTheme as Theme) || 'light';
});

// 또는 useEffect 패턴 유지 (더 안전)
const [theme, setThemeState] = useState<Theme>('light');
const [isHydrated, setIsHydrated] = useState(false);

useEffect(() => {
  setIsHydrated(true);
  const savedTheme = localStorage.getItem('theme');
  if (savedTheme) {
    setThemeState(savedTheme as Theme);
  }
}, []);
```

**예상 소요:** 15분

**위험도:** 🟢 낮음 (HMR 즉시 반영)

---

## Phase 2: 단기 (2주)

### 2.1 ItemMasterContext 분할

**현재 상태:**
```
ItemMasterContext
├── 품목 데이터 (3개 상태)
├── 기준정보 (7개 상태)
├── 폼 구조 (4개 상태)
└── 50개+ 메서드

→ ANY 상태 변경 시 전체 리렌더링
```

**개선 방향:**
```
ItemMasterDataContext     → 품목 기본 데이터
ItemFormContext           → 페이지/섹션/필드 구조
ItemLookupContext         → 단위/재질/처리방식 등 기준정보
```

**작업 내용:**
1. [ ] Context 분할 설계
2. [ ] 각 Context별 Provider 구현
3. [ ] 기존 useItemMaster → 새 Context hooks로 마이그레이션
4. [ ] 테스트

**예상 소요:** 1-2일

**위험도:** 🟡 중간 (기존 코드 변경 필요)

---

### 2.2 IntegratedListTemplate → Zustand Store

**현재 상태:**
```typescript
// 20개+ props 전달
<IntegratedListTemplateV2
  searchValue={searchValue}
  onSearchChange={setSearchValue}
  currentPage={currentPage}
  onPageChange={setCurrentPage}
  selectedItems={selectedItems}
  onSelectionChange={setSelectedItems}
  // ... 더 많은 props
/>
```

**개선 방향:**
```typescript
// Zustand store
const useListStore = create((set) => ({
  // 페이지네이션
  currentPage: 1,
  pageSize: 20,
  setPage: (page) => set({ currentPage: page }),

  // 필터/검색
  searchValue: '',
  filters: {},
  setSearch: (value) => set({ searchValue: value }),

  // 선택
  selectedIds: new Set(),
  toggleSelection: (id) => set((state) => { /* ... */ }),
}));

// 컴포넌트에서 사용
function MyListPage() {
  const { currentPage, setPage } = useListStore();
  return <IntegratedListTemplateV2 />; // props 최소화
}
```

**작업 내용:**
1. [ ] `src/store/listStore.ts` 생성
2. [ ] 공통 리스트 상태 추출
3. [ ] 페이지별 점진적 마이그레이션
4. [ ] IntegratedListTemplateV2 리팩토링

**예상 소요:** 2-3일

**위험도:** 🟡 중간

---

### 2.3 다크모드 스타일 완성

**현재 상태:**
```typescript
// Button - 일부 variant만 dark: 정의
ghost: "hover:bg-accent hover:text-accent-foreground"  // dark: 없음
outline: "border-input bg-background"                   // dark: 없음
```

**작업 내용:**
1. [ ] 모든 UI 컴포넌트 다크모드 스타일 점검
2. [ ] Button, Select, Input 등 주요 컴포넌트 수정
3. [ ] 색상 대비 검증 (WCAG AA 기준)
4. [ ] 다크모드 테스트

**예상 소요:** 1일

**위험도:** 🟢 낮음

---

## Phase 3: 중기 (1개월)

### 3.1 주요 페이지 Server Component 전환

**현재 상태:**
- 259개 'use client' 컴포넌트
- 모든 데이터 페칭: useEffect 내 클라이언트 fetch
- 초기 로딩 지연

**개선 방향:**
```typescript
// Before (Client Component)
'use client';
export default function ItemPage() {
  const [items, setItems] = useState([]);
  useEffect(() => {
    fetch('/api/items').then(r => r.json()).then(setItems);
  }, []);
  return <ItemList items={items} />;
}

// After (Server Component)
export default async function ItemPage() {
  const items = await fetch('/api/items').then(r => r.json());
  return <ItemList items={items} />;  // 클라이언트로 props 전달
}
```

**마이그레이션 우선순위:**
1. [ ] 정적 페이지 (설정, 정보 페이지)
2. [ ] 리스트 페이지 (items, employees)
3. [ ] 상세 페이지

**예상 소요:** 1-2주

**위험도:** 🟡 중간

---

### 3.2 캐싱 전략 수립

**현재 상태:**
```typescript
cache: 'no-store'  // 모든 fetch에 적용 → 성능 저하
```

**개선 방향:**
| 데이터 유형 | 캐싱 전략 | TTL |
|------------|----------|-----|
| 정적 데이터 (카테고리, 단위) | `force-cache` | 1시간 |
| 사용자 데이터 | `no-store` | - |
| 리스트 데이터 | `revalidate: 60` | 1분 |
| 상세 데이터 | `revalidate: 300` | 5분 |

**작업 내용:**
1. [ ] 데이터 유형별 분류
2. [ ] fetch 옵션 표준화
3. [ ] revalidateTag/revalidatePath 활용
4. [ ] 성능 측정

**예상 소요:** 3-5일

**위험도:** 🟢 낮음

---

### 3.3 TanStack Query 도입 검토

**도입 이점:**
- API 상태 자동 관리 (loading, error, data)
- 캐싱 및 백그라운드 리페치
- 낙관적 업데이트
- DevTools 지원

**도입 시 구조:**
```typescript
// hooks/useItems.ts
export function useItems() {
  return useQuery({
    queryKey: ['items'],
    queryFn: () => itemApi.getAll(),
    staleTime: 5 * 60 * 1000,  // 5분
  });
}

// 컴포넌트에서 사용
function ItemList() {
  const { data, isLoading, error } = useItems();
  // 자동으로 loading/error 처리
}
```

**검토 포인트:**
- [ ] 현재 API 호출 패턴 분석
- [ ] 도입 시 마이그레이션 범위
- [ ] 번들 사이즈 영향 (~20KB)
- [ ] 팀 학습 비용

**예상 소요:** 1-2주 (검토 + 파일럿)

**위험도:** 🟡 중간 (큰 변화)

---

## 체크리스트 요약

### Phase 1 (긴급) ⏰ ✅ 완료 (2025-12-20)
- [x] 1.1 타입 에러 해결 + ignoreBuildErrors 제거 ✅
  - 98개 → 0개 에러 수정
  - `npx tsc --noEmit` 성공
  - `npm run build` 성공
- [x] 1.2 API 키 서버 사이드 이동 ✅
  - `NEXT_PUBLIC_API_KEY` → `API_KEY` 변경
  - 프록시 라우트에서 서버 사이드 주입
- [x] 1.3 ThemeContext SSR 수정 ✅
  - `typeof window` 체크 추가

### Phase 2 (단기) 📅
- [ ] 2.1 ItemMasterContext 3개로 분할
- [ ] 2.2 IntegratedListTemplate → Zustand store
- [x] 2.3 다크모드 스타일 완성 ✅ (2025-12-20)
  - Textarea: Input과 스타일 통일 (`dark:bg-input/30` 추가)
  - 모든 UI 컴포넌트 다크모드 지원 확인:
    - Button, Select, Input ✅ (dark: 스타일 적용됨)
    - Card, Dialog, Sheet, Popover ✅ (CSS 변수로 처리)
    - Table, DropdownMenu ✅ (CSS 변수 + dark: 스타일)
    - Badge, Checkbox, RadioGroup, Switch ✅ (dark: 스타일 적용됨)
    - Alert, Tabs ✅ (CSS 변수 + dark: 스타일)
- [x] 2.4 로딩 스피너 표준화 ✅ (2025-12-20)
  - `loading-spinner.tsx` 5가지 변형 컴포넌트 구현:
    - `LoadingSpinner`: 인라인/버튼용 (xs, sm, md, lg 사이즈)
    - `ContentLoadingSpinner`: 상세/수정 페이지용 (min-h-[400px])
    - `PageLoadingSpinner`: 페이지 전환용 (min-h-[calc(100vh-200px)])
    - `TableLoadingSpinner`: 테이블/리스트용 (py-16)
    - `ButtonSpinner`: 버튼 내부 스피너
  - 18개+ 페이지 표준화 적용:
    - HR 페이지 (사원, 휴가, 부서, 급여, 근태관리)
    - 품목기준정보관리, 게시판, 팝업관리
    - 견적관리 상세/수정
  - 빌드 테스트 성공 (231 pages)

### Phase 3 (중기) 📆
- [ ] 3.1 주요 페이지 Server Component 전환
- [ ] 3.2 캐싱 전략 수립
- [ ] 3.3 TanStack Query 도입 검토

---

## 참고 자료

- 분석 리포트: 2025-12-19 프로젝트 헬스체크
- 관련 문서:
  - `claudedocs/guides/[GUIDE-2025-12-16] options-vs-flattened-data.md`
  - `claudedocs/guides/[PLAN-2025-12-19] page-layout-standardization.md`