ERP-node/popdocs/PROBLEMS.md

30 KiB

문제-해결 색인

용도: "이전에 비슷한 문제 어떻게 해결했어?" 검색 팁: Ctrl+F로 키워드 검색 (에러 메시지, 컴포넌트명 등)


렌더링 관련

문제 해결 날짜 키워드
rowSpan이 적용 안됨 gridTemplateRows를 1fr로 변경 2026-02-02 grid, rowSpan, CSS
컴포넌트 크기 스케일 안됨 viewportWidth 기반 scale 계산 추가 2026-02-04 scale, viewport, 반응형
그리드 가이드 셀 크기 불균일 gridAutoRows → gridTemplateRows로 행 높이 강제 고정 2026-02-06 gridAutoRows, gridTemplateRows, 셀 크기, CSS Grid
컴포넌트 콘텐츠가 셀 경계 벗어남 overflow-visible → overflow-hidden 변경 2026-02-06 overflow, 셀 크기, 콘텐츠
뷰어에서 플레이스홀더만 표시 page.tsx에 레지스트리 초기화 import 추가 + renderActualComponent() 실제 컴포넌트 렌더링으로 교체 2026-02-09 뷰어, 플레이스홀더, 레지스트리, side-effect import
뷰어에서 스크롤 불가 (콘텐츠 잘림) 최외곽 overflow-hidden 제거, overflow-auto 공통 적용, min-h-full 추가 2026-02-09 스크롤, overflow, h-screen, 뷰어
글자 크기 커스텀 vs @container 충돌 절대 px 글자 크기 전체 제거, @container 반응형 자동 유지 2026-02-11 글자 크기, @container, 반응형, overflow
.next 빌드 캐시 꼬임 (Docker) docker-compose down -v + --build 재시작 2026-02-11 .next, 캐시, Docker, 익명 볼륨, Turbopack

상태 관리 관련

문제 해결 날짜 키워드
stale closure (handleUpdateComponent) setLayout(prev => ...) 함수적 업데이트, layout 의존성 제거 2026-02-11 stale closure, useCallback, setState

DnD (드래그앤드롭) 관련

문제 해결 날짜 키워드
useDrag 에러 (뷰어에서) isDesignMode 체크 후 early return 2026-02-04 DnD, useDrag, 뷰어
DndProvider 중복 에러 최상위에서만 Provider 사용 2026-02-04 DndProvider, react-dnd
Expected drag drop context (뷰어) isDesignMode=false일 때 DraggableComponent 대신 일반 div 렌더링 2026-02-05 DndProvider, useDrag, 뷰어, context
컴포넌트 중첩(겹침) toast import 누락 → sonner에서 import 2026-02-05 겹침, overlap, toast
리사이즈 핸들 작동 안됨 useDrop 2개 중복 → 단일 useDrop으로 통합 2026-02-05 resize, 핸들, useDrop
드래그 좌표 완전 틀림 (Row 92) 캔버스 scale 보정 누락 → (offset - rect.left) / scale 2026-02-05 scale, 좌표, transform
DND 타입 상수 불일치 3개 파일에 중복 정의 → constants/dnd.ts로 통합 2026-02-05 상수, DND, 타입
컴포넌트 이동 안됨 useDrop accept 타입 불일치 → 공통 상수 사용 2026-02-05 이동, useDrop, accept

타입 관련

문제 해결 날짜 키워드
인터페이스 이름 불일치 V5 접미사 제거, 통일 2026-02-05 타입, interface, Props
v3/v4 타입 혼재 v5 전용으로 통합, 레거시 삭제 2026-02-05 버전, 타입, 마이그레이션

레이아웃 관련

문제 해결 날짜 키워드
화면 밖 컴포넌트 정보 손실 자동 줄바꿈 로직 추가 (col > maxCol → col=1, row=맨아래+1) 2026-02-06 자동배치, 줄바꿈, 정보손실
Flexbox 배치 예측 불가 CSS Grid로 전환 (v5) 2026-02-05 Flexbox, Grid, 반응형
4모드 각각 배치 힘듦 제약조건 기반 시스템 (v4) 2026-02-03 모드, 반응형, 제약조건
4모드 자동 전환 안됨 useResponsiveMode 훅 추가 2026-02-01 모드, 훅, 반응형

