"use client"; import React, { useEffect, useState } from "react"; import { useParams } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Loader2 } from "lucide-react"; import { screenApi } from "@/lib/api/screen"; import { ScreenDefinition, LayoutData } from "@/types/screen"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { initializeComponents } from "@/lib/registry/components"; import { EditModal } from "@/components/screen/EditModal"; import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic"; import { FlowButtonGroup } from "@/components/screen/widgets/FlowButtonGroup"; import { FlowVisibilityConfig } from "@/types/control-management"; import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils"; import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer"; import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext"; import { useAuth } from "@/hooks/useAuth"; // πŸ†• μ‚¬μš©μž 정보 export default function ScreenViewPage() { const params = useParams(); const router = useRouter(); const screenId = parseInt(params.screenId as string); // πŸ†• ν˜„μž¬ λ‘œκ·ΈμΈν•œ μ‚¬μš©μž 정보 const { user, userName, companyCode } = useAuth(); const [screen, setScreen] = useState(null); const [layout, setLayout] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [formData, setFormData] = useState>({}); // ν…Œμ΄λΈ”μ—μ„œ μ„ νƒλœ ν–‰ 데이터 (λ²„νŠΌ μ•‘μ…˜μ— 전달) const [selectedRowsData, setSelectedRowsData] = useState([]); // ν”Œλ‘œμš°μ—μ„œ μ„ νƒλœ 데이터 (λ²„νŠΌ μ•‘μ…˜μ— 전달) const [flowSelectedData, setFlowSelectedData] = useState([]); const [flowSelectedStepId, setFlowSelectedStepId] = useState(null); // ν…Œμ΄λΈ” μƒˆλ‘œκ³ μΉ¨μ„ μœ„ν•œ ν‚€ (값이 λ³€κ²½λ˜λ©΄ ν…Œμ΄λΈ”μ΄ λ¦¬λ Œλ”λ§λ¨) const [tableRefreshKey, setTableRefreshKey] = useState(0); // ν”Œλ‘œμš° μƒˆλ‘œκ³ μΉ¨μ„ μœ„ν•œ ν‚€ (값이 λ³€κ²½λ˜λ©΄ ν”Œλ‘œμš° 데이터가 λ¦¬λ Œλ”λ§λ¨) const [flowRefreshKey, setFlowRefreshKey] = useState(0); // νŽΈμ§‘ λͺ¨λ‹¬ μƒνƒœ const [editModalOpen, setEditModalOpen] = useState(false); const [editModalConfig, setEditModalConfig] = useState<{ screenId?: number; modalSize?: "sm" | "md" | "lg" | "xl" | "full"; editData?: Record; onSave?: () => void; modalTitle?: string; modalDescription?: string; }>({}); const containerRef = React.useRef(null); const [scale, setScale] = useState(1); useEffect(() => { const initComponents = async () => { try { console.log("πŸš€ ν• λ‹Ήλœ ν™”λ©΄μ—μ„œ μ»΄ν¬λ„ŒνŠΈ μ‹œμŠ€ν…œ μ΄ˆκΈ°ν™” μ‹œμž‘..."); await initializeComponents(); console.log("βœ… ν• λ‹Ήλœ ν™”λ©΄μ—μ„œ μ»΄ν¬λ„ŒνŠΈ μ‹œμŠ€ν…œ μ΄ˆκΈ°ν™” μ™„λ£Œ"); } catch (error) { console.error("❌ ν• λ‹Ήλœ ν™”λ©΄μ—μ„œ μ»΄ν¬λ„ŒνŠΈ μ‹œμŠ€ν…œ μ΄ˆκΈ°ν™” μ‹€νŒ¨:", error); } }; initComponents(); }, []); // νŽΈμ§‘ λͺ¨λ‹¬ 이벀트 λ¦¬μŠ€λ„ˆ 등둝 useEffect(() => { const handleOpenEditModal = (event: CustomEvent) => { console.log("🎭 νŽΈμ§‘ λͺ¨λ‹¬ μ—΄κΈ° 이벀트 μˆ˜μ‹ :", event.detail); setEditModalConfig({ screenId: event.detail.screenId, modalSize: event.detail.modalSize, editData: event.detail.editData, onSave: event.detail.onSave, modalTitle: event.detail.modalTitle, modalDescription: event.detail.modalDescription, }); setEditModalOpen(true); }; // @ts-expect-error - CustomEvent type window.addEventListener("openEditModal", handleOpenEditModal); return () => { // @ts-expect-error - CustomEvent type window.removeEventListener("openEditModal", handleOpenEditModal); }; }, []); useEffect(() => { const loadScreen = async () => { try { setLoading(true); setError(null); // ν™”λ©΄ 정보 λ‘œλ“œ const screenData = await screenApi.getScreen(screenId); setScreen(screenData); // λ ˆμ΄μ•„μ›ƒ λ‘œλ“œ try { const layoutData = await screenApi.getLayout(screenId); setLayout(layoutData); } catch (layoutError) { console.warn("λ ˆμ΄μ•„μ›ƒ λ‘œλ“œ μ‹€νŒ¨, 빈 λ ˆμ΄μ•„μ›ƒ μ‚¬μš©:", layoutError); setLayout({ screenId, components: [], gridSettings: { columns: 12, gap: 16, padding: 16, enabled: true, size: 8, color: "#e0e0e0", opacity: 0.5, snapToGrid: true, }, }); } } catch (error) { console.error("ν™”λ©΄ λ‘œλ“œ μ‹€νŒ¨:", error); setError("화면을 λΆˆλŸ¬μ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."); toast.error("화면을 λΆˆλŸ¬μ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."); } finally { setLoading(false); } }; if (screenId) { loadScreen(); } }, [screenId]); // μΊ”λ²„μŠ€ λΉ„μœ¨ μ‘°μ • (μ‚¬μš©μž 화면에 맞게 μžλ™ μŠ€μΌ€μΌ) useEffect(() => { const updateScale = () => { if (containerRef.current && layout) { const designWidth = layout?.screenResolution?.width || 1200; const designHeight = layout?.screenResolution?.height || 800; const containerWidth = containerRef.current.offsetWidth; const containerHeight = containerRef.current.offsetHeight; // κ°€λ‘œ/μ„Έλ‘œ λΉ„μœ¨ 쀑 μž‘μ€ 것을 선택 (화면에 맞게) const scaleX = containerWidth / designWidth; const scaleY = containerHeight / designHeight; const newScale = Math.min(scaleX, scaleY); console.log("πŸ“ μΊ”λ²„μŠ€ μŠ€μΌ€μΌ 계산:", { designWidth, designHeight, containerWidth, containerHeight, scaleX, scaleY, finalScale: newScale, }); setScale(newScale); } }; // 초기 μΈ‘μ • const timer = setTimeout(updateScale, 100); window.addEventListener("resize", updateScale); return () => { clearTimeout(timer); window.removeEventListener("resize", updateScale); }; }, [layout]); if (loading) { return (

