Compare commits
4 Commits
cb9c90fcdb
...
8b3017224f
| Author | SHA1 | Date |
|---|---|---|
|
|
8b3017224f | |
|
|
e8be871d69 | |
|
|
cb0bbd1ff3 | |
|
|
676ec16879 |
|
|
@ -403,18 +403,25 @@ export class EntityJoinService {
|
||||||
const fromClause = `FROM ${tableName} main`;
|
const fromClause = `FROM ${tableName} main`;
|
||||||
|
|
||||||
// LEFT JOIN 절들 (위에서 생성한 별칭 매핑 사용, 각 sourceColumn마다 별도 JOIN)
|
// LEFT JOIN 절들 (위에서 생성한 별칭 매핑 사용, 각 sourceColumn마다 별도 JOIN)
|
||||||
|
// 멀티테넌시: 모든 조인에 company_code 조건 추가 (다른 회사 데이터 혼합 방지)
|
||||||
const joinClauses = uniqueReferenceTableConfigs
|
const joinClauses = uniqueReferenceTableConfigs
|
||||||
.map((config) => {
|
.map((config) => {
|
||||||
const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
|
const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
|
||||||
const alias = aliasMap.get(aliasKey);
|
const alias = aliasMap.get(aliasKey);
|
||||||
|
|
||||||
// table_column_category_values는 특별한 조인 조건 필요 (회사별 필터링만)
|
// table_column_category_values는 특별한 조인 조건 필요 (회사별 필터링)
|
||||||
if (config.referenceTable === "table_column_category_values") {
|
if (config.referenceTable === "table_column_category_values") {
|
||||||
// 멀티테넌시: 회사 데이터만 사용 (공통 데이터 제외)
|
|
||||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn} AND ${alias}.table_name = '${tableName}' AND ${alias}.column_name = '${config.sourceColumn}' AND ${alias}.company_code = main.company_code AND ${alias}.is_active = true`;
|
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn} AND ${alias}.table_name = '${tableName}' AND ${alias}.column_name = '${config.sourceColumn}' AND ${alias}.company_code = main.company_code AND ${alias}.is_active = true`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// user_info는 전역 테이블이므로 company_code 조건 없이 조인
|
||||||
|
if (config.referenceTable === "user_info") {
|
||||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn}`;
|
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 일반 테이블: company_code가 있으면 같은 회사 데이터만 조인 (멀티테넌시)
|
||||||
|
// supplier_mng, customer_mng, item_info 등 회사별 데이터 테이블
|
||||||
|
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn} AND ${alias}.company_code = main.company_code`;
|
||||||
})
|
})
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,11 +149,12 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
|
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
|
||||||
|
|
||||||
// 저장 전에 현재 화면의 테이블명과 menuObjid 자동 설정
|
// 저장 전에 현재 화면의 테이블명과 menuObjid 자동 설정
|
||||||
|
// 메뉴 기반으로 채번규칙 관리 (menuObjid로 필터링)
|
||||||
const ruleToSave = {
|
const ruleToSave = {
|
||||||
...currentRule,
|
...currentRule,
|
||||||
scopeType: "table" as const, // ⚠️ 임시: DB 제약 조건 때문에 table 유지
|
scopeType: "menu" as const, // 메뉴 기반 채번규칙
|
||||||
tableName: currentTableName || currentRule.tableName || null, // 현재 테이블명 자동 설정 (빈 값은 null)
|
tableName: currentTableName || currentRule.tableName || null, // 현재 테이블명 (참고용)
|
||||||
menuObjid: menuObjid || currentRule.menuObjid || null, // 🆕 메뉴 OBJID 설정 (필터링용)
|
menuObjid: menuObjid || currentRule.menuObjid || null, // 메뉴 OBJID (필터링 기준)
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("💾 채번 규칙 저장:", {
|
console.log("💾 채번 규칙 저장:", {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState, useMemo, useCallback } from "react";
|
import React, { useEffect, useState, useMemo, useCallback, useRef } from "react";
|
||||||
import { ComponentRendererProps } from "@/types/component";
|
import { ComponentRendererProps } from "@/types/component";
|
||||||
import { CardDisplayConfig } from "./types";
|
import { CardDisplayConfig } from "./types";
|
||||||
import { tableTypeApi } from "@/lib/api/screen";
|
import { tableTypeApi } from "@/lib/api/screen";
|
||||||
|
|
@ -13,6 +13,8 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { useScreenContextOptional } from "@/contexts/ScreenContext";
|
import { useScreenContextOptional } from "@/contexts/ScreenContext";
|
||||||
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
|
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
|
||||||
import { useModalDataStore } from "@/stores/modalDataStore";
|
import { useModalDataStore } from "@/stores/modalDataStore";
|
||||||
|
import { useTableOptions } from "@/contexts/TableOptionsContext";
|
||||||
|
import { TableFilter, ColumnVisibility, TableColumn } from "@/types/table-options";
|
||||||
|
|
||||||
export interface CardDisplayComponentProps extends ComponentRendererProps {
|
export interface CardDisplayComponentProps extends ComponentRendererProps {
|
||||||
config?: CardDisplayConfig;
|
config?: CardDisplayConfig;
|
||||||
|
|
@ -48,11 +50,32 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
const splitPanelContext = useSplitPanelContext();
|
const splitPanelContext = useSplitPanelContext();
|
||||||
const splitPanelPosition = screenContext?.splitPanelPosition;
|
const splitPanelPosition = screenContext?.splitPanelPosition;
|
||||||
|
|
||||||
|
// TableOptions Context (검색 필터 위젯 연동용)
|
||||||
|
let tableOptionsContext: ReturnType<typeof useTableOptions> | null = null;
|
||||||
|
try {
|
||||||
|
tableOptionsContext = useTableOptions();
|
||||||
|
} catch (e) {
|
||||||
|
// Context가 없으면 (디자이너 모드) 무시
|
||||||
|
}
|
||||||
|
|
||||||
// 테이블 데이터 상태 관리
|
// 테이블 데이터 상태 관리
|
||||||
const [loadedTableData, setLoadedTableData] = useState<any[]>([]);
|
const [loadedTableData, setLoadedTableData] = useState<any[]>([]);
|
||||||
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
|
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// 필터 상태 (검색 필터 위젯에서 전달받은 필터)
|
||||||
|
const [filters, setFiltersInternal] = useState<TableFilter[]>([]);
|
||||||
|
|
||||||
|
// 필터 상태 변경 래퍼 (로깅용)
|
||||||
|
const setFilters = useCallback((newFilters: TableFilter[]) => {
|
||||||
|
console.log("🎴 [CardDisplay] setFilters 호출됨:", {
|
||||||
|
componentId: component.id,
|
||||||
|
filtersCount: newFilters.length,
|
||||||
|
filters: newFilters,
|
||||||
|
});
|
||||||
|
setFiltersInternal(newFilters);
|
||||||
|
}, [component.id]);
|
||||||
|
|
||||||
// 카테고리 매핑 상태 (카테고리 코드 -> 라벨/색상)
|
// 카테고리 매핑 상태 (카테고리 코드 -> 라벨/색상)
|
||||||
const [columnMeta, setColumnMeta] = useState<
|
const [columnMeta, setColumnMeta] = useState<
|
||||||
Record<string, { webType?: string; codeCategory?: string; inputType?: string }>
|
Record<string, { webType?: string; codeCategory?: string; inputType?: string }>
|
||||||
|
|
@ -380,6 +403,195 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
}
|
}
|
||||||
}, [screenContext, component.id, dataProvider]);
|
}, [screenContext, component.id, dataProvider]);
|
||||||
|
|
||||||
|
// TableOptionsContext에 테이블 등록 (검색 필터 위젯 연동용)
|
||||||
|
const tableId = `card-display-${component.id}`;
|
||||||
|
const tableNameToUse = tableName || component.componentConfig?.tableName || '';
|
||||||
|
const tableLabel = component.componentConfig?.title || component.label || "카드 디스플레이";
|
||||||
|
|
||||||
|
// ref로 최신 데이터 참조 (useCallback 의존성 문제 해결)
|
||||||
|
const loadedTableDataRef = useRef(loadedTableData);
|
||||||
|
const categoryMappingsRef = useRef(categoryMappings);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadedTableDataRef.current = loadedTableData;
|
||||||
|
}, [loadedTableData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
categoryMappingsRef.current = categoryMappings;
|
||||||
|
}, [categoryMappings]);
|
||||||
|
|
||||||
|
// 필터가 변경되면 데이터 다시 로드 (테이블 리스트와 동일한 패턴)
|
||||||
|
// 초기 로드 여부 추적
|
||||||
|
const isInitialLoadRef = useRef(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tableNameToUse || isDesignMode) return;
|
||||||
|
|
||||||
|
// 초기 로드는 별도 useEffect에서 처리하므로 스킵
|
||||||
|
if (isInitialLoadRef.current) {
|
||||||
|
isInitialLoadRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadFilteredData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// 필터 값을 검색 파라미터로 변환
|
||||||
|
const searchParams: Record<string, any> = {};
|
||||||
|
filters.forEach(filter => {
|
||||||
|
if (filter.value !== undefined && filter.value !== null && filter.value !== '') {
|
||||||
|
searchParams[filter.columnName] = filter.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("🔍 [CardDisplay] 필터 적용 데이터 로드:", {
|
||||||
|
tableName: tableNameToUse,
|
||||||
|
filtersCount: filters.length,
|
||||||
|
searchParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
// search 파라미터로 검색 조건 전달 (API 스펙에 맞게)
|
||||||
|
const dataResponse = await tableTypeApi.getTableData(tableNameToUse, {
|
||||||
|
page: 1,
|
||||||
|
size: 50,
|
||||||
|
search: searchParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
setLoadedTableData(dataResponse.data);
|
||||||
|
|
||||||
|
// 데이터 건수 업데이트
|
||||||
|
if (tableOptionsContext) {
|
||||||
|
tableOptionsContext.updateTableDataCount(tableId, dataResponse.data?.length || 0);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ [CardDisplay] 필터 적용 실패:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 필터 변경 시 항상 데이터 다시 로드 (빈 필터 = 전체 데이터)
|
||||||
|
loadFilteredData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [filters, tableNameToUse, isDesignMode, tableId]);
|
||||||
|
|
||||||
|
// 컬럼 고유 값 조회 함수 (select 타입 필터용)
|
||||||
|
const getColumnUniqueValues = useCallback(async (columnName: string): Promise<Array<{ label: string; value: string }>> => {
|
||||||
|
if (!tableNameToUse) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 현재 로드된 데이터에서 고유 값 추출
|
||||||
|
const uniqueValues = new Set<string>();
|
||||||
|
loadedTableDataRef.current.forEach(row => {
|
||||||
|
const value = row[columnName];
|
||||||
|
if (value !== null && value !== undefined && value !== '') {
|
||||||
|
uniqueValues.add(String(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 카테고리 매핑이 있으면 라벨 적용
|
||||||
|
const mapping = categoryMappingsRef.current[columnName];
|
||||||
|
return Array.from(uniqueValues).map(value => ({
|
||||||
|
value,
|
||||||
|
label: mapping?.[value]?.label || value,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ [CardDisplay] 고유 값 조회 실패: ${columnName}`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, [tableNameToUse]);
|
||||||
|
|
||||||
|
// TableOptionsContext에 등록
|
||||||
|
// registerTable과 unregisterTable 함수 참조 저장 (의존성 안정화)
|
||||||
|
const registerTableRef = useRef(tableOptionsContext?.registerTable);
|
||||||
|
const unregisterTableRef = useRef(tableOptionsContext?.unregisterTable);
|
||||||
|
|
||||||
|
// setFiltersInternal을 ref로 저장 (등록 시 최신 함수 사용)
|
||||||
|
const setFiltersRef = useRef(setFiltersInternal);
|
||||||
|
const getColumnUniqueValuesRef = useRef(getColumnUniqueValues);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
registerTableRef.current = tableOptionsContext?.registerTable;
|
||||||
|
unregisterTableRef.current = tableOptionsContext?.unregisterTable;
|
||||||
|
}, [tableOptionsContext]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFiltersRef.current = setFiltersInternal;
|
||||||
|
}, [setFiltersInternal]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getColumnUniqueValuesRef.current = getColumnUniqueValues;
|
||||||
|
}, [getColumnUniqueValues]);
|
||||||
|
|
||||||
|
// 테이블 등록 (한 번만 실행, 컬럼 변경 시에만 재등록)
|
||||||
|
const columnsKey = JSON.stringify(loadedTableColumns.map((col: any) => col.columnName || col.column_name));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!registerTableRef.current || !unregisterTableRef.current) return;
|
||||||
|
if (isDesignMode || !tableNameToUse || loadedTableColumns.length === 0) return;
|
||||||
|
|
||||||
|
// 컬럼 정보를 TableColumn 형식으로 변환
|
||||||
|
const columns: TableColumn[] = loadedTableColumns.map((col: any) => ({
|
||||||
|
columnName: col.columnName || col.column_name,
|
||||||
|
columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
|
||||||
|
inputType: columnMeta[col.columnName || col.column_name]?.inputType || 'text',
|
||||||
|
visible: true,
|
||||||
|
width: 200,
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// onFilterChange는 ref를 통해 최신 함수를 호출하는 래퍼 사용
|
||||||
|
const onFilterChangeWrapper = (newFilters: TableFilter[]) => {
|
||||||
|
console.log("🎴 [CardDisplay] onFilterChange 래퍼 호출:", {
|
||||||
|
tableId,
|
||||||
|
filtersCount: newFilters.length,
|
||||||
|
});
|
||||||
|
setFiltersRef.current(newFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getColumnUniqueValuesWrapper = async (columnName: string) => {
|
||||||
|
return getColumnUniqueValuesRef.current(columnName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registration = {
|
||||||
|
tableId,
|
||||||
|
label: tableLabel,
|
||||||
|
tableName: tableNameToUse,
|
||||||
|
columns,
|
||||||
|
dataCount: loadedTableData.length,
|
||||||
|
onFilterChange: onFilterChangeWrapper,
|
||||||
|
onGroupChange: () => {}, // 카드 디스플레이는 그룹핑 미지원
|
||||||
|
onColumnVisibilityChange: () => {}, // 카드 디스플레이는 컬럼 가시성 미지원
|
||||||
|
getColumnUniqueValues: getColumnUniqueValuesWrapper,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("📋 [CardDisplay] TableOptionsContext에 등록:", {
|
||||||
|
tableId,
|
||||||
|
tableName: tableNameToUse,
|
||||||
|
columnsCount: columns.length,
|
||||||
|
dataCount: loadedTableData.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTableRef.current(registration);
|
||||||
|
|
||||||
|
const unregister = unregisterTableRef.current;
|
||||||
|
const currentTableId = tableId;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
console.log("📋 [CardDisplay] TableOptionsContext에서 해제:", currentTableId);
|
||||||
|
unregister(currentTableId);
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [
|
||||||
|
isDesignMode,
|
||||||
|
tableId,
|
||||||
|
tableNameToUse,
|
||||||
|
tableLabel,
|
||||||
|
columnsKey, // 컬럼 변경 시에만 재등록
|
||||||
|
]);
|
||||||
|
|
||||||
// 로딩 중인 경우 로딩 표시
|
// 로딩 중인 경우 로딩 표시
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef, useMemo } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Settings, Filter, Layers, X, Check, ChevronsUpDown } from "lucide-react";
|
import { Settings, Filter, Layers, X, Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
|
@ -41,6 +41,7 @@ interface TableSearchWidgetProps {
|
||||||
showTableSelector?: boolean; // 테이블 선택 드롭다운 표시 여부
|
showTableSelector?: boolean; // 테이블 선택 드롭다운 표시 여부
|
||||||
filterMode?: "dynamic" | "preset"; // 필터 모드
|
filterMode?: "dynamic" | "preset"; // 필터 모드
|
||||||
presetFilters?: PresetFilter[]; // 고정 필터 목록
|
presetFilters?: PresetFilter[]; // 고정 필터 목록
|
||||||
|
targetPanelPosition?: "left" | "right" | "auto"; // 분할 패널에서 대상 패널 위치 (기본: "left")
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
screenId?: number; // 화면 ID
|
screenId?: number; // 화면 ID
|
||||||
|
|
@ -82,19 +83,90 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||||
const showTableSelector = component.componentConfig?.showTableSelector ?? true;
|
const showTableSelector = component.componentConfig?.showTableSelector ?? true;
|
||||||
const filterMode = component.componentConfig?.filterMode ?? "dynamic";
|
const filterMode = component.componentConfig?.filterMode ?? "dynamic";
|
||||||
const presetFilters = component.componentConfig?.presetFilters ?? [];
|
const presetFilters = component.componentConfig?.presetFilters ?? [];
|
||||||
|
const targetPanelPosition = component.componentConfig?.targetPanelPosition ?? "left"; // 기본값: 좌측 패널
|
||||||
|
|
||||||
// Map을 배열로 변환
|
// Map을 배열로 변환
|
||||||
const tableList = Array.from(registeredTables.values());
|
const allTableList = Array.from(registeredTables.values());
|
||||||
const currentTable = selectedTableId ? getTable(selectedTableId) : undefined;
|
|
||||||
|
|
||||||
// 첫 번째 테이블 자동 선택
|
// 대상 패널 위치에 따라 테이블 필터링 (tableId 패턴 기반)
|
||||||
useEffect(() => {
|
const tableList = useMemo(() => {
|
||||||
const tables = Array.from(registeredTables.values());
|
// "auto"면 모든 테이블 반환
|
||||||
|
if (targetPanelPosition === "auto") {
|
||||||
if (autoSelectFirstTable && tables.length > 0 && !selectedTableId) {
|
return allTableList;
|
||||||
setSelectedTableId(tables[0].tableId);
|
|
||||||
}
|
}
|
||||||
}, [registeredTables, selectedTableId, autoSelectFirstTable, setSelectedTableId]);
|
|
||||||
|
// 테이블 ID 패턴으로 필터링
|
||||||
|
// card-display-XXX: 좌측 패널 (카드 디스플레이)
|
||||||
|
// datatable-XXX, table-list-XXX: 우측 패널 (테이블 리스트)
|
||||||
|
const filteredTables = allTableList.filter(table => {
|
||||||
|
const tableId = table.tableId.toLowerCase();
|
||||||
|
|
||||||
|
if (targetPanelPosition === "left") {
|
||||||
|
// 좌측 패널 대상: card-display만
|
||||||
|
return tableId.includes("card-display") || tableId.includes("card");
|
||||||
|
} else if (targetPanelPosition === "right") {
|
||||||
|
// 우측 패널 대상: datatable, table-list 등 (card-display 제외)
|
||||||
|
const isCardDisplay = tableId.includes("card-display") || tableId.includes("card");
|
||||||
|
return !isCardDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 필터링된 결과가 없으면 모든 테이블 반환 (폴백)
|
||||||
|
if (filteredTables.length === 0) {
|
||||||
|
console.log("🔍 [TableSearchWidget] 대상 패널에 테이블 없음, 전체 테이블 사용:", {
|
||||||
|
targetPanelPosition,
|
||||||
|
allTablesCount: allTableList.length,
|
||||||
|
allTableIds: allTableList.map(t => t.tableId),
|
||||||
|
});
|
||||||
|
return allTableList;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🔍 [TableSearchWidget] 테이블 필터링:", {
|
||||||
|
targetPanelPosition,
|
||||||
|
allTablesCount: allTableList.length,
|
||||||
|
filteredCount: filteredTables.length,
|
||||||
|
filteredTableIds: filteredTables.map(t => t.tableId),
|
||||||
|
});
|
||||||
|
|
||||||
|
return filteredTables;
|
||||||
|
}, [allTableList, targetPanelPosition]);
|
||||||
|
|
||||||
|
// currentTable은 tableList(필터링된 목록)에서 가져와야 함
|
||||||
|
const currentTable = useMemo(() => {
|
||||||
|
if (!selectedTableId) return undefined;
|
||||||
|
|
||||||
|
// 먼저 tableList(필터링된 목록)에서 찾기
|
||||||
|
const tableFromList = tableList.find(t => t.tableId === selectedTableId);
|
||||||
|
if (tableFromList) {
|
||||||
|
return tableFromList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tableList에 없으면 전체에서 찾기 (폴백)
|
||||||
|
return getTable(selectedTableId);
|
||||||
|
}, [selectedTableId, tableList, getTable]);
|
||||||
|
|
||||||
|
// 대상 패널의 첫 번째 테이블 자동 선택
|
||||||
|
useEffect(() => {
|
||||||
|
if (!autoSelectFirstTable || tableList.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 현재 선택된 테이블이 대상 패널에 있는지 확인
|
||||||
|
const isCurrentTableInTarget = selectedTableId && tableList.some(t => t.tableId === selectedTableId);
|
||||||
|
|
||||||
|
// 현재 선택된 테이블이 대상 패널에 없으면 대상 패널의 첫 번째 테이블 선택
|
||||||
|
if (!selectedTableId || !isCurrentTableInTarget) {
|
||||||
|
const targetTable = tableList[0];
|
||||||
|
console.log("🔍 [TableSearchWidget] 대상 패널 테이블 자동 선택:", {
|
||||||
|
targetPanelPosition,
|
||||||
|
selectedTableId: targetTable.tableId,
|
||||||
|
tableName: targetTable.tableName,
|
||||||
|
});
|
||||||
|
setSelectedTableId(targetTable.tableId);
|
||||||
|
}
|
||||||
|
}, [tableList, selectedTableId, autoSelectFirstTable, setSelectedTableId, targetPanelPosition]);
|
||||||
|
|
||||||
// 현재 테이블의 저장된 필터 불러오기 (동적 모드) 또는 고정 필터 적용 (고정 모드)
|
// 현재 테이블의 저장된 필터 불러오기 (동적 모드) 또는 고정 필터 적용 (고정 모드)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -302,6 +374,12 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("🔍 [TableSearchWidget] 필터 적용:", {
|
||||||
|
currentTableId: currentTable?.tableId,
|
||||||
|
currentTableName: currentTable?.tableName,
|
||||||
|
filtersCount: filtersWithValues.length,
|
||||||
|
filtersWithValues,
|
||||||
|
});
|
||||||
currentTable?.onFilterChange(filtersWithValues);
|
currentTable?.onFilterChange(filtersWithValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,16 @@ export function TableSearchWidgetConfigPanel({
|
||||||
const [localPresetFilters, setLocalPresetFilters] = useState<PresetFilter[]>(
|
const [localPresetFilters, setLocalPresetFilters] = useState<PresetFilter[]>(
|
||||||
currentConfig.presetFilters ?? []
|
currentConfig.presetFilters ?? []
|
||||||
);
|
);
|
||||||
|
const [localTargetPanelPosition, setLocalTargetPanelPosition] = useState<"left" | "right" | "auto">(
|
||||||
|
currentConfig.targetPanelPosition ?? "left"
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalAutoSelect(currentConfig.autoSelectFirstTable ?? true);
|
setLocalAutoSelect(currentConfig.autoSelectFirstTable ?? true);
|
||||||
setLocalShowSelector(currentConfig.showTableSelector ?? true);
|
setLocalShowSelector(currentConfig.showTableSelector ?? true);
|
||||||
setLocalFilterMode(currentConfig.filterMode ?? "dynamic");
|
setLocalFilterMode(currentConfig.filterMode ?? "dynamic");
|
||||||
setLocalPresetFilters(currentConfig.presetFilters ?? []);
|
setLocalPresetFilters(currentConfig.presetFilters ?? []);
|
||||||
|
setLocalTargetPanelPosition(currentConfig.targetPanelPosition ?? "left");
|
||||||
}, [currentConfig]);
|
}, [currentConfig]);
|
||||||
|
|
||||||
// 설정 업데이트 헬퍼
|
// 설정 업데이트 헬퍼
|
||||||
|
|
@ -164,6 +168,40 @@ export function TableSearchWidgetConfigPanel({
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 대상 패널 위치 (분할 패널용) */}
|
||||||
|
<div className="space-y-2 border-t pt-4">
|
||||||
|
<Label className="text-xs sm:text-sm font-medium">대상 패널 위치 (분할 패널)</Label>
|
||||||
|
<p className="text-[10px] text-muted-foreground mb-2">
|
||||||
|
분할 패널이 있는 화면에서 검색 필터가 어떤 패널의 컴포넌트를 대상으로 할지 선택합니다.
|
||||||
|
</p>
|
||||||
|
<RadioGroup
|
||||||
|
value={localTargetPanelPosition}
|
||||||
|
onValueChange={(value: "left" | "right" | "auto") => {
|
||||||
|
setLocalTargetPanelPosition(value);
|
||||||
|
handleUpdate("targetPanelPosition", value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="left" id="target-left" />
|
||||||
|
<Label htmlFor="target-left" className="text-xs sm:text-sm cursor-pointer font-normal">
|
||||||
|
좌측 패널 (카드 디스플레이 등)
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="right" id="target-right" />
|
||||||
|
<Label htmlFor="target-right" className="text-xs sm:text-sm cursor-pointer font-normal">
|
||||||
|
우측 패널 (테이블 리스트 등)
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="auto" id="target-auto" />
|
||||||
|
<Label htmlFor="target-auto" className="text-xs sm:text-sm cursor-pointer font-normal">
|
||||||
|
자동 (모든 테이블 대상)
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 필터 모드 선택 */}
|
{/* 필터 모드 선택 */}
|
||||||
<div className="space-y-2 border-t pt-4">
|
<div className="space-y-2 border-t pt-4">
|
||||||
<Label className="text-xs sm:text-sm font-medium">필터 모드</Label>
|
<Label className="text-xs sm:text-sm font-medium">필터 모드</Label>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue