'use client'; import { useState, useEffect, useCallback } from 'react'; import { Eye, Wrench, Save, FolderOpen, X, Database } from 'lucide-react'; import { useControlMode } from './hooks/useControlMode'; import { getBusinessRuleList, getBusinessRuleInfo, insertBusinessRule, updateBusinessRule, } from '@/lib/api/businessRule'; import { toast } from 'sonner'; interface ControlCardPanelProps { dashboardId: string; card: Record; } /** * 선택된 카드의 부속 제어 패널 * - 카드가 좌측 상단(예: left:20, top:90, 320x240)으로 축소되면 * 이 패널이 카드 바로 오른쪽 (left:360, top:90 부근)에 floating * - 패널 안: 카드명 / [읽기 | 편집] 토글 / 액션 / ✕ 닫기 */ export function ControlCardPanel({ dashboardId, card }: ControlCardPanelProps) { const { mode, setMode, setSelectedCardId, ruleNodes, ruleConnections, activeRuleId, setActiveRuleId, setRuleNodes, setRuleConnections, } = useControlMode(); const [ruleList, setRuleList] = useState[]>([]); const [showRuleList, setShowRuleList] = useState(false); const cardLabel = card.title ?? card.TITLE ?? card.template_name ?? card.TEMPLATE_NAME ?? '제목 없음'; const cardTable = card.primary_table ?? card.PRIMARY_TABLE ?? card.source_table ?? card.SOURCE_TABLE ?? null; const cardType = card.component_type ?? card.COMPONENT_TYPE ?? card.template_type ?? card.TEMPLATE_TYPE ?? null; // 편집 모드에서만 규칙 목록 로드 useEffect(() => { if (mode !== 'edit') return; getBusinessRuleList(dashboardId) .then((res) => setRuleList(res?.list ?? res?.data ?? [])) .catch(() => setRuleList([])); }, [mode, dashboardId]); const handleLoadRule = useCallback( async (ruleId: string) => { try { const detail = await getBusinessRuleInfo(ruleId); if (!detail) { toast.error('규칙을 찾을 수 없습니다'); return; } setRuleNodes(detail.nodes ?? []); setRuleConnections(detail.connections ?? []); setActiveRuleId(ruleId); setShowRuleList(false); toast.success(`"${detail.name ?? ruleId}" 로드됨`); } catch { toast.error('규칙 로드 실패'); } }, [setRuleNodes, setRuleConnections, setActiveRuleId], ); const handleSave = async () => { if (ruleNodes.length === 0) { toast.warning('저장할 노드가 없습니다'); return; } try { const data = { name: `${cardLabel} 규칙 ${new Date().toLocaleString('ko-KR')}`, nodes: ruleNodes, connections: ruleConnections, card_id: card.card_id ?? card.CARD_ID ?? card.id, }; if (activeRuleId) { await updateBusinessRule(activeRuleId, data); toast.success('규칙 저장됨'); } else { const result = await insertBusinessRule(dashboardId, data); if (result?.rule_id) setActiveRuleId(result.rule_id); toast.success('규칙 생성됨'); } } catch { toast.error('저장 실패'); } }; const handleSourceDragStart = (e: React.DragEvent) => { if (!cardTable) return; e.dataTransfer.setData('text/plain', JSON.stringify({ kind: 'table', name: cardTable })); e.dataTransfer.effectAllowed = 'copy'; }; return (
{/* 헤더 — "제어" + ✕ 닫기 (카드명은 좌측 카드 자체에 이미 보이므로 중복 X) */}
제어
{cardType &&
{cardType}
}
{/* 데이터 소스 칩 (드래그 가능, 편집 모드에서 룰 빌더로 추가) */} {cardTable && (
{cardTable}
)} {/* 모드 토글 — 카드 컨텍스트 안의 segmented */}
{/* 편집 모드 액션 */} {mode === 'edit' && ( <>
{showRuleList && ruleList.length > 0 && (
{ruleList.map((rule) => { const id = rule.rule_id ?? rule.RULE_ID; const name = rule.name ?? rule.NAME ?? id; const isActive = id === activeRuleId; return ( ); })}
)}
{ruleNodes.length > 0 && (
{ruleNodes.length}개 노드 · {ruleConnections.length}개 연결
)} )} {mode === 'view' && (
우측에 데이터 흐름이 자동으로 펼쳐집니다
)}
); }