ERP-node/frontend/lib/registry/components/split-panel-layout2/DataTransferConfigModal.tsx

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;