"use client";
import React from "react";
import {
ComponentData,
WebType,
WidgetComponent,
FileComponent,
AreaComponent,
AreaLayoutType,
DateTypeConfig,
NumberTypeConfig,
SelectTypeConfig,
TextTypeConfig,
TextareaTypeConfig,
CheckboxTypeConfig,
RadioTypeConfig,
FileTypeConfig,
CodeTypeConfig,
EntityTypeConfig,
} from "@/types/screen";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { FileUpload } from "./widgets/FileUpload";
import { useAuth } from "@/hooks/useAuth";
// import { Checkbox } from "@/components/ui/checkbox";
// import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
Database,
Type,
Hash,
List,
AlignLeft,
CheckSquare,
Radio,
Calendar,
Code,
Building,
File,
Group,
ChevronDown,
ChevronRight,
Search,
RotateCcw,
Plus,
Edit,
Trash2,
Upload,
Square,
CreditCard,
Layout,
Grid3x3,
Columns,
Rows,
SidebarOpen,
Folder,
ChevronUp,
} from "lucide-react";
interface RealtimePreviewProps {
component: ComponentData;
isSelected?: boolean;
onClick?: (e?: React.MouseEvent) => void;
onDragStart?: (e: React.DragEvent) => void;
onDragEnd?: () => void;
onGroupToggle?: (groupId: string) => void; // 그룹 접기/펼치기
children?: React.ReactNode; // 그룹 내 자식 컴포넌트들
}
// 영역 레이아웃에 따른 아이콘 반환
const getAreaIcon = (layoutType: AreaLayoutType) => {
switch (layoutType) {
case "box":
return ;
case "card":
return ;
case "panel":
return ;
case "section":
return ;
case "grid":
return ;
case "flex-row":
return ;
case "flex-column":
return ;
case "sidebar":
return ;
case "header-content":
return ;
case "tabs":
return ;
case "accordion":
return ;
default:
return ;
}
};
// 영역 컴포넌트 렌더링
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 (
{title}
{description && {description}}
);
};
// 컨텐츠 영역 스타일
const contentStyle: React.CSSProperties = {
...getLayoutStyle(),
flex: 1,
minHeight: 0,
};
// 자식 컴포넌트가 없을 때 표시할 플레이스홀더
const renderPlaceholder = () => (
{getAreaIcon(layoutType)}
{title || `${layoutType} 영역`}
{description || "컴포넌트를 이 영역에 드래그하세요"}
);
return (
{renderHeader()}
{children && React.Children.count(children) > 0 ? children : renderPlaceholder()}
);
};
// 웹 타입에 따른 위젯 렌더링
const renderWidget = (component: ComponentData) => {
// 위젯 컴포넌트가 아닌 경우 빈 div 반환
if (component.type !== "widget") {
return 위젯이 아닙니다
;
}
const widget = component as WidgetComponent;
const { widgetType, label, placeholder, required, readonly, columnName, style } = widget;
// 디버깅: 실제 widgetType 값 확인
console.log("RealtimePreview - widgetType:", widgetType, "columnName:", columnName);
// 사용자가 테두리를 설정했는지 확인
const hasCustomBorder = style && (style.borderWidth || style.borderStyle || style.borderColor || style.border);
// 기본 테두리 제거 여부 결정 - Shadcn UI 기본 border 클래스를 덮어쓰기
const borderClass = hasCustomBorder ? "!border-0" : "";
const commonProps = {
placeholder: placeholder || "입력하세요...",
disabled: readonly,
required: required,
className: `w-full h-full ${borderClass}`,
};
switch (widgetType) {
case "text":
case "email":
case "tel": {
const config = widget.webTypeConfig as TextTypeConfig | undefined;
// 입력 타입에 따른 처리
const isAutoInput = widget.inputType === "auto";
// 자동 값 생성 함수
const getAutoValue = (autoValueType: string) => {
switch (autoValueType) {
case "current_datetime":
return new Date().toLocaleString("ko-KR");
case "current_date":
return new Date().toLocaleDateString("ko-KR");
case "current_time":
return new Date().toLocaleTimeString("ko-KR");
case "current_user":
return "현재사용자";
case "uuid":
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
case "sequence":
return "SEQ_001";
case "user_defined":
return "사용자정의값";
default:
return "자동생성값";
}
};
// 자동 값 플레이스홀더 생성 함수
const getAutoPlaceholder = (autoValueType: string) => {
switch (autoValueType) {
case "current_datetime":
return "현재 날짜시간";
case "current_date":
return "현재 날짜";
case "current_time":
return "현재 시간";
case "current_user":
return "현재 사용자";
case "uuid":
return "UUID";
case "sequence":
return "시퀀스";
case "user_defined":
return "사용자 정의";
default:
return "자동 생성됨";
}
};
// 플레이스홀더 처리
const finalPlaceholder = isAutoInput
? getAutoPlaceholder(widget.autoValueType || "current_datetime")
: config?.placeholder || placeholder || "텍스트를 입력하세요";
// 자동 값 처리
const autoValue = isAutoInput ? getAutoValue(widget.autoValueType || "current_datetime") : "";
const inputType = widgetType === "email" ? "email" : widgetType === "tel" ? "tel" : "text";
// 형식별 패턴 생성
const getPatternByFormat = (format: string) => {
switch (format) {
case "korean":
return "[가-힣\\s]*";
case "english":
return "[a-zA-Z\\s]*";
case "alphanumeric":
return "[a-zA-Z0-9]*";
case "numeric":
return "[0-9]*";
case "email":
return "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}";
case "phone":
return "\\d{3}-\\d{4}-\\d{4}";
case "url":
return "https?://[\\w\\-]+(\\.[\\w\\-]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?";
default:
return config?.pattern || undefined;
}
};
// 입력 검증 함수
const handleInputChange = (e: React.ChangeEvent) => {
const value = e.target.value;
// 형식별 실시간 검증
if (config?.format && config.format !== "none") {
const pattern = getPatternByFormat(config.format);
if (pattern) {
const regex = new RegExp(`^${pattern}$`);
if (value && !regex.test(value)) {
// 유효하지 않은 입력은 무시
e.preventDefault();
return;
}
}
}
// 길이 제한 검증
if (config?.maxLength && value.length > config.maxLength) {
e.preventDefault();
return;
}
};
const inputProps = {
...commonProps,
placeholder: finalPlaceholder,
value: isAutoInput ? autoValue : undefined, // 자동입력인 경우 자동 값 표시
minLength: config?.minLength,
maxLength: config?.maxLength,
pattern: getPatternByFormat(config?.format || "none"),
onChange: () => {}, // 읽기 전용으로 처리
readOnly: readonly || isAutoInput, // 자동입력인 경우 읽기 전용
className: `w-full h-full ${borderClass} ${isAutoInput ? "bg-gray-50 text-gray-600" : ""}`,
};
// multiline이면 Textarea로 렌더링
if (config?.multiline) {
return ;
}
return ;
}
case "number":
case "decimal": {
const config = widget.webTypeConfig as NumberTypeConfig | undefined;
// 입력 타입에 따른 처리
const isAutoInput = widget.inputType === "auto";
// 자동 값 생성 함수 (숫자용)
const getAutoNumberValue = (autoValueType: string) => {
switch (autoValueType) {
case "current_datetime":
return Date.now().toString();
case "current_date":
return new Date().getDate().toString();
case "current_time":
return new Date().getHours().toString();
case "sequence":
return "1001";
case "uuid":
return Math.floor(Math.random() * 1000000).toString();
case "user_defined":
return "999";
default:
return "0";
}
};
// 자동 값 플레이스홀더 생성 함수 (숫자용)
const getAutoNumberPlaceholder = (autoValueType: string) => {
switch (autoValueType) {
case "current_datetime":
return "타임스탬프";
case "current_date":
return "현재 일";
case "current_time":
return "현재 시";
case "sequence":
return "시퀀스";
case "uuid":
return "랜덤 숫자";
case "user_defined":
return "사용자 정의";
default:
return "자동 생성";
}
};
// 자동 값 처리
const autoValue = isAutoInput ? getAutoNumberValue(widget.autoValueType || "sequence") : "";
// 디버깅: 현재 설정값 확인
console.log("🔢 숫자 위젯 렌더링:", {
componentId: widget.id,
widgetType: widget.widgetType,
config,
placeholder: widget.placeholder,
inputType: widget.inputType,
isAutoInput,
});
// 단계값 결정: webTypeConfig > 기본값 (소수는 0.01, 정수는 1)
const step = config?.step || (widgetType === "decimal" ? 0.01 : 1);
// 플레이스홀더 처리
const finalPlaceholder = isAutoInput
? getAutoNumberPlaceholder(widget.autoValueType || "sequence")
: config?.placeholder || placeholder || "숫자를 입력하세요";
// 형식에 따른 표시값 처리
const formatValue = (value: string) => {
if (!value || !config) return value;
const num = parseFloat(value);
if (isNaN(num)) return value;
switch (config.format) {
case "currency":
return new Intl.NumberFormat("ko-KR", {
style: "currency",
currency: "KRW",
}).format(num);
case "percentage":
return `${num}%`;
case "decimal":
return num.toFixed(config.decimalPlaces || 2);
default:
if (config.thousandSeparator) {
return new Intl.NumberFormat("ko-KR").format(num);
}
return value;
}
};
// 접두사/접미사가 있는 경우 표시용 컨테이너 사용
if (config?.prefix || config?.suffix) {
return (
{config.prefix && (
{config.prefix}
)}
{}} // 읽기 전용으로 처리
readOnly={readonly || isAutoInput}
/>
{config.suffix && (
{config.suffix}
)}
);
}
return (
{}} // 읽기 전용으로 처리
readOnly={readonly || isAutoInput}
/>
);
}
case "date":
case "datetime": {
const config = widget.webTypeConfig as DateTypeConfig | undefined;
// 입력 타입에 따른 처리
const isAutoInput = widget.inputType === "auto";
// 자동 값 생성 함수 (날짜용)
const getAutoDateValue = (autoValueType: string, inputType: string) => {
const now = new Date();
switch (autoValueType) {
case "current_datetime":
return inputType === "datetime-local"
? now.toISOString().slice(0, 16) // YYYY-MM-DDTHH:mm
: now.toISOString().slice(0, 10); // YYYY-MM-DD
case "current_date":
return now.toISOString().slice(0, 10); // YYYY-MM-DD
case "current_time":
return inputType === "datetime-local"
? now.toISOString().slice(0, 16) // YYYY-MM-DDTHH:mm
: now.toTimeString().slice(0, 5); // HH:mm
case "user_defined":
return inputType === "datetime-local" ? "2024-01-01T09:00" : "2024-01-01";
default:
return inputType === "datetime-local" ? now.toISOString().slice(0, 16) : now.toISOString().slice(0, 10);
}
};
// 자동 값 플레이스홀더 생성 함수 (날짜용)
const getAutoDatePlaceholder = (autoValueType: string) => {
switch (autoValueType) {
case "current_datetime":
return "현재 날짜시간";
case "current_date":
return "현재 날짜";
case "current_time":
return "현재 시간";
case "user_defined":
return "사용자 정의";
default:
return "자동 생성";
}
};
// 웹타입 설정에 따른 input type 결정
let inputType = "date";
if (config?.showTime || config?.format?.includes("HH:mm")) {
inputType = "datetime-local";
}
// defaultValue를 inputType에 맞게 변환
let processedDefaultValue = config?.defaultValue || "";
if (processedDefaultValue) {
if (inputType === "datetime-local") {
// datetime-local은 "YYYY-MM-DDTHH:mm" 형식이 필요
if (!processedDefaultValue.includes("T") && processedDefaultValue.includes(" ")) {
processedDefaultValue = processedDefaultValue.replace(" ", "T");
}
// 초가 없으면 제거 (datetime-local은 분까지만)
if (processedDefaultValue.includes(":") && processedDefaultValue.split(":").length > 2) {
processedDefaultValue = processedDefaultValue.substring(0, processedDefaultValue.lastIndexOf(":"));
}
} else if (inputType === "date") {
// date는 "YYYY-MM-DD" 형식만 필요
if (processedDefaultValue.includes(" ") || processedDefaultValue.includes("T")) {
processedDefaultValue = processedDefaultValue.split(/[T ]/)[0];
}
}
}
// 자동 값 처리
const autoValue = isAutoInput ? getAutoDateValue(widget.autoValueType || "current_date", inputType) : "";
// 플레이스홀더 우선순위: webTypeConfig > placeholder > 기본값
const finalPlaceholder = isAutoInput
? getAutoDatePlaceholder(widget.autoValueType || "current_date")
: config?.placeholder || placeholder || "날짜를 선택하세요";
// 디버깅: 현재 설정값 확인
console.log("📅 날짜 위젯 렌더링:", {
componentId: widget.id,
widgetType: widget.widgetType,
config,
configExists: !!config,
configKeys: config ? Object.keys(config) : [],
configStringified: JSON.stringify(config),
placeholder: widget.placeholder,
finalInputType: inputType,
finalPlaceholder,
inputTypeDecision: {
showTimeCheck: config?.showTime,
formatCheck: config?.format?.includes("HH:mm"),
formatValue: config?.format,
resultInputType: inputType,
},
appliedSettings: {
minDate: config?.minDate,
maxDate: config?.maxDate,
defaultValue: config?.defaultValue,
processedDefaultValue,
showTime: config?.showTime,
format: config?.format,
},
inputProps: {
type: inputType,
placeholder: finalPlaceholder,
min: config?.minDate,
max: config?.maxDate,
value: processedDefaultValue,
},
valueConversion: {
originalValue: config?.defaultValue,
processedValue: processedDefaultValue,
inputType,
conversionApplied: config?.defaultValue !== processedDefaultValue,
},
widgetFullData: {
id: widget.id,
type: widget.type,
widgetType: widget.widgetType,
webTypeConfig: widget.webTypeConfig,
webTypeConfigStringified: JSON.stringify(widget.webTypeConfig),
},
timestamp: new Date().toISOString(),
});
return (
{}} // 읽기 전용으로 처리
readOnly={readonly || isAutoInput}
/>
);
}
case "select":
case "dropdown": {
const config = widget.webTypeConfig as SelectTypeConfig | undefined;
// 디버깅: 현재 설정값 확인
console.log("📋 선택박스 위젯 렌더링:", {
componentId: widget.id,
widgetType: widget.widgetType,
config,
options: config?.options,
placeholder: widget.placeholder,
});
// 플레이스홀더 처리
const finalPlaceholder = config?.placeholder || placeholder || "선택하세요...";
// 옵션 목록 (webTypeConfig에서 가져오거나 기본 옵션 사용)
const options = config?.options || [
{ label: "옵션 1", value: "option1" },
{ label: "옵션 2", value: "option2" },
{ label: "옵션 3", value: "option3" },
];
return (
);
}
case "textarea":
case "text_area": {
const config = widget.webTypeConfig as TextareaTypeConfig | undefined;
return (