브레이크포인트/반응형 관련

문제 해결 날짜 키워드
뷰어 반응형 모드 불일치 detectGridMode() 사용으로 일관성 확보 2026-02-06 반응형, 뷰어, 모드
768~839px 모드 불일치 TABLET_MIN 768로 변경, 브레이크포인트 재설계 2026-02-06 브레이크포인트, 768px
useResponsiveMode vs GRID_BREAKPOINTS 불일치 뷰어에서 detectGridMode(viewportWidth) 사용 2026-02-06 훅, 상수, 일관성

저장/로드 관련

문제 해결 날짜 키워드
레이아웃 버전 충돌 isV5Layout 타입 가드로 분기 2026-02-05 버전, 로드, 타입가드
빈 레이아웃 판별 실패 components 존재 여부로 판별 2026-02-04 빈 레이아웃, 로드

UI/UX 관련

문제 해결 날짜 키워드
root 레이아웃 오염 tempLayout 도입 (임시 상태 분리) 2026-02-04 tempLayout, 상태, 오염
속성 패널 다른 모드 수정 isDefaultMode 체크로 비활성화 2026-02-04 속성패널, 모드, 비활성화

브랜치 병합 관련

문제 해결 날짜 키워드
ksh-dashboard + ksh-v2-work 병합 시 7파일 17지점 충돌 의존성 순서로 해결: 타입 - 등록 - 컴포넌트 - 렌더러 - 패널. 양쪽 기능 통합(union) 전략 2026-02-11 병합, merge, 충돌, pop-icon, pop-dashboard
PopComponentType 확장 충돌 양쪽 타입 union (pop-icon + pop-dashboard) 결합, DEFAULT_COMPONENT_GRID_SIZE에 양쪽 항목 추가 2026-02-11 PopComponentType, union type, Record
COMPONENT_TYPE_LABELS 불완전 PopRenderer(Record<PopComponentType, string>)에 4개 항목 모두 추가하여 타입 안전성 확보 2026-02-11 COMPONENT_TYPE_LABELS, Record, 타입 안전
isRealtime useEffect 충돌 (pop-text) ksh-dashboard 로직 채택 - isRealtime 조건부 interval로 불필요한 timer 방지 2026-02-11 isRealtime, useEffect, setInterval, pop-text
CSS 클래스 충돌 (ComponentEditorPanel Tabs) ksh-v2-work의 방어적 CSS 채택 (overflow-hidden, min-h-0, m-0) - 스크롤 동작 안정성 우선 2026-02-11 CSS, overflow, Tabs, TabsContent, 스크롤
레지스트리 패턴에서 불필요 props 전달 버그 아님 - React.ComponentType<any> 타입이므로 extra props 자동 무시. 레지스트리 아키텍처 의도적 패턴 2026-02-11 레지스트리, props, ComponentType, any

그리드 가이드 관련

문제 해결 날짜 키워드
SVG 격자와 CSS Grid 좌표 불일치 GridGuide.tsx 삭제, PopRenderer에서 CSS Grid 셀로 격자 렌더링 2026-02-05 격자, SVG, CSS Grid, 좌표
행/열 라벨 위치 오류 PopCanvas에 absolute positioning 라벨 추가 2026-02-05 라벨, 행, 열, 정렬
격자선과 컴포넌트 불일치 동일한 CSS Grid 좌표계 사용 2026-02-05 통합, 정렬, 일체감

뷰어 플레이스홀더 버그 상세 (2026-02-09)

증상

설계 화면(디자이너)에서는 이미지/텍스트/타이틀 정상 표시. 뷰어(/pop/screens/4114) 접속 시 "pop-text 1", "pop-text 2" 같은 라벨만 보임.

원인 (2가지)

원인 A: renderActualComponent() 함수가 하드코딩된 플레이스홀더만 반환

// 변경 전 (PopRenderer.tsx 555-564)
function renderActualComponent(component) {
  const typeLabel = COMPONENT_TYPE_LABELS[component.type];
  return (
    <div>
      <span>{component.label || typeLabel}</span>
    </div>
  );
}

