802 lines
34 KiB
TypeScript
802 lines
34 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Plus, X, Check, ChevronsUpDown } from "lucide-react";
|
|
import { AutocompleteSearchInputConfig, FieldMapping, ValueFieldStorage } from "./types";
|
|
import { tableManagementApi } from "@/lib/api/tableManagement";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface AutocompleteSearchInputConfigPanelProps {
|
|
config: AutocompleteSearchInputConfig;
|
|
onConfigChange: (config: AutocompleteSearchInputConfig) => void;
|
|
}
|
|
|
|
export function AutocompleteSearchInputConfigPanel({
|
|
config,
|
|
onConfigChange,
|
|
}: AutocompleteSearchInputConfigPanelProps) {
|
|
const [localConfig, setLocalConfig] = useState(config);
|
|
const [allTables, setAllTables] = useState<any[]>([]);
|
|
const [tableColumns, setTableColumns] = useState<any[]>([]);
|
|
const [isLoadingTables, setIsLoadingTables] = useState(false);
|
|
const [isLoadingColumns, setIsLoadingColumns] = useState(false);
|
|
const [openTableCombo, setOpenTableCombo] = useState(false);
|
|
const [openDisplayFieldCombo, setOpenDisplayFieldCombo] = useState(false);
|
|
const [openValueFieldCombo, setOpenValueFieldCombo] = useState(false);
|
|
const [openStorageTableCombo, setOpenStorageTableCombo] = useState(false);
|
|
const [openStorageColumnCombo, setOpenStorageColumnCombo] = useState(false);
|
|
const [storageTableColumns, setStorageTableColumns] = useState<any[]>([]);
|
|
const [isLoadingStorageColumns, setIsLoadingStorageColumns] = useState(false);
|
|
|
|
// 전체 테이블 목록 로드
|
|
useEffect(() => {
|
|
const loadTables = async () => {
|
|
setIsLoadingTables(true);
|
|
try {
|
|
const response = await tableManagementApi.getTableList();
|
|
if (response.success && response.data) {
|
|
setAllTables(response.data);
|
|
}
|
|
} catch (error) {
|
|
console.error("테이블 목록 로드 실패:", error);
|
|
} finally {
|
|
setIsLoadingTables(false);
|
|
}
|
|
};
|
|
loadTables();
|
|
}, []);
|
|
|
|
// 선택된 테이블의 컬럼 목록 로드
|
|
useEffect(() => {
|
|
const loadColumns = async () => {
|
|
if (!localConfig.tableName) {
|
|
setTableColumns([]);
|
|
return;
|
|
}
|
|
|
|
setIsLoadingColumns(true);
|
|
try {
|
|
const response = await tableManagementApi.getColumnList(localConfig.tableName);
|
|
if (response.success && response.data) {
|
|
setTableColumns(response.data.columns);
|
|
}
|
|
} catch (error) {
|
|
console.error("컬럼 목록 로드 실패:", error);
|
|
setTableColumns([]);
|
|
} finally {
|
|
setIsLoadingColumns(false);
|
|
}
|
|
};
|
|
loadColumns();
|
|
}, [localConfig.tableName]);
|
|
|
|
// 저장 대상 테이블의 컬럼 목록 로드
|
|
useEffect(() => {
|
|
const loadStorageColumns = async () => {
|
|
const storageTable = localConfig.valueFieldStorage?.targetTable;
|
|
if (!storageTable) {
|
|
setStorageTableColumns([]);
|
|
return;
|
|
}
|
|
|
|
setIsLoadingStorageColumns(true);
|
|
try {
|
|
const response = await tableManagementApi.getColumnList(storageTable);
|
|
if (response.success && response.data) {
|
|
setStorageTableColumns(response.data.columns);
|
|
}
|
|
} catch (error) {
|
|
console.error("저장 테이블 컬럼 로드 실패:", error);
|
|
setStorageTableColumns([]);
|
|
} finally {
|
|
setIsLoadingStorageColumns(false);
|
|
}
|
|
};
|
|
loadStorageColumns();
|
|
}, [localConfig.valueFieldStorage?.targetTable]);
|
|
|
|
useEffect(() => {
|
|
setLocalConfig(config);
|
|
}, [config]);
|
|
|
|
const updateConfig = (updates: Partial<AutocompleteSearchInputConfig>) => {
|
|
const newConfig = { ...localConfig, ...updates };
|
|
setLocalConfig(newConfig);
|
|
onConfigChange(newConfig);
|
|
};
|
|
|
|
const addSearchField = () => {
|
|
const fields = localConfig.searchFields || [];
|
|
updateConfig({ searchFields: [...fields, ""] });
|
|
};
|
|
|
|
const updateSearchField = (index: number, value: string) => {
|
|
const fields = [...(localConfig.searchFields || [])];
|
|
fields[index] = value;
|
|
updateConfig({ searchFields: fields });
|
|
};
|
|
|
|
const removeSearchField = (index: number) => {
|
|
const fields = [...(localConfig.searchFields || [])];
|
|
fields.splice(index, 1);
|
|
updateConfig({ searchFields: fields });
|
|
};
|
|
|
|
const addAdditionalField = () => {
|
|
const fields = localConfig.additionalFields || [];
|
|
updateConfig({ additionalFields: [...fields, ""] });
|
|
};
|
|
|
|
const updateAdditionalField = (index: number, value: string) => {
|
|
const fields = [...(localConfig.additionalFields || [])];
|
|
fields[index] = value;
|
|
updateConfig({ additionalFields: fields });
|
|
};
|
|
|
|
const removeAdditionalField = (index: number) => {
|
|
const fields = [...(localConfig.additionalFields || [])];
|
|
fields.splice(index, 1);
|
|
updateConfig({ additionalFields: fields });
|
|
};
|
|
|
|
// 필드 매핑 관리 함수
|
|
const addFieldMapping = () => {
|
|
const mappings = localConfig.fieldMappings || [];
|
|
updateConfig({
|
|
fieldMappings: [
|
|
...mappings,
|
|
{ sourceField: "", targetField: "", label: "" },
|
|
],
|
|
});
|
|
};
|
|
|
|
const updateFieldMapping = (index: number, updates: Partial<FieldMapping>) => {
|
|
const mappings = [...(localConfig.fieldMappings || [])];
|
|
mappings[index] = { ...mappings[index], ...updates };
|
|
updateConfig({ fieldMappings: mappings });
|
|
};
|
|
|
|
const removeFieldMapping = (index: number) => {
|
|
const mappings = [...(localConfig.fieldMappings || [])];
|
|
mappings.splice(index, 1);
|
|
updateConfig({ fieldMappings: mappings });
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4 p-4">
|
|
<div className="space-y-2">
|
|
<Label className="text-xs sm:text-sm">테이블명 *</Label>
|
|
<Popover open={openTableCombo} onOpenChange={setOpenTableCombo}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={openTableCombo}
|
|
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
|
disabled={isLoadingTables}
|
|
>
|
|
{localConfig.tableName
|
|
? allTables.find((t) => t.tableName === localConfig.tableName)?.displayName || localConfig.tableName
|
|
: isLoadingTables ? "로딩 중..." : "테이블 선택"}
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
|
<Command>
|
|
<CommandInput placeholder="테이블 검색..." className="text-xs sm:text-sm" />
|
|
<CommandList>
|
|
<CommandEmpty className="text-xs sm:text-sm">테이블을 찾을 수 없습니다.</CommandEmpty>
|
|
<CommandGroup>
|
|
{allTables.map((table) => (
|
|
<CommandItem
|
|
key={table.tableName}
|
|
value={table.tableName}
|
|
onSelect={() => {
|
|
updateConfig({ tableName: table.tableName });
|
|
setOpenTableCombo(false);
|
|
}}
|
|
className="text-xs sm:text-sm"
|
|
>
|
|
<Check className={cn("mr-2 h-4 w-4", localConfig.tableName === table.tableName ? "opacity-100" : "opacity-0")} />
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">{table.displayName || table.tableName}</span>
|
|
{table.displayName && <span className="text-[10px] text-gray-500">{table.tableName}</span>}
|
|
</div>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
검색할 데이터가 저장된 테이블을 선택하세요
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label className="text-xs sm:text-sm">표시 필드 *</Label>
|
|
<Popover open={openDisplayFieldCombo} onOpenChange={setOpenDisplayFieldCombo}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={openDisplayFieldCombo}
|
|
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
|
disabled={!localConfig.tableName || isLoadingColumns}
|
|
>
|
|
{localConfig.displayField
|
|
? tableColumns.find((c) => c.columnName === localConfig.displayField)?.displayName || localConfig.displayField
|
|
: isLoadingColumns ? "로딩 중..." : "필드 선택"}
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
|
<Command>
|
|
<CommandInput placeholder="필드 검색..." className="text-xs sm:text-sm" />
|
|
<CommandList>
|
|
<CommandEmpty className="text-xs sm:text-sm">필드를 찾을 수 없습니다.</CommandEmpty>
|
|
<CommandGroup>
|
|
{tableColumns.map((column) => (
|
|
<CommandItem
|
|
key={column.columnName}
|
|
value={column.columnName}
|
|
onSelect={() => {
|
|
updateConfig({ displayField: column.columnName });
|
|
setOpenDisplayFieldCombo(false);
|
|
}}
|
|
className="text-xs sm:text-sm"
|
|
>
|
|
<Check className={cn("mr-2 h-4 w-4", localConfig.displayField === column.columnName ? "opacity-100" : "opacity-0")} />
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">{column.displayName || column.columnName}</span>
|
|
{column.displayName && <span className="text-[10px] text-gray-500">{column.columnName}</span>}
|
|
</div>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
사용자에게 보여줄 필드 (예: 거래처명)
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label className="text-xs sm:text-sm">값 필드 *</Label>
|
|
<Popover open={openValueFieldCombo} onOpenChange={setOpenValueFieldCombo}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={openValueFieldCombo}
|
|
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
|
disabled={!localConfig.tableName || isLoadingColumns}
|
|
>
|
|
{localConfig.valueField
|
|
? tableColumns.find((c) => c.columnName === localConfig.valueField)?.displayName || localConfig.valueField
|
|
: isLoadingColumns ? "로딩 중..." : "필드 선택"}
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
|
<Command>
|
|
<CommandInput placeholder="필드 검색..." className="text-xs sm:text-sm" />
|
|
<CommandList>
|
|
<CommandEmpty className="text-xs sm:text-sm">필드를 찾을 수 없습니다.</CommandEmpty>
|
|
<CommandGroup>
|
|
{tableColumns.map((column) => (
|
|
<CommandItem
|
|
key={column.columnName}
|
|
value={column.columnName}
|
|
onSelect={() => {
|
|
updateConfig({ valueField: column.columnName });
|
|
setOpenValueFieldCombo(false);
|
|
}}
|
|
className="text-xs sm:text-sm"
|
|
>
|
|
<Check className={cn("mr-2 h-4 w-4", localConfig.valueField === column.columnName ? "opacity-100" : "opacity-0")} />
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">{column.displayName || column.columnName}</span>
|
|
{column.displayName && <span className="text-[10px] text-gray-500">{column.columnName}</span>}
|
|
</div>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
검색 테이블에서 가져올 값의 컬럼 (예: customer_code)
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label className="text-xs sm:text-sm">플레이스홀더</Label>
|
|
<Input
|
|
value={localConfig.placeholder || ""}
|
|
onChange={(e) => updateConfig({ placeholder: e.target.value })}
|
|
placeholder="검색..."
|
|
className="h-8 text-xs sm:h-10 sm:text-sm"
|
|
/>
|
|
</div>
|
|
|
|
{/* 값 필드 저장 위치 설정 */}
|
|
<div className="space-y-4 border rounded-lg p-4 bg-card">
|
|
<div>
|
|
<h3 className="text-sm font-semibold mb-1">값 필드 저장 위치 (고급)</h3>
|
|
<p className="text-xs text-muted-foreground">
|
|
위에서 선택한 "값 필드"의 데이터를 어느 테이블/컬럼에 저장할지 지정합니다.
|
|
<br />
|
|
미설정 시 화면의 연결 테이블에 컴포넌트의 바인딩 필드로 자동 저장됩니다.
|
|
</p>
|
|
</div>
|
|
|
|
{/* 저장 테이블 선택 */}
|
|
<div className="space-y-2">
|
|
<Label className="text-xs sm:text-sm">저장 테이블</Label>
|
|
<Popover open={openStorageTableCombo} onOpenChange={setOpenStorageTableCombo}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={openStorageTableCombo}
|
|
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
|
disabled={isLoadingTables}
|
|
>
|
|
{localConfig.valueFieldStorage?.targetTable
|
|
? allTables.find((t) => t.tableName === localConfig.valueFieldStorage?.targetTable)?.displayName ||
|
|
localConfig.valueFieldStorage.targetTable
|
|
: "기본값 (화면 연결 테이블)"}
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
|
<Command>
|
|
<CommandInput placeholder="테이블 검색..." className="text-xs sm:text-sm" />
|
|
<CommandList>
|
|
<CommandEmpty className="text-xs sm:text-sm">테이블을 찾을 수 없습니다.</CommandEmpty>
|
|
<CommandGroup>
|
|
{/* 기본값 옵션 */}
|
|
<CommandItem
|
|
value=""
|
|
onSelect={() => {
|
|
updateConfig({
|
|
valueFieldStorage: {
|
|
...localConfig.valueFieldStorage,
|
|
targetTable: undefined,
|
|
targetColumn: undefined,
|
|
},
|
|
});
|
|
setOpenStorageTableCombo(false);
|
|
}}
|
|
className="text-xs sm:text-sm"
|
|
>
|
|
<Check className={cn("mr-2 h-4 w-4", !localConfig.valueFieldStorage?.targetTable ? "opacity-100" : "opacity-0")} />
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">기본값</span>
|
|
<span className="text-[10px] text-gray-500">화면의 연결 테이블 사용</span>
|
|
</div>
|
|
</CommandItem>
|
|
{allTables.map((table) => (
|
|
<CommandItem
|
|
key={table.tableName}
|
|
value={table.tableName}
|
|
onSelect={() => {
|
|
updateConfig({
|
|
valueFieldStorage: {
|
|
...localConfig.valueFieldStorage,
|
|
targetTable: table.tableName,
|
|
targetColumn: undefined, // 테이블 변경 시 컬럼 초기화
|
|
},
|
|
});
|
|
setOpenStorageTableCombo(false);
|
|
}}
|
|
className="text-xs sm:text-sm"
|
|
>
|
|
<Check
|
|
className={cn(
|
|
"mr-2 h-4 w-4",
|
|
localConfig.valueFieldStorage?.targetTable === table.tableName ? "opacity-100" : "opacity-0"
|
|
)}
|
|
/>
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">{table.displayName || table.tableName}</span>
|
|
{table.displayName && <span className="text-[10px] text-gray-500">{table.tableName}</span>}
|
|
</div>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
값을 저장할 테이블 (기본값: 화면 연결 테이블)
|
|
</p>
|
|
</div>
|
|
|
|
{/* 저장 컬럼 선택 */}
|
|
{localConfig.valueFieldStorage?.targetTable && (
|
|
<div className="space-y-2">
|
|
<Label className="text-xs sm:text-sm">저장 컬럼</Label>
|
|
<Popover open={openStorageColumnCombo} onOpenChange={setOpenStorageColumnCombo}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={openStorageColumnCombo}
|
|
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
|
disabled={isLoadingStorageColumns}
|
|
>
|
|
{localConfig.valueFieldStorage?.targetColumn
|
|
? storageTableColumns.find((c) => c.columnName === localConfig.valueFieldStorage?.targetColumn)
|
|
?.displayName || localConfig.valueFieldStorage.targetColumn
|
|
: isLoadingStorageColumns
|
|
? "로딩 중..."
|
|
: "컬럼 선택"}
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
|
<Command>
|
|
<CommandInput placeholder="컬럼 검색..." className="text-xs sm:text-sm" />
|
|
<CommandList>
|
|
<CommandEmpty className="text-xs sm:text-sm">컬럼을 찾을 수 없습니다.</CommandEmpty>
|
|
<CommandGroup>
|
|
{storageTableColumns.map((column) => (
|
|
<CommandItem
|
|
key={column.columnName}
|
|
value={column.columnName}
|
|
onSelect={() => {
|
|
updateConfig({
|
|
valueFieldStorage: {
|
|
...localConfig.valueFieldStorage,
|
|
targetColumn: column.columnName,
|
|
},
|
|
});
|
|
setOpenStorageColumnCombo(false);
|
|
}}
|
|
className="text-xs sm:text-sm"
|
|
>
|
|
<Check
|
|
className={cn(
|
|
"mr-2 h-4 w-4",
|
|
localConfig.valueFieldStorage?.targetColumn === column.columnName ? "opacity-100" : "opacity-0"
|
|
)}
|
|
/>
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">{column.displayName || column.columnName}</span>
|
|
{column.displayName && <span className="text-[10px] text-gray-500">{column.columnName}</span>}
|
|
</div>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
값을 저장할 컬럼명
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* 설명 박스 */}
|
|
<div className="p-3 bg-blue-50 dark:bg-blue-950 rounded border border-blue-200 dark:border-blue-800">
|
|
<p className="text-xs font-medium mb-2 text-blue-800 dark:text-blue-200">
|
|
저장 위치 동작
|
|
</p>
|
|
<div className="text-[10px] text-blue-700 dark:text-blue-300 space-y-1">
|
|
{localConfig.valueFieldStorage?.targetTable ? (
|
|
<>
|
|
<p>
|
|
선택한 값(<code className="font-mono bg-blue-100 dark:bg-blue-900 px-1 rounded">{localConfig.valueField}</code>)을
|
|
</p>
|
|
<p>
|
|
<code className="font-mono bg-blue-100 dark:bg-blue-900 px-1 rounded">
|
|
{localConfig.valueFieldStorage.targetTable}
|
|
</code>{" "}
|
|
테이블의{" "}
|
|
<code className="font-mono bg-blue-100 dark:bg-blue-900 px-1 rounded">
|
|
{localConfig.valueFieldStorage.targetColumn || "(컬럼 미지정)"}
|
|
</code>{" "}
|
|
컬럼에 저장합니다.
|
|
</p>
|
|
</>
|
|
) : (
|
|
<p>기본값: 화면의 연결 테이블에 컴포넌트의 바인딩 필드로 저장됩니다.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs sm:text-sm">검색 필드</Label>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={addSearchField}
|
|
className="h-7 text-xs"
|
|
disabled={!localConfig.tableName || isLoadingColumns}
|
|
>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
추가
|
|
</Button>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{(localConfig.searchFields || []).map((field, index) => (
|
|
<div key={index} className="flex items-center gap-2">
|
|
<Select
|
|
value={field}
|
|
onValueChange={(value) => updateSearchField(index, value)}
|
|
disabled={!localConfig.tableName || isLoadingColumns}
|
|
>
|
|
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm flex-1">
|
|
<SelectValue placeholder="필드 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{tableColumns.map((col) => (
|
|
<SelectItem key={col.columnName} value={col.columnName}>
|
|
{col.displayName || col.columnName}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => removeSearchField(index)}
|
|
className="h-8 w-8 p-0"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs sm:text-sm">추가 정보 표시</Label>
|
|
<Switch
|
|
checked={localConfig.showAdditionalInfo || false}
|
|
onCheckedChange={(checked) =>
|
|
updateConfig({ showAdditionalInfo: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{localConfig.showAdditionalInfo && (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs sm:text-sm">추가 필드</Label>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={addAdditionalField}
|
|
className="h-7 text-xs"
|
|
disabled={!localConfig.tableName || isLoadingColumns}
|
|
>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
추가
|
|
</Button>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{(localConfig.additionalFields || []).map((field, index) => (
|
|
<div key={index} className="flex items-center gap-2">
|
|
<Select
|
|
value={field}
|
|
onValueChange={(value) => updateAdditionalField(index, value)}
|
|
disabled={!localConfig.tableName || isLoadingColumns}
|
|
>
|
|
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm flex-1">
|
|
<SelectValue placeholder="필드 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{tableColumns.map((col) => (
|
|
<SelectItem key={col.columnName} value={col.columnName}>
|
|
{col.displayName || col.columnName}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => removeAdditionalField(index)}
|
|
className="h-8 w-8 p-0"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 필드 자동 매핑 설정 */}
|
|
<div className="space-y-4 border rounded-lg p-4 bg-card">
|
|
<div>
|
|
<h3 className="text-sm font-semibold mb-1">필드 자동 매핑</h3>
|
|
<p className="text-xs text-muted-foreground">
|
|
선택한 항목의 필드를 화면의 다른 입력 필드에 자동으로 채워넣습니다
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs sm:text-sm">필드 매핑 활성화</Label>
|
|
<Switch
|
|
checked={localConfig.enableFieldMapping || false}
|
|
onCheckedChange={(checked) =>
|
|
updateConfig({ enableFieldMapping: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
활성화하면 항목 선택 시 설정된 필드들이 자동으로 채워집니다
|
|
</p>
|
|
</div>
|
|
|
|
{localConfig.enableFieldMapping && (
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs sm:text-sm">매핑 필드 목록</Label>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={addFieldMapping}
|
|
className="h-7 text-xs"
|
|
disabled={!localConfig.tableName || isLoadingColumns}
|
|
>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
매핑 추가
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
{(localConfig.fieldMappings || []).map((mapping, index) => (
|
|
<div key={index} className="border rounded-lg p-3 space-y-3 bg-background">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs font-medium text-muted-foreground">
|
|
매핑 #{index + 1}
|
|
</span>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => removeFieldMapping(index)}
|
|
className="h-6 w-6 p-0"
|
|
>
|
|
<X className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 표시명 */}
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs">표시명</Label>
|
|
<Input
|
|
value={mapping.label || ""}
|
|
onChange={(e) =>
|
|
updateFieldMapping(index, { label: e.target.value })
|
|
}
|
|
placeholder="예: 거래처명"
|
|
className="h-8 text-xs sm:h-10 sm:text-sm"
|
|
/>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
이 매핑의 설명 (선택사항)
|
|
</p>
|
|
</div>
|
|
|
|
{/* 소스 필드 (테이블의 컬럼) */}
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs">
|
|
소스 필드 (테이블 컬럼) *
|
|
</Label>
|
|
<Select
|
|
value={mapping.sourceField}
|
|
onValueChange={(value) =>
|
|
updateFieldMapping(index, { sourceField: value })
|
|
}
|
|
disabled={!localConfig.tableName || isLoadingColumns}
|
|
>
|
|
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
|
|
<SelectValue placeholder="컬럼 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{tableColumns.map((col) => (
|
|
<SelectItem key={col.columnName} value={col.columnName}>
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">
|
|
{col.displayName || col.columnName}
|
|
</span>
|
|
{col.displayName && (
|
|
<span className="text-[10px] text-gray-500">
|
|
{col.columnName}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
가져올 데이터의 컬럼명
|
|
</p>
|
|
</div>
|
|
|
|
{/* 타겟 필드 (화면의 input ID) */}
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs">
|
|
타겟 필드 (화면 컴포넌트 ID) *
|
|
</Label>
|
|
<Input
|
|
value={mapping.targetField}
|
|
onChange={(e) =>
|
|
updateFieldMapping(index, { targetField: e.target.value })
|
|
}
|
|
placeholder="예: customer_name_input"
|
|
className="h-8 text-xs sm:h-10 sm:text-sm"
|
|
/>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
값을 채울 화면 컴포넌트의 ID (예: input의 id 속성)
|
|
</p>
|
|
</div>
|
|
|
|
{/* 예시 설명 */}
|
|
<div className="p-2 bg-blue-50 dark:bg-blue-950 rounded border border-blue-200 dark:border-blue-800">
|
|
<p className="text-[10px] text-blue-700 dark:text-blue-300">
|
|
{mapping.sourceField && mapping.targetField ? (
|
|
<>
|
|
<span className="font-semibold">{mapping.label || "이 필드"}</span>: 테이블의{" "}
|
|
<code className="font-mono bg-blue-100 dark:bg-blue-900 px-1 rounded">
|
|
{mapping.sourceField}
|
|
</code>{" "}
|
|
값을 화면의{" "}
|
|
<code className="font-mono bg-blue-100 dark:bg-blue-900 px-1 rounded">
|
|
{mapping.targetField}
|
|
</code>{" "}
|
|
컴포넌트에 자동으로 채웁니다
|
|
</>
|
|
) : (
|
|
"소스 필드와 타겟 필드를 모두 선택하세요"
|
|
)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* 사용 안내 */}
|
|
{localConfig.fieldMappings && localConfig.fieldMappings.length > 0 && (
|
|
<div className="p-3 bg-amber-50 dark:bg-amber-950 rounded border border-amber-200 dark:border-amber-800">
|
|
<p className="text-xs font-medium mb-2 text-amber-800 dark:text-amber-200">
|
|
사용 방법
|
|
</p>
|
|
<ul className="text-[10px] text-amber-700 dark:text-amber-300 space-y-1 list-disc list-inside">
|
|
<li>화면에서 이 검색 컴포넌트로 항목을 선택하면</li>
|
|
<li>설정된 매핑에 따라 다른 입력 필드들이 자동으로 채워집니다</li>
|
|
<li>타겟 필드 ID는 화면 디자이너에서 설정한 컴포넌트 ID와 일치해야 합니다</li>
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|