2025-11-21 02:25:25 +09:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
|
|
|
import { Loader2, Plus, Trash2, GripVertical } from "lucide-react";
|
|
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
|
|
|
import { Separator } from "@/components/ui/separator";
|
|
|
|
|
|
|
|
|
|
|
|
// 계층 레벨 설정 인터페이스
|
|
|
|
|
|
export interface HierarchyLevel {
|
|
|
|
|
|
level: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
tableName: string;
|
|
|
|
|
|
keyColumn: string;
|
|
|
|
|
|
nameColumn: string;
|
|
|
|
|
|
parentKeyColumn: string;
|
|
|
|
|
|
typeColumn?: string;
|
|
|
|
|
|
objectTypes: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 전체 계층 구조 설정
|
|
|
|
|
|
export interface HierarchyConfig {
|
|
|
|
|
|
warehouseKey: string; // 이 레이아웃이 속한 창고 키 (예: "DY99")
|
|
|
|
|
|
warehouse?: {
|
|
|
|
|
|
tableName: string; // 창고 테이블명 (예: "MWARMA")
|
|
|
|
|
|
keyColumn: string;
|
|
|
|
|
|
nameColumn: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
levels: HierarchyLevel[];
|
|
|
|
|
|
material?: {
|
|
|
|
|
|
tableName: string;
|
|
|
|
|
|
keyColumn: string;
|
|
|
|
|
|
locationKeyColumn: string;
|
|
|
|
|
|
layerColumn?: string;
|
|
|
|
|
|
quantityColumn?: string;
|
|
|
|
|
|
displayColumns?: Array<{ column: string; label: string }>; // 우측 패널에 표시할 컬럼들 (컬럼명 + 표시명)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 15:44:52 +09:00
|
|
|
|
interface TableInfo {
|
|
|
|
|
|
table_name: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ColumnInfo {
|
|
|
|
|
|
column_name: string;
|
|
|
|
|
|
data_type?: string;
|
|
|
|
|
|
description?: string;
|
2025-11-25 13:55:00 +09:00
|
|
|
|
// 백엔드에서 내려주는 Primary Key 플래그 ("YES"/"NO" 또는 boolean)
|
|
|
|
|
|
is_primary_key?: string | boolean;
|
2025-11-21 15:44:52 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 02:25:25 +09:00
|
|
|
|
interface HierarchyConfigPanelProps {
|
|
|
|
|
|
externalDbConnectionId: number | null;
|
|
|
|
|
|
hierarchyConfig: HierarchyConfig | null;
|
|
|
|
|
|
onHierarchyConfigChange: (config: HierarchyConfig) => void;
|
2025-11-21 15:44:52 +09:00
|
|
|
|
availableTables: TableInfo[];
|
2025-11-21 02:25:25 +09:00
|
|
|
|
onLoadTables: () => Promise<void>;
|
2025-11-21 15:44:52 +09:00
|
|
|
|
onLoadColumns: (tableName: string) => Promise<ColumnInfo[]>;
|
2025-11-21 02:25:25 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function HierarchyConfigPanel({
|
|
|
|
|
|
externalDbConnectionId,
|
|
|
|
|
|
hierarchyConfig,
|
|
|
|
|
|
onHierarchyConfigChange,
|
|
|
|
|
|
availableTables,
|
|
|
|
|
|
onLoadTables,
|
|
|
|
|
|
onLoadColumns,
|
|
|
|
|
|
}: HierarchyConfigPanelProps) {
|
|
|
|
|
|
const [localConfig, setLocalConfig] = useState<HierarchyConfig>(
|
|
|
|
|
|
hierarchyConfig || {
|
|
|
|
|
|
warehouseKey: "",
|
|
|
|
|
|
levels: [],
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const [loadingColumns, setLoadingColumns] = useState(false);
|
2025-11-21 15:44:52 +09:00
|
|
|
|
const [columnsCache, setColumnsCache] = useState<{ [tableName: string]: ColumnInfo[] }>({});
|
2025-11-21 02:25:25 +09:00
|
|
|
|
|
2025-11-25 13:55:00 +09:00
|
|
|
|
// 동일한 column_name 이 여러 번 내려오는 경우(조인 중복 등) 제거
|
|
|
|
|
|
const normalizeColumns = (columns: ColumnInfo[]): ColumnInfo[] => {
|
|
|
|
|
|
const map = new Map<string, ColumnInfo>();
|
|
|
|
|
|
for (const col of columns) {
|
|
|
|
|
|
const key = col.column_name;
|
|
|
|
|
|
if (!map.has(key)) {
|
|
|
|
|
|
map.set(key, col);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return Array.from(map.values());
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-25 09:53:36 +09:00
|
|
|
|
// 외부에서 변경된 경우 동기화 및 컬럼 자동 로드
|
2025-11-21 16:13:50 +09:00
|
|
|
|
// 외부에서 변경된 경우 동기화 및 컬럼 자동 로드
|
2025-11-21 02:25:25 +09:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (hierarchyConfig) {
|
|
|
|
|
|
setLocalConfig(hierarchyConfig);
|
2025-11-21 16:13:50 +09:00
|
|
|
|
|
2025-11-24 18:23:00 +09:00
|
|
|
|
// 저장된 설정의 테이블들에 대한 컬럼 자동 로드
|
2025-11-21 16:13:50 +09:00
|
|
|
|
const loadSavedColumns = async () => {
|
2025-11-24 18:23:00 +09:00
|
|
|
|
const tablesToLoad: string[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 창고 테이블
|
2025-11-21 16:13:50 +09:00
|
|
|
|
if (hierarchyConfig.warehouse?.tableName) {
|
2025-11-24 18:23:00 +09:00
|
|
|
|
tablesToLoad.push(hierarchyConfig.warehouse.tableName);
|
2025-11-21 16:13:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 18:23:00 +09:00
|
|
|
|
// 계층 레벨 테이블들
|
|
|
|
|
|
hierarchyConfig.levels?.forEach((level) => {
|
|
|
|
|
|
if (level.tableName) {
|
|
|
|
|
|
tablesToLoad.push(level.tableName);
|
2025-11-21 16:13:50 +09:00
|
|
|
|
}
|
2025-11-24 18:23:00 +09:00
|
|
|
|
});
|
2025-11-21 16:13:50 +09:00
|
|
|
|
|
2025-11-24 18:23:00 +09:00
|
|
|
|
// 자재 테이블
|
2025-11-21 16:13:50 +09:00
|
|
|
|
if (hierarchyConfig.material?.tableName) {
|
2025-11-24 18:23:00 +09:00
|
|
|
|
tablesToLoad.push(hierarchyConfig.material.tableName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 14:57:48 +09:00
|
|
|
|
// 중복 제거 후, 아직 캐시에 없는 테이블만 병렬로 로드
|
2025-11-24 18:23:00 +09:00
|
|
|
|
const uniqueTables = [...new Set(tablesToLoad)];
|
2025-11-25 14:57:48 +09:00
|
|
|
|
const tablesToFetch = uniqueTables.filter((tableName) => !columnsCache[tableName]);
|
|
|
|
|
|
|
|
|
|
|
|
if (tablesToFetch.length === 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setLoadingColumns(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
|
tablesToFetch.map(async (tableName) => {
|
2025-11-27 11:32:19 +09:00
|
|
|
|
try {
|
|
|
|
|
|
const columns = await onLoadColumns(tableName);
|
|
|
|
|
|
const normalized = normalizeColumns(columns);
|
|
|
|
|
|
setColumnsCache((prev) => ({ ...prev, [tableName]: normalized }));
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`컬럼 로드 실패 (${tableName}):`, error);
|
|
|
|
|
|
}
|
2025-11-25 14:57:48 +09:00
|
|
|
|
}),
|
|
|
|
|
|
);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoadingColumns(false);
|
2025-11-21 16:13:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-24 18:23:00 +09:00
|
|
|
|
if (externalDbConnectionId) {
|
|
|
|
|
|
loadSavedColumns();
|
|
|
|
|
|
}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
}
|
2025-11-24 18:23:00 +09:00
|
|
|
|
}, [hierarchyConfig, externalDbConnectionId]);
|
2025-11-21 02:25:25 +09:00
|
|
|
|
|
2025-11-25 13:55:00 +09:00
|
|
|
|
// 지정된 컬럼이 Primary Key 인지 여부
|
|
|
|
|
|
const isPrimaryKey = (col: ColumnInfo): boolean => {
|
|
|
|
|
|
if (col.is_primary_key === true) return true;
|
|
|
|
|
|
if (typeof col.is_primary_key === "string") {
|
|
|
|
|
|
const v = col.is_primary_key.toUpperCase();
|
|
|
|
|
|
return v === "YES" || v === "Y" || v === "TRUE" || v === "PK";
|
2025-11-21 16:13:50 +09:00
|
|
|
|
}
|
2025-11-25 13:55:00 +09:00
|
|
|
|
return false;
|
|
|
|
|
|
};
|
2025-11-21 02:25:25 +09:00
|
|
|
|
|
2025-11-25 13:55:00 +09:00
|
|
|
|
// 테이블 선택 시 컬럼 로드 + PK 기반 기본값 설정
|
|
|
|
|
|
const handleTableChange = async (tableName: string, type: "warehouse" | "material" | number) => {
|
|
|
|
|
|
let loadedColumns = columnsCache[tableName];
|
|
|
|
|
|
|
|
|
|
|
|
// 아직 캐시에 없으면 먼저 컬럼 조회
|
|
|
|
|
|
if (!loadedColumns) {
|
|
|
|
|
|
setLoadingColumns(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const fetched = await onLoadColumns(tableName);
|
|
|
|
|
|
loadedColumns = normalizeColumns(fetched);
|
|
|
|
|
|
setColumnsCache((prev) => ({ ...prev, [tableName]: loadedColumns! }));
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("컬럼 로드 실패:", error);
|
|
|
|
|
|
loadedColumns = [];
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoadingColumns(false);
|
|
|
|
|
|
}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
}
|
2025-11-25 13:55:00 +09:00
|
|
|
|
|
|
|
|
|
|
const columns = loadedColumns || [];
|
|
|
|
|
|
|
|
|
|
|
|
// PK 기반으로 keyColumn 기본값 자동 설정 (이미 값이 있으면 건드리지 않음)
|
|
|
|
|
|
// PK 정보가 없으면 첫 번째 컬럼을 기본값으로 사용
|
|
|
|
|
|
setLocalConfig((prev) => {
|
|
|
|
|
|
const next = { ...prev };
|
|
|
|
|
|
const primaryColumns = columns.filter((col) => isPrimaryKey(col));
|
|
|
|
|
|
const pkName = (primaryColumns[0] || columns[0])?.column_name;
|
|
|
|
|
|
|
|
|
|
|
|
if (!pkName) {
|
|
|
|
|
|
return next;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (type === "warehouse") {
|
|
|
|
|
|
const wh = {
|
|
|
|
|
|
...(next.warehouse || { tableName }),
|
|
|
|
|
|
tableName: next.warehouse?.tableName || tableName,
|
|
|
|
|
|
};
|
|
|
|
|
|
if (!wh.keyColumn) {
|
|
|
|
|
|
wh.keyColumn = pkName;
|
|
|
|
|
|
}
|
|
|
|
|
|
next.warehouse = wh;
|
|
|
|
|
|
} else if (type === "material") {
|
|
|
|
|
|
const material = {
|
|
|
|
|
|
...(next.material || { tableName }),
|
|
|
|
|
|
tableName: next.material?.tableName || tableName,
|
|
|
|
|
|
};
|
|
|
|
|
|
if (!material.keyColumn) {
|
|
|
|
|
|
material.keyColumn = pkName;
|
|
|
|
|
|
}
|
|
|
|
|
|
next.material = material as NonNullable<HierarchyConfig["material"]>;
|
|
|
|
|
|
} else if (typeof type === "number") {
|
|
|
|
|
|
// 계층 레벨
|
|
|
|
|
|
next.levels = next.levels.map((lvl) => {
|
|
|
|
|
|
if (lvl.level !== type) return lvl;
|
|
|
|
|
|
const updated: HierarchyLevel = {
|
|
|
|
|
|
...lvl,
|
|
|
|
|
|
tableName: lvl.tableName || tableName,
|
|
|
|
|
|
};
|
|
|
|
|
|
if (!updated.keyColumn) {
|
|
|
|
|
|
updated.keyColumn = pkName;
|
|
|
|
|
|
}
|
|
|
|
|
|
return updated;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return next;
|
|
|
|
|
|
});
|
2025-11-21 02:25:25 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 창고 키 변경 (제거됨 - 상위 컴포넌트에서 처리)
|
|
|
|
|
|
|
|
|
|
|
|
// 레벨 추가
|
|
|
|
|
|
const handleAddLevel = () => {
|
|
|
|
|
|
const maxLevel = localConfig.levels.length > 0 ? Math.max(...localConfig.levels.map((l) => l.level)) : 0;
|
|
|
|
|
|
const newLevel: HierarchyLevel = {
|
|
|
|
|
|
level: maxLevel + 1,
|
|
|
|
|
|
name: `레벨 ${maxLevel + 1}`,
|
|
|
|
|
|
tableName: "",
|
|
|
|
|
|
keyColumn: "",
|
|
|
|
|
|
nameColumn: "",
|
|
|
|
|
|
parentKeyColumn: "",
|
|
|
|
|
|
objectTypes: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const newConfig = {
|
|
|
|
|
|
...localConfig,
|
|
|
|
|
|
levels: [...localConfig.levels, newLevel],
|
|
|
|
|
|
};
|
|
|
|
|
|
setLocalConfig(newConfig);
|
|
|
|
|
|
// onHierarchyConfigChange(newConfig); // 즉시 전달하지 않음
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 레벨 삭제
|
|
|
|
|
|
const handleRemoveLevel = (level: number) => {
|
|
|
|
|
|
const newConfig = {
|
|
|
|
|
|
...localConfig,
|
|
|
|
|
|
levels: localConfig.levels.filter((l) => l.level !== level),
|
|
|
|
|
|
};
|
|
|
|
|
|
setLocalConfig(newConfig);
|
|
|
|
|
|
// onHierarchyConfigChange(newConfig); // 즉시 전달하지 않음
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 레벨 설정 변경
|
|
|
|
|
|
const handleLevelChange = (level: number, field: keyof HierarchyLevel, value: any) => {
|
|
|
|
|
|
const newConfig = {
|
|
|
|
|
|
...localConfig,
|
|
|
|
|
|
levels: localConfig.levels.map((l) => (l.level === level ? { ...l, [field]: value } : l)),
|
|
|
|
|
|
};
|
|
|
|
|
|
setLocalConfig(newConfig);
|
|
|
|
|
|
// onHierarchyConfigChange(newConfig); // 즉시 전달하지 않음
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 자재 설정 변경
|
|
|
|
|
|
const handleMaterialChange = (field: keyof NonNullable<HierarchyConfig["material"]>, value: string) => {
|
|
|
|
|
|
const newConfig = {
|
|
|
|
|
|
...localConfig,
|
|
|
|
|
|
material: {
|
|
|
|
|
|
...localConfig.material,
|
|
|
|
|
|
[field]: value,
|
|
|
|
|
|
} as NonNullable<HierarchyConfig["material"]>,
|
|
|
|
|
|
};
|
|
|
|
|
|
setLocalConfig(newConfig);
|
|
|
|
|
|
// onHierarchyConfigChange(newConfig); // 즉시 전달하지 않음
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 창고 설정 변경
|
|
|
|
|
|
const handleWarehouseChange = (field: keyof NonNullable<HierarchyConfig["warehouse"]>, value: string) => {
|
|
|
|
|
|
const newWarehouse = {
|
|
|
|
|
|
...localConfig.warehouse,
|
|
|
|
|
|
[field]: value,
|
|
|
|
|
|
} as NonNullable<HierarchyConfig["warehouse"]>;
|
|
|
|
|
|
setLocalConfig({ ...localConfig, warehouse: newWarehouse });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 설정 적용
|
|
|
|
|
|
const handleApplyConfig = () => {
|
|
|
|
|
|
onHierarchyConfigChange(localConfig);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (!externalDbConnectionId) {
|
|
|
|
|
|
return <div className="text-muted-foreground p-4 text-center text-sm">외부 DB를 먼저 선택하세요</div>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
|
{/* 창고 설정 */}
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<CardHeader className="p-4 pb-3">
|
|
|
|
|
|
<CardTitle className="text-sm">창고 설정</CardTitle>
|
|
|
|
|
|
<CardDescription className="text-[10px]">창고 테이블 및 컬럼 매핑</CardDescription>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent className="space-y-2 p-4 pt-0">
|
|
|
|
|
|
{/* 창고 테이블 선택 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">테이블</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={localConfig.warehouse?.tableName || ""}
|
|
|
|
|
|
onValueChange={async (value) => {
|
|
|
|
|
|
handleWarehouseChange("tableName", value);
|
|
|
|
|
|
await handleTableChange(value, "warehouse");
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue placeholder="테이블 선택..." />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{availableTables.map((table) => (
|
2025-11-21 15:44:52 +09:00
|
|
|
|
<SelectItem key={table.table_name} value={table.table_name} className="text-[10px]">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{table.table_name}</span>
|
|
|
|
|
|
{table.description && (
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<span className="text-muted-foreground text-[9px]">{table.description}</span>
|
2025-11-21 15:44:52 +09:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
2025-11-21 16:13:50 +09:00
|
|
|
|
{!localConfig.warehouse?.tableName && (
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<p className="text-muted-foreground mt-1 text-[9px]">
|
2025-11-21 16:13:50 +09:00
|
|
|
|
ℹ️ 창고 테이블을 선택하고 "설정 적용"을 눌러주세요
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 창고 컬럼 매핑 */}
|
|
|
|
|
|
{localConfig.warehouse?.tableName && columnsCache[localConfig.warehouse.tableName] && (
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">ID 컬럼</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={localConfig.warehouse.keyColumn || ""}
|
|
|
|
|
|
onValueChange={(value) => handleWarehouseChange("keyColumn", value)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue placeholder="선택..." />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
2025-11-25 13:55:00 +09:00
|
|
|
|
{columnsCache[localConfig.warehouse.tableName].map((col) => {
|
|
|
|
|
|
const pk = isPrimaryKey(col);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-[10px]">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{col.column_name}
|
|
|
|
|
|
{pk && <span className="text-amber-500 ml-1 text-[8px]">PK</span>}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{col.description && (
|
|
|
|
|
|
<span className="text-muted-foreground text-[8px]">{col.description}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">이름 컬럼</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={localConfig.warehouse.nameColumn || ""}
|
|
|
|
|
|
onValueChange={(value) => handleWarehouseChange("nameColumn", value)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue placeholder="선택..." />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{columnsCache[localConfig.warehouse.tableName].map((col) => (
|
2025-11-21 15:44:52 +09:00
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-[10px]">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{col.column_name}</span>
|
|
|
|
|
|
{col.description && (
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<span className="text-muted-foreground text-[8px]">{col.description}</span>
|
2025-11-21 15:44:52 +09:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-11-25 13:55:00 +09:00
|
|
|
|
|
|
|
|
|
|
{localConfig.warehouse?.tableName &&
|
|
|
|
|
|
!columnsCache[localConfig.warehouse.tableName] &&
|
|
|
|
|
|
loadingColumns && (
|
|
|
|
|
|
<div className="flex items-center gap-2 pt-2 text-[10px] text-muted-foreground">
|
|
|
|
|
|
<Loader2 className="h-3 w-3 animate-spin" />
|
|
|
|
|
|
<span>컬럼 정보를 불러오는 중입니다...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 계층 레벨 목록 */}
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<CardHeader className="p-4 pb-3">
|
|
|
|
|
|
<CardTitle className="text-sm">계층 레벨</CardTitle>
|
|
|
|
|
|
<CardDescription className="text-[10px]">영역, 하위 영역 등</CardDescription>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent className="space-y-3 p-4 pt-0">
|
|
|
|
|
|
{localConfig.levels.length === 0 && (
|
|
|
|
|
|
<div className="text-muted-foreground py-6 text-center text-xs">레벨을 추가하세요</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{localConfig.levels.map((level) => (
|
|
|
|
|
|
<Card key={level.level} className="border-muted">
|
|
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between p-3">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<GripVertical className="text-muted-foreground h-4 w-4" />
|
|
|
|
|
|
<Input
|
2025-11-21 12:22:27 +09:00
|
|
|
|
value={level.name || ""}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
onChange={(e) => handleLevelChange(level.level, "name", e.target.value)}
|
|
|
|
|
|
className="h-7 w-32 text-xs"
|
|
|
|
|
|
placeholder="레벨명"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
onClick={() => handleRemoveLevel(level.level)}
|
|
|
|
|
|
className="h-7 w-7 p-0"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trash2 className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent className="space-y-2 p-3 pt-0">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">테이블</Label>
|
|
|
|
|
|
<Select
|
2025-11-21 12:22:27 +09:00
|
|
|
|
value={level.tableName || ""}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
onValueChange={(val) => {
|
|
|
|
|
|
handleLevelChange(level.level, "tableName", val);
|
|
|
|
|
|
handleTableChange(val, level.level);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue placeholder="테이블 선택" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{availableTables.map((table) => (
|
2025-11-21 15:44:52 +09:00
|
|
|
|
<SelectItem key={table.table_name} value={table.table_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{table.table_name}</span>
|
|
|
|
|
|
{table.description && (
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<span className="text-muted-foreground text-[10px]">{table.description}</span>
|
2025-11-21 15:44:52 +09:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{level.tableName && columnsCache[level.tableName] && (
|
|
|
|
|
|
<>
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">ID 컬럼</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={level.keyColumn || ""}
|
|
|
|
|
|
onValueChange={(val) => handleLevelChange(level.level, "keyColumn", val)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
2025-11-25 13:55:00 +09:00
|
|
|
|
{columnsCache[level.tableName].map((col) => {
|
|
|
|
|
|
const pk = isPrimaryKey(col);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{col.column_name}
|
|
|
|
|
|
{pk && <span className="text-amber-500 ml-1 text-[9px]">PK</span>}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{col.description && (
|
|
|
|
|
|
<span className="text-muted-foreground text-[9px]">{col.description}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
2025-11-25 09:53:36 +09:00
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
2025-11-21 03:33:49 +09:00
|
|
|
|
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">이름 컬럼</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={level.nameColumn || ""}
|
|
|
|
|
|
onValueChange={(val) => handleLevelChange(level.level, "nameColumn", val)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{columnsCache[level.tableName].map((col) => (
|
|
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{col.column_name}</span>
|
|
|
|
|
|
{col.description && (
|
|
|
|
|
|
<span className="text-muted-foreground text-[9px]">{col.description}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">부모 키 컬럼</Label>
|
|
|
|
|
|
<Select
|
2025-11-21 12:22:27 +09:00
|
|
|
|
value={level.parentKeyColumn || ""}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
onValueChange={(val) => handleLevelChange(level.level, "parentKeyColumn", val)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue placeholder="부모 키 컬럼" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{columnsCache[level.tableName].map((col) => (
|
2025-11-21 16:13:50 +09:00
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{col.column_name}</span>
|
|
|
|
|
|
{col.description && (
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<span className="text-muted-foreground text-[9px]">{col.description}</span>
|
2025-11-21 16:13:50 +09:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">타입 컬럼 (선택)</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={level.typeColumn || "__none__"}
|
|
|
|
|
|
onValueChange={(val) =>
|
|
|
|
|
|
handleLevelChange(level.level, "typeColumn", val === "__none__" ? undefined : val)
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue placeholder="타입 컬럼 (선택)" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
<SelectItem value="__none__">없음</SelectItem>
|
|
|
|
|
|
{columnsCache[level.tableName].map((col) => (
|
2025-11-21 16:13:50 +09:00
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{col.column_name}</span>
|
|
|
|
|
|
{col.description && (
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<span className="text-muted-foreground text-[9px]">{col.description}</span>
|
2025-11-21 16:13:50 +09:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2025-11-25 13:55:00 +09:00
|
|
|
|
|
|
|
|
|
|
{level.tableName && !columnsCache[level.tableName] && loadingColumns && (
|
|
|
|
|
|
<div className="flex items-center gap-2 pt-2 text-[10px] text-muted-foreground">
|
|
|
|
|
|
<Loader2 className="h-3 w-3 animate-spin" />
|
|
|
|
|
|
<span>컬럼 정보를 불러오는 중입니다...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
|
|
<Button variant="outline" size="sm" onClick={handleAddLevel} className="h-8 w-full text-xs">
|
|
|
|
|
|
<Plus className="mr-1 h-3 w-3" />
|
|
|
|
|
|
레벨 추가
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 자재 설정 */}
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<CardHeader className="p-4 pb-3">
|
|
|
|
|
|
<CardTitle className="text-sm">자재 설정</CardTitle>
|
|
|
|
|
|
<CardDescription className="text-[10px]">최하위 레벨의 데이터</CardDescription>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent className="space-y-3 p-4 pt-0">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">테이블</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={localConfig.material?.tableName || ""}
|
|
|
|
|
|
onValueChange={(val) => {
|
|
|
|
|
|
handleMaterialChange("tableName", val);
|
|
|
|
|
|
handleTableChange(val, "material");
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-8 text-xs">
|
|
|
|
|
|
<SelectValue placeholder="테이블 선택" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{availableTables.map((table) => (
|
2025-11-21 15:44:52 +09:00
|
|
|
|
<SelectItem key={table.table_name} value={table.table_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{table.table_name}</span>
|
|
|
|
|
|
{table.description && (
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<span className="text-muted-foreground text-[10px]">{table.description}</span>
|
2025-11-21 15:44:52 +09:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{localConfig.material?.tableName && columnsCache[localConfig.material.tableName] && (
|
|
|
|
|
|
<>
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">ID 컬럼</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={localConfig.material.keyColumn || ""}
|
|
|
|
|
|
onValueChange={(val) => handleMaterialChange("keyColumn", val)}
|
|
|
|
|
|
>
|
2025-11-25 13:55:00 +09:00
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{columnsCache[localConfig.material.tableName].map((col) => {
|
|
|
|
|
|
const pk = isPrimaryKey(col);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{col.column_name}
|
|
|
|
|
|
{pk && <span className="text-amber-500 ml-1 text-[9px]">PK</span>}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{col.description && (
|
|
|
|
|
|
<span className="text-muted-foreground text-[9px]">{col.description}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</SelectContent>
|
2025-11-25 09:53:36 +09:00
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">위치 키 컬럼</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={localConfig.material.locationKeyColumn || ""}
|
|
|
|
|
|
onValueChange={(val) => handleMaterialChange("locationKeyColumn", val)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{columnsCache[localConfig.material.tableName].map((col) => (
|
|
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{col.column_name}</span>
|
|
|
|
|
|
{col.description && (
|
|
|
|
|
|
<span className="text-muted-foreground text-[9px]">{col.description}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">레이어 컬럼 (선택)</Label>
|
|
|
|
|
|
<Select
|
2025-11-21 02:25:25 +09:00
|
|
|
|
value={localConfig.material.layerColumn || "__none__"}
|
|
|
|
|
|
onValueChange={(val) => handleMaterialChange("layerColumn", val === "__none__" ? undefined : val)}
|
2025-11-25 09:53:36 +09:00
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue placeholder="레이어 컬럼" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
<SelectItem value="__none__">없음</SelectItem>
|
2025-11-25 09:53:36 +09:00
|
|
|
|
{columnsCache[localConfig.material.tableName].map((col) => (
|
|
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{col.column_name}</span>
|
|
|
|
|
|
{col.description && (
|
|
|
|
|
|
<span className="text-muted-foreground text-[9px]">{col.description}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">수량 컬럼 (선택)</Label>
|
|
|
|
|
|
<Select
|
2025-11-21 02:25:25 +09:00
|
|
|
|
value={localConfig.material.quantityColumn || "__none__"}
|
|
|
|
|
|
onValueChange={(val) => handleMaterialChange("quantityColumn", val === "__none__" ? undefined : val)}
|
2025-11-25 09:53:36 +09:00
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-7 text-[10px]">
|
|
|
|
|
|
<SelectValue placeholder="수량 컬럼" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
<SelectItem value="__none__">없음</SelectItem>
|
2025-11-25 09:53:36 +09:00
|
|
|
|
{columnsCache[localConfig.material.tableName].map((col) => (
|
|
|
|
|
|
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span>{col.column_name}</span>
|
|
|
|
|
|
{col.description && (
|
|
|
|
|
|
<span className="text-muted-foreground text-[9px]">{col.description}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Separator className="my-3" />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 표시 컬럼 선택 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-[10px]">우측 패널 표시 컬럼</Label>
|
|
|
|
|
|
<p className="text-muted-foreground mb-2 text-[9px]">
|
|
|
|
|
|
자재 클릭 시 표시할 정보를 선택하고 라벨을 입력하세요
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div className="max-h-60 space-y-2 overflow-y-auto rounded border p-2">
|
|
|
|
|
|
{columnsCache[localConfig.material.tableName].map((col) => {
|
2025-11-21 15:44:52 +09:00
|
|
|
|
const displayItem = localConfig.material?.displayColumns?.find((d) => d.column === col.column_name);
|
2025-11-21 02:25:25 +09:00
|
|
|
|
const isSelected = !!displayItem;
|
|
|
|
|
|
return (
|
2025-11-21 15:44:52 +09:00
|
|
|
|
<div key={col.column_name} className="flex items-center gap-2">
|
2025-11-21 02:25:25 +09:00
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={isSelected}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const currentDisplay = localConfig.material?.displayColumns || [];
|
|
|
|
|
|
const newDisplay = e.target.checked
|
2025-11-21 15:44:52 +09:00
|
|
|
|
? [...currentDisplay, { column: col.column_name, label: col.column_name }]
|
|
|
|
|
|
: currentDisplay.filter((d) => d.column !== col.column_name);
|
2025-11-21 02:25:25 +09:00
|
|
|
|
handleMaterialChange("displayColumns", newDisplay);
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-3 w-3 shrink-0"
|
|
|
|
|
|
/>
|
2025-11-21 15:44:52 +09:00
|
|
|
|
<div className="flex w-24 shrink-0 flex-col">
|
|
|
|
|
|
<span className="text-[10px]">{col.column_name}</span>
|
|
|
|
|
|
{col.description && (
|
2025-11-25 09:53:36 +09:00
|
|
|
|
<span className="text-muted-foreground text-[8px]">{col.description}</span>
|
2025-11-21 15:44:52 +09:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-11-21 02:25:25 +09:00
|
|
|
|
{isSelected && (
|
|
|
|
|
|
<Input
|
2025-11-21 14:29:17 +09:00
|
|
|
|
value={displayItem?.label ?? ""}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const currentDisplay = localConfig.material?.displayColumns || [];
|
|
|
|
|
|
const newDisplay = currentDisplay.map((d) =>
|
2025-11-21 15:44:52 +09:00
|
|
|
|
d.column === col.column_name ? { ...d, label: e.target.value } : d,
|
2025-11-21 02:25:25 +09:00
|
|
|
|
);
|
|
|
|
|
|
handleMaterialChange("displayColumns", newDisplay);
|
|
|
|
|
|
}}
|
|
|
|
|
|
placeholder="표시명 입력..."
|
|
|
|
|
|
className="h-6 flex-1 text-[10px]"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2025-11-25 13:55:00 +09:00
|
|
|
|
|
|
|
|
|
|
{localConfig.material?.tableName &&
|
|
|
|
|
|
!columnsCache[localConfig.material.tableName] &&
|
|
|
|
|
|
loadingColumns && (
|
|
|
|
|
|
<div className="flex items-center gap-2 pt-2 text-[10px] text-muted-foreground">
|
|
|
|
|
|
<Loader2 className="h-3 w-3 animate-spin" />
|
|
|
|
|
|
<span>컬럼 정보를 불러오는 중입니다...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-11-21 02:25:25 +09:00
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 적용 버튼 */}
|
|
|
|
|
|
<div className="flex justify-end">
|
|
|
|
|
|
<Button onClick={handleApplyConfig} className="h-10 gap-2 text-sm font-medium">
|
|
|
|
|
|
설정 적용
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|