refactor(WEB): DataTable 개선 및 회계 상세 컴포넌트 리팩토링
- DataTable 컴포넌트 기능 확장 및 코드 개선 - 회계 상세 컴포넌트(Bill/Deposit/Purchase/Sales/Withdrawal) 리팩토링 - 엑셀 다운로드 유틸리티 개선 - 대시보드 및 각종 리스트 페이지 업데이트 - dashboard_type2 페이지 추가 - 프론트엔드 개선 로드맵 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
207
claudedocs/[PLAN-2025-02-10] frontend-improvement-roadmap.md
Normal file
207
claudedocs/[PLAN-2025-02-10] frontend-improvement-roadmap.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# SAM ERP 프론트엔드 개선 로드맵
|
||||
|
||||
> 작성일: 2025-02-10
|
||||
> 분석 기준: src/ 전체 (500+ 파일, ~163K줄)
|
||||
|
||||
---
|
||||
|
||||
## Phase A: 즉시 개선 (1~2일)
|
||||
|
||||
### 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% 재렌더 감소
|
||||
|
||||
---
|
||||
|
||||
## Phase B: 단기 개선 (1주)
|
||||
|
||||
### 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 중복 → 하나로 통합
|
||||
|
||||
---
|
||||
|
||||
## Phase C: 중기 개선 (2~3주)
|
||||
|
||||
### 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" 접미사 제거
|
||||
|
||||
---
|
||||
|
||||
## 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` 환경변수로 전환
|
||||
|
||||
---
|
||||
|
||||
## 이전 리팩토링 완료 항목 (참고)
|
||||
|
||||
| 항목 | 상태 | 날짜 |
|
||||
|------|------|------|
|
||||
| Phase 1: 공통 훅 추출 (executeServerAction 등) | ✅ 완료 | 이전 세션 |
|
||||
| Phase 3: 공용 유틸 추출 (PaginatedApiResponse 등) | ✅ 완료 | 이전 세션 |
|
||||
| Phase 4: SearchableSelectionModal 공통화 | ✅ 완료 | 이전 세션 |
|
||||
| Phase 5: any 21건 + memo 3개 정리 | ✅ 완료 | 이전 세션 |
|
||||
| console.log 524건 → 22건 정리 | ✅ 완료 | 2025-02-10 |
|
||||
| TODO 주석 정리 (login route) | ✅ 완료 | 2025-02-10 |
|
||||
| SSR 가드 추가 (ThemeContext, ApiErrorContext, useDetailPageState) | ✅ 완료 | 2025-02-10 |
|
||||
| 커스텀 훅 불필요 'use client' 15개 제거 | ✅ 완료 | 2025-02-10 |
|
||||
| formatDate 이름 충돌 해소 → formatCalendarDate | ✅ 완료 | 2025-02-10 |
|
||||
|
||||
---
|
||||
|
||||
## 우선순위 요약
|
||||
|
||||
```
|
||||
즉시 (Phase A) → img 최적화, DataTable 최적화
|
||||
단기 (Phase B) → 코드 스플리팅, API 병렬화, 스토어 통합
|
||||
중기 (Phase C) → 가상화, 캐싱, Action 팩토리, V2 정리
|
||||
장기 (Phase D) → God 컴포넌트 분리, 타입 안전성, 모달 통합
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# claudedocs 문서 맵
|
||||
|
||||
> 프로젝트 기술 문서 인덱스 (Last Updated: 2026-02-09)
|
||||
> 프로젝트 기술 문서 인덱스 (Last Updated: 2026-02-10)
|
||||
|
||||
## 빠른 참조
|
||||
|
||||
@@ -10,6 +10,74 @@
|
||||
|
||||
---
|
||||
|
||||
## 프로젝트 기술 결정 사항
|
||||
|
||||
### `<img>` 태그 사용 — `next/image` 미사용 이유 (2026-02-10)
|
||||
|
||||
**현황**: 프로젝트 전체 `<img>` 태그 10건, `next/image` 0건
|
||||
|
||||
**결정**: `<img>` 유지, `next/image` 전환 불필요
|
||||
|
||||
**근거**:
|
||||
1. **폐쇄형 ERP 시스템** — SEO 불필요, LCP 점수 무의미
|
||||
2. **전량 외부 동적 이미지** — 백엔드 API에서 받아오는 URL (정적 내부 이미지 0건)
|
||||
3. **프린트/문서 레이아웃** — 10건 중 8건이 검사 기준서·도해 등 인쇄용. `next/image`의 `width`/`height` 강제 지정이 프린트 레이아웃을 깰 위험
|
||||
4. **blob URL 비호환** — 업로드 미리보기(blob:)는 `next/image`가 지원 안 함
|
||||
5. **설정 부담 > 이점** — `remotePatterns` 설정 + 백엔드 도메인 관리 비용이 실질 이점보다 큼
|
||||
|
||||
**사용처 (9개 파일)**:
|
||||
| 파일 | 용도 | 이미지 소스 |
|
||||
|------|------|-------------|
|
||||
| `DocumentHeader.tsx` (2건) | 문서 헤더 로고 | `logo.imageUrl` (API) |
|
||||
| `ProductInspectionInputModal.tsx` | 제품검사 사진 미리보기 | blob URL |
|
||||
| `ProductInspectionDocument.tsx` | 제품검사 문서 | `data.productImage` (API) |
|
||||
| `inspection-shared.tsx` | 검사 기준서 이미지 | `standardImage` (API) |
|
||||
| `SlatInspectionContent.tsx` | 도해 이미지 | `schematicImage` (API) |
|
||||
| `ScreenInspectionContent.tsx` | 도해 이미지 | `schematicImage` (API) |
|
||||
| `BendingInspectionContent.tsx` | 도해 이미지 | `schematicImage` (API) |
|
||||
| `SlatJointBarInspectionContent.tsx` | 도해 이미지 | `schematicImage` (API) |
|
||||
| `BendingWipInspectionContent.tsx` | 도해 이미지 | `schematicImage` (API) |
|
||||
|
||||
**참고**: `next/image`가 유효한 케이스는 공개 사이트 + 정적/내부 이미지 + SEO 중요한 상황
|
||||
|
||||
### `next/dynamic` 코드 스플리팅 적용 (2026-02-10)
|
||||
|
||||
**결정**: 대형 컴포넌트 + 무거운 라이브러리에 `next/dynamic` / 동적 `import()` 적용
|
||||
|
||||
**핵심 개념 — Suspense vs dynamic()**:
|
||||
- **`Suspense` + 정적 import** → 코드가 부모와 같은 번들 청크에 포함. 유저가 안 봐도 이미 다운로드됨. UI fallback만 제공하고 **코드 분할은 안 일어남**
|
||||
- **`dynamic()`** → webpack이 별도 `.js` 청크로 분리. 컴포넌트가 실제 렌더될 때만 네트워크 요청으로 해당 청크 다운로드. **진짜 코드 분할**
|
||||
|
||||
**적용 내역**:
|
||||
|
||||
| 파일 | 대상 | 절감 |
|
||||
|------|------|------|
|
||||
| `reports/comprehensive-analysis/page.tsx` | MainDashboard (2,651줄 + recharts) | ~350KB |
|
||||
| `components/business/Dashboard.tsx` | CEODashboard | ~200KB |
|
||||
| `construction/ConstructionDashboard.tsx` | ConstructionMainDashboard | ~100KB |
|
||||
| `production/dashboard/page.tsx` | ProductionDashboard | ~100KB |
|
||||
| `lib/utils/excel-download.ts` | xlsx 라이브러리 (~400KB) | ~400KB |
|
||||
| `quotes/LocationListPanel.tsx` | xlsx 직접 import 제거 | (위와 중복) |
|
||||
|
||||
**xlsx 동적 로드 패턴**:
|
||||
```typescript
|
||||
// Before: 모든 페이지에 xlsx ~400KB 포함
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
// After: 엑셀 버튼 클릭 시에만 로드
|
||||
async function loadXLSX() {
|
||||
return await import('xlsx');
|
||||
}
|
||||
export async function downloadExcel(...) {
|
||||
const XLSX = await loadXLSX();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**총 절감**: 초기 번들에서 ~850KB 제외 (대시보드 미방문 + 엑셀 미사용 시)
|
||||
|
||||
---
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user