뉴 컴포넌트

This commit is contained in:
kjs 2025-12-19 16:40:40 +09:00
parent 034ef59ef9
commit d33daf0a3d
4 changed files with 527 additions and 126 deletions

View File

@ -86,22 +86,8 @@ export function ComponentsPanel({
tags: ["table", "list", "card", "kanban", "unified"], tags: ["table", "list", "card", "kanban", "unified"],
defaultSize: { width: 600, height: 400 }, defaultSize: { width: 600, height: 400 },
}, },
{ // unified-layout: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리
id: "unified-layout", // unified-group: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리
name: "통합 레이아웃",
description: "그리드, 분할 패널, 플렉스 등 다양한 레이아웃 지원",
category: "layout" as ComponentCategory,
tags: ["grid", "split", "flex", "unified"],
defaultSize: { width: 400, height: 300 },
},
{
id: "unified-group",
name: "통합 그룹",
description: "탭, 아코디언, 섹션, 카드섹션, 모달 등 다양한 그룹핑 지원",
category: "layout" as ComponentCategory,
tags: ["tabs", "accordion", "section", "modal", "unified"],
defaultSize: { width: 400, height: 300 },
},
{ {
id: "unified-media", id: "unified-media",
name: "통합 미디어", name: "통합 미디어",

View File

@ -5,27 +5,151 @@
* . * .
*/ */
import React from "react"; import React, { useState, useEffect } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { tableTypeApi } from "@/lib/api/screen";
interface UnifiedBizConfigPanelProps { interface UnifiedBizConfigPanelProps {
config: Record<string, any>; config: Record<string, any>;
onChange: (config: Record<string, any>) => void; onChange: (config: Record<string, any>) => void;
} }
interface TableOption {
tableName: string;
displayName: string;
}
interface ColumnOption {
columnName: string;
displayName: string;
}
export const UnifiedBizConfigPanel: React.FC<UnifiedBizConfigPanelProps> = ({ export const UnifiedBizConfigPanel: React.FC<UnifiedBizConfigPanelProps> = ({
config, config,
onChange, onChange,
}) => { }) => {
// 테이블 목록
const [tables, setTables] = useState<TableOption[]>([]);
const [loadingTables, setLoadingTables] = useState(false);
// 컬럼 목록 (소스/대상/관련 테이블용)
const [sourceColumns, setSourceColumns] = useState<ColumnOption[]>([]);
const [targetColumns, setTargetColumns] = useState<ColumnOption[]>([]);
const [relatedColumns, setRelatedColumns] = useState<ColumnOption[]>([]);
const [categoryColumns, setCategoryColumns] = useState<ColumnOption[]>([]);
const [loadingColumns, setLoadingColumns] = useState(false);
// 설정 업데이트 핸들러 // 설정 업데이트 핸들러
const updateConfig = (field: string, value: any) => { const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value }); onChange({ ...config, [field]: value });
}; };
// 테이블 목록 로드
useEffect(() => {
const loadTables = async () => {
setLoadingTables(true);
try {
const data = await tableTypeApi.getTables();
setTables(data.map(t => ({
tableName: t.tableName,
displayName: t.displayName || t.tableName
})));
} catch (error) {
console.error("테이블 목록 로드 실패:", error);
} finally {
setLoadingTables(false);
}
};
loadTables();
}, []);
// 소스 테이블 선택 시 컬럼 목록 로드
useEffect(() => {
const loadColumns = async () => {
if (!config.sourceTable) {
setSourceColumns([]);
return;
}
try {
const data = await tableTypeApi.getColumns(config.sourceTable);
setSourceColumns(data.map((c: any) => ({
columnName: c.columnName || c.column_name,
displayName: c.displayName || c.columnName || c.column_name
})));
} catch (error) {
console.error("소스 컬럼 로드 실패:", error);
}
};
loadColumns();
}, [config.sourceTable]);
// 대상 테이블 선택 시 컬럼 목록 로드
useEffect(() => {
const loadColumns = async () => {
if (!config.targetTable) {
setTargetColumns([]);
return;
}
try {
const data = await tableTypeApi.getColumns(config.targetTable);
setTargetColumns(data.map((c: any) => ({
columnName: c.columnName || c.column_name,
displayName: c.displayName || c.columnName || c.column_name
})));
} catch (error) {
console.error("대상 컬럼 로드 실패:", error);
}
};
loadColumns();
}, [config.targetTable]);
// 관련 테이블 선택 시 컬럼 목록 로드
useEffect(() => {
const loadColumns = async () => {
if (!config.relatedTable) {
setRelatedColumns([]);
return;
}
try {
const data = await tableTypeApi.getColumns(config.relatedTable);
setRelatedColumns(data.map((c: any) => ({
columnName: c.columnName || c.column_name,
displayName: c.displayName || c.columnName || c.column_name
})));
} catch (error) {
console.error("관련 컬럼 로드 실패:", error);
}
};
loadColumns();
}, [config.relatedTable]);
// 카테고리 테이블 선택 시 컬럼 목록 로드
useEffect(() => {
const loadColumns = async () => {
if (!config.tableName) {
setCategoryColumns([]);
return;
}
setLoadingColumns(true);
try {
const data = await tableTypeApi.getColumns(config.tableName);
setCategoryColumns(data.map((c: any) => ({
columnName: c.columnName || c.column_name,
displayName: c.displayName || c.columnName || c.column_name
})));
} catch (error) {
console.error("카테고리 컬럼 로드 실패:", error);
} finally {
setLoadingColumns(false);
}
};
loadColumns();
}, [config.tableName]);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{/* 비즈니스 타입 */} {/* 비즈니스 타입 */}
@ -173,23 +297,48 @@ export const UnifiedBizConfigPanel: React.FC<UnifiedBizConfigPanelProps> = ({
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
<Input <Select
value={config.tableName || ""} value={config.tableName || ""}
onChange={(e) => updateConfig("tableName", e.target.value)} onValueChange={(value) => {
placeholder="카테고리 테이블명" updateConfig("tableName", value);
className="h-8 text-xs" updateConfig("columnName", "");
/> }}
disabled={loadingTables}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
<div className="space-y-2"> {config.tableName && (
<Label className="text-[10px] text-muted-foreground"></Label> <div className="space-y-2">
<Input <Label className="text-[10px] text-muted-foreground"></Label>
value={config.columnName || ""} <Select
onChange={(e) => updateConfig("columnName", e.target.value)} value={config.columnName || ""}
placeholder="컬럼명" onValueChange={(value) => updateConfig("columnName", value)}
className="h-8 text-xs" disabled={loadingColumns}
/> >
</div> <SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingColumns ? "로딩 중..." : "컬럼 선택"} />
</SelectTrigger>
<SelectContent>
{categoryColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</div> </div>
)} )}
@ -200,22 +349,42 @@ export const UnifiedBizConfigPanel: React.FC<UnifiedBizConfigPanelProps> = ({
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
<Input <Select
value={config.sourceTable || ""} value={config.sourceTable || ""}
onChange={(e) => updateConfig("sourceTable", e.target.value)} onValueChange={(value) => updateConfig("sourceTable", value)}
placeholder="소스 테이블명" disabled={loadingTables}
className="h-8 text-xs" >
/> <SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
<Input <Select
value={config.targetTable || ""} value={config.targetTable || ""}
onChange={(e) => updateConfig("targetTable", e.target.value)} onValueChange={(value) => updateConfig("targetTable", value)}
placeholder="대상 테이블명" disabled={loadingTables}
className="h-8 text-xs" >
/> <SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
</div> </div>
)} )}
@ -227,23 +396,47 @@ export const UnifiedBizConfigPanel: React.FC<UnifiedBizConfigPanelProps> = ({
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
<Input <Select
value={config.relatedTable || ""} value={config.relatedTable || ""}
onChange={(e) => updateConfig("relatedTable", e.target.value)} onValueChange={(value) => {
placeholder="관련 테이블명" updateConfig("relatedTable", value);
className="h-8 text-xs" updateConfig("linkColumn", "");
/> }}
disabled={loadingTables}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
<div className="space-y-2"> {config.relatedTable && (
<Label className="text-[10px] text-muted-foreground"> </Label> <div className="space-y-2">
<Input <Label className="text-[10px] text-muted-foreground"> </Label>
value={config.linkColumn || ""} <Select
onChange={(e) => updateConfig("linkColumn", e.target.value)} value={config.linkColumn || ""}
placeholder="연결 컬럼명" onValueChange={(value) => updateConfig("linkColumn", value)}
className="h-8 text-xs" >
/> <SelectTrigger className="h-8 text-xs">
</div> <SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{relatedColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
@ -263,5 +456,3 @@ export const UnifiedBizConfigPanel: React.FC<UnifiedBizConfigPanelProps> = ({
UnifiedBizConfigPanel.displayName = "UnifiedBizConfigPanel"; UnifiedBizConfigPanel.displayName = "UnifiedBizConfigPanel";
export default UnifiedBizConfigPanel; export default UnifiedBizConfigPanel;

View File

@ -5,27 +5,89 @@
* . * .
*/ */
import React from "react"; import React, { useState, useEffect } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { tableTypeApi } from "@/lib/api/screen";
interface UnifiedHierarchyConfigPanelProps { interface UnifiedHierarchyConfigPanelProps {
config: Record<string, any>; config: Record<string, any>;
onChange: (config: Record<string, any>) => void; onChange: (config: Record<string, any>) => void;
} }
interface TableOption {
tableName: string;
displayName: string;
}
interface ColumnOption {
columnName: string;
displayName: string;
}
export const UnifiedHierarchyConfigPanel: React.FC<UnifiedHierarchyConfigPanelProps> = ({ export const UnifiedHierarchyConfigPanel: React.FC<UnifiedHierarchyConfigPanelProps> = ({
config, config,
onChange, onChange,
}) => { }) => {
// 테이블 목록
const [tables, setTables] = useState<TableOption[]>([]);
const [loadingTables, setLoadingTables] = useState(false);
// 컬럼 목록
const [columns, setColumns] = useState<ColumnOption[]>([]);
const [loadingColumns, setLoadingColumns] = useState(false);
// 설정 업데이트 핸들러 // 설정 업데이트 핸들러
const updateConfig = (field: string, value: any) => { const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value }); onChange({ ...config, [field]: value });
}; };
// 테이블 목록 로드
useEffect(() => {
const loadTables = async () => {
setLoadingTables(true);
try {
const data = await tableTypeApi.getTables();
setTables(data.map(t => ({
tableName: t.tableName,
displayName: t.displayName || t.tableName
})));
} catch (error) {
console.error("테이블 목록 로드 실패:", error);
} finally {
setLoadingTables(false);
}
};
loadTables();
}, []);
// 테이블 선택 시 컬럼 목록 로드
useEffect(() => {
const loadColumns = async () => {
if (!config.tableName) {
setColumns([]);
return;
}
setLoadingColumns(true);
try {
const data = await tableTypeApi.getColumns(config.tableName);
setColumns(data.map((c: any) => ({
columnName: c.columnName || c.column_name,
displayName: c.displayName || c.columnName || c.column_name
})));
} catch (error) {
console.error("컬럼 목록 로드 실패:", error);
} finally {
setLoadingColumns(false);
}
};
loadColumns();
}, [config.tableName]);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{/* 계층 타입 */} {/* 계층 타입 */}
@ -91,44 +153,97 @@ export const UnifiedHierarchyConfigPanel: React.FC<UnifiedHierarchyConfigPanelPr
{/* DB 설정 */} {/* DB 설정 */}
{config.dataSource === "db" && ( {config.dataSource === "db" && (
<div className="space-y-3"> <div className="space-y-3">
{/* 테이블 선택 */}
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"></Label> <Label className="text-[10px] text-muted-foreground"></Label>
<Input <Select
value={config.tableName || ""} value={config.tableName || ""}
onChange={(e) => updateConfig("tableName", e.target.value)} onValueChange={(value) => {
placeholder="테이블명" updateConfig("tableName", value);
className="h-8 text-xs" // 테이블 변경 시 컬럼 초기화
/> updateConfig("idColumn", "");
</div> updateConfig("parentIdColumn", "");
<div className="grid grid-cols-2 gap-2"> updateConfig("labelColumn", "");
<div className="space-y-2"> }}
<Label className="text-[10px] text-muted-foreground">ID </Label> disabled={loadingTables}
<Input >
value={config.idColumn || ""} <SelectTrigger className="h-8 text-xs">
onChange={(e) => updateConfig("idColumn", e.target.value)} <SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
placeholder="id" </SelectTrigger>
className="h-8 text-xs" <SelectContent>
/> {tables.map((table) => (
</div> <SelectItem key={table.tableName} value={table.tableName}>
<div className="space-y-2"> {table.displayName}
<Label className="text-[10px] text-muted-foreground"> ID </Label> </SelectItem>
<Input ))}
value={config.parentIdColumn || ""} </SelectContent>
onChange={(e) => updateConfig("parentIdColumn", e.target.value)} </Select>
placeholder="parent_id"
className="h-8 text-xs"
/>
</div>
</div>
<div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label>
<Input
value={config.labelColumn || ""}
onChange={(e) => updateConfig("labelColumn", e.target.value)}
placeholder="name"
className="h-8 text-xs"
/>
</div> </div>
{/* 컬럼 선택 */}
{config.tableName && (
<>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-2">
<Label className="text-[10px] text-muted-foreground">ID </Label>
<Select
value={config.idColumn || ""}
onValueChange={(value) => updateConfig("idColumn", value)}
disabled={loadingColumns}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingColumns ? "로딩 중..." : "선택"} />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> ID </Label>
<Select
value={config.parentIdColumn || ""}
onValueChange={(value) => updateConfig("parentIdColumn", value)}
disabled={loadingColumns}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingColumns ? "로딩 중..." : "선택"} />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label>
<Select
value={config.labelColumn || ""}
onValueChange={(value) => updateConfig("labelColumn", value)}
disabled={loadingColumns}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingColumns ? "로딩 중..." : "선택"} />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</>
)}
</div> </div>
)} )}
@ -227,12 +342,22 @@ export const UnifiedHierarchyConfigPanel: React.FC<UnifiedHierarchyConfigPanelPr
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
<Input <Select
value={config.quantityColumn || ""} value={config.quantityColumn || ""}
onChange={(e) => updateConfig("quantityColumn", e.target.value)} onValueChange={(value) => updateConfig("quantityColumn", value)}
placeholder="quantity" disabled={loadingColumns || !config.tableName}
className="h-8 text-xs" >
/> <SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
</div> </div>
</> </>
@ -247,12 +372,22 @@ export const UnifiedHierarchyConfigPanel: React.FC<UnifiedHierarchyConfigPanelPr
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
<Input <Select
value={config.parentField || ""} value={config.parentField || ""}
onChange={(e) => updateConfig("parentField", e.target.value)} onValueChange={(value) => updateConfig("parentField", value)}
placeholder="부모 필드명" disabled={loadingColumns || !config.tableName}
className="h-8 text-xs" >
/> <SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -273,5 +408,3 @@ export const UnifiedHierarchyConfigPanel: React.FC<UnifiedHierarchyConfigPanelPr
UnifiedHierarchyConfigPanel.displayName = "UnifiedHierarchyConfigPanel"; UnifiedHierarchyConfigPanel.displayName = "UnifiedHierarchyConfigPanel";
export default UnifiedHierarchyConfigPanel; export default UnifiedHierarchyConfigPanel;

View File

@ -5,45 +5,107 @@
* . * .
*/ */
import React from "react"; import React, { useState, useEffect } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Plus, Trash2 } from "lucide-react"; import { Plus, Trash2 } from "lucide-react";
import { tableTypeApi } from "@/lib/api/screen";
interface UnifiedListConfigPanelProps { interface UnifiedListConfigPanelProps {
config: Record<string, any>; config: Record<string, any>;
onChange: (config: Record<string, any>) => void; onChange: (config: Record<string, any>) => void;
} }
interface TableOption {
tableName: string;
displayName: string;
}
interface ColumnOption {
columnName: string;
displayName: string;
}
export const UnifiedListConfigPanel: React.FC<UnifiedListConfigPanelProps> = ({ export const UnifiedListConfigPanel: React.FC<UnifiedListConfigPanelProps> = ({
config, config,
onChange, onChange,
}) => { }) => {
// 테이블 목록
const [tables, setTables] = useState<TableOption[]>([]);
const [loadingTables, setLoadingTables] = useState(false);
// 컬럼 목록
const [columns, setColumns] = useState<ColumnOption[]>([]);
const [loadingColumns, setLoadingColumns] = useState(false);
// 설정 업데이트 핸들러 // 설정 업데이트 핸들러
const updateConfig = (field: string, value: any) => { const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value }); onChange({ ...config, [field]: value });
}; };
// 테이블 목록 로드
useEffect(() => {
const loadTables = async () => {
setLoadingTables(true);
try {
const data = await tableTypeApi.getTables();
setTables(data.map(t => ({
tableName: t.tableName,
displayName: t.displayName || t.tableName
})));
} catch (error) {
console.error("테이블 목록 로드 실패:", error);
} finally {
setLoadingTables(false);
}
};
loadTables();
}, []);
// 테이블 선택 시 컬럼 목록 로드
useEffect(() => {
const loadColumns = async () => {
if (!config.tableName) {
setColumns([]);
return;
}
setLoadingColumns(true);
try {
const data = await tableTypeApi.getColumns(config.tableName);
setColumns(data.map((c: any) => ({
columnName: c.columnName || c.column_name,
displayName: c.displayName || c.columnName || c.column_name
})));
} catch (error) {
console.error("컬럼 목록 로드 실패:", error);
} finally {
setLoadingColumns(false);
}
};
loadColumns();
}, [config.tableName]);
// 컬럼 관리 // 컬럼 관리
const columns = config.columns || []; const configColumns = config.columns || [];
const addColumn = () => { const addColumn = () => {
const newColumns = [...columns, { key: "", title: "", width: "" }]; const newColumns = [...configColumns, { key: "", title: "", width: "" }];
updateConfig("columns", newColumns); updateConfig("columns", newColumns);
}; };
const updateColumn = (index: number, field: string, value: string) => { const updateColumn = (index: number, field: string, value: string) => {
const newColumns = [...columns]; const newColumns = [...configColumns];
newColumns[index] = { ...newColumns[index], [field]: value }; newColumns[index] = { ...newColumns[index], [field]: value };
updateConfig("columns", newColumns); updateConfig("columns", newColumns);
}; };
const removeColumn = (index: number) => { const removeColumn = (index: number) => {
const newColumns = columns.filter((_: any, i: number) => i !== index); const newColumns = configColumns.filter((_: any, i: number) => i !== index);
updateConfig("columns", newColumns); updateConfig("columns", newColumns);
}; };
@ -91,13 +153,26 @@ export const UnifiedListConfigPanel: React.FC<UnifiedListConfigPanelProps> = ({
{/* DB 설정 */} {/* DB 설정 */}
{config.source === "db" && ( {config.source === "db" && (
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-xs font-medium"></Label> <Label className="text-xs font-medium"></Label>
<Input <Select
value={config.tableName || ""} value={config.tableName || ""}
onChange={(e) => updateConfig("tableName", e.target.value)} onValueChange={(value) => {
placeholder="테이블명" updateConfig("tableName", value);
className="h-8 text-xs" updateConfig("columns", []); // 테이블 변경 시 컬럼 초기화
/> }}
disabled={loadingTables}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
)} )}
@ -132,14 +207,32 @@ export const UnifiedListConfigPanel: React.FC<UnifiedListConfigPanelProps> = ({
</Button> </Button>
</div> </div>
<div className="space-y-2 max-h-40 overflow-y-auto"> <div className="space-y-2 max-h-40 overflow-y-auto">
{columns.map((column: any, index: number) => ( {configColumns.map((column: any, index: number) => (
<div key={index} className="flex items-center gap-2"> <div key={index} className="flex items-center gap-2">
<Input {/* 컬럼 키 - 드롭다운 */}
<Select
value={column.key || ""} value={column.key || ""}
onChange={(e) => updateColumn(index, "key", e.target.value)} onValueChange={(value) => {
placeholder="키" const selectedCol = columns.find(c => c.columnName === value);
className="h-7 text-xs flex-1" updateColumn(index, "key", value);
/> // 제목을 자동으로 설정
if (selectedCol && !column.title) {
updateColumn(index, "title", selectedCol.displayName);
}
}}
disabled={loadingColumns || !config.tableName}
>
<SelectTrigger className="h-7 text-xs flex-1">
<SelectValue placeholder="컬럼" />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
<Input <Input
value={column.title || ""} value={column.title || ""}
onChange={(e) => updateColumn(index, "title", e.target.value)} onChange={(e) => updateColumn(index, "title", e.target.value)}
@ -163,7 +256,7 @@ export const UnifiedListConfigPanel: React.FC<UnifiedListConfigPanelProps> = ({
</Button> </Button>
</div> </div>
))} ))}
{columns.length === 0 && ( {configColumns.length === 0 && (
<p className="text-xs text-muted-foreground text-center py-2"> <p className="text-xs text-muted-foreground text-center py-2">
</p> </p>
@ -242,5 +335,3 @@ export const UnifiedListConfigPanel: React.FC<UnifiedListConfigPanelProps> = ({
UnifiedListConfigPanel.displayName = "UnifiedListConfigPanel"; UnifiedListConfigPanel.displayName = "UnifiedListConfigPanel";
export default UnifiedListConfigPanel; export default UnifiedListConfigPanel;