범용폼모달 외부소스 지원
This commit is contained in:
@@ -6,7 +6,10 @@ import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Search, Database, RefreshCw, Settings, Plus, Activity, Trash2, Copy } from "lucide-react";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
||||
import { Search, Database, RefreshCw, Settings, Plus, Activity, Trash2, Copy, Check, ChevronsUpDown } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||
import { toast } from "sonner";
|
||||
import { useMultiLang } from "@/hooks/useMultiLang";
|
||||
@@ -90,6 +93,13 @@ export default function TableManagementPage() {
|
||||
// 🎯 Entity 조인 관련 상태
|
||||
const [referenceTableColumns, setReferenceTableColumns] = useState<Record<string, ReferenceTableColumn[]>>({});
|
||||
|
||||
// 🆕 Entity 타입 Combobox 열림/닫힘 상태 (컬럼별 관리)
|
||||
const [entityComboboxOpen, setEntityComboboxOpen] = useState<Record<string, {
|
||||
table: boolean;
|
||||
joinColumn: boolean;
|
||||
displayColumn: boolean;
|
||||
}>>({});
|
||||
|
||||
// DDL 기능 관련 상태
|
||||
const [createTableModalOpen, setCreateTableModalOpen] = useState(false);
|
||||
const [addColumnModalOpen, setAddColumnModalOpen] = useState(false);
|
||||
@@ -1388,113 +1398,266 @@ export default function TableManagementPage() {
|
||||
{/* 입력 타입이 'entity'인 경우 참조 테이블 선택 */}
|
||||
{column.inputType === "entity" && (
|
||||
<>
|
||||
{/* 참조 테이블 */}
|
||||
<div className="w-48">
|
||||
{/* 참조 테이블 - 검색 가능한 Combobox */}
|
||||
<div className="w-56">
|
||||
<label className="text-muted-foreground mb-1 block text-xs">참조 테이블</label>
|
||||
<Select
|
||||
value={column.referenceTable || "none"}
|
||||
onValueChange={(value) =>
|
||||
handleDetailSettingsChange(column.columnName, "entity", value)
|
||||
<Popover
|
||||
open={entityComboboxOpen[column.columnName]?.table || false}
|
||||
onOpenChange={(open) =>
|
||||
setEntityComboboxOpen((prev) => ({
|
||||
...prev,
|
||||
[column.columnName]: { ...prev[column.columnName], table: open },
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-background h-8 w-full text-xs">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{referenceTableOptions.map((option, index) => (
|
||||
<SelectItem key={`entity-${option.value}-${index}`} value={option.value}>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{option.label}</span>
|
||||
<span className="text-muted-foreground text-xs">{option.value}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={entityComboboxOpen[column.columnName]?.table || false}
|
||||
className="bg-background h-8 w-full justify-between text-xs"
|
||||
>
|
||||
{column.referenceTable && column.referenceTable !== "none"
|
||||
? referenceTableOptions.find((opt) => opt.value === column.referenceTable)?.label ||
|
||||
column.referenceTable
|
||||
: "테이블 선택..."}
|
||||
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[280px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="h-8 text-xs" />
|
||||
<CommandList className="max-h-[200px]">
|
||||
<CommandEmpty className="py-2 text-center text-xs">
|
||||
테이블을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{referenceTableOptions.map((option) => (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={`${option.label} ${option.value}`}
|
||||
onSelect={() => {
|
||||
handleDetailSettingsChange(column.columnName, "entity", option.value);
|
||||
setEntityComboboxOpen((prev) => ({
|
||||
...prev,
|
||||
[column.columnName]: { ...prev[column.columnName], table: false },
|
||||
}));
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
column.referenceTable === option.value ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{option.label}</span>
|
||||
{option.value !== "none" && (
|
||||
<span className="text-muted-foreground text-[10px]">{option.value}</span>
|
||||
)}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
{/* 조인 컬럼 */}
|
||||
{/* 조인 컬럼 - 검색 가능한 Combobox */}
|
||||
{column.referenceTable && column.referenceTable !== "none" && (
|
||||
<div className="w-48">
|
||||
<div className="w-56">
|
||||
<label className="text-muted-foreground mb-1 block text-xs">조인 컬럼</label>
|
||||
<Select
|
||||
value={column.referenceColumn || "none"}
|
||||
onValueChange={(value) =>
|
||||
handleDetailSettingsChange(
|
||||
column.columnName,
|
||||
"entity_reference_column",
|
||||
value,
|
||||
)
|
||||
<Popover
|
||||
open={entityComboboxOpen[column.columnName]?.joinColumn || false}
|
||||
onOpenChange={(open) =>
|
||||
setEntityComboboxOpen((prev) => ({
|
||||
...prev,
|
||||
[column.columnName]: { ...prev[column.columnName], joinColumn: open },
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-background h-8 w-full text-xs">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">-- 선택 안함 --</SelectItem>
|
||||
{referenceTableColumns[column.referenceTable]?.map((refCol, index) => (
|
||||
<SelectItem
|
||||
key={`ref-col-${refCol.columnName}-${index}`}
|
||||
value={refCol.columnName}
|
||||
>
|
||||
<span className="font-medium">{refCol.columnName}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
{(!referenceTableColumns[column.referenceTable] ||
|
||||
referenceTableColumns[column.referenceTable].length === 0) && (
|
||||
<SelectItem value="loading" disabled>
|
||||
<div className="flex items-center gap-2">
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={entityComboboxOpen[column.columnName]?.joinColumn || false}
|
||||
className="bg-background h-8 w-full justify-between text-xs"
|
||||
disabled={!referenceTableColumns[column.referenceTable] || referenceTableColumns[column.referenceTable].length === 0}
|
||||
>
|
||||
{!referenceTableColumns[column.referenceTable] || referenceTableColumns[column.referenceTable].length === 0 ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<div className="border-primary h-3 w-3 animate-spin rounded-full border border-t-transparent"></div>
|
||||
로딩중
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
로딩중...
|
||||
</span>
|
||||
) : column.referenceColumn && column.referenceColumn !== "none" ? (
|
||||
column.referenceColumn
|
||||
) : (
|
||||
"컬럼 선택..."
|
||||
)}
|
||||
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[280px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="컬럼 검색..." className="h-8 text-xs" />
|
||||
<CommandList className="max-h-[200px]">
|
||||
<CommandEmpty className="py-2 text-center text-xs">
|
||||
컬럼을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
value="none"
|
||||
onSelect={() => {
|
||||
handleDetailSettingsChange(column.columnName, "entity_reference_column", "none");
|
||||
setEntityComboboxOpen((prev) => ({
|
||||
...prev,
|
||||
[column.columnName]: { ...prev[column.columnName], joinColumn: false },
|
||||
}));
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
column.referenceColumn === "none" || !column.referenceColumn ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
-- 선택 안함 --
|
||||
</CommandItem>
|
||||
{referenceTableColumns[column.referenceTable]?.map((refCol) => (
|
||||
<CommandItem
|
||||
key={refCol.columnName}
|
||||
value={`${refCol.columnLabel || ""} ${refCol.columnName}`}
|
||||
onSelect={() => {
|
||||
handleDetailSettingsChange(column.columnName, "entity_reference_column", refCol.columnName);
|
||||
setEntityComboboxOpen((prev) => ({
|
||||
...prev,
|
||||
[column.columnName]: { ...prev[column.columnName], joinColumn: false },
|
||||
}));
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
column.referenceColumn === refCol.columnName ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{refCol.columnName}</span>
|
||||
{refCol.columnLabel && (
|
||||
<span className="text-muted-foreground text-[10px]">{refCol.columnLabel}</span>
|
||||
)}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 표시 컬럼 */}
|
||||
{/* 표시 컬럼 - 검색 가능한 Combobox */}
|
||||
{column.referenceTable &&
|
||||
column.referenceTable !== "none" &&
|
||||
column.referenceColumn &&
|
||||
column.referenceColumn !== "none" && (
|
||||
<div className="w-48">
|
||||
<div className="w-56">
|
||||
<label className="text-muted-foreground mb-1 block text-xs">표시 컬럼</label>
|
||||
<Select
|
||||
value={column.displayColumn || "none"}
|
||||
onValueChange={(value) =>
|
||||
handleDetailSettingsChange(
|
||||
column.columnName,
|
||||
"entity_display_column",
|
||||
value,
|
||||
)
|
||||
<Popover
|
||||
open={entityComboboxOpen[column.columnName]?.displayColumn || false}
|
||||
onOpenChange={(open) =>
|
||||
setEntityComboboxOpen((prev) => ({
|
||||
...prev,
|
||||
[column.columnName]: { ...prev[column.columnName], displayColumn: open },
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-background h-8 w-full text-xs">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">-- 선택 안함 --</SelectItem>
|
||||
{referenceTableColumns[column.referenceTable]?.map((refCol, index) => (
|
||||
<SelectItem
|
||||
key={`ref-col-${refCol.columnName}-${index}`}
|
||||
value={refCol.columnName}
|
||||
>
|
||||
<span className="font-medium">{refCol.columnName}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
{(!referenceTableColumns[column.referenceTable] ||
|
||||
referenceTableColumns[column.referenceTable].length === 0) && (
|
||||
<SelectItem value="loading" disabled>
|
||||
<div className="flex items-center gap-2">
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={entityComboboxOpen[column.columnName]?.displayColumn || false}
|
||||
className="bg-background h-8 w-full justify-between text-xs"
|
||||
disabled={!referenceTableColumns[column.referenceTable] || referenceTableColumns[column.referenceTable].length === 0}
|
||||
>
|
||||
{!referenceTableColumns[column.referenceTable] || referenceTableColumns[column.referenceTable].length === 0 ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<div className="border-primary h-3 w-3 animate-spin rounded-full border border-t-transparent"></div>
|
||||
로딩중
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
로딩중...
|
||||
</span>
|
||||
) : column.displayColumn && column.displayColumn !== "none" ? (
|
||||
column.displayColumn
|
||||
) : (
|
||||
"컬럼 선택..."
|
||||
)}
|
||||
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[280px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="컬럼 검색..." className="h-8 text-xs" />
|
||||
<CommandList className="max-h-[200px]">
|
||||
<CommandEmpty className="py-2 text-center text-xs">
|
||||
컬럼을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
value="none"
|
||||
onSelect={() => {
|
||||
handleDetailSettingsChange(column.columnName, "entity_display_column", "none");
|
||||
setEntityComboboxOpen((prev) => ({
|
||||
...prev,
|
||||
[column.columnName]: { ...prev[column.columnName], displayColumn: false },
|
||||
}));
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
column.displayColumn === "none" || !column.displayColumn ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
-- 선택 안함 --
|
||||
</CommandItem>
|
||||
{referenceTableColumns[column.referenceTable]?.map((refCol) => (
|
||||
<CommandItem
|
||||
key={refCol.columnName}
|
||||
value={`${refCol.columnLabel || ""} ${refCol.columnName}`}
|
||||
onSelect={() => {
|
||||
handleDetailSettingsChange(column.columnName, "entity_display_column", refCol.columnName);
|
||||
setEntityComboboxOpen((prev) => ({
|
||||
...prev,
|
||||
[column.columnName]: { ...prev[column.columnName], displayColumn: false },
|
||||
}));
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
column.displayColumn === refCol.columnName ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{refCol.columnName}</span>
|
||||
{refCol.columnLabel && (
|
||||
<span className="text-muted-foreground text-[10px]">{refCol.columnLabel}</span>
|
||||
)}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1505,8 +1668,8 @@ export default function TableManagementPage() {
|
||||
column.referenceColumn !== "none" &&
|
||||
column.displayColumn &&
|
||||
column.displayColumn !== "none" && (
|
||||
<div className="bg-primary/10 text-primary flex w-48 items-center gap-1 rounded px-2 py-1 text-xs">
|
||||
<span>✓</span>
|
||||
<div className="bg-primary/10 text-primary flex items-center gap-1 rounded px-2 py-1 text-xs">
|
||||
<Check className="h-3 w-3" />
|
||||
<span className="truncate">설정 완료</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user