From 042488d51bbe9966566da0c6e80920835cf6ed5e Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 27 Jan 2026 11:02:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=8B=A4=EC=A4=91=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9D=BC=EA=B4=84=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 카테고리 값 관리 컴포넌트에 체크박스를 통한 다중 선택 기능을 추가하였습니다. - 선택된 카테고리를 일괄 삭제할 수 있는 다이얼로그를 구현하였습니다. - 테이블 관리 서비스에서 다중 선택 처리 로직을 추가하여, 파이프(|)로 구분된 값을 처리하도록 개선하였습니다. - 관련된 로그 메시지를 추가하여 다중 선택 및 삭제 과정에서의 정보를 기록하도록 하였습니다. --- .../src/services/tableManagementService.ts | 25 + frontend/app/registry-provider.tsx | 2 +- .../table-options/TableSettingsModal.tsx | 668 ++++++++++++++++++ .../CategoryValueManagerTree.tsx | 316 +++++++-- .../v2-table-list/TableListComponent.tsx | 221 +----- .../TableSearchWidget.tsx | 154 +--- frontend/lib/utils/buttonActions.ts | 14 +- .../v2-core/adapters/LegacyEventAdapter.ts | 7 - frontend/lib/v2-core/init.ts | 7 - 9 files changed, 1002 insertions(+), 412 deletions(-) create mode 100644 frontend/components/screen/table-options/TableSettingsModal.tsx diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 5b598422..8fc854c5 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -1465,6 +1465,31 @@ export class TableManagementService { const webType = columnInfo.webType; + // 🔧 다중선택 처리: actualValue가 파이프(|)를 포함하고 날짜 타입이 아닌 경우 + if ( + typeof actualValue === "string" && + actualValue.includes("|") && + webType !== "date" && + webType !== "datetime" + ) { + const multiValues = actualValue + .split("|") + .filter((v: string) => v.trim() !== ""); + if (multiValues.length > 0) { + const placeholders = multiValues + .map((_: string, idx: number) => `$${paramIndex + idx}`) + .join(", "); + logger.info( + `🔍 다중선택 필터 적용 (객체): ${columnName} IN (${multiValues.join(", ")})` + ); + return { + whereClause: `${columnName}::text IN (${placeholders})`, + values: multiValues, + paramCount: multiValues.length, + }; + } + } + // 웹타입별 검색 조건 구성 switch (webType) { case "date": diff --git a/frontend/app/registry-provider.tsx b/frontend/app/registry-provider.tsx index d2bd3e32..1595bbe2 100644 --- a/frontend/app/registry-provider.tsx +++ b/frontend/app/registry-provider.tsx @@ -22,7 +22,7 @@ export function RegistryProvider({ children }: RegistryProviderProps) { // V2 Core 초기화 (느슨한 결합 아키텍처) initV2Core({ - debug: process.env.NODE_ENV === "development", + debug: false, legacyBridge: { legacyToV2: true, v2ToLegacy: true, diff --git a/frontend/components/screen/table-options/TableSettingsModal.tsx b/frontend/components/screen/table-options/TableSettingsModal.tsx new file mode 100644 index 00000000..ef07e017 --- /dev/null +++ b/frontend/components/screen/table-options/TableSettingsModal.tsx @@ -0,0 +1,668 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { useTableOptions } from "@/contexts/TableOptionsContext"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { GripVertical, Eye, EyeOff, Lock, ArrowRight, X, Settings, Filter, Layers } from "lucide-react"; +import { ColumnVisibility, TableFilter, GroupSumConfig } from "@/types/table-options"; + +interface Props { + isOpen: boolean; + onClose: () => void; + onFiltersApplied?: (filters: TableFilter[]) => void; + screenId?: number; +} + +// 컬럼 필터 설정 인터페이스 +interface ColumnFilterConfig { + columnName: string; + columnLabel: string; + inputType: string; + enabled: boolean; + filterType: "text" | "number" | "date" | "select"; + width?: number; + selectOptions?: Array<{ label: string; value: string }>; +} + +export const TableSettingsModal: React.FC = ({ isOpen, onClose, onFiltersApplied, screenId }) => { + const { getTable, selectedTableId } = useTableOptions(); + const table = selectedTableId ? getTable(selectedTableId) : undefined; + + const [activeTab, setActiveTab] = useState("columns"); + + // 컬럼 가시성 상태 + const [localColumns, setLocalColumns] = useState([]); + const [draggedColumnIndex, setDraggedColumnIndex] = useState(null); + const [frozenColumnCount, setFrozenColumnCount] = useState(0); + + // 필터 상태 + const [columnFilters, setColumnFilters] = useState([]); + const [selectAllFilters, setSelectAllFilters] = useState(false); + const [groupSumEnabled, setGroupSumEnabled] = useState(false); + const [groupByColumn, setGroupByColumn] = useState(""); + + // 그룹화 상태 + const [selectedGroupColumns, setSelectedGroupColumns] = useState([]); + const [draggedGroupIndex, setDraggedGroupIndex] = useState(null); + + // 테이블 정보 로드 - 컬럼 가시성 + useEffect(() => { + if (table) { + setLocalColumns( + table.columns.map((col) => ({ + columnName: col.columnName, + visible: col.visible, + width: col.width, + order: 0, + })) + ); + setFrozenColumnCount(table.frozenColumnCount ?? 0); + } + }, [table]); + + // 테이블 정보 로드 - 필터 + useEffect(() => { + if (table?.columns && table?.tableName) { + const storageKey = screenId + ? `table_filters_${table.tableName}_screen_${screenId}` + : `table_filters_${table.tableName}`; + const savedFilters = localStorage.getItem(storageKey); + + const groupSumKey = screenId + ? `table_groupsum_${table.tableName}_screen_${screenId}` + : `table_groupsum_${table.tableName}`; + const savedGroupSum = localStorage.getItem(groupSumKey); + + if (savedGroupSum) { + try { + const parsed = JSON.parse(savedGroupSum) as GroupSumConfig; + setGroupSumEnabled(parsed.enabled); + setGroupByColumn(parsed.groupByColumn || ""); + } catch { + setGroupSumEnabled(false); + setGroupByColumn(""); + } + } + + if (savedFilters) { + try { + const parsed = JSON.parse(savedFilters); + setColumnFilters(parsed); + setSelectAllFilters(parsed.every((f: ColumnFilterConfig) => f.enabled)); + } catch { + initializeFilters(); + } + } else { + initializeFilters(); + } + } + }, [table?.columns, table?.tableName, screenId]); + + const initializeFilters = () => { + if (!table?.columns) return; + + const filters: ColumnFilterConfig[] = table.columns + .filter((col) => col.columnName !== "__checkbox__") + .map((col) => { + let filterType: "text" | "number" | "date" | "select" = "text"; + const inputType = col.inputType || ""; + + if (["number", "decimal", "currency", "integer"].includes(inputType)) { + filterType = "number"; + } else if (["date", "datetime", "time"].includes(inputType)) { + filterType = "date"; + } else if (["select", "dropdown", "code", "category", "entity"].includes(inputType)) { + filterType = "select"; + } + + return { + columnName: col.columnName, + columnLabel: col.columnLabel, + inputType, + enabled: false, + filterType, + width: 200, + }; + }); + + setColumnFilters(filters); + setSelectAllFilters(false); + }; + + // 컬럼 가시성 핸들러 + const handleVisibilityChange = (columnName: string, visible: boolean) => { + setLocalColumns((prev) => + prev.map((col) => (col.columnName === columnName ? { ...col, visible } : col)) + ); + }; + + const handleWidthChange = (columnName: string, width: number) => { + setLocalColumns((prev) => + prev.map((col) => (col.columnName === columnName ? { ...col, width } : col)) + ); + }; + + const moveColumn = (fromIndex: number, toIndex: number) => { + const newColumns = [...localColumns]; + const [movedItem] = newColumns.splice(fromIndex, 1); + newColumns.splice(toIndex, 0, movedItem); + setLocalColumns(newColumns); + }; + + // 필터 핸들러 + const handleFilterEnabledChange = (columnName: string, enabled: boolean) => { + setColumnFilters((prev) => + prev.map((f) => (f.columnName === columnName ? { ...f, enabled } : f)) + ); + }; + + const handleFilterTypeChange = (columnName: string, filterType: "text" | "number" | "date" | "select") => { + setColumnFilters((prev) => + prev.map((f) => (f.columnName === columnName ? { ...f, filterType } : f)) + ); + }; + + const handleFilterWidthChange = (columnName: string, width: number) => { + setColumnFilters((prev) => + prev.map((f) => (f.columnName === columnName ? { ...f, width } : f)) + ); + }; + + const handleSelectAll = (checked: boolean) => { + setSelectAllFilters(checked); + setColumnFilters((prev) => prev.map((f) => ({ ...f, enabled: checked }))); + }; + + // 그룹화 핸들러 + const toggleGroupColumn = (columnName: string) => { + if (selectedGroupColumns.includes(columnName)) { + setSelectedGroupColumns(selectedGroupColumns.filter((c) => c !== columnName)); + } else { + setSelectedGroupColumns([...selectedGroupColumns, columnName]); + } + }; + + const removeGroupColumn = (columnName: string) => { + setSelectedGroupColumns(selectedGroupColumns.filter((c) => c !== columnName)); + }; + + const moveGroupColumn = (fromIndex: number, toIndex: number) => { + const newColumns = [...selectedGroupColumns]; + const [movedItem] = newColumns.splice(fromIndex, 1); + newColumns.splice(toIndex, 0, movedItem); + setSelectedGroupColumns(newColumns); + }; + + const clearGrouping = () => { + setSelectedGroupColumns([]); + table?.onGroupChange([]); + }; + + // 틀고정 컬럼 수 변경 핸들러 + const handleFrozenColumnCountChange = (value: string) => { + const count = parseInt(value) || 0; + // 최대값은 표시 가능한 컬럼 수 + const maxCount = localColumns.filter((col) => col.visible).length; + setFrozenColumnCount(Math.min(Math.max(0, count), maxCount)); + }; + + const visibleCount = localColumns.filter((col) => col.visible).length; + + // 저장 + const handleSave = () => { + if (!table) return; + + // 1. 컬럼 가시성 저장 + table.onColumnVisibilityChange(localColumns); + + // 2. 컬럼 순서 변경 콜백 호출 + if (table.onColumnOrderChange) { + const newOrder = localColumns + .map((col) => col.columnName) + .filter((name) => name !== "__checkbox__"); + table.onColumnOrderChange(newOrder); + } + + // 3. 틀고정 컬럼 수 변경 콜백 호출 (현재 컬럼 상태도 함께 전달) + if (table.onFrozenColumnCountChange) { + const updatedColumns = localColumns.map((col) => ({ + columnName: col.columnName, + visible: col.visible, + })); + table.onFrozenColumnCountChange(frozenColumnCount, updatedColumns); + } + + // 2. 필터 설정 저장 + const storageKey = screenId + ? `table_filters_${table.tableName}_screen_${screenId}` + : `table_filters_${table.tableName}`; + localStorage.setItem(storageKey, JSON.stringify(columnFilters)); + + // 그룹별 합산 설정 저장 + const groupSumKey = screenId + ? `table_groupsum_${table.tableName}_screen_${screenId}` + : `table_groupsum_${table.tableName}`; + const groupSumConfig: GroupSumConfig = { + enabled: groupSumEnabled, + groupByColumn: groupByColumn || undefined, + }; + localStorage.setItem(groupSumKey, JSON.stringify(groupSumConfig)); + + // 활성화된 필터만 콜백 + const activeFilters: TableFilter[] = columnFilters + .filter((f) => f.enabled) + .map((f) => ({ + columnName: f.columnName, + operator: "contains", + value: "", + filterType: f.filterType, + width: f.width || 200, + })); + onFiltersApplied?.(activeFilters); + + // 3. 그룹화 저장 + table.onGroupChange(selectedGroupColumns); + + onClose(); + }; + + return ( + + + + 테이블 설정 + + 테이블의 컬럼, 필터, 그룹화를 설정합니다 + + + + + + + + 컬럼 설정 + + + + 필터 설정 + + + + 그룹 설정 + + + + {/* 컬럼 설정 탭 */} + +
+ {/* 상태 표시 및 틀고정 설정 */} +
+
+
+ {visibleCount}/{localColumns.length}개 컬럼 표시 중 +
+ + {/* 틀고정 설정 */} +
+ + + handleFrozenColumnCountChange(e.target.value)} + className="h-7 w-16 text-xs sm:h-8 sm:w-20 sm:text-sm" + min={0} + max={visibleCount} + placeholder="0" + /> + + 개 컬럼 + +
+
+ + +
+ + {/* 컬럼 목록 */} + +
+ {localColumns.map((col, index) => { + const originalCol = table?.columns.find((c) => c.columnName === col.columnName); + if (!originalCol) return null; + + // 표시 가능한 컬럼 중 몇 번째인지 계산 (틀고정 표시용) + const visibleIndex = localColumns + .slice(0, index + 1) + .filter((c) => c.visible).length; + const isFrozen = col.visible && visibleIndex <= frozenColumnCount; + + return ( +
setDraggedColumnIndex(index)} + onDragOver={(e) => { + e.preventDefault(); + if (draggedColumnIndex !== null && draggedColumnIndex !== index) { + moveColumn(draggedColumnIndex, index); + setDraggedColumnIndex(index); + } + }} + onDragEnd={() => setDraggedColumnIndex(null)} + className={`flex cursor-move items-center gap-3 rounded-lg border p-3 transition-colors ${ + isFrozen + ? "border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950/30" + : "bg-background hover:bg-muted/50" + }`} + > + {/* 드래그 핸들 */} + + + {/* 체크박스 */} + + handleVisibilityChange(col.columnName, checked as boolean) + } + /> + + {/* 가시성/틀고정 아이콘 */} + {isFrozen ? ( + + ) : col.visible ? ( + + ) : ( + + )} + + {/* 컬럼명 */} +
+
+ + {originalCol.columnLabel} + + {isFrozen && ( + + (고정) + + )} +
+
+ {col.columnName} +
+
+ + {/* 너비 설정 */} +
+ + handleWidthChange(col.columnName, parseInt(e.target.value) || 150)} + className="h-7 w-16 text-xs sm:h-8 sm:w-20 sm:text-sm" + min={50} + max={500} + /> +
+
+ ); + })} +
+
+
+
+ + {/* 필터 설정 탭 */} + +
+ {/* 전체 선택 */} +
+ handleSelectAll(checked as boolean)} + /> + +
+ + {/* 필터 목록 */} + +
+ {columnFilters.map((filter) => ( +
+ + handleFilterEnabledChange(filter.columnName, checked as boolean) + } + /> +
+
+ {filter.columnLabel} +
+
+ + + handleFilterWidthChange(filter.columnName, parseInt(e.target.value) || 200) + } + className="h-7 w-16 text-center text-xs" + /> + px +
+ ))} +
+
+ + {/* 그룹별 합산 설정 */} +
+
+
+
그룹별 합산
+
+ 같은 값끼리 그룹핑하여 합산 +
+
+ +
+ {groupSumEnabled && ( +
+ +
+ )} +
+
+
+ + {/* 그룹 설정 탭 */} + +
+ {/* 선택된 그룹화 컬럼 */} + {selectedGroupColumns.length > 0 && ( +
+
+
+ 그룹화 순서 ({selectedGroupColumns.length}개) +
+ +
+
+ {selectedGroupColumns.map((colName, index) => { + const col = table?.columns.find((c) => c.columnName === colName); + if (!col) return null; + + return ( +
setDraggedGroupIndex(index)} + onDragOver={(e) => { + e.preventDefault(); + if (draggedGroupIndex !== null && draggedGroupIndex !== index) { + moveGroupColumn(draggedGroupIndex, index); + setDraggedGroupIndex(index); + } + }} + onDragEnd={() => setDraggedGroupIndex(null)} + className="hover:bg-primary/10 bg-primary/5 flex cursor-move items-center gap-2 rounded-lg border p-2 transition-colors" + > + +
+ {index + 1} +
+
+
{col.columnLabel}
+
+ +
+ ); + })} +
+ + {/* 그룹화 순서 미리보기 */} +
+
+ {selectedGroupColumns.map((colName, index) => { + const col = table?.columns.find((c) => c.columnName === colName); + return ( + + {col?.columnLabel} + {index < selectedGroupColumns.length - 1 && ( + + )} + + ); + })} +
+
+
+ )} + + {/* 사용 가능한 컬럼 */} +
+
사용 가능한 컬럼
+ 0 ? "h-[200px]" : "h-[320px]"}> +
+ {table?.columns + .filter((col) => !selectedGroupColumns.includes(col.columnName)) + .map((col) => ( +
toggleGroupColumn(col.columnName)} + > + toggleGroupColumn(col.columnName)} + className="flex-shrink-0" + /> +
+
{col.columnLabel}
+
+ {col.columnName} +
+
+
+ ))} +
+
+
+
+
+
+ + + + + +
+
+ ); +}; + diff --git a/frontend/components/table-category/CategoryValueManagerTree.tsx b/frontend/components/table-category/CategoryValueManagerTree.tsx index b65c2247..d4da04fc 100644 --- a/frontend/components/table-category/CategoryValueManagerTree.tsx +++ b/frontend/components/table-category/CategoryValueManagerTree.tsx @@ -3,9 +3,10 @@ /** * 카테고리 값 관리 - 트리 구조 버전 * - 3단계 트리 구조 지원 (대분류/중분류/소분류) + * - 체크박스를 통한 다중 선택 및 일괄 삭제 지원 */ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { ChevronRight, ChevronDown, @@ -20,6 +21,7 @@ import { } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Checkbox } from "@/components/ui/checkbox"; import { cn } from "@/lib/utils"; import { CategoryValue, @@ -65,11 +67,13 @@ interface TreeNodeProps { expandedNodes: Set; selectedValueId?: number; searchQuery: string; + checkedIds: Set; onToggle: (valueId: number) => void; onSelect: (value: CategoryValue) => void; onAdd: (parentValue: CategoryValue | null) => void; onEdit: (value: CategoryValue) => void; onDelete: (value: CategoryValue) => void; + onCheck: (valueId: number, checked: boolean) => void; } // 검색어가 노드 또는 하위에 매칭되는지 확인 @@ -90,15 +94,18 @@ const TreeNode: React.FC = ({ expandedNodes, selectedValueId, searchQuery, + checkedIds, onToggle, onSelect, onAdd, onEdit, onDelete, + onCheck, }) => { const hasChildren = node.children && node.children.length > 0; const isExpanded = expandedNodes.has(node.valueId); const isSelected = selectedValueId === node.valueId; + const isChecked = checkedIds.has(node.valueId); const canAddChild = node.depth < 3; // 검색 필터링 @@ -138,11 +145,22 @@ const TreeNode: React.FC = ({ className={cn( "group flex items-center gap-1 rounded-md px-2 py-2 transition-colors", isSelected ? "border-primary bg-primary/10 border-l-2" : "hover:bg-muted/50", + isChecked && "bg-primary/5", "cursor-pointer", )} style={{ paddingLeft: `${level * 20 + 8}px` }} onClick={() => onSelect(node)} > + {/* 체크박스 */} + { + onCheck(node.valueId, checked as boolean); + }} + onClick={(e) => e.stopPropagation()} + className="mr-1" + /> + {/* 확장 토글 */} +
+

{columnLabel} 카테고리

+ {checkedIds.size > 0 && ( + + {checkedIds.size}개 선택 + + )} +
+
+ {checkedIds.size > 0 && ( + <> + + + + )} + +
{/* 툴바 */}
{/* 검색 */}
- + =
+ -
@@ -568,18 +744,19 @@ export const CategoryValueManagerTree: React.FC = expandedNodes={expandedNodes} selectedValueId={selectedValue?.valueId} searchQuery={searchQuery} + checkedIds={checkedIds} onToggle={handleToggle} onSelect={setSelectedValue} onAdd={handleOpenAddModal} onEdit={handleOpenEditModal} onDelete={handleOpenDeleteDialog} + onCheck={handleCheck} /> ))}
)} - {/* 추가 모달 */} @@ -588,7 +765,9 @@ export const CategoryValueManagerTree: React.FC = {parentValue ? `"${parentValue.valueLabel}" 하위 추가` : "대분류 추가"} - {parentValue ? `${parentValue.depth + 1}단계 카테고리를 추가합니다` : "1단계 대분류 카테고리를 추가합니다"} + {parentValue + ? `${parentValue.depth + 1}단계 카테고리를 추가합니다` + : "1단계 대분류 카테고리를 추가합니다"} @@ -631,7 +810,11 @@ export const CategoryValueManagerTree: React.FC = - - - - - - + )} - {/* 패널들 */} - setColumnVisibilityOpen(false)} /> - setFilterOpen(false)} + {/* 통합 설정 모달 */} + setSettingsOpen(false)} onFiltersApplied={(filters) => setActiveFilters(filters)} screenId={screenId} /> - setGroupingOpen(false)} /> ); } diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index cb5bdfcc..4e6d2ac4 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -529,8 +529,6 @@ export class ButtonActionExecutor { // 약간의 대기 시간을 주어 이벤트 핸들러가 formData를 업데이트할 수 있도록 함 await new Promise((resolve) => setTimeout(resolve, 100)); - console.log("📦 [handleSave] beforeFormSave 이벤트 후 formData keys:", Object.keys(context.formData || {})); - // 검증 실패 시 저장 중단 if (beforeSaveEventDetail.validationFailed) { console.log("❌ [handleSave] 검증 실패로 저장 중단:", beforeSaveEventDetail.validationErrors); @@ -549,13 +547,13 @@ export class ButtonActionExecutor { ); if (hasTableSectionData) { - console.log("📋 [handleSave] _tableSection_ 데이터 감지 - onSave 콜백 건너뛰고 테이블 섹션 저장 로직 사용"); + } // 🆕 EditModal 등에서 전달된 onSave 콜백이 있으면 우선 사용 // 단, _tableSection_ 데이터가 있으면 건너뛰기 (handleUniversalFormModalTableSectionSave가 처리) if (onSave && !hasTableSectionData) { - console.log("✅ [handleSave] onSave 콜백 발견 - 콜백 실행 (테이블 섹션 데이터 없음)"); + try { await onSave(); return true; @@ -2214,14 +2212,6 @@ export class ButtonActionExecutor { // 섹션별 원본 데이터가 있으면 사용, 없으면 전역 originalGroupedData 사용 const originalDataForDelete = sectionOriginalData.length > 0 ? sectionOriginalData : originalGroupedData; - console.log(`🔍 [DELETE 비교] 섹션 ${sectionId}:`, { - sectionOriginalKey, - sectionOriginalCount: sectionOriginalData.length, - globalOriginalCount: originalGroupedData.length, - usingData: sectionOriginalData.length > 0 ? "섹션별 원본" : "전역 원본", - currentCount: currentItems.length, - }); - // ⚠️ id 타입 통일: 문자열로 변환하여 비교 (숫자 vs 문자열 불일치 방지) const currentIds = new Set(currentItems.map((item) => String(item.id)).filter(Boolean)); const deletedItems = originalDataForDelete.filter((orig) => orig.id && !currentIds.has(String(orig.id))); diff --git a/frontend/lib/v2-core/adapters/LegacyEventAdapter.ts b/frontend/lib/v2-core/adapters/LegacyEventAdapter.ts index b1779b5f..f37aa726 100644 --- a/frontend/lib/v2-core/adapters/LegacyEventAdapter.ts +++ b/frontend/lib/v2-core/adapters/LegacyEventAdapter.ts @@ -319,8 +319,6 @@ class LegacyEventAdapter { this.config = { ...this.config, ...options }; } - console.log("[LegacyEventAdapter] 초기화 시작", this.config); - EVENT_MAPPINGS.forEach((mapping) => { // 레거시 → V2 브릿지 if (this.config.legacyToV2) { @@ -334,9 +332,6 @@ class LegacyEventAdapter { }); this.isActive = true; - console.log( - `[LegacyEventAdapter] 초기화 완료 (${EVENT_MAPPINGS.length}개 매핑)` - ); } private setupLegacyToV2Bridge(mapping: EventMapping): void { @@ -411,8 +406,6 @@ class LegacyEventAdapter { this.bridgedEvents.clear(); this.isActive = false; - - console.log("[LegacyEventAdapter] 정리 완료"); } /** diff --git a/frontend/lib/v2-core/init.ts b/frontend/lib/v2-core/init.ts index 93eabb80..8ec5dc11 100644 --- a/frontend/lib/v2-core/init.ts +++ b/frontend/lib/v2-core/init.ts @@ -55,8 +55,6 @@ export function initV2Core(options?: V2CoreOptions): void { legacyBridge = { legacyToV2: true, v2ToLegacy: true }, } = options ?? {}; - console.log("[V2Core] 초기화 시작..."); - // 디버그 모드 설정 v2EventBus.debug = debug; @@ -64,11 +62,6 @@ export function initV2Core(options?: V2CoreOptions): void { legacyEventAdapter.init(legacyBridge); isInitialized = true; - - console.log("[V2Core] 초기화 완료", { - debug, - legacyBridge: legacyEventAdapter.active, - }); } /**