From 361cb56a1d3b90cbb9a6b1bf3bd9498a6d2ed242 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Fri, 14 Nov 2025 16:30:38 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EA=B1=B0=EB=9E=98=EC=B2=98=EA=B4=80?= =?UTF-8?q?=EB=A6=AC-=ED=92=88=EB=AA=A9=EB=93=B1=EB=A1=9D=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomerItemMappingComponent.tsx | 258 +++++++++++++++++ .../CustomerItemMappingConfigPanel.tsx | 268 ++++++++++++++++++ .../CustomerItemMappingRenderer.tsx | 10 + .../components/customer-item-mapping/index.ts | 40 +++ .../components/customer-item-mapping/types.ts | 23 ++ frontend/lib/registry/components/index.ts | 1 + .../text-display/TextDisplayComponent.tsx | 4 +- .../lib/utils/getComponentConfigPanel.tsx | 2 + 8 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingComponent.tsx create mode 100644 frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel.tsx create mode 100644 frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingRenderer.tsx create mode 100644 frontend/lib/registry/components/customer-item-mapping/index.ts create mode 100644 frontend/lib/registry/components/customer-item-mapping/types.ts diff --git a/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingComponent.tsx b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingComponent.tsx new file mode 100644 index 00000000..588c2ed6 --- /dev/null +++ b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingComponent.tsx @@ -0,0 +1,258 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { CustomerItemMappingConfig } from "./types"; +import { Checkbox } from "@/components/ui/checkbox"; +import { X } from "lucide-react"; +import { cn } from "@/lib/utils"; + +export interface CustomerItemMappingComponentProps { + component: any; + isDesignMode?: boolean; + isSelected?: boolean; + isInteractive?: boolean; + config?: CustomerItemMappingConfig; + className?: string; + style?: React.CSSProperties; + onClick?: (e?: React.MouseEvent) => void; + onDragStart?: (e: React.DragEvent) => void; + onDragEnd?: (e: React.DragEvent) => void; +} + +export const CustomerItemMappingComponent: React.FC = ({ + component, + isDesignMode = false, + isSelected = false, + config, + className, + style, + onClick, + onDragStart, + onDragEnd, +}) => { + const finalConfig = { + ...config, + ...component.config, + } as CustomerItemMappingConfig; + + const [data, setData] = useState([]); + const [selectedRows, setSelectedRows] = useState>(new Set()); + const [isAllSelected, setIsAllSelected] = useState(false); + + // 데이터 로드 (실제 구현 시 API 호출) + useEffect(() => { + if (!isDesignMode && finalConfig.selectedTable) { + // TODO: API 호출로 데이터 로드 + setData([]); + } + }, [finalConfig.selectedTable, isDesignMode]); + + const handleSelectAll = (checked: boolean) => { + if (checked) { + const allIds = data.map((_, index) => `row-${index}`); + setSelectedRows(new Set(allIds)); + setIsAllSelected(true); + } else { + setSelectedRows(new Set()); + setIsAllSelected(false); + } + }; + + const handleRowSelection = (rowId: string, checked: boolean) => { + const newSelected = new Set(selectedRows); + if (checked) { + newSelected.add(rowId); + } else { + newSelected.delete(rowId); + } + setSelectedRows(newSelected); + setIsAllSelected(newSelected.size === data.length && data.length > 0); + }; + + const columns = finalConfig.columns || []; + const showCheckbox = finalConfig.checkbox?.enabled !== false; + + // 스타일 계산 + const componentStyle: React.CSSProperties = { + position: "relative", + width: "100%", + height: "100%", + display: "flex", + flexDirection: "column", + backgroundColor: "hsl(var(--background))", + overflow: "hidden", + boxSizing: "border-box", + }; + + // 이벤트 핸들러 + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onClick?.(); + }; + + return ( +
+ {/* 헤더 */} +
+

+ 품목 추가 - {finalConfig.selectedTable || "[테이블 선택]"} +

+ +
+ + {/* 검색/카테고리 영역 - 회색 배경 */} + {finalConfig.showSearchArea && ( +
+
+
+
+ + + +
+

+ 검색 및 카테고리 필터 영역 +

+

+ 나중에 구현 예정 +

+
+
+
+ )} + + {/* 목록 헤더 */} +
+ 판매품목 목록 +
+ {showCheckbox && finalConfig.checkbox?.selectAll && ( + + )} + + 선택: {selectedRows.size}개 + +
+
+ + {/* 테이블 컨테이너 */} +
+ {/* 테이블 헤더 */} + {columns.length > 0 && ( +
+
+ + + + {showCheckbox && ( + + )} + {columns.map((col, index) => ( + + ))} + + +
+ {col} +
+
+
+ )} + + {/* 데이터 영역 */} +
+ {data.length === 0 ? ( +
+
+ + + +
+
+

+ {finalConfig.emptyMessage || "데이터가 없습니다"} +

+

+ {finalConfig.emptyDescription || "품목 데이터가 추가되면 여기에 표시됩니다"} +

+
+
+ ) : ( +
+ + + {data.map((row, index) => ( + + {showCheckbox && ( + + )} + {columns.map((col, colIndex) => ( + + ))} + + ))} + +
+ + handleRowSelection(`row-${index}`, checked as boolean) + } + /> + + {row[col] || "-"} +
+
+ )} +
+
+
+ ); +}; + diff --git a/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel.tsx b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel.tsx new file mode 100644 index 00000000..555d4f57 --- /dev/null +++ b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel.tsx @@ -0,0 +1,268 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { CustomerItemMappingConfig } from "./types"; +import { tableTypeApi } from "@/lib/api/screen"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Plus, X } from "lucide-react"; + +export interface CustomerItemMappingConfigPanelProps { + config: CustomerItemMappingConfig; + onChange: (config: CustomerItemMappingConfig) => void; +} + +export const CustomerItemMappingConfigPanel: React.FC< + CustomerItemMappingConfigPanelProps +> = ({ config, onChange }) => { + const [tables, setTables] = useState([]); + const [availableColumns, setAvailableColumns] = useState([]); + + // 테이블 목록 로드 + useEffect(() => { + const loadTables = async () => { + try { + const tableList = await tableTypeApi.getTables(); + setTables(tableList); + } catch (error) { + console.error("테이블 목록 로드 실패:", error); + } + }; + loadTables(); + }, []); + + // 선택된 테이블의 컬럼 목록 로드 + useEffect(() => { + if (config.selectedTable) { + const loadColumns = async () => { + try { + const columns = await tableTypeApi.getColumns(config.selectedTable!); + setAvailableColumns(columns); + } catch (error) { + console.error("컬럼 목록 로드 실패:", error); + } + }; + loadColumns(); + } + }, [config.selectedTable]); + + const handleTableChange = (tableName: string) => { + onChange({ + ...config, + selectedTable: tableName, + columns: [], // 테이블 변경 시 컬럼 초기화 + }); + }; + + const handleAddColumn = (columnName: string) => { + if (!config.columns.includes(columnName)) { + onChange({ + ...config, + columns: [...config.columns, columnName], + }); + } + }; + + const handleRemoveColumn = (columnName: string) => { + onChange({ + ...config, + columns: config.columns.filter((col) => col !== columnName), + }); + }; + + return ( +
+ {/* 테이블 선택 */} +
+ + +
+ + {/* 컬럼 설정 */} +
+ +
+ {/* 선택된 컬럼 목록 */} + {config.columns.length > 0 && ( +
+ {config.columns.map((col, index) => ( +
+ {col} + +
+ ))} +
+ )} + + {/* 컬럼 추가 */} + {availableColumns.length > 0 && ( + + )} +
+
+ + {/* 체크박스 설정 */} +
+ +
+ + + + + +
+
+ + {/* 검색 영역 설정 */} +
+ + + + + {config.showSearchArea && ( +
+ + + onChange({ + ...config, + searchAreaHeight: parseInt(e.target.value) || 80, + }) + } + min={60} + max={200} + className="h-8 text-xs" + /> +

+ 권장: 80-120px (나중에 검색창과 카테고리 필터 추가 예정) +

+
+ )} +
+ + {/* 빈 데이터 메시지 */} +
+ + + onChange({ ...config, emptyMessage: e.target.value }) + } + placeholder="데이터가 없습니다" + /> +
+ +
+ + + onChange({ ...config, emptyDescription: e.target.value }) + } + placeholder="품목 데이터가 추가되면 여기에 표시됩니다" + /> +
+
+ ); +}; + diff --git a/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingRenderer.tsx b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingRenderer.tsx new file mode 100644 index 00000000..9baebb9b --- /dev/null +++ b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingRenderer.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { ComponentRegistry } from "../../ComponentRegistry"; +import { CustomerItemMappingDefinition } from "./index"; + +// 컴포넌트 자동 등록 +ComponentRegistry.registerComponent(CustomerItemMappingDefinition); + +console.log("✅ CustomerItemMapping 컴포넌트 등록 완료"); + diff --git a/frontend/lib/registry/components/customer-item-mapping/index.ts b/frontend/lib/registry/components/customer-item-mapping/index.ts new file mode 100644 index 00000000..1de07a05 --- /dev/null +++ b/frontend/lib/registry/components/customer-item-mapping/index.ts @@ -0,0 +1,40 @@ +"use client"; + +import React from "react"; +import { createComponentDefinition } from "../../utils/createComponentDefinition"; +import { ComponentCategory } from "@/types/component"; +import { CustomerItemMappingComponent } from "./CustomerItemMappingComponent"; +import { CustomerItemMappingConfigPanel } from "./CustomerItemMappingConfigPanel"; +import { CustomerItemMappingConfig } from "./types"; + +export const CustomerItemMappingDefinition = createComponentDefinition({ + id: "customer-item-mapping", + name: "거래처별 품목정보", + nameEng: "Customer Item Mapping", + description: "거래처별 품목 정보를 표시하고 선택하는 컴포넌트", + category: ComponentCategory.DISPLAY, + webType: "text", + component: CustomerItemMappingComponent, + defaultConfig: { + selectedTable: undefined, + columns: [], + checkbox: { + enabled: true, + multiple: true, + selectAll: true, + }, + showSearchArea: false, + searchAreaHeight: 80, + emptyMessage: "데이터가 없습니다", + emptyDescription: "품목 데이터가 추가되면 여기에 표시됩니다", + } as CustomerItemMappingConfig, + defaultSize: { width: 800, height: 600 }, + configPanel: CustomerItemMappingConfigPanel, + icon: "Package", + tags: ["거래처", "품목", "매핑", "목록"], + version: "1.0.0", + author: "개발팀", +}); + +export type { CustomerItemMappingConfig } from "./types"; + diff --git a/frontend/lib/registry/components/customer-item-mapping/types.ts b/frontend/lib/registry/components/customer-item-mapping/types.ts new file mode 100644 index 00000000..976bb568 --- /dev/null +++ b/frontend/lib/registry/components/customer-item-mapping/types.ts @@ -0,0 +1,23 @@ +export interface CustomerItemMappingConfig { + // 테이블 설정 + selectedTable?: string; + + // 컬럼 설정 + columns: string[]; // 표시할 컬럼 목록 + + // 체크박스 설정 + checkbox: { + enabled: boolean; + multiple: boolean; + selectAll: boolean; + }; + + // 검색/필터 영역 (나중에 추가할 공간) + showSearchArea?: boolean; + searchAreaHeight?: number; + + // 빈 데이터 메시지 + emptyMessage?: string; + emptyDescription?: string; +} + diff --git a/frontend/lib/registry/components/index.ts b/frontend/lib/registry/components/index.ts index adc86414..17682ec8 100644 --- a/frontend/lib/registry/components/index.ts +++ b/frontend/lib/registry/components/index.ts @@ -43,6 +43,7 @@ import "./flow-widget/FlowWidgetRenderer"; import "./numbering-rule/NumberingRuleRenderer"; import "./category-manager/CategoryManagerRenderer"; import "./table-search-widget"; // 🆕 테이블 검색 필터 위젯 +import "./customer-item-mapping/CustomerItemMappingRenderer"; // 🆕 거래처별 품목정보 /** * 컴포넌트 초기화 함수 diff --git a/frontend/lib/registry/components/text-display/TextDisplayComponent.tsx b/frontend/lib/registry/components/text-display/TextDisplayComponent.tsx index a1e96a78..07ce3f34 100644 --- a/frontend/lib/registry/components/text-display/TextDisplayComponent.tsx +++ b/frontend/lib/registry/components/text-display/TextDisplayComponent.tsx @@ -76,8 +76,8 @@ export const TextDisplayComponent: React.FC = ({ : componentConfig.textAlign === "right" ? "flex-end" : "flex-start", - wordBreak: "break-word", - overflow: "hidden", + whiteSpace: "nowrap", // ← 한 줄로 유지 // ← 넘치는 부분 숨김 + textOverflow: "ellipsis", // ← 넘치면 ... 표시 (선택사항) transition: "all 0.2s ease-in-out", boxShadow: "none", }; diff --git a/frontend/lib/utils/getComponentConfigPanel.tsx b/frontend/lib/utils/getComponentConfigPanel.tsx index b4af1632..d4b31d14 100644 --- a/frontend/lib/utils/getComponentConfigPanel.tsx +++ b/frontend/lib/utils/getComponentConfigPanel.tsx @@ -26,6 +26,7 @@ const CONFIG_PANEL_MAP: Record Promise> = { "split-panel-layout": () => import("@/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel"), "repeater-field-group": () => import("@/components/webtypes/config/RepeaterConfigPanel"), "flow-widget": () => import("@/components/screen/config-panels/FlowWidgetConfigPanel"), + "customer-item-mapping": () => import("@/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel"), }; // ConfigPanel 컴포넌트 캐시 @@ -55,6 +56,7 @@ export async function getComponentConfigPanel(componentId: string): Promise Date: Fri, 14 Nov 2025 16:49:49 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EA=B1=B0=EB=9E=98=EC=B2=98=EB=B3=84=20?= =?UTF-8?q?=ED=92=88=EB=AA=A9=EC=A0=95=EB=B3=B4=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EB=B0=94=20=EB=B0=8F=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomerItemMappingComponent.tsx | 60 +++--- .../CustomerItemMappingConfigPanel.tsx | 187 +++++++++++++++--- .../components/customer-item-mapping/index.ts | 8 +- .../components/customer-item-mapping/types.ts | 12 +- 4 files changed, 209 insertions(+), 58 deletions(-) diff --git a/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingComponent.tsx b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingComponent.tsx index 588c2ed6..a1cd73e5 100644 --- a/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingComponent.tsx +++ b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingComponent.tsx @@ -103,42 +103,48 @@ export const CustomerItemMappingComponent: React.FC