원인 B: 뷰어 page.tsx에서 PopComponentRegistry 초기화 import 누락

  • 디자이너에서는 PopDesigner.tsx가 컴포넌트를 import하여 레지스트리 초기화됨
  • 뷰어에서는 아무도 레지스트리를 초기화하지 않아 빈 상태

해결

// page.tsx - 레지스트리 초기화 (PopRenderer보다 먼저!)
import "@/lib/registry/pop-components";
import PopRenderer from "@/components/pop/designer/renderers/PopRenderer";

// PopRenderer.tsx - 실제 컴포넌트 렌더링
function renderActualComponent(component) {
  const registeredComp = PopComponentRegistry.getComponent(component.type);
  const ActualComp = registeredComp?.component;
  if (ActualComp) {
    return <ActualComp config={component.config} label={component.label} />;
  }
  // fallback: 플레이스홀더
}

교훈

side-effect import (import "...")는 해당 레지스트리를 사용하는 컴포넌트 import보다 반드시 앞에 위치해야 한다. JavaScript 모듈은 import 선언 순서대로 실행되므로, 순서가 뒤바뀌면 빈 레지스트리를 참조할 수 있다. 새 POP 컴포넌트를 등록할 때, 뷰어 페이지에도 초기화 import가 있는지 반드시 확인.


해결 완료 (이번 세션)

문제 상태 해결 방법
PopCanvas 타입 오류 해결 임시 타입 가드 추가
팔레트 UI 없음 해결 ComponentPalette.tsx 신규 추가
SVG 격자 좌표 불일치 해결 CSS Grid 기반 통합
드래그 좌표 완전 틀림 해결 scale 보정 + calcGridPosition 함수
DND 타입 상수 불일치 해결 constants/dnd.ts 통합
컴포넌트 이동 안됨 해결 useDrop/useDrag 타입 통일
컴포넌트 중첩(겹침) 해결 toast import 추가 → 겹침 감지 로직 정상 작동
리사이즈 핸들 작동 안됨 해결 useDrop 통합 (2개 → 1개)
숨김 컴포넌트 드래그 안됨 해결 handleMoveComponent에서 숨김 해제 + 위치 저장 단일 상태 업데이트
그리드 범위 초과 에러 해결 adjustedCol 계산으로 드롭 위치 자동 조정
Expected drag drop context (뷰어) 해결 isDesignMode=false일 때 일반 div 렌더링
hiddenComponentIds 중복 정의 해결 중복 useMemo 제거 (라인 410-412)
뷰어 반응형 모드 불일치 해결 detectGridMode() 사용
그리드 가이드 셀 크기 불균일 해결 gridTemplateRows로 행 높이 강제 고정
Canvas vs Renderer 행 수 불일치 해결 숨김 필터 통일, 여유행 +3으로 통일
디버깅 console.log 잔존 해결 reviewComponents 내 console.log 삭제
뷰어 실제 컴포넌트 렌더링 안 됨 해결 레지스트리 초기화 import + renderActualComponent 수정

드래그 좌표 버그 상세 (2026-02-05)

증상

  • 컴포넌트를 아래로 드래그 → 위로 올라감
  • Row 92 같은 비정상 좌표
  • 드래그 이동/리사이즈 전혀 작동 안됨

원인

캔버스: transform: scale(0.8)

getBoundingClientRect() → 스케일 적용된 크기 (1024px → 819px)
getClientOffset()       → 뷰포트 기준 실제 마우스 좌표

이 둘을 그대로 계산하면 좌표 완전 틀림

해결

// 스케일 보정된 상대 좌표 계산
const relX = (offset.x - canvasRect.left) / canvasScale;
const relY = (offset.y - canvasRect.top) / canvasScale;

// 실제 캔버스 크기로 그리드 계산
calcGridPosition(relX, relY, customWidth, ...);

교훈

CSS transform: scale() 적용된 요소에서 좌표 계산 시, getBoundingClientRect()는 스케일 적용된 값을 반환하지만 마우스 좌표는 뷰포트 기준이므로 반드시 스케일 보정 필요


Expected drag drop context 에러 상세 (2026-02-05 심야)

증상

Invariant Violation: Expected drag drop context
at useDrag (...)
at DraggableComponent (...)

뷰어 페이지(/pop/viewer/[screenId])에서 POP 화면 조회 시 에러 발생

