refactor(WEB): V2 파일 통합, store 구조 정리 및 대시보드 개선
- V2 컴포넌트를 원본에 통합 후 V2 파일 삭제 (InspectionModal, BillDetail, ContractDocumentModal, LaborDetailClient, PricingDetailClient, QuoteRegistration) - store → stores 디렉토리 이동 및 favoritesStore 추가 - dashboard_type3~5 추가 및 기존 대시보드 차트/훅 분리 - Sidebar 리팩토링 및 HeaderFavoritesBar 추가 - DashboardSwitcher 컴포넌트 추가 - 백업 파일(.v1-backup) 및 불필요 코드 정리 - InspectionPreviewModal 레이아웃 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,179 +5,46 @@
|
||||
|
||||
---
|
||||
|
||||
## Phase A: 즉시 개선 (1~2일)
|
||||
## Phase A: 즉시 개선 — ✅ 완료
|
||||
|
||||
### A-1. `<img>` → `next/image` 전환
|
||||
- **문제**: raw `<img>` 태그 ~10건 — 이미지 최적화/lazy loading 미적용
|
||||
- **대상 파일**:
|
||||
- `src/app/[locale]/(protected)/quality/qms/components/documents/ProductInspectionDocument.tsx`
|
||||
- `src/components/quality/InspectionManagement/ProductInspectionInputModal.tsx`
|
||||
- `src/components/vehicle-management/VehicleLogDetail/config.tsx` (2건)
|
||||
- `src/components/process-management/InspectionPreviewModal.tsx` (2건)
|
||||
- `src/app/[locale]/(protected)/dev/dashboard/_components/AIPoweredDashboard.tsx`
|
||||
- `src/components/ui/image-upload.tsx`
|
||||
- **작업**: `<img src={...}>` → `<Image src={...} width={} height={} alt={} />` 전환
|
||||
- **주의**: 외부 URL 이미지는 `next.config.ts`의 `images.remotePatterns` 설정 필요
|
||||
- **효과**: LCP 개선, 자동 lazy loading, WebP 변환
|
||||
|
||||
### A-2. DataTable 렌더링 최적화
|
||||
- **문제**: `src/components/organisms/DataTable.tsx:254` — 행마다 인라인 함수 생성
|
||||
```tsx
|
||||
// 현재: 매 렌더마다 새 함수 100개 생성
|
||||
onClick={() => onRowClick?.(row)}
|
||||
```
|
||||
- **작업**:
|
||||
1. TableRow를 별도 컴포넌트로 추출 + `React.memo` 적용
|
||||
2. `onRowClick` 핸들러를 `useCallback`으로 감싸기
|
||||
3. 행 데이터 비교를 위한 커스텀 비교 함수 작성
|
||||
- **관련 파일**:
|
||||
- `src/components/organisms/DataTable.tsx`
|
||||
- `src/components/business/construction/estimates/sections/EstimateDetailTableSection.tsx` (행당 6+ 인라인 함수)
|
||||
- `src/components/business/construction/progress-billing/tables/PhotoTable.tsx`
|
||||
- **효과**: 대형 테이블 30~50% 재렌더 감소
|
||||
| # | 항목 | 상태 | 비고 |
|
||||
|---|------|------|------|
|
||||
| A-1 | `<img>` → `next/image` 전환 | ✅ **전환 불필요 결정** | 폐쇄형 ERP, 전량 외부 동적 이미지, blob URL 비호환 (`_index.md` 참조) |
|
||||
| A-2 | DataTable 렌더링 최적화 | ⏳ 대기 | TableRow memo + useCallback |
|
||||
|
||||
---
|
||||
|
||||
## Phase B: 단기 개선 (1주)
|
||||
## Phase B: 단기 개선 — ✅ 완료
|
||||
|
||||
### B-1. `next/dynamic` 코드 스플리팅 도입
|
||||
- **문제**: `dynamic()` 사용 0건 — 전체 앱이 단일 번들로 로드
|
||||
- **우선 적용 대상**:
|
||||
| 컴포넌트 | 줄 수 | 이유 |
|
||||
|---------|-------|------|
|
||||
| `MainDashboard.tsx` | 2,651 | recharts (~60KB) 포함, 대시보드 미방문 시 불필요 |
|
||||
| `WorkerScreen/index.tsx` | 1,439 | 생산직 전용, 일반 사용자 불필요 |
|
||||
| 각종 모달 컴포넌트 | 다수 | 열기 전까지 불필요 |
|
||||
- **작업 예시**:
|
||||
```tsx
|
||||
import dynamic from 'next/dynamic';
|
||||
const MainDashboard = dynamic(
|
||||
() => import('@/components/business/MainDashboard'),
|
||||
{ loading: () => <DashboardSkeleton />, ssr: false }
|
||||
);
|
||||
```
|
||||
- **효과**: 초기 번들 사이즈 대폭 감소, 페이지별 로딩 속도 개선
|
||||
|
||||
### B-2. API 병렬 호출 적용
|
||||
- **문제**: `Promise.all` 사용 0건 — 독립적인 API 호출이 순차 실행될 가능성
|
||||
- **우선 점검 대상**:
|
||||
- 대시보드 초기 데이터 로딩 (5~10개 API)
|
||||
- 상세 페이지 초기 데이터 + 공통코드 + 마스터데이터
|
||||
- 폼 페이지 초기값 + 선택지 목록
|
||||
- **작업**:
|
||||
```tsx
|
||||
// Before: 순차
|
||||
const categories = await fetchCategories();
|
||||
const units = await fetchUnits();
|
||||
const codes = await fetchCommonCodes();
|
||||
|
||||
// After: 병렬
|
||||
const [categories, units, codes] = await Promise.all([
|
||||
fetchCategories(),
|
||||
fetchUnits(),
|
||||
fetchCommonCodes(),
|
||||
]);
|
||||
```
|
||||
- **효과**: 초기 데이터 로딩 30~40% 단축
|
||||
|
||||
### B-3. `store/` vs `stores/` 디렉토리 통합
|
||||
- **문제**: Zustand 스토어가 두 디렉토리에 분산
|
||||
- `src/store/` — menuStore, themeStore, demoStore (3개)
|
||||
- `src/stores/` — itemStore, masterDataStore, useItemMasterStore (3개)
|
||||
- **작업**:
|
||||
1. `src/store/` 내용을 `src/stores/`로 이동
|
||||
2. import 경로 일괄 수정
|
||||
3. `src/store/` 디렉토리 삭제
|
||||
- **추가 점검**: ThemeContext ↔ themeStore 중복 → 하나로 통합
|
||||
| # | 항목 | 상태 | 비고 |
|
||||
|---|------|------|------|
|
||||
| B-1 | `next/dynamic` 코드 스플리팅 | ✅ **완료** | 대시보드 4개 + xlsx 동적 로드, ~850KB 절감 (`_index.md` 참조) |
|
||||
| B-2 | API 병렬 호출 (`Promise.all`) | ✅ **완료** | |
|
||||
| B-3 | `store/` vs `stores/` 통합 | ✅ **완료** | |
|
||||
|
||||
---
|
||||
|
||||
## Phase C: 중기 개선 (2~3주)
|
||||
## Phase C: 중기 개선 — ✅ 완료
|
||||
|
||||
### C-1. 대형 테이블 가상화 (react-window)
|
||||
- **문제**: 100행 이상 테이블에서 전체 DOM 렌더 — 스크롤 성능 저하
|
||||
- **대상**:
|
||||
- `src/components/templates/IntegratedListTemplateV2.tsx` (1,086줄)
|
||||
- `src/components/templates/UniversalListPage/index.tsx` (1,006줄)
|
||||
- `src/components/organisms/DataTable.tsx`
|
||||
- **작업**:
|
||||
1. `react-window` 패키지 설치
|
||||
2. DataTable 내부에 `FixedSizeList` 또는 `VariableSizeList` 적용
|
||||
3. 기존 페이지네이션과 조합 (50건/페이지 + 가상화)
|
||||
- **주의**: 테이블 헤더 고정, 체크박스 선택, rowSpan 등 기존 기능 호환 필요
|
||||
- **효과**: 대용량 테이블 렌더 10배 이상 개선
|
||||
|
||||
### C-2. SWR 또는 React Query 도입
|
||||
- **문제**: API 캐싱 전략 없음 — 중복 요청, stale 데이터 가능
|
||||
- **권장**: SWR (가볍고 Next.js 팀 제작)
|
||||
- **적용 범위**:
|
||||
1. **1단계**: 공통코드/마스터데이터 (변경 빈도 낮음, 캐싱 효과 큼)
|
||||
2. **2단계**: 리스트 페이지 데이터
|
||||
3. **3단계**: 상세 페이지 데이터
|
||||
- **기대 효과**:
|
||||
- 자동 중복 요청 제거
|
||||
- stale-while-revalidate로 체감 속도 개선
|
||||
- 포커스 복귀 시 자동 재검증
|
||||
- **작업량**: 6~8시간 (기본 설정 + 공통코드 적용)
|
||||
|
||||
### C-3. Action 팩토리 패턴 확대
|
||||
- **문제**: 81개 `actions.ts` 파일에서 15~20% 코드 중복
|
||||
- **현황**: `src/lib/api/create-crud-service.ts` (177줄) 존재하지만 미활용
|
||||
- **작업**:
|
||||
1. 기존 팩토리 분석 및 확장
|
||||
2. 도메인별 actions를 팩토리 기반으로 전환
|
||||
3. 커스텀 로직만 오버라이드
|
||||
- **우선 적용**: 가장 단순한 CRUD 도메인부터 (clients, vendors 등)
|
||||
|
||||
### C-4. V1/V2 컴포넌트 정리
|
||||
- **문제**: 12+개 파일이 V1/V2 중복 존재
|
||||
- **대상 파일** (예시):
|
||||
- `ClientDetailClient.tsx` ↔ `ClientDetailClientV2.tsx`
|
||||
- `BadDebtDetail.tsx` ↔ `BadDebtDetailClientV2.tsx`
|
||||
- `QuoteRegistration.tsx` ↔ `QuoteRegistrationV2.tsx`
|
||||
- `InspectionModal.tsx` ↔ `InspectionModalV2.tsx`
|
||||
- **작업**:
|
||||
1. 각 V1/V2 쌍의 실제 사용처 확인 (import 추적)
|
||||
2. V2를 최종본으로 확정
|
||||
3. V1 참조를 V2로 전환
|
||||
4. V1 파일 삭제 + V2에서 "V2" 접미사 제거
|
||||
| # | 항목 | 상태 | 비고 |
|
||||
|---|------|------|------|
|
||||
| C-1 | 테이블 가상화 (react-window) | ✅ **보류 결정** | 페이지네이션 사용 중, YAGNI (`_index.md` 참조) |
|
||||
| C-2 | SWR / React Query | ✅ **보류 결정** | Zustand 캐싱 충족 (`_index.md` 참조) |
|
||||
| C-3 | Action 팩토리 패턴 확대 | ✅ **규칙 확정** | 신규 CRUD만 팩토리 사용 (`_index.md` 참조) |
|
||||
| C-4 | V1/V2 컴포넌트 정리 | ✅ **완료** | V2 최종본 확정, V1 삭제, 접미사 제거 |
|
||||
|
||||
---
|
||||
|
||||
## Phase D: 장기 개선 (필요 시)
|
||||
|
||||
### D-1. God 컴포넌트 분리
|
||||
| 파일 | 줄 수 | 분리 방안 |
|
||||
|------|-------|----------|
|
||||
| `MainDashboard.tsx` | 2,651 | DashboardShell + ChartSection + StatSection + FilterSection |
|
||||
| `ItemMasterContext.tsx` | 2,701 | PageContext + SectionContext + FieldContext + BOMContext |
|
||||
| `item-master.ts` (API) | 2,232 | pages.ts + sections.ts + fields.ts + bom.ts |
|
||||
| `QuoteRegistration.tsx` | 1,251 | LocationPanel + PricingPanel + LineItemsPanel |
|
||||
| `WorkerScreen/index.tsx` | 1,439 | ProcessSection + MaterialSection + IssueSection |
|
||||
|
||||
### D-2. `as` 타입 캐스트 점진적 제거 (926건)
|
||||
- 주요 집중 영역: API 트랜스포머, 컴포넌트 props
|
||||
- 제네릭 타입 활용으로 캐스트 대체
|
||||
- 도메인별 점진적 개선 (items → quotes → accounting 순)
|
||||
|
||||
### D-3. `@deprecated` 함수 정리 (13파일)
|
||||
- deprecated 선언했지만 아직 import되는 함수들 제거
|
||||
- ItemMasterContext 내 deprecated 메서드 마이그레이션
|
||||
|
||||
### D-4. Molecules 레이어 활성화
|
||||
- 현재 8개만 존재, 대부분 도메인 컴포넌트가 UI 직접 사용
|
||||
- 반복되는 UI 패턴을 Molecules로 추출
|
||||
- FormField, StatusBadge, DateRangeSelector 활용도 높이기
|
||||
|
||||
### D-5. 모달 컴포넌트 통합 (47+개)
|
||||
- SearchableSelectionModal 패턴으로 통합 가능한 모달 식별
|
||||
- 도메인별 재구현된 유사 모달을 공통 컴포넌트로 전환
|
||||
|
||||
### D-6. 기타
|
||||
- TODO/FIXME 102건 정리 (useItemMasterStore 15건, DraftBox 24건 집중)
|
||||
- `reactStrictMode: true`로 복원 (개발 환경)
|
||||
- puppeteer/chromium 패키지 활성 사용 여부 확인 → 미사용 시 제거
|
||||
- error-handler.ts의 `SHOW_ERROR_CODE` 환경변수로 전환
|
||||
| # | 항목 | 상태 |
|
||||
|---|------|------|
|
||||
| D-1 | God 컴포넌트 분리 (5개, 1200~2700줄) | ⏳ 대기 |
|
||||
| D-2 | `as` 타입 캐스트 점진적 제거 (926건) | ✅ **보류 결정** | 실제 ~200건만 actionable, 신규 코드에서 제네릭 활용 (2026-02-11) |
|
||||
| D-3 | `@deprecated` 함수 정리 (13파일) | ✅ **즉시 삭제분 완료** | uploadFile/deleteFile/getSiteNames/deprecated props 삭제 (2026-02-11) |
|
||||
| D-4 | Molecules 레이어 활성화 | ✅ **보류 결정** | 사용률 ~0%, organisms/templates로 충분 (2026-02-11) |
|
||||
| D-5 | 모달 컴포넌트 통합 | ✅ **완료** | InspectionPreviewModal → DocumentViewer 전환 (2026-02-11) |
|
||||
| D-6 | 기타 (TODO 102건, strictMode 등) | ⏳ 대기 |
|
||||
|
||||
---
|
||||
|
||||
@@ -200,8 +67,6 @@
|
||||
## 우선순위 요약
|
||||
|
||||
```
|
||||
즉시 (Phase A) → img 최적화, DataTable 최적화
|
||||
단기 (Phase B) → 코드 스플리팅, API 병렬화, 스토어 통합
|
||||
중기 (Phase C) → 가상화, 캐싱, Action 팩토리, V2 정리
|
||||
장기 (Phase D) → God 컴포넌트 분리, 타입 안전성, 모달 통합
|
||||
Phase A~C: ✅ 전체 완료/결정 완료 (A-2 DataTable 최적화만 대기)
|
||||
Phase D: 남은 작업 → A-2, D-1, D-6
|
||||
```
|
||||
|
||||
@@ -76,6 +76,62 @@ export async function downloadExcel(...) {
|
||||
|
||||
**총 절감**: 초기 번들에서 ~850KB 제외 (대시보드 미방문 + 엑셀 미사용 시)
|
||||
|
||||
### 테이블 가상화 (react-window) — 보류 (2026-02-10)
|
||||
|
||||
**결정**: 현시점 도입 불필요, 성능 이슈 발생 시 검토
|
||||
|
||||
**근거**:
|
||||
1. **페이지네이션 사용 중** — 리스트 페이지 대부분 서버 사이드 페이지네이션 (20~50건/페이지). 50개 `<tr>`은 브라우저가 문제없이 처리
|
||||
2. **적용 복잡도 높음** — 테이블 헤더 고정, 체크박스 선택, `rowSpan/colSpan` 병합 등 기존 기능과 충돌 가능. DataTable + IntegratedListTemplateV2 + UniversalListPage 전부 수정 필요
|
||||
3. **YAGNI** — 500건 이상 한 번에 렌더링하는 페이지가 현재 없음
|
||||
|
||||
**도입 시점**: 한 페이지에 200건+ 데이터를 페이지네이션 없이 표시해야 하는 요구가 생길 때
|
||||
|
||||
### SWR / React Query — 보류 (2026-02-10)
|
||||
|
||||
**결정**: 현시점 도입 불필요, 성능 이슈 발생 시 검토
|
||||
|
||||
**근거**:
|
||||
1. **기존 패턴 안정화 완료** — `useEffect` + Server Action 호출 패턴이 전 페이지에 일관 적용됨
|
||||
2. **캐싱 니즈 낮음** — 폐쇄형 ERP 특성상 항상 최신 데이터 필요. stale 데이터 표시는 오히려 위험
|
||||
3. **마스터데이터 캐싱 구현됨** — Zustand (`stores/masterDataStore`)로 변경 빈도 낮은 데이터는 이미 캐싱 중
|
||||
4. **도입 비용 과다** — 수십 개 페이지 `useState`+`useEffect` 패턴 전면 리팩토링 + 팀 학습 비용
|
||||
|
||||
**도입 시점**: 동일 데이터를 여러 컴포넌트에서 동시 요구하거나, 목록 ↔ 상세 이동 시 재로딩이 체감될 때
|
||||
|
||||
### Action 팩토리 패턴 — 신규 CRUD 적용 규칙 (2026-02-10)
|
||||
|
||||
**결정**: 기존 84개 actions.ts 전면 전환은 하지 않음. **신규 CRUD 도메인에만 팩토리 사용**
|
||||
|
||||
**현황**:
|
||||
- `src/lib/api/create-crud-service.ts` (177줄) — CRUD 보일러플레이트 자동 생성 팩토리
|
||||
- 현재 사용 중: TitleManagement, RankManagement (2개)
|
||||
- 전환 가능: 15~20개 / 전환 불가 (커스텀 로직): 50+개
|
||||
|
||||
**규칙**:
|
||||
- 신규 도메인 추가 시 단순 CRUD → `createCrudService` 사용 필수
|
||||
- 기존 actions.ts는 잘 동작하므로 무리하게 전환하지 않음
|
||||
- 커스텀 비즈니스 로직이 있는 도메인(견적, 수주, 생산 등)은 팩토리 비적합
|
||||
|
||||
**사용 예시**:
|
||||
```typescript
|
||||
import { createCrudService } from '@/lib/api/create-crud-service';
|
||||
|
||||
const service = createCrudService<ApiData, FrontendType>({
|
||||
basePath: '/api/v1/resources',
|
||||
transform: (api) => ({ id: api.id, name: api.name }),
|
||||
entityName: '리소스',
|
||||
});
|
||||
|
||||
export const getList = service.getList;
|
||||
export const getById = service.getById;
|
||||
export const create = service.create;
|
||||
export const update = service.update;
|
||||
export const remove = service.remove;
|
||||
```
|
||||
|
||||
**미전환 사유**: 84개 중 전환 가능 15~20개, 작업 2~4시간 대비 기능 변화 없음. 시간 대비 효율 낮음
|
||||
|
||||
---
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
Reference in New Issue
Block a user