fix: 분할 패널 컬럼 순서 변경 및 필터링 개선
문제: 1. ColumnVisibilityPanel에서 순서 변경 후 onColumnOrderChange가 호출되지 않음 2. 필터 입력 시 데이터가 제대로 필터링되지 않음 3. useAuth 훅 import 경로 오류 (@/hooks/use-auth → @/hooks/useAuth) 해결: 1. ColumnVisibilityPanel.handleApply()에 onColumnOrderChange 호출 추가 2. 필터 변경 감지 및 데이터 로드 로직 디버깅 로그 추가 3. useAuth import 경로 수정 테스트: - 거래처관리 화면에서 컬럼 순서 변경 → 실시간 반영 ✅ - 페이지 새로고침 → 순서 유지 (localStorage) ✅ - 필터 입력 → 필터 변경 감지 (추가 디버깅 필요)
This commit is contained in:
@@ -85,6 +85,15 @@ export const ColumnVisibilityPanel: React.FC<Props> = ({
|
|||||||
|
|
||||||
const handleApply = () => {
|
const handleApply = () => {
|
||||||
table?.onColumnVisibilityChange(localColumns);
|
table?.onColumnVisibilityChange(localColumns);
|
||||||
|
|
||||||
|
// 컬럼 순서 변경 콜백 호출
|
||||||
|
if (table?.onColumnOrderChange) {
|
||||||
|
const newOrder = localColumns
|
||||||
|
.map((col) => col.columnName)
|
||||||
|
.filter((name) => name !== "__checkbox__");
|
||||||
|
table.onColumnOrderChange(newOrder);
|
||||||
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useCallback, useEffect } from "react";
|
import React, { useState, useCallback, useEffect, useMemo } from "react";
|
||||||
import { ComponentRendererProps } from "../../types";
|
import { ComponentRendererProps } from "../../types";
|
||||||
import { SplitPanelLayoutConfig } from "./types";
|
import { SplitPanelLayoutConfig } from "./types";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
@@ -8,12 +8,14 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp, Save, ChevronRight, Pencil, Trash2 } from "lucide-react";
|
import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp, Save, ChevronRight, Pencil, Trash2 } from "lucide-react";
|
||||||
import { dataApi } from "@/lib/api/data";
|
import { dataApi } from "@/lib/api/data";
|
||||||
|
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { tableTypeApi } from "@/lib/api/screen";
|
import { tableTypeApi } from "@/lib/api/screen";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useTableOptions } from "@/contexts/TableOptionsContext";
|
import { useTableOptions } from "@/contexts/TableOptionsContext";
|
||||||
import { TableFilter, ColumnVisibility } from "@/types/table-options";
|
import { TableFilter, ColumnVisibility } from "@/types/table-options";
|
||||||
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
|
|
||||||
export interface SplitPanelLayoutComponentProps extends ComponentRendererProps {
|
export interface SplitPanelLayoutComponentProps extends ComponentRendererProps {
|
||||||
// 추가 props
|
// 추가 props
|
||||||
@@ -44,6 +46,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||||||
const [leftFilters, setLeftFilters] = useState<TableFilter[]>([]);
|
const [leftFilters, setLeftFilters] = useState<TableFilter[]>([]);
|
||||||
const [leftGrouping, setLeftGrouping] = useState<string[]>([]);
|
const [leftGrouping, setLeftGrouping] = useState<string[]>([]);
|
||||||
const [leftColumnVisibility, setLeftColumnVisibility] = useState<ColumnVisibility[]>([]);
|
const [leftColumnVisibility, setLeftColumnVisibility] = useState<ColumnVisibility[]>([]);
|
||||||
|
const [leftColumnOrder, setLeftColumnOrder] = useState<string[]>([]); // 🔧 컬럼 순서
|
||||||
const [rightFilters, setRightFilters] = useState<TableFilter[]>([]);
|
const [rightFilters, setRightFilters] = useState<TableFilter[]>([]);
|
||||||
const [rightGrouping, setRightGrouping] = useState<string[]>([]);
|
const [rightGrouping, setRightGrouping] = useState<string[]>([]);
|
||||||
const [rightColumnVisibility, setRightColumnVisibility] = useState<ColumnVisibility[]>([]);
|
const [rightColumnVisibility, setRightColumnVisibility] = useState<ColumnVisibility[]>([]);
|
||||||
@@ -160,6 +163,9 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||||||
return rootItems;
|
return rootItems;
|
||||||
}, [componentConfig.leftPanel?.itemAddConfig]);
|
}, [componentConfig.leftPanel?.itemAddConfig]);
|
||||||
|
|
||||||
|
// 🔧 사용자 ID 가져오기
|
||||||
|
const { userId: currentUserId } = useAuth();
|
||||||
|
|
||||||
// 🔄 필터를 searchValues 형식으로 변환
|
// 🔄 필터를 searchValues 형식으로 변환
|
||||||
const searchValues = useMemo(() => {
|
const searchValues = useMemo(() => {
|
||||||
if (!leftFilters || leftFilters.length === 0) return {};
|
if (!leftFilters || leftFilters.length === 0) return {};
|
||||||
@@ -176,22 +182,44 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||||||
return values;
|
return values;
|
||||||
}, [leftFilters]);
|
}, [leftFilters]);
|
||||||
|
|
||||||
// 🔄 컬럼 가시성 처리
|
// 🔄 컬럼 가시성 및 순서 처리
|
||||||
const visibleLeftColumns = useMemo(() => {
|
const visibleLeftColumns = useMemo(() => {
|
||||||
const displayColumns = componentConfig.leftPanel?.columns || [];
|
const displayColumns = componentConfig.leftPanel?.columns || [];
|
||||||
|
console.log("🔍 [분할패널] visibleLeftColumns 계산:", {
|
||||||
|
displayColumns: displayColumns.length,
|
||||||
|
leftColumnVisibility: leftColumnVisibility.length,
|
||||||
|
leftColumnOrder: leftColumnOrder.length,
|
||||||
|
});
|
||||||
|
|
||||||
if (displayColumns.length === 0) return [];
|
if (displayColumns.length === 0) return [];
|
||||||
|
|
||||||
|
let columns = displayColumns;
|
||||||
|
|
||||||
// columnVisibility가 있으면 가시성 적용
|
// columnVisibility가 있으면 가시성 적용
|
||||||
if (leftColumnVisibility.length > 0) {
|
if (leftColumnVisibility.length > 0) {
|
||||||
const visibilityMap = new Map(leftColumnVisibility.map(cv => [cv.columnName, cv.visible]));
|
const visibilityMap = new Map(leftColumnVisibility.map(cv => [cv.columnName, cv.visible]));
|
||||||
return displayColumns.filter((col: any) => {
|
columns = columns.filter((col: any) => {
|
||||||
const colName = typeof col === 'string' ? col : (col.name || col.columnName);
|
const colName = typeof col === 'string' ? col : (col.name || col.columnName);
|
||||||
return visibilityMap.get(colName) !== false;
|
return visibilityMap.get(colName) !== false;
|
||||||
});
|
});
|
||||||
|
console.log("✅ [분할패널] 가시성 적용 후:", columns.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return displayColumns;
|
// 🔧 컬럼 순서 적용
|
||||||
}, [componentConfig.leftPanel?.columns, leftColumnVisibility]);
|
if (leftColumnOrder.length > 0) {
|
||||||
|
const orderMap = new Map(leftColumnOrder.map((name, index) => [name, index]));
|
||||||
|
columns = [...columns].sort((a, b) => {
|
||||||
|
const aName = typeof a === 'string' ? a : (a.name || a.columnName);
|
||||||
|
const bName = typeof b === 'string' ? b : (b.name || b.columnName);
|
||||||
|
const aIndex = orderMap.get(aName) ?? 999;
|
||||||
|
const bIndex = orderMap.get(bName) ?? 999;
|
||||||
|
return aIndex - bIndex;
|
||||||
|
});
|
||||||
|
console.log("✅ [분할패널] 순서 적용 후:", columns.map((c: any) => typeof c === 'string' ? c : (c.name || c.columnName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}, [componentConfig.leftPanel?.columns, leftColumnVisibility, leftColumnOrder]);
|
||||||
|
|
||||||
// 🔄 데이터 그룹화
|
// 🔄 데이터 그룹화
|
||||||
const groupedLeftData = useMemo(() => {
|
const groupedLeftData = useMemo(() => {
|
||||||
@@ -227,13 +255,26 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||||||
|
|
||||||
setIsLoadingLeft(true);
|
setIsLoadingLeft(true);
|
||||||
try {
|
try {
|
||||||
// 🎯 필터 조건을 API에 전달
|
// 🎯 필터 조건을 API에 전달 (entityJoinApi 사용)
|
||||||
const filters = Object.keys(searchValues).length > 0 ? searchValues : undefined;
|
const filters = Object.keys(searchValues).length > 0 ? searchValues : undefined;
|
||||||
|
|
||||||
const result = await dataApi.getTableData(leftTableName, {
|
console.log("📡 [분할패널] API 호출 시작:", {
|
||||||
|
tableName: leftTableName,
|
||||||
|
filters,
|
||||||
|
searchValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await entityJoinApi.getTableDataWithJoins(leftTableName, {
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 100,
|
size: 100,
|
||||||
search: filters, // 필터 조건 전달
|
search: filters, // 필터 조건 전달
|
||||||
|
enableEntityJoin: true, // 엔티티 조인 활성화
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("📡 [분할패널] API 응답:", {
|
||||||
|
success: result.success,
|
||||||
|
dataLength: result.data?.length || 0,
|
||||||
|
totalItems: result.totalItems,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 가나다순 정렬 (좌측 패널의 표시 컬럼 기준)
|
// 가나다순 정렬 (좌측 패널의 표시 컬럼 기준)
|
||||||
@@ -346,6 +387,29 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||||||
[rightTableColumns],
|
[rightTableColumns],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🔧 컬럼의 고유값 가져오기 함수
|
||||||
|
const getLeftColumnUniqueValues = useCallback(async (columnName: string) => {
|
||||||
|
const leftTableName = componentConfig.leftPanel?.tableName;
|
||||||
|
if (!leftTableName || leftData.length === 0) return [];
|
||||||
|
|
||||||
|
// 현재 로드된 데이터에서 고유값 추출
|
||||||
|
const uniqueValues = new Set<string>();
|
||||||
|
|
||||||
|
leftData.forEach((item) => {
|
||||||
|
const value = item[columnName];
|
||||||
|
if (value !== null && value !== undefined && value !== '') {
|
||||||
|
// _name 필드 우선 사용 (category/entity type)
|
||||||
|
const displayValue = item[`${columnName}_name`] || value;
|
||||||
|
uniqueValues.add(String(displayValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(uniqueValues).map(value => ({
|
||||||
|
value: value,
|
||||||
|
label: value,
|
||||||
|
}));
|
||||||
|
}, [componentConfig.leftPanel?.tableName, leftData]);
|
||||||
|
|
||||||
// 좌측 테이블 등록 (Context에 등록)
|
// 좌측 테이블 등록 (Context에 등록)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const leftTableName = componentConfig.leftPanel?.tableName;
|
const leftTableName = componentConfig.leftPanel?.tableName;
|
||||||
@@ -379,10 +443,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||||||
onFilterChange: setLeftFilters,
|
onFilterChange: setLeftFilters,
|
||||||
onGroupChange: setLeftGrouping,
|
onGroupChange: setLeftGrouping,
|
||||||
onColumnVisibilityChange: setLeftColumnVisibility,
|
onColumnVisibilityChange: setLeftColumnVisibility,
|
||||||
|
onColumnOrderChange: setLeftColumnOrder, // 🔧 컬럼 순서 변경 콜백 추가
|
||||||
|
getColumnUniqueValues: getLeftColumnUniqueValues, // 🔧 고유값 가져오기 함수 추가
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => unregisterTable(leftTableId);
|
return () => unregisterTable(leftTableId);
|
||||||
}, [component.id, componentConfig.leftPanel?.tableName, componentConfig.leftPanel?.columns, leftColumnLabels, component.title, isDesignMode]);
|
}, [component.id, componentConfig.leftPanel?.tableName, componentConfig.leftPanel?.columns, leftColumnLabels, component.title, isDesignMode, getLeftColumnUniqueValues]);
|
||||||
|
|
||||||
// 우측 테이블은 검색 컴포넌트 등록 제외 (좌측 마스터 테이블만 검색 가능)
|
// 우측 테이블은 검색 컴포넌트 등록 제외 (좌측 마스터 테이블만 검색 가능)
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
@@ -858,6 +924,51 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||||||
}
|
}
|
||||||
}, [addModalPanel, componentConfig, addModalFormData, toast, selectedLeftItem, loadLeftData, loadRightData]);
|
}, [addModalPanel, componentConfig, addModalFormData, toast, selectedLeftItem, loadLeftData, loadRightData]);
|
||||||
|
|
||||||
|
// 🔧 좌측 컬럼 가시성 설정 저장 및 불러오기
|
||||||
|
useEffect(() => {
|
||||||
|
const leftTableName = componentConfig.leftPanel?.tableName;
|
||||||
|
if (leftTableName && currentUserId) {
|
||||||
|
// localStorage에서 저장된 설정 불러오기
|
||||||
|
const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`;
|
||||||
|
const savedSettings = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
|
if (savedSettings) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(savedSettings) as ColumnVisibility[];
|
||||||
|
setLeftColumnVisibility(parsed);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("저장된 컬럼 설정 불러오기 실패:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [componentConfig.leftPanel?.tableName, currentUserId]);
|
||||||
|
|
||||||
|
// 🔧 컬럼 가시성 변경 시 localStorage에 저장 및 순서 업데이트
|
||||||
|
useEffect(() => {
|
||||||
|
const leftTableName = componentConfig.leftPanel?.tableName;
|
||||||
|
console.log("🔍 [분할패널] 컬럼 가시성 변경 감지:", {
|
||||||
|
leftColumnVisibility: leftColumnVisibility.length,
|
||||||
|
leftTableName,
|
||||||
|
currentUserId,
|
||||||
|
visibility: leftColumnVisibility,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (leftColumnVisibility.length > 0 && leftTableName && currentUserId) {
|
||||||
|
// 순서 업데이트
|
||||||
|
const newOrder = leftColumnVisibility
|
||||||
|
.map((cv) => cv.columnName)
|
||||||
|
.filter((name) => name !== "__checkbox__"); // 체크박스 제외
|
||||||
|
|
||||||
|
console.log("✅ [분할패널] 컬럼 순서 업데이트:", newOrder);
|
||||||
|
setLeftColumnOrder(newOrder);
|
||||||
|
|
||||||
|
// localStorage에 저장
|
||||||
|
const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`;
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(leftColumnVisibility));
|
||||||
|
console.log("💾 [분할패널] localStorage 저장:", storageKey);
|
||||||
|
}
|
||||||
|
}, [leftColumnVisibility, componentConfig.leftPanel?.tableName, currentUserId]);
|
||||||
|
|
||||||
// 초기 데이터 로드
|
// 초기 데이터 로드
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isDesignMode && componentConfig.autoLoad !== false) {
|
if (!isDesignMode && componentConfig.autoLoad !== false) {
|
||||||
@@ -868,7 +979,16 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||||||
|
|
||||||
// 🔄 필터 변경 시 데이터 다시 로드
|
// 🔄 필터 변경 시 데이터 다시 로드
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log("🔍 [분할패널] 필터 변경 감지:", {
|
||||||
|
leftFilters: leftFilters.length,
|
||||||
|
filters: leftFilters,
|
||||||
|
isDesignMode,
|
||||||
|
autoLoad: componentConfig.autoLoad,
|
||||||
|
searchValues,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isDesignMode && componentConfig.autoLoad !== false) {
|
if (!isDesignMode && componentConfig.autoLoad !== false) {
|
||||||
|
console.log("✅ [분할패널] loadLeftData 호출 (필터 변경)");
|
||||||
loadLeftData();
|
loadLeftData();
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
Reference in New Issue
Block a user