424 lines
15 KiB
TypeScript
424 lines
15 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import { Plus, X, Settings, ArrowRight } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import type { DataTransferField } from "./types";
|
|
|
|
interface ColumnInfo {
|
|
column_name: string;
|
|
column_comment?: string;
|
|
data_type?: string;
|
|
}
|
|
|
|
interface DataTransferConfigModalProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
dataTransferFields: DataTransferField[];
|
|
onChange: (fields: DataTransferField[]) => void;
|
|
leftColumns: ColumnInfo[];
|
|
rightColumns: ColumnInfo[];
|
|
leftTableName?: string;
|
|
rightTableName?: string;
|
|
}
|
|
|
|
// 컬럼 선택 컴포넌트
|
|
const ColumnSelect: React.FC<{
|
|
columns: ColumnInfo[];
|
|
value: string;
|
|
onValueChange: (value: string) => void;
|
|
placeholder: string;
|
|
disabled?: boolean;
|
|
}> = ({ columns, value, onValueChange, placeholder, disabled = false }) => {
|
|
return (
|
|
<Select value={value} onValueChange={onValueChange} disabled={disabled}>
|
|
<SelectTrigger className="h-8 text-xs">
|
|
<SelectValue placeholder={placeholder} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{columns.map((col) => (
|
|
<SelectItem key={col.column_name} value={col.column_name}>
|
|
{col.column_comment || col.column_name}
|
|
<span className="text-muted-foreground ml-1 text-[10px]">({col.column_name})</span>
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
);
|
|
};
|
|
|
|
// 개별 필드 편집 모달
|
|
const FieldEditModal: React.FC<{
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
field: DataTransferField | null;
|
|
onSave: (field: DataTransferField) => void;
|
|
leftColumns: ColumnInfo[];
|
|
rightColumns: ColumnInfo[];
|
|
leftTableName?: string;
|
|
rightTableName?: string;
|
|
isNew?: boolean;
|
|
}> = ({
|
|
open,
|
|
onOpenChange,
|
|
field,
|
|
onSave,
|
|
leftColumns,
|
|
rightColumns,
|
|
leftTableName,
|
|
rightTableName,
|
|
isNew = false,
|
|
}) => {
|
|
const [editingField, setEditingField] = useState<DataTransferField>({
|
|
id: "",
|
|
panel: "left",
|
|
sourceColumn: "",
|
|
targetColumn: "",
|
|
label: "",
|
|
description: "",
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (field) {
|
|
setEditingField({ ...field });
|
|
} else {
|
|
setEditingField({
|
|
id: `field_${Date.now()}`,
|
|
panel: "left",
|
|
sourceColumn: "",
|
|
targetColumn: "",
|
|
label: "",
|
|
description: "",
|
|
});
|
|
}
|
|
}, [field, open]);
|
|
|
|
const handleSave = () => {
|
|
if (!editingField.sourceColumn || !editingField.targetColumn) {
|
|
return;
|
|
}
|
|
onSave(editingField);
|
|
onOpenChange(false);
|
|
};
|
|
|
|
const currentColumns = editingField.panel === "left" ? leftColumns : rightColumns;
|
|
const currentTableName = editingField.panel === "left" ? leftTableName : rightTableName;
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-[95vw] sm:max-w-[450px]">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-base">{isNew ? "데이터 전달 필드 추가" : "데이터 전달 필드 편집"}</DialogTitle>
|
|
<DialogDescription className="text-xs">
|
|
선택한 항목의 데이터를 모달에 자동으로 전달합니다.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 py-2">
|
|
{/* 패널 선택 */}
|
|
<div>
|
|
<Label className="text-xs">소스 패널</Label>
|
|
<Select
|
|
value={editingField.panel}
|
|
onValueChange={(value: "left" | "right") => {
|
|
setEditingField({ ...editingField, panel: value, sourceColumn: "" });
|
|
}}
|
|
>
|
|
<SelectTrigger className="mt-1 h-9">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="left">
|
|
좌측 패널 {leftTableName && <span className="text-muted-foreground">({leftTableName})</span>}
|
|
</SelectItem>
|
|
<SelectItem value="right">
|
|
우측 패널 {rightTableName && <span className="text-muted-foreground">({rightTableName})</span>}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<p className="text-muted-foreground mt-1 text-[10px]">데이터를 가져올 패널을 선택합니다.</p>
|
|
</div>
|
|
|
|
{/* 소스 컬럼 */}
|
|
<div>
|
|
<Label className="text-xs">
|
|
소스 컬럼 <span className="text-destructive">*</span>
|
|
</Label>
|
|
<div className="mt-1">
|
|
<ColumnSelect
|
|
columns={currentColumns}
|
|
value={editingField.sourceColumn}
|
|
onValueChange={(value) => {
|
|
const col = currentColumns.find((c) => c.column_name === value);
|
|
setEditingField({
|
|
...editingField,
|
|
sourceColumn: value,
|
|
// 타겟 컬럼이 비어있으면 소스와 동일하게 설정
|
|
targetColumn: editingField.targetColumn || value,
|
|
// 라벨이 비어있으면 컬럼 코멘트 사용
|
|
label: editingField.label || col?.column_comment || "",
|
|
});
|
|
}}
|
|
placeholder="컬럼 선택..."
|
|
disabled={currentColumns.length === 0}
|
|
/>
|
|
</div>
|
|
{currentColumns.length === 0 && (
|
|
<p className="text-destructive mt-1 text-[10px]">
|
|
{currentTableName ? "테이블에 컬럼이 없습니다." : "테이블을 먼저 선택해주세요."}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 타겟 컬럼 */}
|
|
<div>
|
|
<Label className="text-xs">
|
|
타겟 컬럼 (모달 필드명) <span className="text-destructive">*</span>
|
|
</Label>
|
|
<Input
|
|
value={editingField.targetColumn}
|
|
onChange={(e) => setEditingField({ ...editingField, targetColumn: e.target.value })}
|
|
placeholder="모달에서 사용할 필드명"
|
|
className="mt-1 h-9 text-sm"
|
|
/>
|
|
<p className="text-muted-foreground mt-1 text-[10px]">모달 폼에서 이 값을 받을 필드명입니다.</p>
|
|
</div>
|
|
|
|
{/* 라벨 (선택) */}
|
|
<div>
|
|
<Label className="text-xs">표시 라벨 (선택)</Label>
|
|
<Input
|
|
value={editingField.label || ""}
|
|
onChange={(e) => setEditingField({ ...editingField, label: e.target.value })}
|
|
placeholder="표시용 이름"
|
|
className="mt-1 h-9 text-sm"
|
|
/>
|
|
</div>
|
|
|
|
{/* 설명 (선택) */}
|
|
<div>
|
|
<Label className="text-xs">설명 (선택)</Label>
|
|
<Input
|
|
value={editingField.description || ""}
|
|
onChange={(e) => setEditingField({ ...editingField, description: e.target.value })}
|
|
placeholder="이 필드에 대한 설명"
|
|
className="mt-1 h-9 text-sm"
|
|
/>
|
|
</div>
|
|
|
|
{/* 미리보기 */}
|
|
{editingField.sourceColumn && editingField.targetColumn && (
|
|
<div className="bg-muted/50 rounded-md p-3">
|
|
<p className="mb-2 text-xs font-medium">미리보기</p>
|
|
<div className="flex items-center gap-2 text-xs">
|
|
<Badge variant="outline" className="text-[10px]">
|
|
{editingField.panel === "left" ? "좌측" : "우측"}
|
|
</Badge>
|
|
<span className="font-mono">{editingField.sourceColumn}</span>
|
|
<ArrowRight className="h-3 w-3" />
|
|
<span className="font-mono">{editingField.targetColumn}</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter className="gap-2 sm:gap-0">
|
|
<Button variant="outline" onClick={() => onOpenChange(false)} className="h-9 text-sm">
|
|
취소
|
|
</Button>
|
|
<Button
|
|
onClick={handleSave}
|
|
disabled={!editingField.sourceColumn || !editingField.targetColumn}
|
|
className="h-9 text-sm"
|
|
>
|
|
{isNew ? "추가" : "저장"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
// 메인 모달 컴포넌트
|
|
const DataTransferConfigModal: React.FC<DataTransferConfigModalProps> = ({
|
|
open,
|
|
onOpenChange,
|
|
dataTransferFields,
|
|
onChange,
|
|
leftColumns,
|
|
rightColumns,
|
|
leftTableName,
|
|
rightTableName,
|
|
}) => {
|
|
const [fields, setFields] = useState<DataTransferField[]>([]);
|
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
|
const [editingField, setEditingField] = useState<DataTransferField | null>(null);
|
|
const [isNewField, setIsNewField] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
// 기존 필드에 panel이 없으면 left로 기본 설정 (하위 호환성)
|
|
const normalizedFields = (dataTransferFields || []).map((field, idx) => ({
|
|
...field,
|
|
id: field.id || `field_${idx}`,
|
|
panel: field.panel || ("left" as const),
|
|
}));
|
|
setFields(normalizedFields);
|
|
}
|
|
}, [open, dataTransferFields]);
|
|
|
|
const handleAddField = () => {
|
|
setEditingField(null);
|
|
setIsNewField(true);
|
|
setEditModalOpen(true);
|
|
};
|
|
|
|
const handleEditField = (field: DataTransferField) => {
|
|
setEditingField(field);
|
|
setIsNewField(false);
|
|
setEditModalOpen(true);
|
|
};
|
|
|
|
const handleSaveField = (field: DataTransferField) => {
|
|
if (isNewField) {
|
|
setFields([...fields, field]);
|
|
} else {
|
|
setFields(fields.map((f) => (f.id === field.id ? field : f)));
|
|
}
|
|
};
|
|
|
|
const handleRemoveField = (id: string) => {
|
|
setFields(fields.filter((f) => f.id !== id));
|
|
};
|
|
|
|
const handleSave = () => {
|
|
onChange(fields);
|
|
onOpenChange(false);
|
|
};
|
|
|
|
const getColumnLabel = (panel: "left" | "right", columnName: string) => {
|
|
const columns = panel === "left" ? leftColumns : rightColumns;
|
|
const col = columns.find((c) => c.column_name === columnName);
|
|
return col?.column_comment || columnName;
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="flex max-h-[85vh] max-w-[95vw] flex-col sm:max-w-[550px]">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-base">데이터 전달 설정</DialogTitle>
|
|
<DialogDescription className="text-xs">
|
|
버튼 클릭 시 모달에 자동으로 전달할 데이터를 설정합니다.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="min-h-0 flex-1 overflow-hidden">
|
|
<div className="mb-3 flex items-center justify-between">
|
|
<span className="text-muted-foreground text-xs">전달 필드 ({fields.length}개)</span>
|
|
<Button size="sm" variant="outline" className="h-7 text-xs" onClick={handleAddField}>
|
|
<Plus className="mr-1 h-3 w-3" />
|
|
추가
|
|
</Button>
|
|
</div>
|
|
|
|
<ScrollArea className="h-[300px]">
|
|
<div className="space-y-2 pr-2">
|
|
{fields.length === 0 ? (
|
|
<div className="text-muted-foreground rounded-md border py-8 text-center text-xs">
|
|
<p className="mb-2">전달할 필드가 없습니다</p>
|
|
<Button size="sm" variant="ghost" className="h-7 text-xs" onClick={handleAddField}>
|
|
<Plus className="mr-1 h-3 w-3" />
|
|
필드 추가
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
fields.map((field) => (
|
|
<div
|
|
key={field.id}
|
|
className="hover:bg-muted/50 flex items-center gap-2 rounded-md border p-2 transition-colors"
|
|
>
|
|
<Badge variant={field.panel === "left" ? "default" : "secondary"} className="shrink-0 text-[10px]">
|
|
{field.panel === "left" ? "좌측" : "우측"}
|
|
</Badge>
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-1 text-xs">
|
|
<span className="font-mono">{getColumnLabel(field.panel, field.sourceColumn)}</span>
|
|
<ArrowRight className="text-muted-foreground h-3 w-3 shrink-0" />
|
|
<span className="font-mono truncate">{field.targetColumn}</span>
|
|
</div>
|
|
{field.description && (
|
|
<p className="text-muted-foreground mt-0.5 truncate text-[10px]">{field.description}</p>
|
|
)}
|
|
</div>
|
|
<div className="flex shrink-0 items-center gap-1">
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
className="h-6 w-6 p-0"
|
|
onClick={() => handleEditField(field)}
|
|
>
|
|
<Settings className="h-3 w-3" />
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
className="text-destructive hover:text-destructive h-6 w-6 p-0"
|
|
onClick={() => handleRemoveField(field.id || "")}
|
|
>
|
|
<X className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</ScrollArea>
|
|
</div>
|
|
|
|
<div className="bg-muted/50 text-muted-foreground rounded-md p-2 text-[10px]">
|
|
<p>버튼별로 개별 데이터 전달 설정이 있으면 해당 설정이 우선 적용됩니다.</p>
|
|
</div>
|
|
|
|
<DialogFooter className="gap-2 sm:gap-0">
|
|
<Button variant="outline" onClick={() => onOpenChange(false)} className="h-9 text-sm">
|
|
취소
|
|
</Button>
|
|
<Button onClick={handleSave} className="h-9 text-sm">
|
|
저장
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* 필드 편집 모달 */}
|
|
<FieldEditModal
|
|
open={editModalOpen}
|
|
onOpenChange={setEditModalOpen}
|
|
field={editingField}
|
|
onSave={handleSaveField}
|
|
leftColumns={leftColumns}
|
|
rightColumns={rightColumns}
|
|
leftTableName={leftTableName}
|
|
rightTableName={rightTableName}
|
|
isNew={isNewField}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default DataTransferConfigModal;
|