diff --git a/.gitignore b/.gitignore index 0194c053..18a191ed 100644 --- a/.gitignore +++ b/.gitignore @@ -292,4 +292,5 @@ uploads/ claude.md # 개인 작업 문서 (popdocs) -popdocs/ \ No newline at end of file +popdocs/ +.cursor/rules/popdocs-safety.mdc \ No newline at end of file diff --git a/PLAN.MD b/PLAN.MD index 45468fa4..49d2d7e4 100644 --- a/PLAN.MD +++ b/PLAN.MD @@ -1,404 +1,202 @@ -# 현재 구현 계획: pop-dashboard 4가지 아이템 모드 완성 +# 현재 구현 계획: pop-card-list 입력 필드/계산 필드 구조 개편 -> **작성일**: 2026-02-10 -> **상태**: 코딩 완료 (방어 로직 패치 포함) -> **목적**: 대시보드 설정 패널의 미구현/버그 4건을 수정하여 KPI카드, 차트, 게이지, 통계카드 모두 실제 데이터로 동작하도록 완성 +> **작성일**: 2026-02-24 +> **상태**: 계획 완료, 코딩 대기 +> **목적**: 입력 필드 설정 단순화 + 본문 필드에 계산식 통합 + 기존 계산 필드 섹션 제거 --- -## 1. 문제 요약 +## 1. 변경 개요 -pop-dashboard 컴포넌트의 4가지 아이템 모드 중 **설정 UI가 누락**되거나 **데이터 처리 로직에 버그**가 있어 실제 테스트 불가. +### 배경 +- 기존: "입력 필드", "계산 필드", "담기 버튼" 3개가 별도 섹션으로 분리 +- 문제: 계산 필드가 본문 필드와 동일한 위치에 표시되어야 하는데 별도 영역에 있음 +- 문제: 입력 필드의 min/max 고정값은 비실용적 (실제로는 DB 컬럼 기준 제한이 필요) +- 문제: step, columnName, sourceColumns, resultColumn 등 죽은 코드 존재 -| # | 문제 | 심각도 | 영향 | -|---|------|--------|------| -| BUG-1 | 차트: groupBy 설정 UI 없음 | 높음 | 차트가 단일 값만 표시, X축 카테고리 분류 불가 | -| BUG-2 | 차트: xAxisColumn 설정 UI 없음 | 높음 | groupBy 결과의 컬럼명과 xKey 불일치로 차트 빈 화면 | -| BUG-3 | 통계 카드: 카테고리 설정 UI 없음 | 높음 | statConfig.categories를 설정할 방법 없음 | -| BUG-4 | 통계 카드: 카테고리별 필터 미적용 | 높음 | 모든 카테고리에 동일 값(rows.length) 입력되는 버그 | +### 목표 +1. **본문 필드에 계산식 지원 추가** - 필드별로 "DB 컬럼" 또는 "계산식" 선택 +2. **입력 필드 설정 단순화** - 고정 min/max 제거, 제한 기준 컬럼 방식으로 변경 +3. **기존 "계산 필드" 섹션 제거** - 본문 필드에 통합되므로 불필요 +4. **죽은 코드 정리** --- -## 2. 수정 대상 파일 (2개) +## 2. 수정 대상 파일 (3개) -### 파일 A: `frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx` +### 파일 A: `frontend/lib/registry/pop-components/types.ts` -**변경 유형**: 설정 UI 추가 3건 +#### 변경 A-1: CardFieldBinding 타입 확장 -#### 변경 A-1: DataSourceEditor에 groupBy 설정 추가 (라인 253~362 부근, 집계 함수 아래) - -집계 함수가 선택된 상태에서 "그룹핑 컬럼"을 선택할 수 있는 드롭다운 추가. - -**추가할 위치**: `{/* 집계 함수 + 대상 컬럼 */}` 블록 다음, `{/* 자동 새로고침 */}` 블록 이전 - -**추가할 코드** (약 50줄): - -```tsx -{/* 그룹핑 (차트용 X축 분류) */} -{dataSource.aggregation && ( -
- 차트에서 X축 카테고리로 사용됩니다 -
-- 그룹핑 컬럼명과 동일하게 입력. 비우면 첫 번째 groupBy 컬럼 사용 -
-- 카테고리를 추가하면 각 조건에 맞는 건수가 표시됩니다 -
- )} -필터할 컬럼
@@ -607,6 +628,17 @@ function ReceiveSection({ // 유틸 // ======================================== +function isEventTypeConnection( + sourceMeta: ComponentConnectionMeta | undefined, + outputKey: string, + targetMeta: ComponentConnectionMeta | null | undefined, + inputKey: string, +): boolean { + const sourceItem = sourceMeta?.sendable?.find((s) => s.key === outputKey); + const targetItem = targetMeta?.receivable?.find((r) => r.key === inputKey); + return sourceItem?.type === "event" || targetItem?.type === "event"; +} + function buildConnectionLabel( source: PopComponentDefinitionV5, _outputKey: string, diff --git a/frontend/hooks/pop/index.ts b/frontend/hooks/pop/index.ts index 3a6c792b..7ae7e953 100644 --- a/frontend/hooks/pop/index.ts +++ b/frontend/hooks/pop/index.ts @@ -22,5 +22,9 @@ export type { PendingConfirmState } from "./usePopAction"; // 연결 해석기 export { useConnectionResolver } from "./useConnectionResolver"; +// 장바구니 동기화 훅 +export { useCartSync } from "./useCartSync"; +export type { UseCartSyncReturn } from "./useCartSync"; + // SQL 빌더 유틸 (고급 사용 시) export { buildAggregationSQL, validateDataSourceConfig } from "./popSqlBuilder"; diff --git a/frontend/hooks/pop/useCartSync.ts b/frontend/hooks/pop/useCartSync.ts new file mode 100644 index 00000000..e3b76ed5 --- /dev/null +++ b/frontend/hooks/pop/useCartSync.ts @@ -0,0 +1,338 @@ +/** + * useCartSync - 장바구니 DB 동기화 훅 + * + * DB(cart_items 테이블) <-> 로컬 상태를 동기화하고 변경사항(dirty)을 감지한다. + * + * 동작 방식: + * 1. 마운트 시 DB에서 해당 screen_id + user_id의 장바구니를 로드 + * 2. addItem/removeItem/updateItem은 로컬 상태만 변경 (DB 미반영, dirty 상태) + * 3. saveToDb 호출 시 로컬 상태를 DB에 일괄 반영 (추가/수정/삭제) + * 4. isDirty = 로컬 상태와 DB 마지막 로드 상태의 차이 존재 여부 + * + * 사용 예시: + * ```typescript + * const cart = useCartSync("SCR-001", "item_info"); + * + * // 품목 추가 (로컬만, DB 미반영) + * cart.addItem({ row, quantity: 10 }, "D1710008"); + * + * // DB 저장 (pop-icon 확인 모달에서 호출) + * await cart.saveToDb(); + * ``` + */ + +"use client"; + +import { useState, useCallback, useRef, useEffect } from "react"; +import { dataApi } from "@/lib/api/data"; +import type { + CartItem, + CartItemWithId, + CartSyncStatus, + CartItemStatus, +} from "@/lib/registry/pop-components/types"; + +// ===== 반환 타입 ===== + +export interface UseCartSyncReturn { + cartItems: CartItemWithId[]; + savedItems: CartItemWithId[]; + syncStatus: CartSyncStatus; + cartCount: number; + isDirty: boolean; + loading: boolean; + + addItem: (item: CartItem, rowKey: string) => void; + removeItem: (rowKey: string) => void; + updateItemQuantity: (rowKey: string, quantity: number, packageUnit?: string, packageEntries?: CartItem["packageEntries"]) => void; + isItemInCart: (rowKey: string) => boolean; + getCartItem: (rowKey: string) => CartItemWithId | undefined; + + saveToDb: (selectedColumns?: string[]) => Promise
+ {target}
+
+ {desc && (
+ {desc}
+ )} +- 프리셋에 의해 고정됨. 변경하려면 "직접 설정" 선택 -
- )} -+ 저장 완료 후 이동할 장바구니 리스트 화면 ID입니다. + 비어있으면 이동 없이 저장만 합니다. +
+
+ 카드 목록에서 "담기" 클릭 시 아래와 같이 cart_items 테이블에 저장됩니다.
+
사용자 입력
+원본 행 데이터
+ + {/* 저장 모드 선택 */} +
+ 연결: {connectedTableName}
+
+ 카드 목록과 연결(cart_save_trigger)하면 컬럼이 자동으로 표시됩니다. +
+ )} + + {colLoading && ( +컬럼 불러오는 중...
+ )} + + {/* 불러온 컬럼 체크박스 */} + {loadedColumns.length > 0 && ( ++ 저장할 컬럼을 선택하세요. 미선택 시 전체 저장됩니다. +
+ )} +자동 설정
+
+ 장바구니 목록 화면에서 row_data의 JSON을 풀어서
+ 최종 대상 테이블로 매핑할 수 있습니다.
+
+ 프리셋에 의해 고정됨. 변경하려면 "직접 설정" 선택 +
+ )} +