ERP-node/popdocs/PLAN.md

552 lines
23 KiB
Markdown
Raw Normal View History

# POP 개발 계획
---
## 현재 상태 (2026-02-11)
**Phase 0 공통 인프라 (usePopEvent + useDataSource) 구현 완료, ksh-v2-work 병합 + 원격 push 완료. Phase 2 pop-button 설계 진행 중.**
---
## 작업 순서
```
[Phase 1~3] [Phase 5] [정의서] [Phase 0~6]
v4 Flexbox → v5 CSS Grid → 컴포넌트 설계 → 실제 구현
완료 완료 (v5.2) 완료 (v8.0) 다음
```
---
## 완료된 Phase
### Phase 1~3: v4 Flexbox 시스템 (완료, 레거시 삭제됨)
v4 Flexbox 기반 시스템은 v5 CSS Grid로 완전히 대체되었습니다.
v4 관련 파일은 모두 삭제되었습니다.
- [x] v4 기본 구조, 렌더러, 디자이너 통합
- [x] Undo/Redo, 드래그 리사이즈, Flexbox 가로 배치
- [x] 비율 스케일링 시스템
- [x] 오버라이드 기능 (모드별 배치 고정)
- [x] 컴포넌트 표시/숨김, 줄바꿈
### Phase 5: v5 CSS Grid 시스템 (완료)
#### Phase 5.1: 타입 정의 (완료)
- [x] `PopLayoutDataV5` 인터페이스
- [x] `PopGridConfig`, `PopGridPosition` 타입
- [x] `GridMode`, `GRID_BREAKPOINTS` 상수
- [x] `createEmptyPopLayoutV5()`, `isV5Layout()`, `detectGridMode()`
#### Phase 5.2: 그리드 렌더러 (완료)
- [x] `PopRenderer.tsx` - CSS Grid 기반 렌더링
- [x] 격자 셀 렌더링 (CSS Grid 동일 좌표계)
- [x] 위치 변환 (12칸 -> 4/6/8칸)
#### Phase 5.3: 디자이너 UI (완료)
- [x] `PopCanvas.tsx` - 그리드 캔버스 + 행/열 라벨
- [x] 드래그 스냅 (칸에 맞춤)
- [x] `ComponentEditorPanel.tsx` - 위치 편집
#### Phase 5.4: 반응형 자동화 (완료)
- [x] 자동 변환 알고리즘 (12칸 -> 4칸)
- [x] 겹침 감지 및 재배치
- [x] 모드별 오버라이드 저장
#### v5.1 추가 기능 (완료)
- [x] 자동 줄바꿈 (col > maxCol -> 맨 아래 배치)
- [x] "검토 필요" 알림 시스템
- [x] Gap 프리셋 (좁게/보통/넓게)
- [x] 숨김 기능 (모드별)
#### v5.2 브레이크포인트 재설계 + 세로 자동 확장 (완료)
- [x] 기기 기반 브레이크포인트 (479/767/1023px)
- [x] 세로 자동 확장 (dynamicCanvasHeight)
- [x] 뷰어 반응형 일관성 (detectGridMode 사용)
- [x] VIEWPORT_PRESETS에서 height 제거
### 컴포넌트 설계 (완료)
- [x] 9개 컴포넌트 정의 (POPUPDATE_2.md v8.0)
- [x] POP 헌법 9조 작성
- [x] 공통 인프라 설계 (DataSourceConfig, ColumnBinding, JoinConfig, useDataSource, usePopEvent, PopActionConfig)
- [x] 모달 화면 설계 방식 확정 (인라인 + 외부 참조)
- [x] 기존 시스템 호환성 검증 (DB/백엔드/프론트 변경 불필요 확인)
---
## 다음 작업
### Phase 0: 공통 인프라
모든 데이터 연동 컴포넌트가 공유하는 기반 시스템:
- [x] ColumnBinding 타입 정의 (read/write/readwrite/hidden) -- types.ts에 추가 완료
- [x] JoinConfig 타입 정의 (테이블 조인) -- types.ts에 추가 완료
- [x] DataSourceConfig 타입 정의 (데이터 소스 설정) -- types.ts에 추가 완료
- [x] PopActionConfig 타입 정의 (액션 설정) -- types.ts에 추가 완료
- [x] usePopEvent 훅 구현 (이벤트 버스, 데이터 전달, 화면 단위 격리) -- 완료
- [x] useDataSource 훅 구현 (CRUD 포함, 기존 dataApi 활용) -- 완료
### Phase 1: pop-dashboard -- 완료
> **2026-02-10**: 17단계 코딩 + 검수 + 팔레트 등록 완료
- [x] PopDashboardConfig, DashboardItem 타입 정의
- [x] 멀티 아이템 컨테이너 구현 (여러 아이템 묶음)
- [x] 4개 서브타입: kpi-card, chart, gauge, stat-card
- [x] 4개 표시 모드: arrows, auto-slide, grid, scroll
- [x] 계산식(formula) 지원: "생산량/총재고량" 같은 복합 표현
- [x] 설정 패널: 드롭다운 기반 쉬운 집계 설정 (SQL 불필요)
- [x] PopComponentRegistry 등록 + 디자이너 팔레트 등록
- [ ] 이벤트: filter_changed 수신, kpi_clicked 발행 -- usePopEvent 완성 후
- [ ] 기존 `components/pop/dashboard/` 폴더 폐기 (모든 기능 대체 확인 후)
### Phase 2: pop-button, pop-icon
- [ ] pop-button: 저장/삭제/API 호출 액션
- [ ] pop-icon: 화면 이동/URL/새로고침
### Phase 3: pop-table (테이블형 우선)
- [ ] table-list 서브타입: 행/열 장부형
- [ ] ColumnBinding 기반 컬럼별 read/write
- [ ] card-list 서브타입: 카드 템플릿 (후순위)
### Phase 4: pop-search, pop-field, pop-lookup
- [ ] pop-search: 필터 조건 입력 (text/date/select/combo)
- [ ] pop-field: 저장용 입력 (text/number/date/select/numpad)
- [ ] pop-lookup: 모달 값 선택 (인라인 + 외부 참조)
### Phase 5: 고도화
- [ ] pop-table 카드 템플릿 디자이너
### Phase 6: pop-system
- [ ] 프로필, 테마, 대시보드 보이기/숨기기 통합
### 후속 작업
- [ ] 워크플로우 연동 (버튼 액션, 화면 전환)
- [ ] 실기기 테스트 (아이폰 SE, iPad Mini 등)
**참고 문서**: [POPUPDATE_2.md](../POPUPDATE_2.md) (컴포넌트 정의서 v8.0)
---
## 현재 구현 계획
> **용도**: 이 섹션은 "지금 바로 실행할 구체적 계획"입니다.
> 새 세션에서 이 섹션만 읽으면 코딩을 시작할 수 있어야 합니다.
> 완료되면 다음 기능의 계획으로 **교체**합니다.
### 대상: Phase 0 공통 인프라 (usePopEvent + useDataSource 훅)
#### 배경 (2026-02-11)
모든 데이터 연동 POP 컴포넌트(pop-button, pop-table, pop-search 등)가 공유하는 2개 핵심 훅을 구현한다.
- **usePopEvent**: 같은 화면(screenId) 안에서 컴포넌트 간 이벤트 통신 (publish/subscribe)
- **useDataSource**: DB 테이블 CRUD 통합 (조회/생성/수정/삭제)
**핵심 원칙**: 새로 만드는 것이 아니라, 기존 코드를 공통화하는 작업이다.
- `useDataSource`는 대시보드의 `dataFetcher.ts` 조회 로직 + 기존 `dataApi` CRUD를 훅으로 래핑
- `usePopEvent`는 신규 구현 (Map 기반 이벤트 버스)
- 대시보드는 **이번에 교체하지 않는다** (훅 안정화 후 별도 교체)
#### 결정사항 (2026-02-11)
| 항목 | 결정 | 이유 |
|------|------|------|
| usePopEvent 범위 | 같은 screenId 안에서만 통신 | 화면 간 의존성 방지 |
| usePopEvent 모달 | 같은 screenId면 모달 안 컴포넌트도 통신 가능 | 모달은 별도 화면이 아님 |
| useDataSource 조회 분기 | 집계/조인이면 SQL 빌더 + executeQuery, 단순이면 dataApi.getTableData | 대시보드 dataFetcher.ts와 동일 전략 |
| useDataSource CRUD | dataApi.createRecord/updateRecord/deleteRecord 래핑 | 백엔드 API 이미 완성됨 |
| 대시보드 교체 시점 | 이번에 하지 않음, 훅 안정화 후 별도 작업 | 안정성 우선 |
| SQL 빌더 위치 | dataFetcher.ts에서 추출하여 별도 유틸로 분리 | 훅과 대시보드 모두 사용 |
| 이벤트 버스 저장소 | 전역 Map (screenId -> EventEmitter) | React 외부에서도 접근 가능, GC 관리 용이 |
---
#### 구현 순서 (의존성 기반)
| 순서 | 파일 | 작업 | 의존성 | 상태 |
|------|------|------|--------|------|
| 1 | `hooks/pop/usePopEvent.ts` | 이벤트 버스 훅 (신규) | 없음 | **완료** |
| 2 | `hooks/pop/popSqlBuilder.ts` | SQL 빌더 유틸 분리 (dataFetcher.ts에서 추출) | 없음 | **완료** |
| 3 | `hooks/pop/useDataSource.ts` | 데이터 CRUD 훅 (신규) | 2 | **완료** |
| 4 | `hooks/pop/index.ts` | 배럴 파일 (re-export) | 1, 3 | **완료** |
---
#### STEP 1: `usePopEvent.ts` (신규 생성)
**파일**: `frontend/hooks/pop/usePopEvent.ts`
**역할**: 같은 화면(screenId) 안에서 컴포넌트 간 이벤트 통신
**핵심 구조**:
```
전역 저장소 (React 외부)
screenBuses: Map<string, Map<string, Set<callback>>>
└── screenId: "S001"
├── "supplier-selected" → [콜백A, 콜백B]
├── "data-saved" → [콜백C]
└── sharedData: Map<string, unknown>
sharedDataStore: Map<string, Map<string, unknown>>
└── screenId: "S001"
├── "selectedSupplier" → { id: "SUP-001", name: "삼성" }
└── "inputQuantity" → 50
```
**외부 API (훅이 반환하는 것)**:
```typescript
function usePopEvent(screenId: string) {
return {
publish, // (eventName, payload) => void
subscribe, // (eventName, callback) => unsubscribe 함수
getSharedData, // (key) => unknown
setSharedData, // (key, value) => void
};
}
```
**상세 구현 명세**:
1. **전역 Map 저장소** (모듈 스코프, React 외부)
- `screenBuses: Map<string, Map<string, Set<Function>>>` - 이벤트 리스너
- `sharedDataStore: Map<string, Map<string, unknown>>` - 공유 데이터
2. **`publish(eventName, payload)`**
- 해당 screenId의 eventName에 등록된 모든 콜백을 순회하며 payload 전달
- 등록된 리스너가 없으면 아무 일도 안 함 (에러 아님)
3. **`subscribe(eventName, callback)`**
- 해당 screenId의 eventName에 콜백 등록
- **반환값**: unsubscribe 함수
- **useEffect 내부에서 호출되어야 함** (cleanup으로 unsubscribe)
4. **`getSharedData(key)`** / **`setSharedData(key, value)`**
- screenId별 격리된 key-value 저장소
- publish/subscribe는 "이벤트"(일회성), sharedData는 "상태"(지속)
- 용도: 버튼이 저장할 때 다른 컴포넌트들의 현재 값을 수집
5. **`cleanupScreen(screenId)`** (내부 유틸)
- 화면 언마운트 시 해당 screenId의 모든 리스너 + sharedData 정리
- 메모리 누수 방지
**사용 예시**:
```typescript
// 거래처 선택 버튼
const { publish, setSharedData } = usePopEvent("S001");
const onSelect = (supplier) => {
setSharedData("selectedSupplier", supplier); // 상태 저장
publish("supplier-selected", { supplierId: supplier.id }); // 이벤트 발행
};
// 발주 테이블 (다른 컴포넌트)
const { subscribe } = usePopEvent("S001");
useEffect(() => {
const unsub = subscribe("supplier-selected", (payload) => {
refetch({ filters: { supplier_id: payload.supplierId } });
});
return unsub; // cleanup
}, []);
// 저장 버튼 (다른 컴포넌트)
const { getSharedData } = usePopEvent("S001");
const handleSave = () => {
const supplier = getSharedData("selectedSupplier"); // 다른 컴포넌트가 저장한 값 수집
const quantity = getSharedData("inputQuantity");
save({ supplier_id: supplier.id, quantity });
};
```
---
#### STEP 2: `popSqlBuilder.ts` (신규 생성 - dataFetcher.ts에서 추출)
**파일**: `frontend/hooks/pop/popSqlBuilder.ts`
**역할**: DataSourceConfig를 SQL 문자열로 변환하는 순수 유틸리티
**기존 dataFetcher.ts에서 그대로 추출할 함수 5개** (로직 변경 없음):
| 함수 | 원본 위치 (dataFetcher.ts) | 역할 |
|------|--------------------------|------|
| `escapeSQL(value)` | 라인 41~48 | SQL 값 이스케이프 |
| `sanitizeIdentifier(name)` | 라인 124~127 | 테이블/컬럼명 위험 문자 제거 |
| `validateDataSourceConfig(config)` | 라인 59~88 | 설정 완료 여부 검증 |
| `buildWhereClause(filters)` | 라인 93~118 | 필터 -> WHERE 절 변환 |
| `buildAggregationSQL(config)` | 라인 137~215 | DataSourceConfig -> SELECT SQL 변환 |
**export 대상**: `validateDataSourceConfig`, `buildAggregationSQL` (나머지는 내부 함수)
**주의**: dataFetcher.ts는 **수정하지 않는다**. 대시보드가 계속 사용 중이므로, 복사만 한다. 대시보드 교체 시점에 dataFetcher.ts에서 이 파일을 import하도록 변경할 예정.
---
#### STEP 3: `useDataSource.ts` (신규 생성)
**파일**: `frontend/hooks/pop/useDataSource.ts`
**역할**: DataSourceConfig 기반 DB 테이블 CRUD 통합 훅
**내부 의존성**:
- `popSqlBuilder.ts` - SQL 빌더 (STEP 2)
- `@/lib/api/data` - dataApi (기존, 조회/생성/수정/삭제)
- `@/lib/api/dashboard` - dashboardApi (기존, SQL 직접 실행)
- `@/lib/api/client` - apiClient (기존, axios 기반)
**외부 API (훅이 반환하는 것)**:
```typescript
function useDataSource(config: DataSourceConfig) {
return {
// 상태
data: { rows: [], value: 0, total: 0 },
loading: boolean,
error: string | null,
// 조회
refetch: (overrideFilters?) => Promise<void>,
// 쓰기
save: (record) => Promise<MutationResult>,
update: (id, record) => Promise<MutationResult>,
remove: (id) => Promise<MutationResult>,
};
}
```
**MutationResult 타입** (신규):
```typescript
interface MutationResult {
success: boolean;
data?: any;
error?: string;
}
```
**조회 분기 로직 (핵심)**:
```
config에 aggregation 또는 joins가 있는가?
├── YES → buildAggregationSQL(config) → apiClient.post("/dashboards/execute-query")
│ (대시보드와 동일한 경로, SQL 직접 실행)
│ 실패 시 → dashboardApi.executeQuery() 폴백
└── NO → dataApi.getTableData(tableName, { page, size, filters, sortBy, sortOrder })
(단순 테이블 조회)
```
**CRUD 메서드 구현**:
| 메서드 | 내부 호출 | 비고 |
|--------|----------|------|
| `save(record)` | `dataApi.createRecord(config.tableName, record)` | company_code 자동 추가는 백엔드가 처리 |
| `update(id, record)` | `dataApi.updateRecord(config.tableName, id, record)` | |
| `remove(id)` | `dataApi.deleteRecord(config.tableName, id)` | 복합키 객체도 지원 |
**자동 새로고침**:
```typescript
useEffect(() => {
if (config.tableName) refetch();
if (config.refreshInterval && config.refreshInterval > 0) {
const sec = Math.max(5, config.refreshInterval); // 최소 5초
const timer = setInterval(refetch, sec * 1000);
return () => clearInterval(timer);
}
}, [config.tableName, config.refreshInterval]);
```
**refetch 오버라이드 필터**:
```typescript
// 기본 조회
refetch();
// 필터 추가하여 조회 (usePopEvent와 연동 시)
refetch({ filters: { supplier_id: "SUP-001" } });
```
내부적으로 `overrideFilters`가 있으면 `config.filters`에 병합하여 조회한다.
**사용 예시**:
```typescript
// 대시보드 스타일 (집계)
const { data, loading } = useDataSource({
tableName: "sales_order",
aggregation: { type: "sum", column: "amount", groupBy: ["category"] },
refreshInterval: 30,
});
// data.rows → [{ category: "A", value: 1500 }, ...]
// data.value → 첫 번째 행의 value
// 테이블 스타일 (목록)
const { data, refetch } = useDataSource({
tableName: "purchase_order",
sort: [{ column: "created_at", direction: "desc" }],
limit: 20,
});
// data.rows → [{ id: 1, item_name: "볼트", ... }, ...]
// data.total → 전체 행 수
// 버튼 스타일 (저장만)
const { save, remove, loading } = useDataSource({
tableName: "inbound_record",
});
const result = await save({ supplier_id: "SUP-001", quantity: 50 });
// result.success → true/false
```
---
#### STEP 4: `index.ts` (신규 생성 - 배럴 파일)
**파일**: `frontend/hooks/pop/index.ts`
```typescript
export { usePopEvent, cleanupScreen } from "./usePopEvent";
export { useDataSource } from "./useDataSource";
export type { MutationResult } from "./useDataSource";
export { buildAggregationSQL, validateDataSourceConfig } from "./popSqlBuilder";
```
외부에서 사용할 때: `import { usePopEvent, useDataSource } from "@/hooks/pop";`
---
#### 사전 충돌 검사 결과 (2026-02-11)
| 이름 | 유형 | 검색 범위 | 검색 결과 | 판정 |
|------|------|-----------|-----------|------|
| `usePopEvent` | 훅 이름 | frontend 전체 | **주석 2건** (PopDashboardComponent.tsx 라인 10, `@INFRA-EXTRACT` 교체 예정 주석) | **충돌 없음** (실제 코드 아님) |
| `useDataSource` | 훅 이름 | frontend 전체 | **주석 4건** (dataFetcher.ts 라인 4,227,355 + PopDashboardComponent.tsx 라인 9, 모두 `@INFRA-EXTRACT` 주석) | **충돌 없음** (실제 코드 아님) |
| `PopEventBus` | 클래스명 | frontend 전체 | **1건** (PopDashboardComponent.tsx 라인 10, `@INFRA-EXTRACT` 주석 내) | **충돌 없음** (주석) |
| `MutationResult` | 타입명 | frontend 전체 | **0건** | **충돌 없음** |
| `popSqlBuilder` | 파일명 | frontend 전체 | **0건** | **충돌 없음** |
| `cleanupScreen` | 함수명 | frontend 전체 | **0건** | **충돌 없음** |
| `screenBuses` | 변수명 | frontend 전체 | **0건** | **충돌 없음** |
| `sharedDataStore` | 변수명 | frontend 전체 | **0건** | **충돌 없음** |
| `buildAggregationSQL` | 함수명 | frontend 전체 | **2건** (dataFetcher.ts 정의 + PopDashboardComponent에서 import) | **충돌 주의**: 동일 이름을 popSqlBuilder.ts에서 재정의. 대시보드는 여전히 dataFetcher.ts 것을 사용하므로 런타임 충돌 없음. 향후 대시보드 교체 시 import 경로만 변경. |
| `validateDataSourceConfig` | 함수명 | frontend 전체 | **1건** (dataFetcher.ts 정의) | 위와 동일 |
| `escapeSQL` | 함수명 | frontend 전체 | **1건** (dataFetcher.ts 내부 함수) | **충돌 없음** (export 안 됨) |
| `sanitizeIdentifier` | 함수명 | frontend 전체 | **1건** (dataFetcher.ts 내부 함수) | **충돌 없음** (export 안 됨) |
| `AggregatedResult` | 타입명 | frontend 전체 | **1건** (dataFetcher.ts 정의) | **충돌 주의**: useDataSource 내부에서 동일 타입 사용. types.ts 정의를 공유하므로 별도 재정의 불필요. |
#### 정의-사용 매핑
| 정의 | 정의 위치 | 사용 위치 |
|------|-----------|-----------|
| `usePopEvent` | `hooks/pop/usePopEvent.ts` (신규) | pop-button (Phase 2), pop-table (Phase 3), pop-search (Phase 4) 등 |
| `useDataSource` | `hooks/pop/useDataSource.ts` (신규) | pop-button (Phase 2), pop-table (Phase 3), pop-dashboard (향후 교체) 등 |
| `MutationResult` | `hooks/pop/useDataSource.ts` (신규) | useDataSource 반환 타입으로 사용 |
| `buildAggregationSQL` | `hooks/pop/popSqlBuilder.ts` (신규) | useDataSource 내부에서 호출 |
| `validateDataSourceConfig` | `hooks/pop/popSqlBuilder.ts` (신규) | useDataSource 내부에서 호출 |
| `cleanupScreen` | `hooks/pop/usePopEvent.ts` (신규) | 화면 언마운트 시 호출 (PopRenderer 또는 뷰어에서) |
| `DataSourceConfig` | `lib/registry/pop-components/types.ts` (기존) | useDataSource 파라미터 타입 |
| `DataSourceFilter` | `lib/registry/pop-components/types.ts` (기존) | popSqlBuilder 내부 |
| `dataApi` | `lib/api/data.ts` (기존) | useDataSource 내부에서 CRUD 호출 |
| `dashboardApi` | `lib/api/dashboard.ts` (기존) | useDataSource 내부에서 SQL 실행 폴백 |
| `apiClient` | `lib/api/client.ts` (기존) | useDataSource 내부에서 SQL 실행 1차 |
**누락 검사**: 모든 신규 정의에 사용처 있음. 모든 사용처에 정의 존재.
단, `cleanupScreen`의 호출 시점은 Phase 2 이후 뷰어 통합 시 결정 (이번에는 export만 해둠).
---
#### 함정 경고
| 번호 | 위험 | 설명 | 해결 방안 |
|------|------|------|-----------|
| W1 | **subscribe를 useEffect 밖에서 호출하면 메모리 누수** | subscribe는 콜백을 등록하므로, 컴포넌트 언마운트 시 해제해야 함 | subscribe의 반환값(unsubscribe)을 useEffect cleanup에서 호출. JSDoc에 사용 패턴 명시. |
| W2 | **DataSourceConfig의 import 경로** | `DataSourceConfig``lib/registry/pop-components/types.ts`에 정의됨. hooks 디렉토리에서 import 시 경로가 김 | `@/lib/registry/pop-components/types`로 import. 별도 re-export 하지 않음 (타입 중복 방지). |
| W3 | **buildAggregationSQL 동일 이름 2곳** | dataFetcher.ts와 popSqlBuilder.ts에 같은 이름의 함수가 존재 | 의도적 복사. 대시보드는 dataFetcher.ts, 새 컴포넌트는 popSqlBuilder.ts 사용. 향후 대시보드 교체 시 dataFetcher.ts를 popSqlBuilder.ts import로 변경. |
| W4 | **apiClient.post와 dashboardApi.executeQuery 이중 경로** | 대시보드 dataFetcher.ts에서 apiClient 우선 + dashboardApi 폴백 패턴을 그대로 복사함 | 동일 패턴 유지 (안정성 검증 완료). 향후 하나로 통합 가능. |
| W5 | **refetch overrideFilters와 config.filters 병합 순서** | overrideFilters가 config.filters를 완전 대체하는지, 추가하는지 모호 | **추가(append) 방식**: config.filters + overrideFilters를 합침. overrideFilters에 같은 column이 있으면 덮어씀. 이 동작을 JSDoc에 명시. |
| W6 | **SSR 환경에서 전역 Map** | Next.js SSR에서 전역 Map이 서버/클라이언트 간 공유될 수 있음 | `typeof window !== "undefined"` 가드. 이벤트 버스는 클라이언트 전용. |
| W7 | **hooks/pop/ 디렉토리 신규** | `frontend/hooks/pop/` 디렉토리가 존재하지 않음 | STEP 1에서 파일 생성 시 디렉토리 자동 생성됨. 수동으로 mkdir 불필요. |
---
#### 작업 완료 후 확인 체크리스트
##### 코드 레벨
- [ ] `usePopEvent` - publish/subscribe 기본 동작 (같은 screenId)
- [ ] `usePopEvent` - 다른 screenId 간 격리 확인
- [ ] `usePopEvent` - subscribe cleanup (메모리 누수 없음)
- [ ] `usePopEvent` - sharedData set/get
- [ ] `useDataSource` - 단순 조회 (aggregation 없음 → dataApi.getTableData)
- [ ] `useDataSource` - 집계 조회 (aggregation 있음 → SQL 빌더 → executeQuery)
- [ ] `useDataSource` - save/update/remove
- [ ] `useDataSource` - loading/error 상태 관리
- [ ] `useDataSource` - refreshInterval 자동 새로고침
- [ ] `popSqlBuilder` - buildAggregationSQL이 dataFetcher.ts와 동일 결과 생성
- [ ] TypeScript 컴파일 에러 0건
- [ ] 린트 에러 0건
- [ ] 기존 대시보드 동작에 영향 없음 (dataFetcher.ts 미수정)
##### 구조 레벨
- [ ] `frontend/hooks/pop/` 디렉토리 생성됨
- [ ] index.ts 배럴 파일에서 모든 public API export 됨
- [ ] `@/hooks/pop`으로 import 가능
---
#### 이전 완료된 계획 (보관)
**대시보드 스타일 정리 (2026-02-11, 완료)**:
글자 크기 커스텀 제거, 라벨 정렬만 유지, stale closure 수정, .next 캐시 해결.
상세: `popdocs/sessions/2026-02-11.md`
**브라우저 확인 체크리스트 (대기)**:
- [ ] 라벨 정렬, 페이지 미리보기, 차트 디자인, 게이지/통계카드 동작 확인
---
## 브레이크포인트 (v5.2 현재)
| 모드 | 화면 너비 | 칸 수 | 대상 기기 |
|------|----------|-------|----------|
| mobile_portrait | ~479px | 4칸 | 아이폰 SE ~ 갤럭시 S |
| mobile_landscape | 480~767px | 6칸 | 스마트폰 가로 |
| tablet_portrait | 768~1023px | 8칸 | 8~10인치 태블릿 세로 |
| tablet_landscape | 1024px~ | 12칸 | 10~14인치 태블릿 가로 |
---
## 관련 문서
| 문서 | 내용 |
|------|------|
| [STATUS.md](./STATUS.md) | 현재 진행 상태 |
| [SPEC.md](./SPEC.md) | 기술 스펙 |
| [ARCHITECTURE.md](./ARCHITECTURE.md) | 코드 구조 |
| [POPUPDATE_2.md](../POPUPDATE_2.md) | 컴포넌트 정의서 v8.0 (최신) |
| [components-spec.md](./components-spec.md) | 컴포넌트 상세 설계 (v4 기준, 갱신 필요) |
| [decisions/005](./decisions/005-breakpoint-redesign.md) | 브레이크포인트 재설계 ADR |
---
*최종 업데이트: 2026-02-11 (Phase 0 공통 인프라 완료, Phase 2 pop-button 설계 시작)*