2f398ae0b3
- 제어모드 IDE: ControlCardPanel, control/ide/* (Canvas/LeftRail/RightRail/PanZoomStage/V3RuleNode 등), schemas, lib/api/control - 레지스트리 정리: aggregation-widget, status-count, section-card/paper, table-list(legacy/v2), tabs-widget 폐기 → table/_shared/ 로 통합 - InvLegacyButtonConfigPanel cp 마이그레이션 - canonical data view cleanup 후속 노트
130 lines
4.3 KiB
TypeScript
130 lines
4.3 KiB
TypeScript
'use client';
|
|
|
|
import { useRef, useCallback } from 'react';
|
|
import { Database, X } from 'lucide-react';
|
|
import { PortHandle } from './PortHandle';
|
|
import { useControlMode } from './hooks/useControlMode';
|
|
|
|
interface TableNodeProps {
|
|
tableName: string;
|
|
label: string;
|
|
/** 호환용 — 더 이상 사용 X (V3 컴팩트로 갈아엎으면서 이모지 폐기, Lucide Database 아이콘 고정) */
|
|
icon?: string;
|
|
columns: Record<string, any>[];
|
|
x: number;
|
|
y: number;
|
|
style?: React.CSSProperties;
|
|
onMove?: (name: string, x: number, y: number) => void;
|
|
/** 룰 노드 ID (PortHandle 연결용). 없으면 시각 카드만 (read-only) */
|
|
nodeId?: string;
|
|
onPortDragStart?: (nodeId: string, port: string, e: React.MouseEvent) => void;
|
|
onPortDragEnd?: (nodeId: string, port: string) => void;
|
|
}
|
|
|
|
/**
|
|
* 테이블 카드 — V3RuleNode 와 일관된 컴팩트 디자인
|
|
* - 180px 폭, cyan top stripe, Lucide Database 아이콘
|
|
* - 한글 라벨 메인 + mono 영문 sub
|
|
* - stats row: `{N} cols · {K} FK`
|
|
* - 좌·우 edge 에 단일 port 1개씩 (테이블 단위 연결 — 컬럼은 노드 설정창 dropdown 에서)
|
|
*/
|
|
export function TableNode({
|
|
tableName, label, columns, x, y, style, onMove, nodeId, onPortDragStart, onPortDragEnd,
|
|
}: TableNodeProps) {
|
|
const nodeRef = useRef<HTMLDivElement>(null);
|
|
const removeRuleNode = useControlMode((s) => s.removeRuleNode);
|
|
|
|
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
|
if (!onMove) return;
|
|
const target = e.target as HTMLElement;
|
|
if (target.closest('.ctrl-io-port, button')) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
const sx = e.clientX, sy = e.clientY;
|
|
const sl = x, st = y;
|
|
const el = nodeRef.current;
|
|
if (el) el.style.zIndex = '30';
|
|
let moved = false;
|
|
|
|
const move = (ev: MouseEvent) => {
|
|
const dx = ev.clientX - sx, dy = ev.clientY - sy;
|
|
if (!moved && Math.abs(dx) + Math.abs(dy) < 2) return;
|
|
moved = true;
|
|
onMove(tableName, sl + dx, st + dy);
|
|
};
|
|
const up = () => {
|
|
if (el) el.style.zIndex = '20';
|
|
document.removeEventListener('mousemove', move);
|
|
document.removeEventListener('mouseup', up);
|
|
};
|
|
document.addEventListener('mousemove', move);
|
|
document.addEventListener('mouseup', up);
|
|
}, [onMove, tableName, x, y]);
|
|
|
|
// stats
|
|
const totalCols = columns?.length ?? 0;
|
|
const fkCount = (columns ?? []).filter((c) => c.mark === 'FK' || c.type === 'entity').length;
|
|
const pkCount = (columns ?? []).filter((c) => c.pk).length;
|
|
const hasKoLabel = label && label !== tableName;
|
|
|
|
return (
|
|
<div
|
|
ref={nodeRef}
|
|
className="tbl-node tbl-node-compact"
|
|
data-table={tableName}
|
|
data-node-id={nodeId}
|
|
onMouseDown={handleMouseDown}
|
|
style={{ left: x, top: y, ...style }}
|
|
>
|
|
{/* cyan top stripe (V3RuleNode cat-stripe 와 일관) */}
|
|
<div className="tbl-node-stripe" />
|
|
|
|
<div className="tbl-node-head">
|
|
<div className="tbl-node-ico"><Database size={11} /></div>
|
|
<div className="tbl-node-title">
|
|
<div className="tbl-node-label">{hasKoLabel ? label : tableName}</div>
|
|
{hasKoLabel && <div className="tbl-node-sub">{tableName}</div>}
|
|
</div>
|
|
{nodeId && (
|
|
<button
|
|
type="button"
|
|
className="tbl-node-del"
|
|
title="삭제"
|
|
onClick={(e) => { e.stopPropagation(); removeRuleNode(nodeId); }}
|
|
>
|
|
<X size={10} />
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="tbl-node-stats">
|
|
<span>{totalCols} cols</span>
|
|
{pkCount > 0 && <span>· {pkCount} PK</span>}
|
|
{fkCount > 0 && <span>· {fkCount} FK</span>}
|
|
</div>
|
|
|
|
{/* 좌·우 단일 port — 테이블 단위 연결 (컬럼 선택은 노드 설정창 dropdown) */}
|
|
{nodeId && (
|
|
<>
|
|
<PortHandle
|
|
nodeId={nodeId}
|
|
port="in"
|
|
type="in"
|
|
onDragEnd={onPortDragEnd}
|
|
onDragStart={onPortDragStart}
|
|
/>
|
|
<div className="ctrl-an-ports-out">
|
|
<PortHandle
|
|
nodeId={nodeId}
|
|
port="out"
|
|
type="out"
|
|
onDragStart={onPortDragStart}
|
|
onDragEnd={onPortDragEnd}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|