화면관리 삭제기능구현

This commit is contained in:
kjs
2025-09-08 13:10:09 +09:00
parent 87ce1b74d4
commit 1eeda775ef
19 changed files with 2506 additions and 167 deletions

View File

@@ -6,6 +6,8 @@ import {
WebType,
WidgetComponent,
FileComponent,
AreaComponent,
AreaLayoutType,
DateTypeConfig,
NumberTypeConfig,
SelectTypeConfig,
@@ -50,6 +52,15 @@ import {
Edit,
Trash2,
Upload,
Square,
CreditCard,
Layout,
Grid3x3,
Columns,
Rows,
SidebarOpen,
Folder,
ChevronUp,
} from "lucide-react";
interface RealtimePreviewProps {
@@ -62,6 +73,159 @@ interface RealtimePreviewProps {
children?: React.ReactNode; // 그룹 내 자식 컴포넌트들
}
// 영역 레이아웃에 따른 아이콘 반환
const getAreaIcon = (layoutType: AreaLayoutType) => {
switch (layoutType) {
case "box":
return <Square className="h-6 w-6 text-blue-600" />;
case "card":
return <CreditCard className="h-6 w-6 text-blue-600" />;
case "panel":
return <Layout className="h-6 w-6 text-blue-600" />;
case "section":
return <Layout className="h-6 w-6 text-blue-600" />;
case "grid":
return <Grid3x3 className="h-6 w-6 text-blue-600" />;
case "flex-row":
return <Columns className="h-6 w-6 text-blue-600" />;
case "flex-column":
return <Rows className="h-6 w-6 text-blue-600" />;
case "sidebar":
return <SidebarOpen className="h-6 w-6 text-blue-600" />;
case "header-content":
return <Layout className="h-6 w-6 text-blue-600" />;
case "tabs":
return <Folder className="h-6 w-6 text-blue-600" />;
case "accordion":
return <ChevronUp className="h-6 w-6 text-blue-600" />;
default:
return <Square className="h-6 w-6 text-blue-600" />;
}
};
// 영역 컴포넌트 렌더링
const renderArea = (component: AreaComponent, children?: React.ReactNode) => {
const { layoutType, title, description, layoutConfig, areaStyle } = component;
// 기본 스타일
const baseStyle: React.CSSProperties = {
width: "100%",
height: "100%",
position: "relative",
backgroundColor: areaStyle?.backgroundColor || "#ffffff",
border: areaStyle?.borderWidth
? `${areaStyle.borderWidth}px ${areaStyle.borderStyle || "solid"} ${areaStyle.borderColor || "#e5e7eb"}`
: "1px solid #e5e7eb",
borderRadius: `${areaStyle?.borderRadius || 8}px`,
padding: `${areaStyle?.padding || 16}px`,
margin: `${areaStyle?.margin || 0}px`,
...(areaStyle?.shadow &&
areaStyle.shadow !== "none" && {
boxShadow:
{
sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
md: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1)",
xl: "0 20px 25px -5px rgba(0, 0, 0, 0.1)",
}[areaStyle.shadow] || "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
}),
};
// 레이아웃별 컨테이너 스타일
const getLayoutStyle = (): React.CSSProperties => {
switch (layoutType) {
case "grid":
return {
display: "grid",
gridTemplateColumns: `repeat(${layoutConfig?.gridColumns || 3}, 1fr)`,
gridTemplateRows: layoutConfig?.gridRows ? `repeat(${layoutConfig.gridRows}, 1fr)` : "auto",
gap: `${layoutConfig?.gridGap || 16}px`,
};
case "flex-row":
return {
display: "flex",
flexDirection: "row",
justifyContent: layoutConfig?.justifyContent || "flex-start",
alignItems: layoutConfig?.alignItems || "stretch",
gap: `${layoutConfig?.gap || 16}px`,
flexWrap: layoutConfig?.flexWrap || "nowrap",
};
case "flex-column":
return {
display: "flex",
flexDirection: "column",
justifyContent: layoutConfig?.justifyContent || "flex-start",
alignItems: layoutConfig?.alignItems || "stretch",
gap: `${layoutConfig?.gap || 16}px`,
};
case "sidebar":
return {
display: "flex",
flexDirection: layoutConfig?.sidebarPosition === "right" ? "row-reverse" : "row",
};
default:
return {};
}
};
// 헤더 렌더링 (panel, section 타입용)
const renderHeader = () => {
if (!title || (layoutType !== "panel" && layoutType !== "section")) return null;
const headerStyle: React.CSSProperties = {
backgroundColor: areaStyle?.headerBackgroundColor || "#f3f4f6",
color: areaStyle?.headerTextColor || "#374151",
height: `${areaStyle?.headerHeight || 48}px`,
padding: `${areaStyle?.headerPadding || 16}px`,
borderBottom: layoutType === "panel" ? "1px solid #e5e7eb" : "none",
borderTopLeftRadius: `${areaStyle?.borderRadius || 8}px`,
borderTopRightRadius: `${areaStyle?.borderRadius || 8}px`,
display: "flex",
alignItems: "center",
fontWeight: "600",
fontSize: "14px",
};
return (
<div style={headerStyle}>
{title}
{description && <span style={{ marginLeft: "8px", fontSize: "12px", opacity: 0.7 }}>{description}</span>}
</div>
);
};
// 컨텐츠 영역 스타일
const contentStyle: React.CSSProperties = {
...getLayoutStyle(),
flex: 1,
minHeight: 0,
};
// 자식 컴포넌트가 없을 때 표시할 플레이스홀더
const renderPlaceholder = () => (
<div className="pointer-events-none flex h-full flex-col items-center justify-center p-4">
<div className="flex flex-col items-center space-y-2">
{getAreaIcon(layoutType)}
<div className="text-center">
<div className="text-sm font-medium text-gray-700">{title || `${layoutType} 영역`}</div>
<div className="text-xs text-gray-500">{description || "컴포넌트를 이 영역에 드래그하세요"}</div>
</div>
</div>
</div>
);
return (
<div style={baseStyle}>
{renderHeader()}
<div style={contentStyle}>{children && React.Children.count(children) > 0 ? children : renderPlaceholder()}</div>
</div>
);
};
// 웹 타입에 따른 위젯 렌더링
const renderWidget = (component: ComponentData) => {
// 위젯 컴포넌트가 아닌 경우 빈 div 반환
@@ -1193,6 +1357,17 @@ export const RealtimePreview: React.FC<RealtimePreviewProps> = ({
</div>
)}
{type === "area" && (
<div
className="relative h-full w-full"
data-area-container="true"
data-component-id={component.id}
data-layout-type={(component as AreaComponent).layoutType}
>
{renderArea(component as AreaComponent, children)}
</div>
)}
{false &&
(() => {
const dataTableComponent = component as any; // DataTableComponent 타입