diff --git a/backend-node/src/controllers/screenManagementController.ts b/backend-node/src/controllers/screenManagementController.ts index 7130777c..f7900b94 100644 --- a/backend-node/src/controllers/screenManagementController.ts +++ b/backend-node/src/controllers/screenManagementController.ts @@ -104,6 +104,30 @@ export const updateScreen = async ( } }; +// 화면 정보 수정 (메타데이터만) +export const updateScreenInfo = async ( + req: AuthenticatedRequest, + res: Response +) => { + try { + const { id } = req.params; + const { companyCode } = req.user as any; + const { screenName, description, isActive } = req.body; + + await screenManagementService.updateScreenInfo( + parseInt(id), + { screenName, description, isActive }, + companyCode + ); + res.json({ success: true, message: "화면 정보가 수정되었습니다." }); + } catch (error) { + console.error("화면 정보 수정 실패:", error); + res + .status(500) + .json({ success: false, message: "화면 정보 수정에 실패했습니다." }); + } +}; + // 화면 의존성 체크 export const checkScreenDependencies = async ( req: AuthenticatedRequest, diff --git a/backend-node/src/routes/screenManagementRoutes.ts b/backend-node/src/routes/screenManagementRoutes.ts index bc15c279..3fed9129 100644 --- a/backend-node/src/routes/screenManagementRoutes.ts +++ b/backend-node/src/routes/screenManagementRoutes.ts @@ -5,6 +5,7 @@ import { getScreen, createScreen, updateScreen, + updateScreenInfo, deleteScreen, checkScreenDependencies, restoreScreen, @@ -34,6 +35,7 @@ router.get("/screens", getScreens); router.get("/screens/:id", getScreen); router.post("/screens", createScreen); router.put("/screens/:id", updateScreen); +router.put("/screens/:id/info", updateScreenInfo); // 화면 정보만 수정 router.get("/screens/:id/dependencies", checkScreenDependencies); // 의존성 체크 router.delete("/screens/:id", deleteScreen); // 휴지통으로 이동 router.post("/screens/:id/copy", copyScreen); diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index 6da8d16a..a984fa85 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -300,6 +300,51 @@ export class ScreenManagementService { return this.mapToScreenDefinition(screen); } + /** + * 화면 정보 수정 (메타데이터만) - 편집 기능용 + */ + async updateScreenInfo( + screenId: number, + updateData: { screenName: string; description?: string; isActive: string }, + userCompanyCode: string + ): Promise { + // 권한 확인 + const existingResult = await query<{ company_code: string | null }>( + `SELECT company_code FROM screen_definitions WHERE screen_id = $1 LIMIT 1`, + [screenId] + ); + + if (existingResult.length === 0) { + throw new Error("화면을 찾을 수 없습니다."); + } + + const existingScreen = existingResult[0]; + + if ( + userCompanyCode !== "*" && + existingScreen.company_code !== userCompanyCode + ) { + throw new Error("이 화면을 수정할 권한이 없습니다."); + } + + // 화면 정보 업데이트 + await query( + `UPDATE screen_definitions + SET screen_name = $1, + description = $2, + is_active = $3, + updated_date = $4 + WHERE screen_id = $5`, + [ + updateData.screenName, + updateData.description || null, + updateData.isActive, + new Date(), + screenId, + ] + ); + } + /** * 화면 의존성 체크 - 다른 화면에서 이 화면을 참조하는지 확인 */ diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 8352502a..03abcd11 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -132,3 +132,16 @@ @apply bg-background text-foreground; } } + +/* Dialog 오버레이 커스터마이징 - 어두운 배경 */ +[data-radix-dialog-overlay], +.fixed.inset-0.z-50.bg-black { + background-color: rgba(0, 0, 0, 0.6) !important; + backdrop-filter: none !important; +} + +/* DialogPrimitive.Overlay 클래스 오버라이드 */ +.fixed.inset-0.z-50 { + background-color: rgba(0, 0, 0, 0.6) !important; + backdrop-filter: none !important; +} diff --git a/frontend/components/screen/InteractiveScreenViewer.tsx b/frontend/components/screen/InteractiveScreenViewer.tsx index fca43a6c..b7f77ab5 100644 --- a/frontend/components/screen/InteractiveScreenViewer.tsx +++ b/frontend/components/screen/InteractiveScreenViewer.tsx @@ -80,6 +80,12 @@ export const InteractiveScreenViewer: React.FC = ( showValidationPanel = false, validationOptions = {}, }) => { + // component가 없으면 빈 div 반환 + if (!component) { + console.warn("⚠️ InteractiveScreenViewer: component가 undefined입니다."); + return
; + } + const { userName, user } = useAuth(); // 현재 로그인한 사용자명과 사용자 정보 가져오기 const [localFormData, setLocalFormData] = useState>({}); const [dateValues, setDateValues] = useState>({}); diff --git a/frontend/components/screen/ScreenList.tsx b/frontend/components/screen/ScreenList.tsx index dc6bab87..746a2218 100644 --- a/frontend/components/screen/ScreenList.tsx +++ b/frontend/components/screen/ScreenList.tsx @@ -26,11 +26,26 @@ import { } from "@/components/ui/alert-dialog"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { MoreHorizontal, Edit, Trash2, Copy, Eye, Plus, Search, Palette, RotateCcw, Trash } from "lucide-react"; import { ScreenDefinition } from "@/types/screen"; import { screenApi } from "@/lib/api/screen"; import CreateScreenModal from "./CreateScreenModal"; import CopyScreenModal from "./CopyScreenModal"; +import dynamic from "next/dynamic"; +import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer"; +import { DynamicWebTypeRenderer } from "@/lib/registry"; +import { isFileComponent, getComponentWebType } from "@/lib/utils/componentTypeUtils"; + +// InteractiveScreenViewer를 동적으로 import (SSR 비활성화) +const InteractiveScreenViewer = dynamic( + () => import("./InteractiveScreenViewer").then((mod) => mod.InteractiveScreenViewer), + { + ssr: false, + loading: () =>
로딩 중...
, + }, +); interface ScreenListProps { onScreenSelect: (screen: ScreenDefinition) => void; @@ -82,6 +97,22 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr const [bulkDeleteDialogOpen, setBulkDeleteDialogOpen] = useState(false); const [bulkDeleting, setBulkDeleting] = useState(false); + // 편집 관련 상태 + const [editDialogOpen, setEditDialogOpen] = useState(false); + const [screenToEdit, setScreenToEdit] = useState(null); + const [editFormData, setEditFormData] = useState({ + screenName: "", + description: "", + isActive: "Y", + }); + + // 미리보기 관련 상태 + const [previewDialogOpen, setPreviewDialogOpen] = useState(false); + const [screenToPreview, setScreenToPreview] = useState(null); + const [previewLayout, setPreviewLayout] = useState(null); + const [isLoadingPreview, setIsLoadingPreview] = useState(false); + const [previewFormData, setPreviewFormData] = useState>({}); + // 화면 목록 로드 (실제 API) useEffect(() => { let abort = false; @@ -138,8 +169,42 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr }; const handleEdit = (screen: ScreenDefinition) => { - // 편집 모달 열기 - // console.log("편집:", screen); + setScreenToEdit(screen); + setEditFormData({ + screenName: screen.screenName, + description: screen.description || "", + isActive: screen.isActive, + }); + setEditDialogOpen(true); + }; + + const handleEditSave = async () => { + if (!screenToEdit) return; + + try { + // 화면 정보 업데이트 API 호출 + await screenApi.updateScreenInfo(screenToEdit.screenId, editFormData); + + // 목록에서 해당 화면 정보 업데이트 + setScreens((prev) => + prev.map((s) => + s.screenId === screenToEdit.screenId + ? { + ...s, + screenName: editFormData.screenName, + description: editFormData.description, + isActive: editFormData.isActive, + } + : s, + ), + ); + + setEditDialogOpen(false); + setScreenToEdit(null); + } catch (error) { + console.error("화면 정보 업데이트 실패:", error); + alert("화면 정보 업데이트에 실패했습니다."); + } }; const handleDelete = async (screen: ScreenDefinition) => { @@ -295,9 +360,22 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr setIsCopyOpen(true); }; - const handleView = (screen: ScreenDefinition) => { - // 미리보기 모달 열기 - // console.log("미리보기:", screen); + const handleView = async (screen: ScreenDefinition) => { + setScreenToPreview(screen); + setPreviewDialogOpen(true); + setIsLoadingPreview(true); + + try { + // 화면 레이아웃 로드 + const layoutData = await screenApi.getLayout(screen.screenId); + console.log("📊 미리보기 레이아웃 로드:", layoutData); + setPreviewLayout(layoutData); + } catch (error) { + console.error("❌ 레이아웃 로드 실패:", error); + toast.error("화면 레이아웃을 불러오는데 실패했습니다."); + } finally { + setIsLoadingPreview(false); + } }; const handleCopySuccess = () => { @@ -329,11 +407,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr />
- @@ -386,7 +460,9 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr - {screen.tableLabel || screen.tableName} + + {screen.tableLabel || screen.tableName} + -
{screen.createdDate.toLocaleDateString()}
+
{screen.createdDate.toLocaleDateString()}
{screen.createdBy}
@@ -504,16 +580,18 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr - {screen.tableLabel || screen.tableName} + + {screen.tableLabel || screen.tableName} + -
{screen.deletedDate?.toLocaleDateString()}
+
{screen.deletedDate?.toLocaleDateString()}
-
{screen.deletedBy}
+
{screen.deletedBy}
-
+
{screen.deleteReason || "-"}
@@ -563,7 +641,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr > 이전 - + {currentPage} / {totalPages}