# 테이블 그룹핑 기능 구현 계획서 ## 📋 개요 테이블 리스트 컴포넌트와 플로우 위젯에 그룹핑 기능을 추가하여, 사용자가 선택한 컬럼(들)을 기준으로 데이터를 그룹화하여 표시합니다. ## 🎯 핵심 요구사항 ### 1. 기능 요구사항 - ✅ 그룹핑할 컬럼을 다중 선택 가능 - ✅ 선택한 컬럼 순서대로 계층적 그룹화 - ✅ 그룹 헤더에 그룹 정보와 데이터 개수 표시 - ✅ 그룹 펼치기/접기 기능 - ✅ localStorage에 그룹 설정 저장/복원 - ✅ 그룹 해제 기능 ### 2. 적용 대상 - TableListComponent (`frontend/lib/registry/components/table-list/TableListComponent.tsx`) - FlowWidget (`frontend/components/screen/widgets/FlowWidget.tsx`) ## 🎨 UI 디자인 ### 그룹 설정 다이얼로그 ```tsx ┌─────────────────────────────────────┐ │ 📊 그룹 설정 │ │ 데이터를 그룹화할 컬럼을 선택하세요 │ ├─────────────────────────────────────┤ │ │ │ [x] 통화 │ │ [ ] 단위 │ │ [ ] 품목코드 │ │ [ ] 품목명 │ │ [ ] 규격 │ │ │ │ 💡 선택된 그룹: 통화 │ │ │ ├─────────────────────────────────────┤ │ [취소] [적용] │ └─────────────────────────────────────┘ ``` ### 그룹화된 테이블 표시 ```tsx ┌─────────────────────────────────────────────────────┐ │ 📦 판매품목 목록 총 3개 [🎨 그룹: 통화 ×] │ ├─────────────────────────────────────────────────────┤ │ │ │ ▼ 통화: KRW > 단위: EA (2건) │ │ ┌─────────────────────────────────────────────┐ │ │ │ 품목코드 │ 품목명 │ 규격 │ 단위 │ │ │ ├─────────────────────────────────────────────┤ │ │ │ SALE-001 │ 볼트 M8x20 │ M8x20 │ EA │ │ │ │ SALE-004 │ 스프링 와셔 │ M10 │ EA │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ ▼ 통화: USD > 단위: EA (1건) │ │ ┌─────────────────────────────────────────────┐ │ │ │ 품목코드 │ 품목명 │ 규격 │ 단위 │ │ │ ├─────────────────────────────────────────────┤ │ │ │ SALE-002 │ 너트 M8 │ M8 │ EA │ │ │ └─────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────┘ ``` ## 🔧 기술 구현 ### 1. 상태 관리 ```typescript // 그룹 설정 관련 상태 const [groupByColumns, setGroupByColumns] = useState([]); // 그룹화할 컬럼 목록 const [isGroupSettingOpen, setIsGroupSettingOpen] = useState(false); // 그룹 설정 다이얼로그 const [collapsedGroups, setCollapsedGroups] = useState>(new Set()); // 접힌 그룹 ``` ### 2. 데이터 그룹화 로직 ```typescript interface GroupedData { groupKey: string; // "통화:KRW > 단위:EA" groupValues: Record; // { 통화: "KRW", 단위: "EA" } items: any[]; // 그룹에 속한 데이터 count: number; // 항목 개수 } const groupDataByColumns = ( data: any[], groupColumns: string[] ): GroupedData[] => { if (groupColumns.length === 0) return []; const grouped = new Map(); data.forEach(item => { // 그룹 키 생성: "통화:KRW > 단위:EA" const keyParts = groupColumns.map(col => `${col}:${item[col] || '-'}`); const groupKey = keyParts.join(' > '); if (!grouped.has(groupKey)) { grouped.set(groupKey, []); } grouped.get(groupKey)!.push(item); }); return Array.from(grouped.entries()).map(([groupKey, items]) => { const groupValues: Record = {}; groupColumns.forEach(col => { groupValues[col] = items[0]?.[col]; }); return { groupKey, groupValues, items, count: items.length, }; }); }; ``` ### 3. localStorage 저장/로드 ```typescript // 저장 키 const groupSettingKey = useMemo(() => { if (!tableConfig.selectedTable) return null; return `table-list-group-${tableConfig.selectedTable}`; }, [tableConfig.selectedTable]); // 그룹 설정 저장 const saveGroupSettings = useCallback(() => { if (!groupSettingKey) return; try { localStorage.setItem(groupSettingKey, JSON.stringify(groupByColumns)); setIsGroupSettingOpen(false); toast.success("그룹 설정이 저장되었습니다"); } catch (error) { console.error("그룹 설정 저장 실패:", error); toast.error("설정 저장에 실패했습니다"); } }, [groupSettingKey, groupByColumns]); // 그룹 설정 로드 useEffect(() => { if (!groupSettingKey || visibleColumns.length === 0) return; try { const saved = localStorage.getItem(groupSettingKey); if (saved) { const savedGroups = JSON.parse(saved); setGroupByColumns(savedGroups); } } catch (error) { console.error("그룹 설정 불러오기 실패:", error); } }, [groupSettingKey, visibleColumns]); ``` ### 4. 그룹 헤더 렌더링 ```tsx const renderGroupHeader = (group: GroupedData) => { const isCollapsed = collapsedGroups.has(group.groupKey); return (
toggleGroupCollapse(group.groupKey)} > {/* 펼치기/접기 아이콘 */} {isCollapsed ? ( ) : ( )} {/* 그룹 정보 */} {groupByColumns.map((col, idx) => ( {idx > 0 && > } {columnLabels[col] || col}: {" "} {group.groupValues[col]} ))} {/* 항목 개수 */} ({group.count}건)
); }; ``` ### 5. 그룹 설정 다이얼로그 ```tsx 그룹 설정 데이터를 그룹화할 컬럼을 선택하세요. 여러 컬럼을 선택하면 계층적으로 그룹화됩니다.
{/* 컬럼 목록 */}
{visibleColumns .filter((col) => col.columnName !== "__checkbox__") .map((col) => (
toggleGroupColumn(col.columnName)} />
))}
{/* 선택된 그룹 안내 */}
{groupByColumns.length === 0 ? ( 그룹화할 컬럼을 선택하세요 ) : ( 선택된 그룹: {groupByColumns.map((col) => columnLabels[col] || col).join(" → ")} )}
``` ### 6. 그룹 해제 버튼 ```tsx {/* 헤더 영역 */}