원인

PopRenderer의 DraggableComponent에서 useDrag 훅을 무조건 호출
→ 뷰어 페이지에는 DndProvider가 없음
→ React 훅은 조건부 호출 불가 (Rules of Hooks)
→ DndProvider 없이 useDrag 호출 시 context 에러

해결

// PopRenderer.tsx - 컴포넌트 렌더링 부분
if (isDesignMode) {
  return (
    <DraggableComponent ... />  // useDrag 사용
  );
}

// 뷰어 모드: 드래그 없는 일반 렌더링
return (
  <div className="..." style={positionStyle}>
    <ComponentContent ... />
  </div>
);

교훈

React DnD의 useDrag/useDrop 훅은 반드시 DndProvider 내부에서만 호출해야 함. 디자인 모드와 뷰어 모드를 분기할 때, 훅이 포함된 컴포넌트 자체를 조건부 렌더링해야 함. 훅 내부에서 canDrag: false로 설정해도 훅 자체는 호출되므로 context 에러 발생.

관련 파일

  • gridUtils.ts: convertAndResolvePositions(), needsReview()
  • PopCanvas.tsx: ReviewPanel, ReviewItem
  • PopRenderer.tsx: 자동 배치 위치 렌더링

뷰어 반응형 모드 불일치 상세 (2026-02-06)

증상

- 아이폰 SE, iPad Pro 프리셋은 정상 작동
- 브라우저 수동 리사이즈 시 6칸 모드(mobile_landscape)가 적용 안 됨
- 768~839px 구간에서 8칸으로 표시됨 (예상: 6칸)

원인

useResponsiveMode 훅:
- deviceType: width/height 비율로 "mobile"/"tablet" 판정
- isLandscape: width > height로 판정
- BREAKPOINTS.TABLET_MIN = 840 (당시)

GRID_BREAKPOINTS:
- mobile_landscape: 600~839px (6칸)
- tablet_portrait: 840~1023px (8칸)

결과:
- 768px 화면 → useResponsiveMode: "tablet" (768 < 840이지만 비율 판정)
- 768px 화면 → GRID_BREAKPOINTS: "mobile_landscape" (6칸)
- → 모드 불일치!

해결

1단계: 브레이크포인트 재설계

// 기존
mobile_landscape: { minWidth: 600, maxWidth: 839 }
tablet_portrait: { minWidth: 840, maxWidth: 1023 }

// 변경 후
mobile_landscape: { minWidth: 480, maxWidth: 767 }
tablet_portrait: { minWidth: 768, maxWidth: 1023 }

2단계: 훅 연동

// useDeviceOrientation.ts
BREAKPOINTS.TABLET_MIN: 768  // was 840

3단계: 뷰어 모드 감지 방식 변경

// page.tsx (뷰어)
const currentModeKey = isPreviewMode
  ? getModeKey(deviceType, isLandscape)  // 프리뷰: 수동 선택
  : detectGridMode(viewportWidth);        // 일반: 너비 기반 (일관성 확보)

교훈

반응형 모드 판정은 **단일 소스(GRID_BREAKPOINTS)**를 기준으로 해야 함. 훅과 상수가 각각 다른 기준을 사용하면 구간별 불일치 발생. 뷰어에서는 detectGridMode(viewportWidth) 직접 사용으로 일관성 확보.


뷰어 렌더링 관련

문제 해결 날짜 키워드
뷰어에서 실제 컴포넌트 대신 플레이스홀더 표시 (1) page.tsx에 레지스트리 초기화 import 추가 (2) renderActualComponent()에서 PopComponentRegistry.getComponent() 조회 후 실제 렌더링 2026-02-09 뷰어, 플레이스홀더, 레지스트리, import, renderActualComponent
datetime 실시간 업데이트 기본값 불일치 미해결 - DateTimeDisplayif (!config?.isRealtime) return에서 undefined가 false로 평가되어 타이머 미작동. 설정 패널은 ?? true로 기본 켜짐 표시. 권장: const isRealtime = config?.isRealtime ?? true 2026-02-09 datetime, isRealtime, undefined, 기본값, 타이머

병합 관련

