ERP-node/frontend/lib/registry/layouts/SplitLayoutRenderer.tsx

205 lines
6.7 KiB
TypeScript

"use client";
import React, { useState, useCallback } from "react";
import { BaseLayoutRenderer, LayoutRendererProps } from "./BaseLayoutRenderer";
export default class SplitLayoutRenderer extends BaseLayoutRenderer {
render(): React.ReactElement {
const { layout, isDesignMode, isSelected, onClick, className } = this.props;
if (!layout.layoutConfig.split) {
return <div className="error-layout"> .</div>;
}
return (
<SplitLayoutComponent
layout={layout}
isDesignMode={isDesignMode}
isSelected={isSelected}
onClick={onClick}
className={className}
renderer={this}
/>
);
}
}
interface SplitLayoutComponentProps {
layout: any;
isDesignMode?: boolean;
isSelected?: boolean;
onClick?: (e: React.MouseEvent) => void;
className?: string;
renderer: SplitLayoutRenderer;
}
const SplitLayoutComponent: React.FC<SplitLayoutComponentProps> = ({
layout,
isDesignMode,
isSelected,
onClick,
className,
renderer,
}) => {
const splitConfig = layout.layoutConfig.split;
const [sizes, setSizes] = useState(splitConfig.ratio || [50, 50]);
const [isDragging, setIsDragging] = useState(false);
const containerStyle = renderer.getLayoutContainerStyle();
// 분할 컨테이너 스타일
const splitStyle: React.CSSProperties = {
...containerStyle,
display: "flex",
flexDirection: splitConfig.direction === "horizontal" ? "row" : "column",
overflow: "hidden",
};
// 디자인 모드 스타일
if (isDesignMode) {
splitStyle.border = isSelected ? "2px solid #3b82f6" : "1px solid #e2e8f0";
splitStyle.borderRadius = "8px";
}
// 스플리터 드래그 핸들러
const handleSplitterDrag = useCallback(
(e: React.MouseEvent, index: number) => {
if (!splitConfig.resizable || !isDesignMode) return;
setIsDragging(true);
const startPos = splitConfig.direction === "horizontal" ? e.clientX : e.clientY;
const startSizes = [...sizes];
const handleMouseMove = (moveEvent: MouseEvent) => {
const currentPos = splitConfig.direction === "horizontal" ? moveEvent.clientX : moveEvent.clientY;
const delta = currentPos - startPos;
const containerSize =
splitConfig.direction === "horizontal"
? (e.currentTarget as HTMLElement).parentElement!.clientWidth
: (e.currentTarget as HTMLElement).parentElement!.clientHeight;
const deltaPercent = (delta / containerSize) * 100;
const newSizes = [...startSizes];
newSizes[index] = Math.max(splitConfig.minSize?.[index] || 10, Math.min(90, startSizes[index] + deltaPercent));
newSizes[index + 1] = Math.max(
splitConfig.minSize?.[index + 1] || 10,
Math.min(90, startSizes[index + 1] - deltaPercent),
);
setSizes(newSizes);
};
const handleMouseUp = () => {
setIsDragging(false);
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
},
[splitConfig, sizes, isDesignMode],
);
return (
<div
className={`split-layout ${isDesignMode ? "design-mode" : ""} ${className || ""}`}
style={splitStyle}
onClick={onClick}
draggable={isDesignMode && !isDragging}
onDragStart={(e) => !isDragging && renderer.props.onDragStart?.(e)}
onDragEnd={(e) => !isDragging && renderer.props.onDragEnd?.(e)}
>
{layout.zones.map((zone: any, index: number) => {
const zoneChildren = renderer.getZoneChildren(zone.id);
const isHorizontal = splitConfig.direction === "horizontal";
// 패널 크기 계산
const panelSize = sizes[index] || 100 / layout.zones.length;
const panelStyle: React.CSSProperties = {
[isHorizontal ? "width" : "height"]: `${panelSize}%`,
[isHorizontal ? "height" : "width"]: "100%",
overflow: "auto",
};
return (
<React.Fragment key={zone.id}>
{/* 패널 */}
{renderer.renderZone(zone, zoneChildren, {
style: panelStyle,
className: "split-panel",
})}
{/* 스플리터 (마지막 패널 제외) */}
{index < layout.zones.length - 1 && (
<div
className={`splitter ${isHorizontal ? "horizontal" : "vertical"} ${isDragging ? "dragging" : ""}`}
style={{
[isHorizontal ? "width" : "height"]: `${splitConfig.splitterSize || 4}px`,
[isHorizontal ? "height" : "width"]: "100%",
backgroundColor: "#e2e8f0",
cursor: isHorizontal ? "col-resize" : "row-resize",
position: "relative",
zIndex: 10,
...(isDragging && {
backgroundColor: "#3b82f6",
}),
}}
onMouseDown={(e) => handleSplitterDrag(e, index)}
>
{/* 스플리터 핸들 */}
<div
className="splitter-handle"
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
[isHorizontal ? "width" : "height"]: "20px",
[isHorizontal ? "height" : "width"]: "4px",
backgroundColor: "#94a3b8",
borderRadius: "2px",
opacity: splitConfig.resizable && isDesignMode ? 1 : 0,
transition: "opacity 0.2s ease",
}}
/>
</div>
)}
</React.Fragment>
);
})}
{/* 디자인 모드에서 존이 없을 때 안내 메시지 */}
{isDesignMode && layout.zones.length === 0 && (
<div
className="empty-split-container"
style={{
flex: 1,
border: "2px dashed #cbd5e1",
borderRadius: "8px",
backgroundColor: "rgba(148, 163, 184, 0.05)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: "14px",
color: "#64748b",
minHeight: "100px",
padding: "20px",
textAlign: "center",
}}
>
</div>
)}
</div>
);
};
// React 컴포넌트로 래핑
export const SplitLayout: React.FC<LayoutRendererProps> = (props) => {
const renderer = new SplitLayoutRenderer(props);
return renderer.render();
};