{tableLabel}

{/* 그룹 표시 배지 */} {groupByColumns.length > 0 && (
그룹: {groupByColumns.map(col => columnLabels[col] || col).join(", ")}
)} {/* 그룹 설정 버튼 */}
``` ## 📝 구현 순서 ### Phase 1: TableListComponent 구현 1. ✅ 상태 관리 추가 (groupByColumns, isGroupSettingOpen, collapsedGroups) 2. ✅ 그룹화 로직 구현 (groupDataByColumns 함수) 3. ✅ localStorage 저장/로드 로직 4. ✅ 그룹 설정 다이얼로그 UI 5. ✅ 그룹 헤더 렌더링 6. ✅ 그룹별 데이터 렌더링 7. ✅ 그룹 해제 기능 ### Phase 2: FlowWidget 구현 1. ✅ TableListComponent와 동일한 로직 적용 2. ✅ 스텝 데이터에 그룹화 적용 3. ✅ UI 통일성 유지 ### Phase 3: 테스트 및 최적화 1. ✅ 다중 그룹 계층 테스트 2. ✅ 대량 데이터 성능 테스트 3. ✅ localStorage 저장/복원 테스트 4. ✅ 그룹 펼치기/접기 테스트 ## 🎯 예상 효과 ### 사용자 경험 개선 - 데이터를 논리적으로 그룹화하여 가독성 향상 - 대량 데이터를 효율적으로 탐색 가능 - 사용자 정의 뷰 제공 ### 데이터 분석 지원 - 카테고리별 데이터 분석 용이 - 통계 정보 제공 (그룹별 개수) - 계층적 데이터 구조 시각화 ## ⚠️ 주의사항 ### 성능 고려사항 - 그룹화는 클라이언트 측에서 수행 - 대량 데이터의 경우 성능 저하 가능 - 필요시 서버 측 그룹화로 전환 검토 ### 사용성 - 그룹화 해제가 쉽게 가능해야 함 - 그룹 설정이 직관적이어야 함 - 모바일에서도 사용 가능한 UI ## 📊 구현 상태 - [ ] Phase 1: TableListComponent 구현 - [ ] 상태 관리 추가 - [ ] 그룹화 로직 구현 - [ ] localStorage 연동 - [ ] UI 구현 - [ ] Phase 2: FlowWidget 구현 - [ ] Phase 3: 테스트 및 최적화 --- **작성일**: 2025-11-03 **버전**: 1.0 **상태**: 구현 예정