화면을 λΆˆλŸ¬μ˜€λŠ” 쀑...

); } if (error || !screen) { return (
⚠️

화면을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€

{error || "μš”μ²­ν•˜μ‹  화면이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."}

); } // ν™”λ©΄ 해상도 정보가 있으면 ν•΄λ‹Ή 크기둜, μ—†μœΌλ©΄ κΈ°λ³Έ 크기 μ‚¬μš© const screenWidth = layout?.screenResolution?.width || 1200; const screenHeight = layout?.screenResolution?.height || 800; return (
{/* μ ˆλŒ€ μœ„μΉ˜ 기반 λ Œλ”λ§ */} {layout && layout.components.length > 0 ? (
{/* μ΅œμƒμœ„ μ»΄ν¬λ„ŒνŠΈλ“€ λ Œλ”λ§ */} {(() => { // πŸ†• ν”Œλ‘œμš° λ²„νŠΌ κ·Έλ£Ή 감지 및 처리 const topLevelComponents = layout.components.filter((component) => !component.parentId); const buttonGroups: Record = {}; const processedButtonIds = new Set(); topLevelComponents.forEach((component) => { const isButton = component.type === "button" || (component.type === "component" && ["button-primary", "button-secondary"].includes((component as any).componentType)); if (isButton) { const flowConfig = (component as any).webTypeConfig?.flowVisibilityConfig as | FlowVisibilityConfig | undefined; if (flowConfig?.enabled && flowConfig.layoutBehavior === "auto-compact" && flowConfig.groupId) { if (!buttonGroups[flowConfig.groupId]) { buttonGroups[flowConfig.groupId] = []; } buttonGroups[flowConfig.groupId].push(component); processedButtonIds.add(component.id); } } }); const regularComponents = topLevelComponents.filter((c) => !processedButtonIds.has(c.id)); return ( <> {/* 일반 μ»΄ν¬λ„ŒνŠΈλ“€ */} {regularComponents.map((component) => ( {}} screenId={screenId} tableName={screen?.tableName} userId={user?.userId} userName={userName} companyCode={companyCode} selectedRowsData={selectedRowsData} onSelectedRowsChange={(_, selectedData) => { console.log("πŸ” ν™”λ©΄μ—μ„œ μ„ νƒλœ ν–‰ 데이터:", selectedData); setSelectedRowsData(selectedData); }} flowSelectedData={flowSelectedData} flowSelectedStepId={flowSelectedStepId} onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => { console.log("πŸ” [page.tsx] ν”Œλ‘œμš° μ„ νƒλœ 데이터 λ°›μŒ:", { dataCount: selectedData.length, selectedData, stepId, }); setFlowSelectedData(selectedData); setFlowSelectedStepId(stepId); console.log("πŸ” [page.tsx] μƒνƒœ μ—…λ°μ΄νŠΈ μ™„λ£Œ"); }} refreshKey={tableRefreshKey} onRefresh={() => { console.log("πŸ”„ ν…Œμ΄λΈ” μƒˆλ‘œκ³ μΉ¨ μš”μ²­λ¨"); setTableRefreshKey((prev) => prev + 1); setSelectedRowsData([]); // 선택 ν•΄μ œ }} flowRefreshKey={flowRefreshKey} onFlowRefresh={() => { console.log("πŸ”„ ν”Œλ‘œμš° μƒˆλ‘œκ³ μΉ¨ μš”μ²­λ¨"); setFlowRefreshKey((prev) => prev + 1); setFlowSelectedData([]); // 선택 ν•΄μ œ setFlowSelectedStepId(null); }} formData={formData} onFormDataChange={(fieldName, value) => { console.log("πŸ“ 폼 데이터 λ³€κ²½:", fieldName, "=", value); setFormData((prev) => ({ ...prev, [fieldName]: value })); }} > {/* μžμ‹ μ»΄ν¬λ„ŒνŠΈλ“€ */} {(component.type === "group" || component.type === "container" || component.type === "area") && layout.components .filter((child) => child.parentId === component.id) .map((child) => { // μžμ‹ μ»΄ν¬λ„ŒνŠΈμ˜ μœ„μΉ˜λ₯Ό λΆ€λͺ¨ κΈ°μ€€ μƒλŒ€ μ’Œν‘œλ‘œ μ‘°μ • const relativeChildComponent = { ...child, position: { x: child.position.x - component.position.x, y: child.position.y - component.position.y, z: child.position.z || 1, }, }; return ( {}} screenId={screenId} tableName={screen?.tableName} userId={user?.userId} userName={userName} companyCode={companyCode} selectedRowsData={selectedRowsData} onSelectedRowsChange={(_, selectedData) => { console.log("πŸ” ν™”λ©΄μ—μ„œ μ„ νƒλœ ν–‰ 데이터 (μžμ‹):", selectedData); setSelectedRowsData(selectedData); }} refreshKey={tableRefreshKey} onRefresh={() => { console.log("πŸ”„ ν…Œμ΄λΈ” μƒˆλ‘œκ³ μΉ¨ μš”μ²­λ¨ (μžμ‹)"); setTableRefreshKey((prev) => prev + 1); setSelectedRowsData([]); // 선택 ν•΄μ œ }} formData={formData} onFormDataChange={(fieldName, value) => { console.log("πŸ“ 폼 데이터 λ³€κ²½ (μžμ‹):", fieldName, "=", value); setFormData((prev) => ({ ...prev, [fieldName]: value })); }} /> ); })} ))} {/* πŸ†• ν”Œλ‘œμš° λ²„νŠΌ κ·Έλ£Ήλ“€ */} {Object.entries(buttonGroups).map(([groupId, buttons]) => { if (buttons.length === 0) return null; const firstButton = buttons[0]; const groupConfig = (firstButton as any).webTypeConfig ?.flowVisibilityConfig as FlowVisibilityConfig; // 그룹의 μœ„μΉ˜λŠ” λͺ¨λ“  λ²„νŠΌ 쀑 κ°€μž₯ μ™Όμͺ½/μœ„μͺ½ λ²„νŠΌμ˜ μœ„μΉ˜ μ‚¬μš© const groupPosition = buttons.reduce( (min, button) => ({ x: Math.min(min.x, button.position.x), y: Math.min(min.y, button.position.y), z: min.z, }), { x: buttons[0].position.x, y: buttons[0].position.y, z: buttons[0].position.z || 2 }, ); // 그룹의 크기 계산: λ²„νŠΌλ“€μ˜ μ‹€μ œ 크기 + 간격을 κΈ°μ€€μœΌλ‘œ 계산 const direction = groupConfig.groupDirection || "horizontal"; const gap = groupConfig.groupGap ?? 8; let groupWidth = 0; let groupHeight = 0; if (direction === "horizontal") { groupWidth = buttons.reduce((total, button, index) => { const buttonWidth = button.size?.width || 100; const gapWidth = index < buttons.length - 1 ? gap : 0; return total + buttonWidth + gapWidth; }, 0); groupHeight = Math.max(...buttons.map((b) => b.size?.height || 40)); } else { groupWidth = Math.max(...buttons.map((b) => b.size?.width || 100)); groupHeight = buttons.reduce((total, button, index) => { const buttonHeight = button.size?.height || 40; const gapHeight = index < buttons.length - 1 ? gap : 0; return total + buttonHeight + gapHeight; }, 0); } return (
{ const relativeButton = { ...button, position: { x: 0, y: 0, z: button.position.z || 1 }, }; return (
{}} screenId={screenId} tableName={screen?.tableName} userId={user?.userId} userName={userName} companyCode={companyCode} selectedRowsData={selectedRowsData} onSelectedRowsChange={(_, selectedData) => { setSelectedRowsData(selectedData); }} flowSelectedData={flowSelectedData} flowSelectedStepId={flowSelectedStepId} onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => { setFlowSelectedData(selectedData); setFlowSelectedStepId(stepId); }} refreshKey={tableRefreshKey} onRefresh={() => { setTableRefreshKey((prev) => prev + 1); setSelectedRowsData([]); }} flowRefreshKey={flowRefreshKey} onFlowRefresh={() => { setFlowRefreshKey((prev) => prev + 1); setFlowSelectedData([]); setFlowSelectedStepId(null); }} onFormDataChange={(fieldName, value) => { setFormData((prev) => ({ ...prev, [fieldName]: value })); }} />
); }} />
); })} ); })()}
) : ( // 빈 화면일 λ•Œ
πŸ“„

화면이 λΉ„μ–΄μžˆμŠ΅λ‹ˆλ‹€

이 ν™”λ©΄μ—λŠ” 아직 μ„€κ³„λœ μ»΄ν¬λ„ŒνŠΈκ°€ μ—†μŠ΅λ‹ˆλ‹€.

)} {/* νŽΈμ§‘ λͺ¨λ‹¬ */} { setEditModalOpen(false); setEditModalConfig({}); }} screenId={editModalConfig.screenId} modalSize={editModalConfig.modalSize} editData={editModalConfig.editData} onSave={editModalConfig.onSave} modalTitle={editModalConfig.modalTitle} modalDescription={editModalConfig.modalDescription} onDataChange={(changedFormData) => { console.log("πŸ“ EditModalμ—μ„œ 데이터 λ³€κ²½ μˆ˜μ‹ :", changedFormData); // λ³€κ²½λœ 데이터λ₯Ό 메인 폼에 반영 setFormData((prev) => { const updatedFormData = { ...prev, ...changedFormData, // λ³€κ²½λœ ν•„λ“œλ“€λ§Œ μ—…λ°μ΄νŠΈ }; console.log("πŸ“Š 메인 폼 데이터 μ—…λ°μ΄νŠΈ:", updatedFormData); return updatedFormData; }); }} />
); }