ERP-node/frontend/components/common/ScreenModal.tsx

261 lines
8.4 KiB
TypeScript
Raw Normal View History

2025-09-12 14:24:25 +09:00
"use client";
import React, { useState, useEffect } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
2025-09-12 14:24:25 +09:00
import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic";
import { screenApi } from "@/lib/api/screen";
import { ComponentData } from "@/types/screen";
import { toast } from "sonner";
interface ScreenModalState {
isOpen: boolean;
screenId: number | null;
title: string;
size: "sm" | "md" | "lg" | "xl";
}
interface ScreenModalProps {
className?: string;
}
export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
const [modalState, setModalState] = useState<ScreenModalState>({
isOpen: false,
screenId: null,
title: "",
size: "md",
});
2025-09-18 10:05:50 +09:00
2025-09-12 14:24:25 +09:00
const [screenData, setScreenData] = useState<{
components: ComponentData[];
screenInfo: any;
} | null>(null);
2025-09-18 10:05:50 +09:00
2025-09-12 14:24:25 +09:00
const [loading, setLoading] = useState(false);
const [screenDimensions, setScreenDimensions] = useState<{
width: number;
height: number;
} | null>(null);
2025-09-18 10:05:50 +09:00
// 폼 데이터 상태 추가
const [formData, setFormData] = useState<Record<string, any>>({});
2025-09-12 14:24:25 +09:00
// 화면의 실제 크기 계산 함수
const calculateScreenDimensions = (components: ComponentData[]) => {
// 모든 컴포넌트의 경계 찾기
let minX = Infinity;
let minY = Infinity;
let maxX = 0;
let maxY = 0;
2025-09-12 14:24:25 +09:00
components.forEach((component) => {
const x = parseFloat(component.position?.x?.toString() || "0");
const y = parseFloat(component.position?.y?.toString() || "0");
const width = parseFloat(component.size?.width?.toString() || "100");
const height = parseFloat(component.size?.height?.toString() || "40");
2025-09-12 14:24:25 +09:00
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x + width);
maxY = Math.max(maxY, y + height);
2025-09-12 14:24:25 +09:00
});
// 컨텐츠 실제 크기 + 넉넉한 여백 (양쪽 각 64px)
const contentWidth = maxX - minX;
const contentHeight = maxY - minY;
const padding = 128; // 좌우 또는 상하 합계 여백
const finalWidth = Math.max(contentWidth + padding, 400); // 최소 400px
const finalHeight = Math.max(contentHeight + padding, 300); // 최소 300px
return {
width: Math.min(finalWidth, window.innerWidth * 0.98),
height: Math.min(finalHeight, window.innerHeight * 0.95),
2025-09-12 14:24:25 +09:00
};
};
// 전역 모달 이벤트 리스너
useEffect(() => {
const handleOpenModal = (event: CustomEvent) => {
const { screenId, title, size } = event.detail;
setModalState({
isOpen: true,
screenId,
title,
size,
});
};
const handleCloseModal = () => {
console.log("🚪 ScreenModal 닫기 이벤트 수신");
setModalState({
isOpen: false,
screenId: null,
title: "",
size: "md",
});
setScreenData(null);
setFormData({});
};
2025-09-12 14:24:25 +09:00
window.addEventListener("openScreenModal", handleOpenModal as EventListener);
window.addEventListener("closeSaveModal", handleCloseModal);
2025-09-12 14:24:25 +09:00
return () => {
window.removeEventListener("openScreenModal", handleOpenModal as EventListener);
window.removeEventListener("closeSaveModal", handleCloseModal);
2025-09-12 14:24:25 +09:00
};
}, []);
// 화면 데이터 로딩
useEffect(() => {
if (modalState.isOpen && modalState.screenId) {
loadScreenData(modalState.screenId);
}
}, [modalState.isOpen, modalState.screenId]);
const loadScreenData = async (screenId: number) => {
try {
setLoading(true);
2025-09-18 10:05:50 +09:00
2025-09-12 14:24:25 +09:00
console.log("화면 데이터 로딩 시작:", screenId);
2025-09-18 10:05:50 +09:00
2025-09-12 14:24:25 +09:00
// 화면 정보와 레이아웃 데이터 로딩
const [screenInfo, layoutData] = await Promise.all([
screenApi.getScreen(screenId),
screenApi.getLayout(screenId),
]);
console.log("API 응답:", { screenInfo, layoutData });
// screenApi는 직접 데이터를 반환하므로 .success 체크 불필요
if (screenInfo && layoutData) {
const components = layoutData.components || [];
2025-09-18 10:05:50 +09:00
2025-09-12 14:24:25 +09:00
// 화면의 실제 크기 계산
const dimensions = calculateScreenDimensions(components);
setScreenDimensions(dimensions);
2025-09-18 10:05:50 +09:00
2025-09-12 14:24:25 +09:00
setScreenData({
components,
screenInfo: screenInfo,
});
console.log("화면 데이터 설정 완료:", {
componentsCount: components.length,
dimensions,
screenInfo,
});
} else {
throw new Error("화면 데이터가 없습니다");
}
} catch (error) {
console.error("화면 데이터 로딩 오류:", error);
toast.error("화면을 불러오는 중 오류가 발생했습니다.");
handleClose();
} finally {
setLoading(false);
}
};
const handleClose = () => {
setModalState({
isOpen: false,
screenId: null,
title: "",
size: "md",
});
setScreenData(null);
2025-09-18 10:05:50 +09:00
setFormData({}); // 폼 데이터 초기화
2025-09-12 14:24:25 +09:00
};
// 모달 크기 설정 - 화면 내용에 맞게 동적 조정
const getModalStyle = () => {
if (!screenDimensions) {
return {
className: "w-fit min-w-[400px] max-w-4xl max-h-[80vh] overflow-hidden",
2025-09-18 10:05:50 +09:00
style: {},
2025-09-12 14:24:25 +09:00
};
}
// 헤더 높이만 고려 (패딩 제거)
const headerHeight = 73; // DialogHeader 실제 높이 (border-b px-6 py-4 포함)
2025-09-12 14:24:25 +09:00
const totalHeight = screenDimensions.height + headerHeight;
return {
className: "overflow-hidden p-0",
style: {
width: `${Math.min(screenDimensions.width, window.innerWidth * 0.98)}px`, // 화면 크기 그대로
height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`, // 헤더 + 화면 높이
maxWidth: "98vw",
maxHeight: "95vh",
2025-09-18 10:05:50 +09:00
},
2025-09-12 14:24:25 +09:00
};
};
const modalStyle = getModalStyle();
return (
<Dialog open={modalState.isOpen} onOpenChange={handleClose}>
2025-09-18 10:05:50 +09:00
<DialogContent className={`${modalStyle.className} ${className || ""}`} style={modalStyle.style}>
<DialogHeader className="border-b px-6 py-4">
2025-09-12 14:24:25 +09:00
<DialogTitle>{modalState.title}</DialogTitle>
<DialogDescription>{loading ? "화면을 불러오는 중입니다..." : "화면 내용을 표시합니다."}</DialogDescription>
2025-09-12 14:24:25 +09:00
</DialogHeader>
2025-09-18 10:05:50 +09:00
<div className="flex-1 flex items-center justify-center overflow-hidden">
2025-09-12 14:24:25 +09:00
{loading ? (
2025-09-18 10:05:50 +09:00
<div className="flex h-full items-center justify-center">
2025-09-12 14:24:25 +09:00
<div className="text-center">
2025-09-18 10:05:50 +09:00
<div className="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"></div>
<p className="text-muted-foreground"> ...</p>
2025-09-12 14:24:25 +09:00
</div>
</div>
) : screenData ? (
2025-09-18 10:05:50 +09:00
<div
className="relative bg-white"
2025-09-12 14:24:25 +09:00
style={{
2025-09-18 10:05:50 +09:00
width: screenDimensions?.width || 800,
height: screenDimensions?.height || 600,
transformOrigin: 'center center',
maxWidth: '100%',
maxHeight: '100%',
2025-09-12 14:24:25 +09:00
}}
>
{screenData.components.map((component) => (
<InteractiveScreenViewerDynamic
key={component.id}
component={component}
allComponents={screenData.components}
2025-09-18 10:05:50 +09:00
formData={formData}
onFormDataChange={(fieldName, value) => {
console.log(`🎯 ScreenModal onFormDataChange 호출: ${fieldName} = "${value}"`);
console.log("📋 현재 formData:", formData);
2025-09-18 10:05:50 +09:00
setFormData((prev) => {
const newFormData = {
...prev,
[fieldName]: value,
};
console.log("📝 ScreenModal 업데이트된 formData:", newFormData);
2025-09-18 10:05:50 +09:00
return newFormData;
});
}}
2025-09-12 14:24:25 +09:00
screenInfo={{
id: modalState.screenId!,
tableName: screenData.screenInfo?.tableName,
}}
/>
))}
</div>
) : (
2025-09-18 10:05:50 +09:00
<div className="flex h-full items-center justify-center">
<p className="text-muted-foreground"> .</p>
2025-09-12 14:24:25 +09:00
</div>
)}
</div>
</DialogContent>
</Dialog>
);
};
export default ScreenModal;