문제 해결 날짜 키워드
ScreenDesigner.tsx 3건 충돌 (origin/main 병합) 함수 시그니처: ksh-v2-work 유지(isPop/defaultDevicePreview), 저장 로직: 3단계 분기 유지+console.log 제거, 툴바 props: origin/main 채택 2026-02-09 병합, merge, ScreenDesigner, 충돌
usePanelState 중복 선언 (병합 시 발견) 충돌 1 해결 과정에서 L175의 중복 usePanelState 제거, L215의 완전한 버전만 유지 2026-02-09 usePanelState, 중복, 병합
툴바 JSX 들여쓰기 불일치 (병합 후 린트) origin/main 코드가 ksh-v2-work와 2칸 들여쓰기 차이. 기능 영향 없음. 추후 포매팅 정리 권장 2026-02-09 들여쓰기, 포매팅, 린트, prettier

컴포넌트 등록 관련

문제 해결 날짜 키워드
pop-dashboard 팔레트 미노출 PopComponentType/PALETTE_ITEMS/DEFAULT_COMPONENT_GRID_SIZE/COMPONENT_TYPE_LABELS 4곳에 수동 등록 누락. 4곳 모두 추가 2026-02-10 팔레트, 등록, PopComponentType, 하드코딩, 레지스트리
미사용 import (AggregatedResult) PopDashboardComponent.tsx에서 type AggregatedResult import 제거 2026-02-10 import, 미사용, 코드 품질

pop-dashboard 데이터/SQL 관련

문제 해결 날짜 키워드
대상 컬럼 조회 안 됨 fetchTableColumns에서 tableManagementApi(axios) 우선 사용, dashboardApi(fetch) 폴백 2026-02-10 컬럼, schema, API, 인증, fetch, axios
SUM()/COUNT() 빈 괄호 SQL validateDataSourceConfig으로 중간 상태 검증, 미완료 시 SQL 생성 차단 2026-02-10 SQL, 집계, validate, 중간상태, 백엔드
API 30초 타임아웃 (auth/me 등) 잘못된 SQL이 백엔드 과부하 유발 -> SQL 차단 + 브라우저 새로고침 2026-02-10 timeout, auth, 과부하, 브라우저 멈춤
Docker unhealthy (curl 미설치) healthcheck에 curl이 없어 false 양성. 서비스 자체는 정상. 확인만 필요 2026-02-10 docker, healthcheck, curl, unhealthy

pop-dashboard 렌더링 관련

문제 해결 날짜 키워드
2열 설정이 1열로 렌더링 GridMode.tsx MIN_CELL_WIDTH 160->80. 초기 containerWidth=300에서 (300-8)/2=146 < 160이라 축소됨 2026-02-10 그리드, 열, 레이아웃, MIN_CELL_WIDTH, containerWidth
라벨/단위/증감율 잘림 4개 아이템에서 truncate/hidden 제거, text-[10px]->text-xs, p-2->p-3 2026-02-10 truncate, hidden, 라벨, 잘림, @container
useEffect 불필요 데이터 재호출 visibleItems 배열 참조 -> visibleItemIds(JSON 문자열) 의존성 안정화 2026-02-10 useEffect, 의존성, 배열참조, JSON.stringify
파이 차트 미표시 (1) fetch 기반 API가 iframe에서 간헐 실패 -> apiClient(axios) 우선 (2) PostgreSQL bigint 문자열 -> Number() 변환 2026-02-10 PieChart, fetch, axios, bigint, 문자열, Number
파이 차트 라벨/레전드 없음 ChartItem.tsx에 Legend 컴포넌트 + custom label 포맷(name value (percent%)) 추가 2026-02-10 PieChart, Legend, label, Recharts
게이지 가로 레이아웃 비율 깨짐 max-w-[200px] 고정 -> h-full w-auto max-w-full 높이 기반 스케일링. SVG 래퍼를 flex-1 min-h-0으로 변경 2026-02-10 게이지, SVG, 비율, max-width, 가로 레이아웃
KPI/통계 카드 왼쪽 정렬 KpiCard에 items-center, StatCard에 items-center justify-center 추가. 4개 아이템 정렬 통일 2026-02-10 정렬, items-center, KPI, StatCard

pop-dashboard 설정 패널 관련

