ERP-node/frontend/components/screen/RealtimePreview.tsx

265 lines
8.1 KiB
TypeScript
Raw Normal View History

"use client";
import React from "react";
import { ComponentData, WebType } from "@/types/screen";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
// import { Checkbox } from "@/components/ui/checkbox";
// import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
2025-09-01 15:22:47 +09:00
import {
Database,
Type,
Hash,
List,
AlignLeft,
CheckSquare,
Radio,
Calendar,
Code,
Building,
File,
Group,
ChevronDown,
ChevronRight,
} from "lucide-react";
interface RealtimePreviewProps {
component: ComponentData;
isSelected?: boolean;
2025-09-01 16:40:24 +09:00
onClick?: (e?: React.MouseEvent) => void;
onDragStart?: (e: React.DragEvent) => void;
onDragEnd?: () => void;
2025-09-01 15:22:47 +09:00
onGroupToggle?: (groupId: string) => void; // 그룹 접기/펼치기
children?: React.ReactNode; // 그룹 내 자식 컴포넌트들
}
// 웹 타입에 따른 위젯 렌더링
const renderWidget = (component: ComponentData) => {
const { widgetType, label, placeholder, required, readonly, columnName } = component;
2025-09-01 15:22:47 +09:00
// 디버깅: 실제 widgetType 값 확인
2025-09-01 15:22:47 +09:00
console.log("RealtimePreview - widgetType:", widgetType, "columnName:", columnName);
const commonProps = {
placeholder: placeholder || `입력하세요...`,
disabled: readonly,
required: required,
className: "w-full h-full",
};
switch (widgetType) {
case "text":
case "email":
case "tel":
return <Input type={widgetType === "email" ? "email" : widgetType === "tel" ? "tel" : "text"} {...commonProps} />;
case "number":
case "decimal":
return <Input type="number" step={widgetType === "decimal" ? "0.01" : "1"} {...commonProps} />;
case "date":
case "datetime":
2025-09-01 15:22:47 +09:00
return <Input type={widgetType === "datetime" ? "datetime-local" : "date"} {...commonProps} />;
case "select":
case "dropdown":
return (
<select
disabled={readonly}
required={required}
2025-09-01 15:22:47 +09:00
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100"
>
<option value="">{placeholder || "선택하세요..."}</option>
<option value="option1"> 1</option>
<option value="option2"> 2</option>
<option value="option3"> 3</option>
</select>
);
case "textarea":
case "text_area":
return <Textarea {...commonProps} rows={3} />;
case "boolean":
case "checkbox":
return (
<div className="flex items-center space-x-2">
<input
type="checkbox"
id={`checkbox-${component.id}`}
disabled={readonly}
required={required}
className="h-4 w-4"
/>
<Label htmlFor={`checkbox-${component.id}`} className="text-sm">
{label || columnName}
</Label>
</div>
);
case "radio":
return (
<div className="space-y-2">
<div className="flex items-center space-x-2">
<input
type="radio"
id={`radio1-${component.id}`}
name={`radio-${component.id}`}
disabled={readonly}
className="h-4 w-4"
/>
<Label htmlFor={`radio1-${component.id}`} className="text-sm">
1
</Label>
</div>
<div className="flex items-center space-x-2">
<input
type="radio"
id={`radio2-${component.id}`}
name={`radio-${component.id}`}
disabled={readonly}
className="h-4 w-4"
/>
<Label htmlFor={`radio2-${component.id}`} className="text-sm">
2
</Label>
</div>
</div>
);
case "code":
return (
2025-09-01 15:22:47 +09:00
<Textarea {...commonProps} rows={4} className="w-full font-mono text-sm" placeholder="코드를 입력하세요..." />
);
case "entity":
return (
<select
disabled={readonly}
required={required}
2025-09-01 15:22:47 +09:00
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100"
>
<option value=""> ...</option>
<option value="user"></option>
<option value="product"></option>
<option value="order"></option>
</select>
);
case "file":
return (
<input
type="file"
disabled={readonly}
required={required}
2025-09-01 15:22:47 +09:00
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100"
/>
);
default:
return <Input type="text" {...commonProps} />;
}
};
// 위젯 타입 아이콘
const getWidgetIcon = (widgetType: WebType | undefined) => {
switch (widgetType) {
case "text":
case "email":
case "tel":
return <Type className="h-4 w-4 text-blue-600" />;
case "number":
case "decimal":
return <Hash className="h-4 w-4 text-green-600" />;
case "date":
case "datetime":
return <Calendar className="h-4 w-4 text-purple-600" />;
case "select":
case "dropdown":
return <List className="h-4 w-4 text-orange-600" />;
case "textarea":
case "text_area":
return <AlignLeft className="h-4 w-4 text-indigo-600" />;
case "boolean":
case "checkbox":
return <CheckSquare className="h-4 w-4 text-blue-600" />;
case "radio":
return <Radio className="h-4 w-4 text-blue-600" />;
case "code":
return <Code className="h-4 w-4 text-gray-600" />;
case "entity":
return <Building className="h-4 w-4 text-cyan-600" />;
case "file":
return <File className="h-4 w-4 text-yellow-600" />;
default:
return <Type className="h-4 w-4 text-gray-500" />;
}
};
export const RealtimePreview: React.FC<RealtimePreviewProps> = ({
component,
isSelected = false,
onClick,
onDragStart,
onDragEnd,
2025-09-01 15:22:47 +09:00
children,
onGroupToggle,
}) => {
const { type, label, tableName, columnName, widgetType, size, style } = component;
return (
<div
className={`absolute cursor-move transition-all ${
isSelected ? "ring-opacity-50 ring-2 ring-blue-500" : "hover:ring-opacity-50 hover:ring-1 hover:ring-gray-300"
}`}
style={{
left: `${component.position.x}px`,
top: `${component.position.y}px`,
2025-09-01 16:40:24 +09:00
width: `${size.width}px`, // 격자 기반 계산 제거
height: `${size.height}px`,
...style,
}}
2025-09-01 16:40:24 +09:00
onClick={(e) => {
e.stopPropagation();
onClick?.(e);
}}
draggable
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onMouseDown={(e) => {
// 드래그 시작을 위한 마우스 다운 이벤트
e.stopPropagation();
}}
>
{type === "container" && (
<div className="pointer-events-none flex h-full flex-col items-center justify-center p-2">
<div className="flex flex-col items-center space-y-1">
<Database className="h-6 w-6 text-blue-600" />
<div className="text-center">
<div className="text-xs font-medium">{label}</div>
<div className="text-xs text-gray-500">{tableName}</div>
</div>
</div>
</div>
)}
2025-09-01 15:22:47 +09:00
{type === "group" && (
2025-09-01 16:40:24 +09:00
<div className="relative h-full w-full">
{/* 그룹 박스/헤더 제거: 투명 컨테이너 */}
<div className="absolute inset-0">{children}</div>
2025-09-01 15:22:47 +09:00
</div>
)}
{type === "widget" && (
<div className="flex h-full flex-col">
{/* 위젯 본체 - 실제 웹 위젯처럼 보이도록 */}
<div className="pointer-events-none flex-1">{renderWidget(component)}</div>
</div>
)}
</div>
);
};
export default RealtimePreview;