197 lines
9.6 KiB
Markdown
197 lines
9.6 KiB
Markdown
# 세션: 2026-02-11
|
|
|
|
## 작업 요약
|
|
|
|
**대시보드 스타일 정리 + 페이지 미리보기 + 차트 디자인 개선 + .next 캐시 이슈 해결**
|
|
|
|
---
|
|
|
|
## 수행한 작업
|
|
|
|
### 1. 대시보드 스타일 정리 (글자 크기 제거 + 라벨 정렬만 유지)
|
|
|
|
이전 작업에서 구현했던 글자 크기 3그룹(라벨/메인값/보조) 커스텀 기능이 `@container` 반응형 자동 크기 조절과 충돌하여 정보가 잘리는 문제 발생. 분석 후 글자 크기 커스텀을 제거하고 라벨 정렬만 유지하기로 결정.
|
|
|
|
**변경 파일**:
|
|
- `types.ts`: `ItemStyleConfig` 단순화 (`labelAlign`만 유지)
|
|
- `KpiCard.tsx`, `StatCard.tsx`, `GaugeItem.tsx`, `ChartItem.tsx`: `FONT_SIZE_PX` 제거, 반응형 복원, `labelAlignClass` 적용
|
|
- `PopDashboardConfig.tsx`: `ItemStyleEditor` 단순화 (글자 크기 Select 3개 제거, 접기/펼치기 지원)
|
|
- `PopDashboardComponent.tsx`: `setRenderTick`/`itemStyleKey` 불필요 코드 제거
|
|
- `PopDesigner.tsx`: `handleUpdateComponent` stale closure 수정 (함수적 setState)
|
|
|
|
### 2. 페이지 미리보기 기능
|
|
|
|
디자이너 캔버스에서 특정 페이지의 실제 데이터를 렌더링하는 미리보기 기능 추가.
|
|
|
|
- `PopDashboardConfig.tsx`: 각 페이지 옆에 Eye(미리보기) 버튼 추가
|
|
- `PopDashboardComponent.tsx`: `previewPageIndex` prop으로 특정 페이지만 단독 렌더링
|
|
- `PopDesigner.tsx` -> `PopCanvas.tsx` -> `PopRenderer.tsx` -> `ComponentEditorPanel.tsx`: previewPageIndex 전달 체인
|
|
|
|
### 3. 차트 디자인 개선
|
|
|
|
- `CartesianGrid` 추가 (얇은 격자선으로 수치 읽기 개선)
|
|
- `abbreviateNumber` 적용 (큰 숫자 K/M 약어 표시)
|
|
- X축 라벨 7자 이상 시 대각선 표시
|
|
- Y축 숫자 자동 약어 처리
|
|
- 긴 라벨 시 하단 여백 자동 확보
|
|
|
|
### 4. .next 빌드 캐시 이슈 해결
|
|
|
|
라벨 정렬 코드가 브라우저에 반영되지 않는 문제 발생. 디버그 로그 추가로 분석한 결과, `ChartItem.tsx`의 디버그 로그가 콘솔에 전혀 나타나지 않아 **Next.js Turbopack 빌드 캐시** 문제로 확인.
|
|
|
|
Docker 설정에서 `.next`가 익명 볼륨(`/app/.next`)으로 분리되어 있어, 호스트의 `.next` 삭제만으로는 해결 불가. `docker-compose down -v`로 볼륨까지 제거 후 `--build`로 재시작하여 해결.
|
|
|
|
### 5. 디버그 로그 정리
|
|
|
|
문제 해결 과정에서 추가한 `console.log("[DEBUG]...")` 8개를 커밋 전 전부 제거.
|
|
|
|
---
|
|
|
|
## 커밋
|
|
|
|
| 해시 | 메시지 |
|
|
|------|--------|
|
|
| `960b1c99` | feat(pop-dashboard): 라벨 정렬 + 페이지 미리보기 + 차트 디자인 개선 |
|
|
|
|
---
|
|
|
|
## 발견된 문제 및 해결
|
|
|
|
| 문제 | 원인 | 해결 |
|
|
|------|------|------|
|
|
| 글자 크기 커스텀이 반응형과 충돌 | 절대 px(12~64px)가 유동적 그리드 셀 크기와 충돌 | 글자 크기 커스텀 제거, `@container` 반응형 자동 적용 유지 |
|
|
| Select 기본값 미발동 | Shadcn Select의 `onValueChange`가 같은 값 선택 시 미발동 | 글자 크기 제거로 해결 (근본 원인 소멸) |
|
|
| stale closure (handleUpdateComponent) | `useCallback`이 `layout` state를 직접 참조하여 빠른 연속 변경 시 이전 값 유실 | `setLayout(prev => ...)` 함수적 업데이트로 수정 |
|
|
| setRenderTick 불필요 이중 렌더링 | 글자 크기 강제 반영용이었으나 제거 후 불필요 | state + useEffect 삭제 |
|
|
| ChartItem.tsx 코드가 브라우저에 반영 안됨 | Docker 익명 볼륨에 캐시된 .next가 호스트 삭제와 독립적 | `docker-compose down -v`로 볼륨 포함 삭제 후 재빌드 |
|
|
|
|
---
|
|
|
|
## 이번 작업에서 배운 것
|
|
|
|
### 새로 알게 된 기술 개념
|
|
|
|
- **Docker 익명 볼륨과 호스트 파일시스템의 독립성**: `docker-compose.yml`에서 `/app/.next`처럼 익명 볼륨으로 지정된 경로는 호스트의 동일 경로와 완전히 독립적. 호스트에서 `rm -rf .next`를 해도 컨테이너 내부 캐시에는 영향 없음. `docker-compose down -v`로 볼륨까지 제거해야 함.
|
|
|
|
- **Shadcn/Radix Select 동작**: `onValueChange`는 현재 값과 동일한 값을 선택하면 발동하지 않음. 기본값이 있는 Select에서 이를 인지하지 못하면 "설정이 안 됨" 버그로 보임.
|
|
|
|
### 발생했던 에러와 원인 패턴
|
|
|
|
- **stale closure 패턴**: React의 `useCallback`에서 외부 state를 직접 참조하면 의존성 배열이 변경될 때까지 이전 값이 캡처됨. 빠른 연속 호출(정렬 버튼 클릭 등) 시 이전 state로 덮어씌워져 변경이 유실됨. **해결 패턴**: `setState(prev => ...)` 함수적 업데이트.
|
|
|
|
- **빌드 캐시 꼬임 패턴**: 파일을 여러 번 수정하고 구조 변경이 많으면 Turbopack/Webpack의 모듈 캐시가 특정 파일의 변경을 인식하지 못하는 경우 발생. 디버그 로그가 콘솔에 안 나오면 캐시 문제를 먼저 의심.
|
|
|
|
### 다음에 비슷한 작업할 때 주의할 점
|
|
|
|
1. **`useCallback` 안에서 state 직접 참조하지 않기** - 항상 `setState(prev => ...)`로 최신 state 접근
|
|
2. **Docker 익명 볼륨 인지하기** - `.next` 캐시 초기화가 필요하면 `docker-compose down -v` 사용
|
|
3. **대시보드 같은 고밀도 정보 컴포넌트에서 절대 크기 지양** - `@container` 반응형 자동 크기가 더 안정적
|
|
4. **디버그 console.log는 커밋 전 반드시 제거** - Grep으로 `[DEBUG]` 검색하여 확인
|
|
|
|
---
|
|
|
|
---
|
|
|
|
## 작업 요약 (2차 - Phase 0 공통 인프라 구현)
|
|
|
|
**usePopEvent + useDataSource 공통 훅 구현 + 검수 + 커밋/병합/푸시**
|
|
|
|
---
|
|
|
|
## 수행한 작업 (2차)
|
|
|
|
### 1. 프로젝트 현황 파악 + 기존 코드 분석
|
|
|
|
대시보드의 `dataFetcher.ts` 조회 로직과 `dataApi` CRUD를 분석하여 공통 훅 설계의 기반을 마련.
|
|
기존 백엔드 API(`getTableData`, `createRecord`, `updateRecord`, `deleteRecord`, `executeQuery`)가 모두 완성되어 있어 프론트 래핑만 필요함을 확인.
|
|
|
|
### 2. usePopEvent 훅 구현 (STEP 1)
|
|
|
|
**파일**: `frontend/hooks/pop/usePopEvent.ts` (신규)
|
|
|
|
화면(screenId) 단위로 격리된 이벤트 버스. 전역 Map 2개(`screenBuses`, `sharedDataStore`)를 모듈 스코프에 두고, SSR 환경 대응(`typeof window !== "undefined"` 가드).
|
|
|
|
- `publish(eventName, payload)`: 같은 화면의 구독자에게 이벤트 전파
|
|
- `subscribe(eventName, callback)`: 이벤트 구독, unsubscribe 함수 반환
|
|
- `getSharedData(key)` / `setSharedData(key, value)`: screenId별 격리된 key-value 저장소
|
|
- `cleanupScreen(screenId)`: 화면 언마운트 시 전체 정리 (메모리 누수 방지)
|
|
|
|
### 3. popSqlBuilder 유틸 구현 (STEP 2)
|
|
|
|
**파일**: `frontend/hooks/pop/popSqlBuilder.ts` (신규)
|
|
|
|
`dataFetcher.ts`에서 SQL 빌더 로직 5개 함수를 추출 (로직 변경 없이 복사):
|
|
- `escapeSQL`, `sanitizeIdentifier`, `validateDataSourceConfig`, `buildWhereClause`, `buildAggregationSQL`
|
|
|
|
대시보드 `dataFetcher.ts`는 미수정 (안정성 우선, 향후 교체 예정).
|
|
|
|
### 4. useDataSource 훅 구현 (STEP 3)
|
|
|
|
**파일**: `frontend/hooks/pop/useDataSource.ts` (신규)
|
|
|
|
`DataSourceConfig` 기반 DB 테이블 CRUD 통합 훅:
|
|
- **조회 분기**: 집계/조인이면 SQL 빌더 + executeQuery, 단순이면 dataApi.getTableData
|
|
- **CRUD**: `save` -> dataApi.createRecord, `update` -> dataApi.updateRecord, `remove` -> dataApi.deleteRecord
|
|
- **자동 새로고침**: `refreshInterval` 기반 (최소 5초)
|
|
- **refetch 필터 병합**: overrideFilters가 config.filters에 추가/덮어쓰기
|
|
|
|
### 5. 배럴 파일 (STEP 4)
|
|
|
|
**파일**: `frontend/hooks/pop/index.ts` (신규)
|
|
|
|
public API re-export: `usePopEvent`, `cleanupScreen`, `useDataSource`, `MutationResult`, `DataSourceResult`, `buildAggregationSQL`, `validateDataSourceConfig`
|
|
|
|
### 6. 종합 검수
|
|
|
|
- 린트 에러: 0건
|
|
- 중복 정의: 0건 (20개 함수/타입 전수 Grep)
|
|
- 미사용 import: 0건
|
|
- 누락 import: 0건
|
|
- interface props 불일치: 0건
|
|
- 가상 시뮬레이션 8가지 시나리오: 전부 정상
|
|
|
|
### 7. Git 작업
|
|
|
|
- `ksh-button` 브랜치에서 커밋
|
|
- `ksh-v2-work`로 fast-forward merge
|
|
- `origin/ksh-v2-work`로 push 완료
|
|
|
|
---
|
|
|
|
## 커밋 (2차)
|
|
|
|
| 해시 | 메시지 |
|
|
|------|--------|
|
|
| `300542d9` | feat(pop): usePopEvent, useDataSource 공통 훅 구현 |
|
|
|
|
---
|
|
|
|
## 발견된 문제 및 해결 (2차)
|
|
|
|
이번 작업에서는 코드 품질 문제가 발견되지 않았습니다.
|
|
|
|
사전 충돌 검사에서 `buildAggregationSQL`과 `validateDataSourceConfig`이 `dataFetcher.ts`에도 존재하지만, 이는 의도적 복사이며 런타임 충돌은 없음을 확인. 향후 대시보드 교체 시 import 경로만 변경 예정.
|
|
|
|
---
|
|
|
|
## 이번 작업에서 배운 것 (2차)
|
|
|
|
### 새로 알게 된 기술 개념
|
|
|
|
- **전역 Map 기반 이벤트 버스**: React 외부(모듈 스코프)에 전역 Map을 두면 컴포넌트 마운트/언마운트와 무관하게 이벤트 리스너가 유지됨. 단, `cleanupScreen`으로 명시적 정리 필요.
|
|
- **SSR 가드 패턴**: Next.js에서 전역 변수 초기화 시 `typeof window !== "undefined"` 조건이 필수. 서버 렌더링 시 window 참조 에러 방지.
|
|
|
|
### 다음에 비슷한 작업할 때 주의할 점
|
|
|
|
1. **subscribe는 반드시 useEffect 안에서 호출** - cleanup에서 unsubscribe 반환값을 호출해야 메모리 누수 방지
|
|
2. **dataFetcher.ts를 복사할 때 import 경로 주의** - types.ts 경로가 상대적으로 달라짐
|
|
3. **useRef로 config 최신값 유지** - useCallback 안에서 config를 직접 참조하면 stale closure 발생, configRef.current 사용
|
|
|
|
---
|
|
|
|
## 다음 작업
|
|
|
|
1. Phase 2: pop-button 컴포넌트 구현 계획 수립
|
|
2. Phase 2: pop-icon 컴포넌트 검토/개선
|
|
3. 브라우저 확인: 대시보드 라벨 정렬, 페이지 미리보기, 차트 디자인
|