품목 추가 - {finalConfig.selectedTable || "[테이블 선택]"} + {finalConfig.showCompanyName && finalConfig.companyNameColumn && ( + + | {finalConfig.companyNameColumn} + + )}

- {/* 검색/카테고리 영역 - 회색 배경 */} + {/* 검색/카테고리 영역 */} {finalConfig.showSearchArea && ( -
-
-
-
- - - +
+
+ {/* 검색 입력 */} +
+
+
-

- 검색 및 카테고리 필터 영역 -

-

- 나중에 구현 예정 -

+ + {/* 카테고리 필터 */} + {finalConfig.enableCategoryFilter && ( +
+ +
+ )}
)} diff --git a/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel.tsx b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel.tsx index 555d4f57..5aead062 100644 --- a/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel.tsx +++ b/frontend/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel.tsx @@ -19,11 +19,33 @@ import { Plus, X } from "lucide-react"; export interface CustomerItemMappingConfigPanelProps { config: CustomerItemMappingConfig; onChange: (config: CustomerItemMappingConfig) => void; + onConfigChange?: (config: CustomerItemMappingConfig) => void; + screenTableName?: string; + tableColumns?: any[]; + tables?: any[]; + allTables?: any[]; + onTableChange?: (tableName: string) => void; + menuObjid?: number; } export const CustomerItemMappingConfigPanel: React.FC< CustomerItemMappingConfigPanelProps -> = ({ config, onChange }) => { +> = ({ + config, + onChange, + onConfigChange, + screenTableName, + tableColumns: propTableColumns, + tables: propTables, + allTables, + onTableChange: propOnTableChange, + menuObjid, +}) => { + // onChange와 onConfigChange를 통합 + const handleChange = (newConfig: CustomerItemMappingConfig) => { + onChange?.(newConfig); + onConfigChange?.(newConfig); + }; const [tables, setTables] = useState([]); const [availableColumns, setAvailableColumns] = useState([]); @@ -56,16 +78,18 @@ export const CustomerItemMappingConfigPanel: React.FC< }, [config.selectedTable]); const handleTableChange = (tableName: string) => { - onChange({ + const newConfig = { ...config, selectedTable: tableName, columns: [], // 테이블 변경 시 컬럼 초기화 - }); + }; + handleChange(newConfig); + propOnTableChange?.(tableName); }; const handleAddColumn = (columnName: string) => { if (!config.columns.includes(columnName)) { - onChange({ + handleChange({ ...config, columns: [...config.columns, columnName], }); @@ -73,7 +97,7 @@ export const CustomerItemMappingConfigPanel: React.FC< }; const handleRemoveColumn = (columnName: string) => { - onChange({ + handleChange({ ...config, columns: config.columns.filter((col) => col !== columnName), }); @@ -152,7 +176,7 @@ export const CustomerItemMappingConfigPanel: React.FC< - onChange({ + handleChange({ ...config, checkbox: { ...config.checkbox, @@ -168,7 +192,7 @@ export const CustomerItemMappingConfigPanel: React.FC< - onChange({ + handleChange({ ...config, checkbox: { ...config.checkbox, @@ -184,7 +208,7 @@ export const CustomerItemMappingConfigPanel: React.FC< - onChange({ + handleChange({ ...config, checkbox: { ...config.checkbox, @@ -198,6 +222,52 @@ export const CustomerItemMappingConfigPanel: React.FC<
+ {/* 헤더 설정 */} +
+ + + + {config.showCompanyName && availableColumns.length > 0 && ( +
+ + +

+ 헤더에 표시할 회사명 데이터가 있는 컬럼을 선택하세요 +

+
+ )} +
+ {/* 검색 영역 설정 */}
@@ -206,7 +276,7 @@ export const CustomerItemMappingConfigPanel: React.FC< - onChange({ + handleChange({ ...config, showSearchArea: checked as boolean, }) @@ -218,24 +288,83 @@ export const CustomerItemMappingConfigPanel: React.FC< {config.showSearchArea && ( -
- - - onChange({ - ...config, - searchAreaHeight: parseInt(e.target.value) || 80, - }) - } - min={60} - max={200} - className="h-8 text-xs" - /> -

- 권장: 80-120px (나중에 검색창과 카테고리 필터 추가 예정) -

+
+
+ + + handleChange({ + ...config, + searchPlaceholder: e.target.value, + }) + } + placeholder="품목코드, 품목명, 규격 검색" + className="h-8 text-xs" + /> +
+ + {/* 카테고리 필터 설정 */} +
+ + + {config.enableCategoryFilter && ( +
+ + + handleChange({ + ...config, + categories: e.target.value.split(",").map((c) => c.trim()).filter(Boolean), + }) + } + placeholder="전체, 원자재, 반도체, 완제품" + className="h-8 text-xs" + /> +

+ 예: 전체, 원자재, 반도체, 완제품 +

+ + {availableColumns.length > 0 && ( + <> + + + + )} +
+ )} +
)}
@@ -246,7 +375,7 @@ export const CustomerItemMappingConfigPanel: React.FC< - onChange({ ...config, emptyMessage: e.target.value }) + handleChange({ ...config, emptyMessage: e.target.value }) } placeholder="데이터가 없습니다" /> @@ -257,7 +386,7 @@ export const CustomerItemMappingConfigPanel: React.FC< - onChange({ ...config, emptyDescription: e.target.value }) + handleChange({ ...config, emptyDescription: e.target.value }) } placeholder="품목 데이터가 추가되면 여기에 표시됩니다" /> diff --git a/frontend/lib/registry/components/customer-item-mapping/index.ts b/frontend/lib/registry/components/customer-item-mapping/index.ts index 1de07a05..a0d9d6c9 100644 --- a/frontend/lib/registry/components/customer-item-mapping/index.ts +++ b/frontend/lib/registry/components/customer-item-mapping/index.ts @@ -23,8 +23,14 @@ export const CustomerItemMappingDefinition = createComponentDefinition({ multiple: true, selectAll: true, }, - showSearchArea: false, + showSearchArea: true, // 기본적으로 검색 영역 표시 searchAreaHeight: 80, + searchPlaceholder: "품목코드, 품목명, 규격 검색", + enableCategoryFilter: true, // 기본적으로 카테고리 필터 표시 + categoryColumn: undefined, + categories: ["전체", "원자재", "반도체", "완제품"], + showCompanyName: false, + companyNameColumn: undefined, emptyMessage: "데이터가 없습니다", emptyDescription: "품목 데이터가 추가되면 여기에 표시됩니다", } as CustomerItemMappingConfig, diff --git a/frontend/lib/registry/components/customer-item-mapping/types.ts b/frontend/lib/registry/components/customer-item-mapping/types.ts index 976bb568..68382623 100644 --- a/frontend/lib/registry/components/customer-item-mapping/types.ts +++ b/frontend/lib/registry/components/customer-item-mapping/types.ts @@ -12,9 +12,19 @@ export interface CustomerItemMappingConfig { selectAll: boolean; }; - // 검색/필터 영역 (나중에 추가할 공간) + // 검색/필터 영역 showSearchArea?: boolean; searchAreaHeight?: number; + searchPlaceholder?: string; // 검색 플레이스홀더 + + // 카테고리 필터 + enableCategoryFilter?: boolean; // 카테고리 필터 활성화 + categoryColumn?: string; // 카테고리 데이터 컬럼명 + categories?: string[]; // 카테고리 목록 (예: ["전체", "원자재", "반도체", "완제품"]) + + // 헤더 설정 + showCompanyName?: boolean; // 회사명 표시 여부 + companyNameColumn?: string; // 회사명을 가져올 컬럼명 // 빈 데이터 메시지 emptyMessage?: string;