문제 해결 날짜 키워드
설정 패널 표시 모드/아이템 탭 클릭 시 에러 ConfigPanelProps에서 onChangeonUpdate로 변경. ComponentEditorPanel이 onUpdate로 전달하는데 대시보드만 onChange로 수신하여 TypeError 발생. onUpdate: onChange 구조분해로 해결 2026-02-10 onChange, onUpdate, props, TypeError, 설정패널, 컨벤션
gaugeConfig min/max/target 변경 안 됨 스프레드 연산자 순서 버그. { max: newValue, ...item.gaugeConfig } -> { ...item.gaugeConfig, max: newValue }. 나중에 오는 속성이 우선 2026-02-10 스프레드, 순서, gaugeConfig, 덮어쓰기, onChange
차트 X축/Y축 입력 혼동 "X축 컬럼"과 "그룹핑(X축)" 중복으로 사용자 혼동. 수동 입력 제거, 자동 설정 안내 텍스트로 교체 2026-02-10 xAxisColumn, groupBy, 중복, UX, 설정패널
React hooks 규칙 위반 (early return) PopDashboardComponent.tsx에서 if (!config) early return이 hooks보다 앞에 위치. 모든 hooks 선언 이후로 early return 이동, visibleItems?? [] 안전 처리 추가 2026-02-10 hooks, early return, Rules of Hooks, useState, useEffect
config가 빈 객체 {}로 전달되어 items undefined ComponentEditorPanelcomponent.config || {}로 빈 객체 전달 -> ??가 빈 객체를 통과 -> cfg.items undefined -> .map(), .length, .filter() TypeError. ConfigPanel: {...DEFAULT_CONFIG, ...config} spread 병합, Preview: Array.isArray() 가드 추가, Component: Array.isArray() 가드 추가 2026-02-10 config, 빈 객체, nullish coalescing, undefined, items, TypeError

|| 설정 탭 세로 스크롤 불가 (페이지 추가 시 콘텐츠 잘림) | ComponentEditorPanel.tsxTabs와 모든 TabsContentmin-h-0 누락. Flexbox에서 flex 자식의 기본 min-height: auto가 콘텐츠 크기 이하로 축소를 막아 overflow-auto가 작동하지 않음. Tabsmin-h-0, TabsListshrink-0, 4개 TabsContentmin-h-0 추가로 해결 | 2026-02-10 | 스크롤, overflow, min-h-0, flex, TabsContent, 설정패널, 페이지 탭 |


설정 탭 스크롤 버그 상세 (2026-02-10)

증상

POP 디자이너에서 대시보드 컴포넌트 선택 -> 설정 탭 -> 페이지 탭에서 페이지를 3개 이상 추가하면 아래쪽 페이지가 잘려서 보이지 않음. 세로 스크롤 불가.

잘못된 접근 (실패 기록)

처음에 PopDashboardConfigPanel(자식)에서 문제를 해결하려고 시도:

  1. 외곽 divflex h-full flex-col로 변경
  2. 탭 콘텐츠에 overflow-y-auto 래퍼 추가

실패 이유: PopDashboardConfigPanel은 부모(ComponentSettingsForm)의 overflow-auto가 담당하는 스크롤 영역 안에 있으므로, 자식이 h-full로 높이를 채우면 부모의 스크롤 영역이 깨짐. 문제는 자식이 아니라 부모의 높이 제약이 제대로 전파되지 않는 것.

근본 원인

ResizablePanel (높이 확정)
  └ ComponentEditorPanel: div.flex.h-full.flex-col
      ├ 헤더 (고정)
      └ Tabs: flex.flex-1.flex-col        ← min-h-0 누락!
          ├ TabsList (고정)
          └ TabsContent: flex-1.overflow-auto  ← min-h-0 누락!
              └ ComponentSettingsForm
                  └ PopDashboardConfigPanel  ← 콘텐츠가 길어짐

Flexbox의 min-height: auto 기본값 때문에:

  • Tabs(flex-1)가 콘텐츠에 의해 무한 확장
  • TabsContent(overflow-auto)도 콘텐츠에 의해 확장 -> 스크롤 발생 안 함
  • 결과: overflow-auto가 있지만 높이 제약이 없어 스크롤바 미생성

해결

