97 lines
3.9 KiB
TypeScript
97 lines
3.9 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { X, Info } from "lucide-react";
|
||
|
|
import { useFlowEditorStore } from "@/lib/stores/flowEditorStore";
|
||
|
|
import { PropertiesPanel } from "./panels/PropertiesPanel";
|
||
|
|
import { getNodePaletteItem } from "./sidebar/nodePaletteConfig";
|
||
|
|
|
||
|
|
const TOSS_NODE_HINTS: Record<string, string> = {
|
||
|
|
tableSource: "어떤 테이블에서 데이터를 가져올지 선택해 주세요",
|
||
|
|
externalDBSource: "외부 데이터베이스 연결 정보를 입력해 주세요",
|
||
|
|
restAPISource: "호출할 API의 URL과 방식을 설정해 주세요",
|
||
|
|
condition: "어떤 조건으로 데이터를 분기할지 설정해 주세요",
|
||
|
|
dataTransform: "데이터를 어떻게 변환할지 규칙을 정해 주세요",
|
||
|
|
aggregate: "어떤 기준으로 집계할지 설정해 주세요",
|
||
|
|
formulaTransform: "계산에 사용할 수식을 입력해 주세요",
|
||
|
|
insertAction: "데이터를 저장할 테이블과 필드를 매핑해 주세요",
|
||
|
|
updateAction: "수정할 조건과 필드를 설정해 주세요",
|
||
|
|
deleteAction: "삭제 조건을 설정해 주세요",
|
||
|
|
upsertAction: "저장/수정 조건과 필드를 설정해 주세요",
|
||
|
|
emailAction: "메일 서버와 발송 정보를 설정해 주세요",
|
||
|
|
scriptAction: "실행할 스크립트 내용을 입력해 주세요",
|
||
|
|
httpRequestAction: "요청 URL과 방식을 설정해 주세요",
|
||
|
|
procedureCallAction: "호출할 프로시저 정보를 입력해 주세요",
|
||
|
|
comment: "메모 내용을 자유롭게 작성해 주세요",
|
||
|
|
};
|
||
|
|
|
||
|
|
interface SlideOverSheetProps {
|
||
|
|
isOpen: boolean;
|
||
|
|
onClose: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function SlideOverSheet({ isOpen, onClose }: SlideOverSheetProps) {
|
||
|
|
const { nodes, selectedNodes } = useFlowEditorStore();
|
||
|
|
|
||
|
|
const selectedNode =
|
||
|
|
selectedNodes.length === 1
|
||
|
|
? nodes.find((n) => n.id === selectedNodes[0])
|
||
|
|
: null;
|
||
|
|
|
||
|
|
const nodeInfo = selectedNode
|
||
|
|
? getNodePaletteItem(selectedNode.type as string)
|
||
|
|
: null;
|
||
|
|
|
||
|
|
const hint = selectedNode
|
||
|
|
? TOSS_NODE_HINTS[(selectedNode.type as string)] || "이 노드의 속성을 설정해 주세요"
|
||
|
|
: "";
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={`absolute right-0 top-0 z-40 flex h-full w-[380px] flex-col border-l border-zinc-700 bg-zinc-900 shadow-2xl shadow-black/40 transition-transform duration-300 ease-out ${
|
||
|
|
isOpen ? "translate-x-0" : "translate-x-full"
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
{/* 헤더 */}
|
||
|
|
<div className="flex flex-shrink-0 items-center justify-between border-b border-zinc-800 px-4 py-3">
|
||
|
|
<div className="min-w-0 flex-1">
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
{nodeInfo && (
|
||
|
|
<span
|
||
|
|
className="inline-block h-2.5 w-2.5 rounded-full"
|
||
|
|
style={{ backgroundColor: nodeInfo.color }}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
<h3 className="truncate text-sm font-semibold text-zinc-200">
|
||
|
|
{nodeInfo?.label || "속성"}
|
||
|
|
</h3>
|
||
|
|
</div>
|
||
|
|
{selectedNode && (
|
||
|
|
<p className="mt-0.5 truncate text-[11px] text-zinc-500">
|
||
|
|
{(selectedNode.data as any)?.displayName || "이름 없음"}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="ml-2 flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-zinc-500 transition-colors hover:bg-zinc-800 hover:text-zinc-300"
|
||
|
|
>
|
||
|
|
<X className="h-4 w-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 힌트 배너 */}
|
||
|
|
{hint && (
|
||
|
|
<div className="flex items-start gap-2 border-b border-zinc-800 bg-violet-500/[0.06] px-4 py-2.5">
|
||
|
|
<Info className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-violet-400" />
|
||
|
|
<p className="text-[11px] leading-relaxed text-violet-300/80">{hint}</p>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 속성 패널 내용 (라이트 배경으로 폼 가독성 유지) */}
|
||
|
|
<div className="min-h-0 flex-1 overflow-y-auto bg-white dark:bg-zinc-800">
|
||
|
|
<PropertiesPanel />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|