// ComponentEditorPanel.tsx
// 변경 전
<Tabs defaultValue="position" className="flex flex-1 flex-col">
  <TabsList className="w-full justify-start ...">
  <TabsContent value="settings" className="flex-1 overflow-auto p-4">

// 변경 후
<Tabs defaultValue="position" className="flex min-h-0 flex-1 flex-col">
  <TabsList className="w-full shrink-0 justify-start ...">
  <TabsContent value="settings" className="min-h-0 flex-1 overflow-auto p-4">

교훈

Flexbox에서 overflow-auto가 작동하려면, 해당 요소와 모든 flex 조상에 min-h-0(또는 min-w-0)이 필요하다. flex-1만으로는 높이가 제한되지 않는다. flex 자식의 기본 min-height: auto가 콘텐츠 크기 이하로 축소를 막기 때문이다. 스크롤 문제가 발생하면 자식 컴포넌트가 아니라 부모 flex 체인부터 위로 추적해야 한다.


폼 중간 상태 -> 잘못된 SQL -> 백엔드 장애 상세 (2026-02-10)

증상

브라우저 콘솔에 timeout of 30000ms exceeded 에러 반복. /auth/me, /auth/status, /admin/menus 등 핵심 API가 모두 30초 타임아웃. 브라우저가 멈추거나 자동 종료.

원인 체인

1. 대시보드 설정에서 집계 유형(SUM/AVG)만 선택, 대상 컬럼 미선택
   ↓
2. dataFetcher.ts의 buildAggregationSQL이 "SUM()" 같은 잘못된 SQL 생성
   ↓
3. 잘못된 SQL이 백엔드로 전송, PostgreSQL "function sum() does not exist" 에러
   ↓
4. refreshInterval(기본값)로 이 요청이 반복 전송
   ↓
5. 백엔드 과부하 -> Docker 컨테이너 unhealthy
   ↓
6. 인증/메뉴 등 다른 API도 30초 타임아웃
   ↓
7. 여러 API가 병렬로 30초씩 대기 -> 브라우저 멈춤

해결

// dataFetcher.ts - SQL 생성 전 필수값 검증
function validateDataSourceConfig(ds: DataSourceConfig): string | null {
  if (!ds.tableName) return "테이블이 선택되지 않았습니다";
  if (ds.aggregation?.type && ds.aggregation.type !== "COUNT" && !ds.aggregation.column) {
    return "집계 대상 컬럼이 선택되지 않았습니다";
  }
  // ... 조인 검증 등
  return null; // 유효
}

// fetchAggregatedData에서 호출
const validationError = validateDataSourceConfig(dataSource);
if (validationError) {
  return { value: 0, error: validationError };
}

교훈

프론트엔드 설정 폼의 "중간 상태"(일부만 입력)가 백엔드에 전달되면 연쇄 장애로 이어질 수 있다. SQL/API 호출 전에 반드시 validate 함수를 배치하고, 미완료 상태에서는 요청 자체를 차단해야 한다. 특히 refreshInterval이 있는 자동 갱신 기능은 잘못된 요청을 반복 전송할 수 있어 더 위험하다.


그리드 2열 -> 1열 축소 상세 (2026-02-10)

증상

디자이너에서 페이지를 2열로 설정했는데, 실제 화면에서는 아이템이 세로로 쌓여 1열로 표시됨.

원인

GridModeComponent에서 반응형 열 축소 로직:
  MIN_CELL_WIDTH = 160px
  containerWidth = 300px (초기값, ResizeObserver 발동 전)
  gap = 8px
  
  cellWidth = (300 - 8) / 2 = 146px
  146 < 160 → actualColumns = 1 (축소!)
  
  ResizeObserver가 실제 너비를 반영해도,
  초기 렌더링에서 이미 1열로 결정됨

해결

// GridMode.tsx
const MIN_CELL_WIDTH = 80; // was 160
// 80px이면 146 >= 80 → 2열 유지

교훈

반응형 열 축소 로직의 MIN_CELL_WIDTH는 실제 사용 시나리오를 고려해야 한다. ResizeObserver가 발동하기 전 초기 containerWidth 기본값이 작을 수 있으므로, MIN_CELL_WIDTH를 너무 크게 설정하면 의도치 않게 열이 축소된다.


stale closure (handleUpdateComponent)

항목 내용
문제 PopDesigner.tsxhandleUpdateComponent에서 layout state를 직접 참조하여, 빠른 연속 설정 변경 시 이전 state가 캡처되어 변경값 유실
증상 정렬 버튼을 빠르게 클릭하면 이전 클릭의 변경이 덮어씌워짐
원인 useCallback의 의존성 배열에 layout이 있지만, layout 갱신 전에 다음 호출이 발생하면 이전 클로저가 사용됨
해결 setLayout(prev => ...) 함수적 업데이트로 변경, 의존성에서 layout 제거 → [saveToHistory]만 유지
날짜 2026-02-11
키워드 stale closure, useCallback, setState, 함수적 업데이트, 빠른 연속 변경

교훈

useCallback 안에서 state를 직접 참조하면 빠른 연속 호출 시 이전 값이 캡처된다. 항상 setState(prev => ...) 패턴으로 최신 state에 접근해야 한다. 특히 사용자 인터랙션(버튼 클릭)이 빠르게 반복될 수 있는 곳에서 주의.


.next 빌드 캐시 꼬임 (Docker 익명 볼륨)

항목 내용
문제 ChartItem.tsx를 수정했으나 브라우저에 반영되지 않음. 디버그 console.log도 콘솔에 나타나지 않음
증상 다른 컴포넌트(StatCard, Gauge)는 최신 코드 실행, 차트만 이전 코드 실행
원인 Docker compose에서 /app/.next가 익명 볼륨으로 분리되어 호스트의 .next 삭제와 독립적. Turbopack 모듈 캐시가 특정 파일 변경을 인식하지 못함
해결 docker-compose -f ... down -v (볼륨 포함 삭제) + docker-compose -f ... up -d --build (재빌드)
날짜 2026-02-11
키워드 .next, 캐시, Docker, 익명 볼륨, Turbopack, HMR, 빌드

교훈

Docker compose에서 /app/.next가 익명 볼륨인 경우, 호스트에서 rm -rf .next를 해도 컨테이너 캐시에 영향 없음. docker-compose down -v로 볼륨까지 제거해야 캐시가 초기화됨. 파일 수정이 브라우저에 반영 안 되면: (1) 디버그 로그 추가 (2) 로그가 안 나오면 캐시 문제 의심 (3) 볼륨 포함 재빌드.


글자 크기 커스텀 vs @container 반응형 충돌

항목 내용
문제 글자 크기 3그룹(라벨/메인값/보조)을 절대 px(12~64px)로 설정하면, 유동적인 그리드 셀 크기와 충돌하여 텍스트가 잘리거나 넘침
증상 "매우 크게(64px)" 설정 시 값이 셀 경계를 벗어남. 작은 셀에 큰 글자가 들어가면서 정보 가독성 저하
원인 @container 반응형(text-xs @[200px]:text-3xl)은 셀 크기에 따라 자동 조절되는데, 절대 px가 이를 덮어씀
해결 글자 크기 커스텀 기능 전체 제거. @container 반응형 자동 크기만 유지. 정렬은 라벨만 좌/중/우 선택
날짜 2026-02-11
키워드 글자 크기, @container, 반응형, overflow, 대시보드

교훈

대시보드처럼 고밀도 정보를 표시하는 컴포넌트에서는 절대 크기 지정을 피하고, 컨테이너 크기에 따른 자동 반응형이 더 안정적이다. "정보를 한눈에 파악"이 목적인 컴포넌트에서는 정보가 잘리게 만드는 기능 자체가 목적에 반한다.


미사용 import (PopComponentType)

항목 내용
문제 ComponentEditorPanel.tsx에서 COMPONENT_TYPE_LABELS 타입을 Record<PopComponentType, string> -> Record<string, string>으로 변경한 후, PopComponentType의 유일한 사용처가 사라졌으나 import가 남아있었음
해결 import에서 PopComponentType 제거
날짜 2026-02-10
키워드 미사용 import, 타입 변경, PopComponentType

교훈

타입을 변경하거나 제거할 때, 해당 타입이 import된 것이라면 변경 후 파일 내 다른 참조가 남아있는지 Grep으로 확인해야 한다.


새 문제-해결 추가 시 해당 카테고리 테이블에 행 추가