{loading ? (
@@ -728,8 +722,8 @@ export const ScreenModal: React.FC = ({ className }) => {
-
-
+
+
);
};
diff --git a/frontend/components/common/TableHistoryModal.tsx b/frontend/components/common/TableHistoryModal.tsx
index 033c18ac..f2970b4f 100644
--- a/frontend/components/common/TableHistoryModal.tsx
+++ b/frontend/components/common/TableHistoryModal.tsx
@@ -7,12 +7,12 @@
import React, { useEffect, useState } from "react";
import {
- ResizableDialog,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogTitle,
- ResizableDialogDescription,
-} from "@/components/ui/resizable-dialog";
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -209,7 +209,7 @@ export function TableHistoryModal({
-
+
변경 이력{" "}
{!recordId && (
@@ -217,12 +217,12 @@ export function TableHistoryModal({
전체
)}
-
-
+
+
{recordId
? `${recordDisplayValue || recordLabel || "-"} - ${tableName} 테이블`
: `${tableName} 테이블 전체 이력`}
-
+
{loading ? (
diff --git a/frontend/components/common/TableOptionsModal.tsx b/frontend/components/common/TableOptionsModal.tsx
index f19a1a07..64b2b02d 100644
--- a/frontend/components/common/TableOptionsModal.tsx
+++ b/frontend/components/common/TableOptionsModal.tsx
@@ -2,13 +2,13 @@
import React, { useState, useEffect } from "react";
import {
- ResizableDialog,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogTitle,
- ResizableDialogDescription,
- ResizableDialogFooter,
-} from "@/components/ui/resizable-dialog";
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
@@ -150,23 +150,14 @@ export function TableOptionsModal({
};
return (
-
-
-
- 테이블 옵션
-
- 컬럼 표시/숨기기, 순서 변경, 틀고정 등을 설정할 수 있습니다. 모달 테두리를 드래그하여 크기를 조절할 수 있습니다.
-
-
+
+
+
+ 테이블 옵션
+
+ 컬럼 표시/숨기기, 순서 변경, 틀고정 등을 설정할 수 있습니다.
+
+
@@ -303,7 +294,7 @@ export function TableOptionsModal({
-
+
저장
-
-
-
+
+
+
);
}
diff --git a/frontend/components/dataflow/ConnectionSetupModal.tsx b/frontend/components/dataflow/ConnectionSetupModal.tsx
index 450509ee..9b6482a4 100644
--- a/frontend/components/dataflow/ConnectionSetupModal.tsx
+++ b/frontend/components/dataflow/ConnectionSetupModal.tsx
@@ -2,13 +2,13 @@
import React, { useState, useEffect, useCallback } from "react";
import {
- ResizableDialog,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogTitle,
- ResizableDialogDescription,
- ResizableDialogFooter,
-} from "@/components/ui/resizable-dialog";
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
import {
AlertDialog,
AlertDialogAction,
@@ -673,14 +673,14 @@ export const ConnectionSetupModal: React.FC
= ({
return (
<>
-
-
-
-
+
+
+
+
필드 연결 설정
-
-
+
+
{/* 기본 연결 설정 */}
@@ -719,16 +719,16 @@ export const ConnectionSetupModal: React.FC = ({
{renderConnectionTypeSettings()}
-
+
취소
연결 생성
-
-
-
+
+
+
diff --git a/frontend/components/dataflow/SaveDiagramModal.tsx b/frontend/components/dataflow/SaveDiagramModal.tsx
index 70a12e39..af7f004f 100644
--- a/frontend/components/dataflow/SaveDiagramModal.tsx
+++ b/frontend/components/dataflow/SaveDiagramModal.tsx
@@ -2,13 +2,13 @@
import React, { useState, useEffect } from "react";
import {
- ResizableDialog,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogTitle,
- ResizableDialogDescription,
- ResizableDialogFooter,
-} from "@/components/ui/resizable-dialog";
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
import {
AlertDialog,
AlertDialogAction,
@@ -133,11 +133,11 @@ const SaveDiagramModal: React.FC = ({
return (
<>
-
-
-
- 📊 관계도 저장
-
+
+
+
+ 📊 관계도 저장
+
{/* 관계도 이름 입력 */}
@@ -203,7 +203,7 @@ const SaveDiagramModal: React.FC
= ({
관계 목록
-
+
{relationships.map((relationship, index) => (
= ({
)}
-
+
취소
@@ -260,9 +260,9 @@ const SaveDiagramModal: React.FC = ({
"저장하기"
)}
-
-
-
+
+
+
{/* 저장 성공 알림 모달 */}
diff --git a/frontend/components/dataflow/node-editor/dialogs/LoadFlowDialog.tsx b/frontend/components/dataflow/node-editor/dialogs/LoadFlowDialog.tsx
index 7cdd28fd..d5cc9b18 100644
--- a/frontend/components/dataflow/node-editor/dialogs/LoadFlowDialog.tsx
+++ b/frontend/components/dataflow/node-editor/dialogs/LoadFlowDialog.tsx
@@ -6,7 +6,7 @@
import { useEffect, useState } from "react";
import { Loader2, FileJson, Calendar, Trash2 } from "lucide-react";
-import { ResizableDialog, ResizableDialogContent, ResizableDialogDescription, ResizableDialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getNodeFlows, deleteNodeFlow } from "@/lib/api/nodeFlows";
diff --git a/frontend/components/flow/FlowDataListModal.tsx b/frontend/components/flow/FlowDataListModal.tsx
index 61264ffb..352860e5 100644
--- a/frontend/components/flow/FlowDataListModal.tsx
+++ b/frontend/components/flow/FlowDataListModal.tsx
@@ -1,7 +1,7 @@
"use client";
import React, { useEffect, useState } from "react";
-import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogDescription } from "@/components/ui/resizable-dialog";
+import { Dialog, DialogContent, DialogHeader, DialogDescription } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table";
@@ -130,11 +130,11 @@ export function FlowDataListModal({
-
+
{stepName}
{data.length}건
-
- 이 단계에 해당하는 데이터 목록입니다
+
+ 이 단계에 해당하는 데이터 목록입니다
diff --git a/frontend/components/layout/ProfileModal.tsx b/frontend/components/layout/ProfileModal.tsx
index e79d3357..ad23acb4 100644
--- a/frontend/components/layout/ProfileModal.tsx
+++ b/frontend/components/layout/ProfileModal.tsx
@@ -1,11 +1,11 @@
import {
- ResizableDialog,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogTitle,
- ResizableDialogDescription,
- ResizableDialogFooter,
-} from "@/components/ui/resizable-dialog";
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -48,11 +48,11 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
};
return (
-
-
-
- {title}
-
+
+
+
+ {title}
+
@@ -61,8 +61,8 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
확인
-
-
+
+
);
}
@@ -165,11 +165,11 @@ export function ProfileModal({
};
return (
<>
-
-
-
- 프로필 수정
-
+
+
+
+ 프로필 수정
+
{/* 프로필 사진 섹션 */}
@@ -449,16 +449,16 @@ export function ProfileModal({
)}
-
+
취소
{isSaving ? "저장 중..." : "저장"}
-
-
-
+
+
+
{/* 알림 모달 */}
-
-
- 새 차량 등록
-
+
+
+
+ 새 차량 등록
+
새로운 차량 정보를 입력해주세요.
-
-
+
+
@@ -501,16 +501,16 @@ export function ProfileModal({
-
+
취소
등록
-
-
-
+
+
+
)}
>
);
diff --git a/frontend/components/mail/MailDetailModal.tsx b/frontend/components/mail/MailDetailModal.tsx
index e945bbd9..0a25c2a3 100644
--- a/frontend/components/mail/MailDetailModal.tsx
+++ b/frontend/components/mail/MailDetailModal.tsx
@@ -6,7 +6,7 @@ import {
DialogContent,
DialogHeader,
-} from "@/components/ui/resizable-dialog";
+} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
@@ -186,13 +186,13 @@ export default function MailDetailModal({
};
return (
-
-
-
-
+
+
+
+
메일 상세
-
-
+
+
{loading ? (
@@ -375,8 +375,8 @@ export default function MailDetailModal({
) : null}
-
-
+
+
);
}
diff --git a/frontend/components/multilang/LangKeyModal.tsx b/frontend/components/multilang/LangKeyModal.tsx
index c25164f5..06189c58 100644
--- a/frontend/components/multilang/LangKeyModal.tsx
+++ b/frontend/components/multilang/LangKeyModal.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect } from "react";
-import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -141,9 +141,9 @@ export function LangKeyModal({
return (
-
+
- {langKey ? "다국어 키 수정" : "새 다국어 키 추가"}
+ {langKey ? "다국어 키 수정" : "새 다국어 키 추가"}
diff --git a/frontend/components/order/OrderRegistrationModal.tsx b/frontend/components/order/OrderRegistrationModal.tsx
index 615f0426..e47e124f 100644
--- a/frontend/components/order/OrderRegistrationModal.tsx
+++ b/frontend/components/order/OrderRegistrationModal.tsx
@@ -210,7 +210,7 @@ export function OrderRegistrationModal({
return (
-
+
수주 등록
diff --git a/frontend/components/report/ReportCreateModal.tsx b/frontend/components/report/ReportCreateModal.tsx
index ef2a325d..c51dd982 100644
--- a/frontend/components/report/ReportCreateModal.tsx
+++ b/frontend/components/report/ReportCreateModal.tsx
@@ -4,11 +4,10 @@ import { useState, useEffect } from "react";
import {
Dialog,
DialogContent,
-
-
DialogHeader,
-
-} from "@/components/ui/resizable-dialog";
+ DialogTitle,
+ DialogFooter,
+} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -120,8 +119,8 @@ export function ReportCreateModal({ isOpen, onClose, onSuccess }: ReportCreateMo
- 새 리포트 생성
- 새로운 리포트를 생성합니다. 필수 항목을 입력해주세요.
+ 새 리포트 생성
+ 새로운 리포트를 생성합니다. 필수 항목을 입력해주세요.
@@ -207,7 +206,7 @@ export function ReportCreateModal({ isOpen, onClose, onSuccess }: ReportCreateMo
-
+
취소
@@ -221,7 +220,7 @@ export function ReportCreateModal({ isOpen, onClose, onSuccess }: ReportCreateMo
"생성"
)}
-
+
);
diff --git a/frontend/components/screen/CopyScreenModal.tsx b/frontend/components/screen/CopyScreenModal.tsx
index 75493e4f..c37603c5 100644
--- a/frontend/components/screen/CopyScreenModal.tsx
+++ b/frontend/components/screen/CopyScreenModal.tsx
@@ -424,7 +424,7 @@ export default function CopyScreenModal({
return (
-
+
diff --git a/frontend/components/screen/CreateScreenModal.tsx b/frontend/components/screen/CreateScreenModal.tsx
index 4f0e5eb9..fc39140d 100644
--- a/frontend/components/screen/CreateScreenModal.tsx
+++ b/frontend/components/screen/CreateScreenModal.tsx
@@ -2,12 +2,12 @@
import { useEffect, useMemo, useState, useRef } from "react";
import {
- ResizableDialog,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogTitle,
- ResizableDialogFooter,
-} from "@/components/ui/resizable-dialog";
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -271,21 +271,11 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre
};
return (
-
-
-
- 새 화면 생성
-
+
+
+
+ 새 화면 생성
+
@@ -603,15 +593,15 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre
)}
-
+
onOpenChange(false)} disabled={submitting}>
취소
생성
-
-
-
+
+
+
);
}
diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx
index 024f7ac7..2a3050fc 100644
--- a/frontend/components/screen/EditModal.tsx
+++ b/frontend/components/screen/EditModal.tsx
@@ -2,13 +2,13 @@
import React, { useState, useEffect } from "react";
import {
- ResizableDialog,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogTitle,
- ResizableDialogDescription,
- ResizableDialogFooter,
-} from "@/components/ui/resizable-dialog";
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic";
import { screenApi } from "@/lib/api/screen";
import { ComponentData } from "@/types/screen";
@@ -678,14 +678,17 @@ export const EditModal: React.FC
= ({ className }) => {
}
// 화면관리에서 설정한 크기 = 컨텐츠 영역 크기
- // 실제 모달 크기 = 컨텐츠 + 헤더
- const headerHeight = 60; // DialogHeader
- const totalHeight = screenDimensions.height + headerHeight;
+ // 실제 모달 크기 = 컨텐츠 + 헤더 + gap + padding
+ const headerHeight = 52; // DialogHeader (타이틀 + border-b + py-3)
+ const dialogGap = 16; // DialogContent gap-4
+ const extraPadding = 24; // 추가 여백 (안전 마진)
+
+ const totalHeight = screenDimensions.height + headerHeight + dialogGap + extraPadding;
return {
className: "overflow-hidden p-0",
style: {
- width: `${Math.min(screenDimensions.width, window.innerWidth * 0.98)}px`,
+ width: `${Math.min(screenDimensions.width + 48, window.innerWidth * 0.98)}px`, // 좌우 패딩 추가
height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`,
maxWidth: "98vw",
maxHeight: "95vh",
@@ -696,32 +699,24 @@ export const EditModal: React.FC = ({ className }) => {
const modalStyle = getModalStyle();
return (
-
-
+
-
+
- {modalState.title || "데이터 수정"}
+ {modalState.title || "데이터 수정"}
{modalState.description && !loading && (
- {modalState.description}
+ {modalState.description}
)}
{loading && (
- {loading ? "화면을 불러오는 중입니다..." : ""}
+ {loading ? "화면을 불러오는 중입니다..." : ""}
)}
-
+
-
+
{loading ? (
@@ -812,8 +807,8 @@ export const EditModal: React.FC = ({ className }) => {
)}
-
-
+
+
);
};
diff --git a/frontend/components/screen/FileAttachmentDetailModal.tsx b/frontend/components/screen/FileAttachmentDetailModal.tsx
index 77015589..835f8940 100644
--- a/frontend/components/screen/FileAttachmentDetailModal.tsx
+++ b/frontend/components/screen/FileAttachmentDetailModal.tsx
@@ -1,7 +1,7 @@
"use client";
import React, { useState, useCallback, useEffect } from "react";
-import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
@@ -352,9 +352,9 @@ export const FileAttachmentDetailModal: React.FC
-
+
파일 첨부 관리 - {component.label || component.id}
-
+
diff --git a/frontend/components/screen/InteractiveDataTable.tsx b/frontend/components/screen/InteractiveDataTable.tsx
index 88d11447..b681aa35 100644
--- a/frontend/components/screen/InteractiveDataTable.tsx
+++ b/frontend/components/screen/InteractiveDataTable.tsx
@@ -2471,7 +2471,7 @@ export const InteractiveDataTable: React.FC
= ({
{/* 기존 데이터 추가 모달 (제거 예정 - SaveModal로 대체됨) */}
{}}>
-
+
{component.addModalConfig?.title || "새 데이터 추가"}
@@ -2517,7 +2517,7 @@ export const InteractiveDataTable: React.FC = ({
{/* 기존 데이터 수정 모달 (제거 예정 - SaveModal로 대체됨) */}
{}}>
-
+
데이터 수정
선택된 데이터를 수정합니다.
@@ -2773,7 +2773,7 @@ export const InteractiveDataTable: React.FC = ({
{/* 파일 관리 모달 */}
-
+
diff --git a/frontend/components/screen/InteractiveScreenViewer.tsx b/frontend/components/screen/InteractiveScreenViewer.tsx
index 8e1f1ce3..d9186999 100644
--- a/frontend/components/screen/InteractiveScreenViewer.tsx
+++ b/frontend/components/screen/InteractiveScreenViewer.tsx
@@ -8,7 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
-import { Dialog, DialogContent, DialogHeader, DialogTitle, ResizableDialog, ResizableDialogContent, ResizableDialogHeader } from "@/components/ui/dialog";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { CalendarIcon, File, Upload, X } from "lucide-react";
import { format } from "date-fns";
import { ko } from "date-fns/locale";
diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx
index 3c9d16f5..41983df3 100644
--- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx
+++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx
@@ -3,8 +3,7 @@
import React, { useState, useCallback, useEffect } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
-import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader } from "@/components/ui/resizable-dialog";
-import { DialogTitle, DialogHeader } from "@/components/ui/dialog";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { useAuth } from "@/hooks/useAuth";
import { uploadFilesAndCreateData } from "@/lib/api/file";
import { toast } from "sonner";
@@ -776,17 +775,15 @@ export const InteractiveScreenViewerDynamic: React.FC setPopupScreen(null)}>
- setPopupScreen(null)}>
+
{popupScreen.title}
@@ -820,8 +817,8 @@ export const InteractiveScreenViewerDynamic: React.FC
)}
-
-
+
+
)}
>
);
diff --git a/frontend/components/screen/MenuAssignmentModal.tsx b/frontend/components/screen/MenuAssignmentModal.tsx
index 6fd586a8..fddf0bcc 100644
--- a/frontend/components/screen/MenuAssignmentModal.tsx
+++ b/frontend/components/screen/MenuAssignmentModal.tsx
@@ -2,13 +2,13 @@
import React, { useState, useEffect, useRef } from "react";
import {
- ResizableDialog,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogTitle,
- ResizableDialogDescription,
- ResizableDialogFooter,
-} from "@/components/ui/resizable-dialog";
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -345,26 +345,26 @@ export const MenuAssignmentModal: React.FC = ({
return (
<>
-
-
+
+
{assignmentSuccess ? (
// 성공 화면
<>
-
-
+
+
{assignmentMessage.includes("나중에") ? "화면 저장 완료" : "화면 할당 완료"}
-
-
+
+
{assignmentMessage.includes("나중에")
? "화면이 성공적으로 저장되었습니다. 나중에 메뉴에 할당할 수 있습니다."
: "화면이 성공적으로 메뉴에 할당되었습니다."}
-
-
+
+
@@ -386,7 +386,7 @@ export const MenuAssignmentModal: React.FC = ({
-
+
{
// 타이머 정리
@@ -407,19 +407,19 @@ export const MenuAssignmentModal: React.FC = ({
화면 목록으로 이동
-
+
>
) : (
// 기본 할당 화면
<>
-
-
+
+
메뉴에 화면 할당
-
-
+
+
저장된 화면을 메뉴에 할당하여 사용자가 접근할 수 있도록 설정합니다.
-
+
{screenInfo && (
@@ -432,7 +432,7 @@ export const MenuAssignmentModal: React.FC
= ({
{screenInfo.description && {screenInfo.description}
}
)}
-
+
{/* 메뉴 선택 (검색 기능 포함) */}
@@ -550,7 +550,7 @@ export const MenuAssignmentModal: React.FC = ({
)}
-
+
나중에 할당
@@ -572,22 +572,22 @@ export const MenuAssignmentModal: React.FC = ({
>
)}
-
+
>
)}
-
-
+
+
{/* 화면 교체 확인 대화상자 */}
-
-
-
-
+
+
+
+
화면 교체 확인
-
- 선택한 메뉴에 이미 할당된 화면이 있습니다.
-
+
+ 선택한 메뉴에 이미 할당된 화면이 있습니다.
+
{/* 기존 화면 목록 */}
@@ -628,7 +628,7 @@ export const MenuAssignmentModal: React.FC = ({
-
+
setShowReplaceDialog(false)} disabled={assigning}>
취소
@@ -652,9 +652,9 @@ export const MenuAssignmentModal: React.FC = ({
>
)}
-
-
-
+
+
+
>
);
};
diff --git a/frontend/components/screen/ResponsivePreviewModal.tsx b/frontend/components/screen/ResponsivePreviewModal.tsx
index 3b121e58..1e05a86b 100644
--- a/frontend/components/screen/ResponsivePreviewModal.tsx
+++ b/frontend/components/screen/ResponsivePreviewModal.tsx
@@ -1,7 +1,7 @@
"use client";
import React, { useState, createContext, useContext } from "react";
-import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Monitor, Tablet, Smartphone } from "lucide-react";
import { ComponentData } from "@/types/screen";
@@ -76,7 +76,7 @@ export const ResponsivePreviewModal: React.FC = ({
- 반응형 미리보기
+ 반응형 미리보기
{/* 디바이스 선택 버튼들 */}
diff --git a/frontend/components/screen/SaveModal.tsx b/frontend/components/screen/SaveModal.tsx
index bf8ee9ce..4e158719 100644
--- a/frontend/components/screen/SaveModal.tsx
+++ b/frontend/components/screen/SaveModal.tsx
@@ -1,7 +1,7 @@
"use client";
import React, { useState, useEffect } from "react";
-import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, ResizableDialogTitle } from "@/components/ui/resizable-dialog";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { X, Save, Loader2 } from "lucide-react";
import { toast } from "sonner";
@@ -232,22 +232,19 @@ export const SaveModal: React.FC
= ({
const dynamicSize = calculateDynamicSize();
return (
- !isSaving && !open && onClose()}>
- !isSaving && !open && onClose()}>
+
-
+
-
{initialData ? "데이터 수정" : "데이터 등록"}
+
{initialData ? "데이터 수정" : "데이터 등록"}
{isSaving ? (
@@ -267,7 +264,7 @@ export const SaveModal: React.FC = ({
-
+
{loading ? (
@@ -376,7 +373,7 @@ export const SaveModal: React.FC
= ({
화면에 컴포넌트가 없습니다.
)}
-
-
+
+
);
};
diff --git a/frontend/components/screen/templates/DataTableTemplate.tsx b/frontend/components/screen/templates/DataTableTemplate.tsx
index d83d2fc1..b24f27c3 100644
--- a/frontend/components/screen/templates/DataTableTemplate.tsx
+++ b/frontend/components/screen/templates/DataTableTemplate.tsx
@@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
-import { ResizableDialog, ResizableDialogContent, ResizableDialogDescription, DialogFooter, ResizableDialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { toast } from "sonner";
import { useAuth } from "@/hooks/useAuth";
diff --git a/frontend/components/ui/dialog.tsx b/frontend/components/ui/dialog.tsx
index 4256e329..5552ade5 100644
--- a/frontend/components/ui/dialog.tsx
+++ b/frontend/components/ui/dialog.tsx
@@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
) => (
-
+
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
-
+
);
DialogFooter.displayName = "DialogFooter";
diff --git a/frontend/components/ui/resizable-dialog.tsx b/frontend/components/ui/resizable-dialog.tsx
deleted file mode 100644
index 54d18ed7..00000000
--- a/frontend/components/ui/resizable-dialog.tsx
+++ /dev/null
@@ -1,601 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as DialogPrimitive from "@radix-ui/react-dialog";
-import { X } from "lucide-react";
-import { cn } from "@/lib/utils";
-
-// 🆕 Context를 사용하여 open 상태 공유
-const ResizableDialogContext = React.createContext<{ open: boolean }>({ open: false });
-
-// 🆕 ResizableDialog를 래핑하여 Context 제공
-const ResizableDialog: React.FC> = ({
- children,
- open = false,
- ...props
-}) => {
- return (
-
-
- {children}
-
-
- );
-};
-
-const ResizableDialogTrigger = DialogPrimitive.Trigger;
-
-const ResizableDialogPortal = DialogPrimitive.Portal;
-
-const ResizableDialogClose = DialogPrimitive.Close;
-
-const ResizableDialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-ResizableDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
-
-interface ResizableDialogContentProps
- extends React.ComponentPropsWithoutRef {
- minWidth?: number;
- minHeight?: number;
- maxWidth?: number;
- maxHeight?: number;
- defaultWidth?: number;
- defaultHeight?: number;
- modalId?: string; // localStorage 저장용 고유 ID
- userId?: string; // 사용자별 저장용
- open?: boolean; // 🆕 모달 열림/닫힘 상태 (외부에서 전달)
- disableFlexLayout?: boolean; // 🆕 flex 레이아웃 비활성화 (absolute 레이아웃용)
-}
-
-const ResizableDialogContent = React.forwardRef<
- React.ElementRef,
- ResizableDialogContentProps
->(
- (
- {
- className,
- children,
- minWidth = 400,
- minHeight = 300,
- maxWidth = 1600,
- maxHeight = 1200,
- defaultWidth = 600,
- defaultHeight = 500,
- modalId,
- userId = "guest",
- open: externalOpen, // 🆕 외부에서 전달받은 open 상태
- disableFlexLayout = false, // 🆕 flex 레이아웃 비활성화
- style: userStyle,
- ...props
- },
- ref
- ) => {
- const contentRef = React.useRef(null);
-
- // 고정된 ID 생성 (한번 생성되면 컴포넌트 생명주기 동안 유지)
- const stableIdRef = React.useRef(null);
-
- if (!stableIdRef.current) {
- if (modalId) {
- stableIdRef.current = modalId;
- // // console.log("✅ ResizableDialog - 명시적 modalId 사용:", modalId);
- } else {
- // className 기반 ID 생성
- if (className) {
- const hash = className.split('').reduce((acc, char) => {
- return ((acc << 5) - acc) + char.charCodeAt(0);
- }, 0);
- stableIdRef.current = `modal-${Math.abs(hash).toString(36)}`;
- // console.log("🔄 ResizableDialog - className 기반 ID 생성:", { className, generatedId: stableIdRef.current });
- } else if (userStyle) {
- // userStyle 기반 ID 생성
- const styleStr = JSON.stringify(userStyle);
- const hash = styleStr.split('').reduce((acc, char) => {
- return ((acc << 5) - acc) + char.charCodeAt(0);
- }, 0);
- stableIdRef.current = `modal-${Math.abs(hash).toString(36)}`;
- // console.log("🔄 ResizableDialog - userStyle 기반 ID 생성:", { userStyle, generatedId: stableIdRef.current });
- } else {
- // 기본 ID
- stableIdRef.current = 'modal-default';
- // console.log("⚠️ ResizableDialog - 기본 ID 사용 (모든 모달이 같은 크기 공유)");
- }
- }
- }
-
- const effectiveModalId = stableIdRef.current;
-
- // 실제 렌더링된 크기를 감지하여 초기 크기로 사용
- const getInitialSize = React.useCallback(() => {
- if (typeof window === 'undefined') return { width: defaultWidth, height: defaultHeight };
-
- // 1순위: userStyle에서 크기 추출 (화면관리에서 지정한 크기 - 항상 초기값으로 사용)
- if (userStyle) {
- const styleWidth = typeof userStyle.width === 'string'
- ? parseInt(userStyle.width)
- : userStyle.width;
- const styleHeight = typeof userStyle.height === 'string'
- ? parseInt(userStyle.height)
- : userStyle.height;
-
- if (styleWidth && styleHeight) {
- const finalSize = {
- width: Math.max(minWidth, Math.min(maxWidth, styleWidth)),
- height: Math.max(minHeight, Math.min(maxHeight, styleHeight)),
- };
- return finalSize;
- }
- }
-
- // 2순위: 현재 렌더링된 크기 사용 (주석처리 - 모달이 열린 후 늘어나는 현상 방지)
- // if (contentRef.current) {
- // const rect = contentRef.current.getBoundingClientRect();
- // if (rect.width > 0 && rect.height > 0) {
- // return {
- // width: Math.max(minWidth, Math.min(maxWidth, rect.width)),
- // height: Math.max(minHeight, Math.min(maxHeight, rect.height)),
- // };
- // }
- // }
-
- // 3순위: defaultWidth/defaultHeight 사용
- return { width: defaultWidth, height: defaultHeight };
- }, [defaultWidth, defaultHeight, minWidth, minHeight, maxWidth, maxHeight, userStyle]);
-
- const [size, setSize] = React.useState(getInitialSize);
- const [isResizing, setIsResizing] = React.useState(false);
- const [resizeDirection, setResizeDirection] = React.useState("");
- const [isInitialized, setIsInitialized] = React.useState(false);
-
- // userStyle이 변경되면 크기 업데이트 (화면 데이터 로딩 완료 시)
- React.useEffect(() => {
- // 1. localStorage에서 사용자가 리사이징한 크기 확인
- let savedSize: { width: number; height: number; userResized: boolean } | null = null;
-
- if (effectiveModalId && typeof window !== 'undefined') {
- try {
- const storageKey = `modal_size_${effectiveModalId}_${userId}`;
- const saved = localStorage.getItem(storageKey);
-
- if (saved) {
- const parsed = JSON.parse(saved);
- if (parsed.userResized) {
- savedSize = {
- width: Math.max(minWidth, Math.min(maxWidth, parsed.width)),
- height: Math.max(minHeight, Math.min(maxHeight, parsed.height)),
- userResized: true,
- };
- // console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
- }
- }
- } catch (error) {
- console.error("❌ 모달 크기 복원 실패:", error);
- }
- }
-
- // 2. 우선순위: 사용자 리사이징 > userStyle > 기본값
- if (savedSize && savedSize.userResized) {
- // 사용자가 리사이징한 크기 우선
- setSize({ width: savedSize.width, height: savedSize.height });
- setUserResized(true);
- } else if (userStyle && userStyle.width && userStyle.height) {
- // 화면관리에서 설정한 크기
- const styleWidth = typeof userStyle.width === 'string'
- ? parseInt(userStyle.width)
- : userStyle.width;
- const styleHeight = typeof userStyle.height === 'string'
- ? parseInt(userStyle.height)
- : userStyle.height;
-
- if (styleWidth && styleHeight) {
- const newSize = {
- width: Math.max(minWidth, Math.min(maxWidth, styleWidth)),
- height: Math.max(minHeight, Math.min(maxHeight, styleHeight)),
- };
- setSize(newSize);
- }
- }
- }, [userStyle, minWidth, maxWidth, minHeight, maxHeight, effectiveModalId, userId]);
- const [lastModalId, setLastModalId] = React.useState(null);
- const [userResized, setUserResized] = React.useState(false); // 사용자가 실제로 리사이징했는지 추적
-
- // 🆕 Context에서 open 상태 가져오기 (우선순위: externalOpen > context.open)
- const context = React.useContext(ResizableDialogContext);
- const actualOpen = externalOpen !== undefined ? externalOpen : context.open;
-
- // 🆕 모달이 닫혔다가 다시 열릴 때 초기화 리셋
- const [wasOpen, setWasOpen] = React.useState(false);
-
- React.useEffect(() => {
- // console.log("🔍 모달 상태 변화 감지:", { actualOpen, wasOpen, externalOpen, contextOpen: context.open, effectiveModalId });
-
- if (actualOpen && !wasOpen) {
- // 모달이 방금 열림
- // console.log("🔓 모달 열림 감지, 초기화 리셋:", { effectiveModalId });
- setIsInitialized(false);
- setWasOpen(true);
- } else if (!actualOpen && wasOpen) {
- // 모달이 방금 닫힘
- // console.log("🔒 모달 닫힘 감지:", { effectiveModalId });
- setWasOpen(false);
- }
- }, [actualOpen, wasOpen, effectiveModalId, externalOpen, context.open]);
-
- // modalId가 변경되면 초기화 리셋 (다른 모달이 열린 경우)
- React.useEffect(() => {
- if (effectiveModalId !== lastModalId) {
- // console.log("🔄 모달 ID 변경 감지, 초기화 리셋:", { 이전: lastModalId, 현재: effectiveModalId, isInitialized });
- setIsInitialized(false);
- setUserResized(false); // 사용자 리사이징 플래그도 리셋
- setLastModalId(effectiveModalId);
- }
- }, [effectiveModalId, lastModalId, isInitialized]);
-
- // 모달이 열릴 때 초기 크기 설정 (localStorage와 내용 크기 중 큰 값 사용)
- // 주석처리 - 사용자가 설정한 크기(userStyle)만 사용하도록 변경
- // React.useEffect(() => {
- // // console.log("🔍 초기 크기 설정 useEffect 실행:", { isInitialized, hasContentRef: !!contentRef.current, effectiveModalId });
- //
- // if (!isInitialized) {
- // // 내용의 실제 크기 측정 (약간의 지연 후, contentRef가 준비될 때까지 대기)
- // // 여러 번 시도하여 contentRef가 준비될 때까지 대기
- // let attempts = 0;
- // const maxAttempts = 10;
- //
- // const measureContent = () => {
- // attempts++;
- //
- // // scrollHeight/scrollWidth를 사용하여 실제 내용 크기 측정 (스크롤 포함)
- // let contentWidth = defaultWidth;
- // let contentHeight = defaultHeight;
- //
- // // if (contentRef.current) {
- // // // scrollHeight/scrollWidth 그대로 사용 (여유 공간 제거)
- // // contentWidth = contentRef.current.scrollWidth || defaultWidth;
- // // contentHeight = contentRef.current.scrollHeight || defaultHeight;
- // //
- // // // console.log("📏 모달 내용 크기 측정:", { attempt: attempts, scrollWidth: contentRef.current.scrollWidth, scrollHeight: contentRef.current.scrollHeight, clientWidth: contentRef.current.clientWidth, clientHeight: contentRef.current.clientHeight, contentWidth, contentHeight });
- // // } else {
- // // // console.log("⚠️ contentRef 없음, 재시도:", { attempt: attempts, maxAttempts, defaultWidth, defaultHeight });
- // //
- // // // contentRef가 아직 없으면 재시도
- // // if (attempts < maxAttempts) {
- // // setTimeout(measureContent, 100);
- // // return;
- // // }
- // // }
- //
- // // 패딩 추가 (p-6 * 2 = 48px)
- // const paddingAndMargin = 48;
- // const initialSize = getInitialSize();
- //
- // // 내용 크기 기반 최소 크기 계산
- // const contentBasedSize = {
- // width: Math.max(minWidth, Math.min(maxWidth, Math.max(contentWidth + paddingAndMargin, initialSize.width))),
- // height: Math.max(minHeight, Math.min(maxHeight, Math.max(contentHeight + paddingAndMargin, initialSize.height))),
- // };
- //
- // // console.log("📐 내용 기반 크기:", contentBasedSize);
- //
- // // localStorage에서 저장된 크기 확인
- // let finalSize = contentBasedSize;
- //
- // if (effectiveModalId && typeof window !== 'undefined') {
- // try {
- // const storageKey = `modal_size_${effectiveModalId}_${userId}`;
- // const saved = localStorage.getItem(storageKey);
- //
- // // console.log("📦 localStorage 확인:", { effectiveModalId, userId, storageKey, saved: saved ? "있음" : "없음" });
- //
- // if (saved) {
- // const parsed = JSON.parse(saved);
- //
- // // userResized 플래그 확인
- // if (parsed.userResized) {
- // const savedSize = {
- // width: Math.max(minWidth, Math.min(maxWidth, parsed.width)),
- // height: Math.max(minHeight, Math.min(maxHeight, parsed.height)),
- // };
- //
- // // console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
- //
- // // ✅ 중요: 사용자가 명시적으로 리사이징한 경우, 사용자 크기를 우선 사용
- // // (사용자가 의도적으로 작게 만든 것을 존중)
- // finalSize = savedSize;
- // setUserResized(true);
- //
- // // console.log("✅ 최종 크기 (사용자가 설정한 크기 우선 적용):", { savedSize, contentBasedSize, finalSize, note: "사용자가 리사이징한 크기를 그대로 사용합니다" });
- // } else {
- // // console.log("ℹ️ 자동 계산된 크기는 무시, 내용 크기 사용");
- // }
- // } else {
- // // console.log("ℹ️ localStorage에 저장된 크기 없음, 내용 크기 사용");
- // }
- // } catch (error) {
- // // console.error("❌ 모달 크기 복원 실패:", error);
- // }
- // }
- //
- // setSize(finalSize);
- // setIsInitialized(true);
- // };
- //
- // // 첫 시도는 300ms 후에 시작
- // setTimeout(measureContent, 300);
- // }
- // }, [isInitialized, getInitialSize, effectiveModalId, userId, minWidth, maxWidth, minHeight, maxHeight, defaultWidth, defaultHeight]);
-
- const startResize = (direction: string) => (e: React.MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
- setIsResizing(true);
- setResizeDirection(direction);
-
- const startX = e.clientX;
- const startY = e.clientY;
- const startWidth = size.width;
- const startHeight = size.height;
-
- const handleMouseMove = (moveEvent: MouseEvent) => {
- const deltaX = moveEvent.clientX - startX;
- const deltaY = moveEvent.clientY - startY;
-
- let newWidth = startWidth;
- let newHeight = startHeight;
-
- if (direction.includes("e")) {
- newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
- }
- if (direction.includes("w")) {
- newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth - deltaX));
- }
- if (direction.includes("s")) {
- newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
- }
- if (direction.includes("n")) {
- newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight - deltaY));
- }
-
- setSize({ width: newWidth, height: newHeight });
- };
-
- const handleMouseUp = () => {
- setIsResizing(false);
- setResizeDirection("");
- document.removeEventListener("mousemove", handleMouseMove);
- document.removeEventListener("mouseup", handleMouseUp);
-
- // 사용자가 리사이징했음을 표시
- setUserResized(true);
-
- // ✅ 중요: 현재 실제 DOM 크기를 저장 (state가 아닌 실제 크기)
- if (effectiveModalId && typeof window !== 'undefined' && contentRef.current) {
- try {
- const storageKey = `modal_size_${effectiveModalId}_${userId}`;
-
- // contentRef의 부모 요소(모달 컨테이너)의 실제 크기 사용
- const modalElement = contentRef.current.parentElement;
- const actualWidth = modalElement?.offsetWidth || size.width;
- const actualHeight = modalElement?.offsetHeight || size.height;
-
- const currentSize = {
- width: actualWidth,
- height: actualHeight,
- userResized: true, // 사용자가 직접 리사이징했음을 표시
- };
- localStorage.setItem(storageKey, JSON.stringify(currentSize));
- // console.log("💾 localStorage에 크기 저장 (사용자 리사이징):", { effectiveModalId, userId, storageKey, size: currentSize, stateSize: { width: size.width, height: size.height } });
- } catch (error) {
- // console.error("❌ 모달 크기 저장 실패:", error);
- }
- }
- };
-
- document.addEventListener("mousemove", handleMouseMove);
- document.addEventListener("mouseup", handleMouseUp);
- };
-
- return (
-
-
-
-
- {children}
-
-
- {/* 리사이즈 핸들 */}
- {/* 오른쪽 */}
-
- {/* 아래 */}
-
- {/* 오른쪽 아래 */}
-
- {/* 왼쪽 */}
-
- {/* 위 */}
-
- {/* 왼쪽 아래 */}
-
- {/* 오른쪽 위 */}
-
- {/* 왼쪽 위 */}
-
-
- {/* 리셋 버튼 (사용자가 리사이징한 경우만 표시) */}
- {userResized && (
- {
- // localStorage에서 저장된 크기 삭제
- if (effectiveModalId && typeof window !== 'undefined') {
- const storageKey = `modal_size_${effectiveModalId}_${userId}`;
- localStorage.removeItem(storageKey);
- console.log("🗑️ 저장된 모달 크기 삭제:", storageKey);
- }
-
- // 화면관리 설정 크기로 복원
- const initialSize = getInitialSize();
- setSize(initialSize);
- setUserResized(false);
- console.log("🔄 기본 크기로 리셋:", initialSize);
- }}
- className="absolute right-12 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
- style={{ zIndex: 20 }}
- title="기본 크기로 리셋"
- >
-
-
-
-
-
-
- 기본 크기로 리셋
-
- )}
-
-
-
- Close
-
-
-
- );
- }
-);
-ResizableDialogContent.displayName = DialogPrimitive.Content.displayName;
-
-const ResizableDialogHeader = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-);
-ResizableDialogHeader.displayName = "ResizableDialogHeader";
-
-const ResizableDialogFooter = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-);
-ResizableDialogFooter.displayName = "ResizableDialogFooter";
-
-const ResizableDialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-ResizableDialogTitle.displayName = DialogPrimitive.Title.displayName;
-
-const ResizableDialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-ResizableDialogDescription.displayName =
- DialogPrimitive.Description.displayName;
-
-export {
- ResizableDialog,
- ResizableDialogPortal,
- ResizableDialogOverlay,
- ResizableDialogClose,
- ResizableDialogTrigger,
- ResizableDialogContent,
- ResizableDialogHeader,
- ResizableDialogFooter,
- ResizableDialogTitle,
- ResizableDialogDescription,
-};
-
diff --git a/frontend/components/webtypes/RepeaterInput.tsx b/frontend/components/webtypes/RepeaterInput.tsx
index ade700e1..ce9d4cf6 100644
--- a/frontend/components/webtypes/RepeaterInput.tsx
+++ b/frontend/components/webtypes/RepeaterInput.tsx
@@ -428,6 +428,31 @@ export const RepeaterInput: React.FC = ({
return {option?.label || value} ;
}
+ // 🆕 카테고리 매핑이 있는 경우 라벨로 변환 (조인된 테이블의 카테고리 필드)
+ const mapping = categoryMappings[field.name];
+ if (mapping && value) {
+ const valueStr = String(value);
+ const categoryData = mapping[valueStr];
+ if (categoryData) {
+ // 색상이 있으면 배지로 표시
+ if (categoryData.color && categoryData.color !== "none" && categoryData.color !== "#64748b") {
+ return (
+
+ {categoryData.label}
+
+ );
+ }
+ // 색상이 없으면 텍스트로 표시
+ return {categoryData.label} ;
+ }
+ }
+
// 일반 텍스트
return (
@@ -556,44 +581,40 @@ export const RepeaterInput: React.FC = ({
}
};
- // 카테고리 매핑 로드 (카테고리 필드가 있을 때 자동 로드)
+ // 카테고리 매핑 로드 (카테고리 필드 + readonly 필드에 대해 자동 로드)
// 테이블 리스트와 동일한 API 사용: /table-categories/{tableName}/{columnName}/values
useEffect(() => {
+ // 카테고리 타입 필드 + readonly 필드 (조인된 테이블에서 온 데이터일 가능성)
const categoryFields = fields.filter(f => f.type === "category");
- if (categoryFields.length === 0) return;
+ const readonlyFields = fields.filter(f => f.displayMode === "readonly" && f.type === "text");
+
+ if (categoryFields.length === 0 && readonlyFields.length === 0) return;
const loadCategoryMappings = async () => {
const apiClient = (await import("@/lib/api/client")).apiClient;
+ // 1. 카테고리 타입 필드 매핑 로드
for (const field of categoryFields) {
- const columnName = field.name; // 실제 컬럼명
- const categoryCode = field.categoryCode || columnName;
+ const columnName = field.name;
- // 이미 로드된 경우 스킵
if (categoryMappings[columnName]) continue;
try {
- // config에서 targetTable 가져오기, 없으면 스킵
const tableName = config.targetTable;
- if (!tableName) {
- console.warn(`[RepeaterInput] targetTable이 설정되지 않아 카테고리 매핑을 로드할 수 없습니다.`);
- continue;
- }
+ if (!tableName) continue;
console.log(`📡 [RepeaterInput] 카테고리 매핑 로드: ${tableName}/${columnName}`);
- // 테이블 리스트와 동일한 API 사용
const response = await apiClient.get(`/table-categories/${tableName}/${columnName}/values`);
if (response.data.success && response.data.data && Array.isArray(response.data.data)) {
const mapping: Record = {};
response.data.data.forEach((item: any) => {
- // valueCode를 문자열로 변환하여 키로 사용 (테이블 리스트와 동일)
const key = String(item.valueCode);
mapping[key] = {
label: item.valueLabel || key,
- color: item.color || "#64748b", // color 필드 사용 (DB 컬럼명과 동일)
+ color: item.color || "#64748b",
};
});
@@ -608,6 +629,50 @@ export const RepeaterInput: React.FC = ({
console.error(`❌ [RepeaterInput] 카테고리 매핑 로드 실패 (${columnName}):`, error);
}
}
+
+ // 2. 🆕 readonly 필드에 대해 조인된 테이블 (item_info)에서 카테고리 매핑 로드
+ // material, division 등 조인된 테이블의 카테고리 필드
+ const joinedTableFields = ['material', 'division', 'status', 'currency_code'];
+ const fieldsToLoadFromJoinedTable = readonlyFields.filter(f => joinedTableFields.includes(f.name));
+
+ if (fieldsToLoadFromJoinedTable.length > 0) {
+ // item_info 테이블에서 카테고리 매핑 로드
+ const joinedTableName = 'item_info';
+
+ for (const field of fieldsToLoadFromJoinedTable) {
+ const columnName = field.name;
+
+ if (categoryMappings[columnName]) continue;
+
+ try {
+ console.log(`📡 [RepeaterInput] 조인 테이블 카테고리 매핑 로드: ${joinedTableName}/${columnName}`);
+
+ const response = await apiClient.get(`/table-categories/${joinedTableName}/${columnName}/values`);
+
+ if (response.data.success && response.data.data && Array.isArray(response.data.data)) {
+ const mapping: Record = {};
+
+ response.data.data.forEach((item: any) => {
+ const key = String(item.valueCode);
+ mapping[key] = {
+ label: item.valueLabel || key,
+ color: item.color || "#64748b",
+ };
+ });
+
+ console.log(`✅ [RepeaterInput] 조인 테이블 카테고리 매핑 로드 완료 [${columnName}]:`, mapping);
+
+ setCategoryMappings(prev => ({
+ ...prev,
+ [columnName]: mapping,
+ }));
+ }
+ } catch (error) {
+ // 카테고리가 없는 필드는 무시
+ console.log(`ℹ️ [RepeaterInput] 조인 테이블 카테고리 없음 (${columnName})`);
+ }
+ }
+ }
};
loadCategoryMappings();
diff --git a/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx b/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx
index f8bf39c7..55f6ea25 100644
--- a/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx
+++ b/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx
@@ -968,7 +968,7 @@ export const CardDisplayComponent: React.FC = ({
{/* 상세보기 모달 */}
-
+
📋
@@ -1041,7 +1041,7 @@ export const CardDisplayComponent: React.FC = ({
{/* 편집 모달 */}
-
+
✏️
diff --git a/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx b/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx
index 00daed0a..7f841ec3 100644
--- a/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx
+++ b/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx
@@ -79,7 +79,7 @@ export function EntitySearchModal({
return (
-
+
{modalTitle}
diff --git a/frontend/lib/registry/components/file-upload/FileViewerModal.tsx b/frontend/lib/registry/components/file-upload/FileViewerModal.tsx
index 8ccb93ff..9eb0edeb 100644
--- a/frontend/lib/registry/components/file-upload/FileViewerModal.tsx
+++ b/frontend/lib/registry/components/file-upload/FileViewerModal.tsx
@@ -491,7 +491,7 @@ export const FileViewerModal: React.FC = ({ file, isOpen,
return (
{}}>
-
+
@@ -506,7 +506,7 @@ export const FileViewerModal: React.FC
= ({ file, isOpen,
- {renderPreview()}
+ {renderPreview()}
{/* 파일 정보 및 액션 버튼 */}
diff --git a/frontend/lib/registry/components/modal-repeater-table/ItemSelectionModal.tsx b/frontend/lib/registry/components/modal-repeater-table/ItemSelectionModal.tsx
index 60da98f8..456594c2 100644
--- a/frontend/lib/registry/components/modal-repeater-table/ItemSelectionModal.tsx
+++ b/frontend/lib/registry/components/modal-repeater-table/ItemSelectionModal.tsx
@@ -166,7 +166,7 @@ export function ItemSelectionModal({
return (
-
+
{modalTitle}
@@ -222,8 +222,8 @@ export function ItemSelectionModal({
)}
{/* 검색 결과 테이블 */}
-
-
+
+
diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx
index 4f78ed23..22c26e45 100644
--- a/frontend/lib/registry/components/table-list/TableListComponent.tsx
+++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx
@@ -969,15 +969,30 @@ export const TableListComponent: React.FC = ({
try {
const mappings: Record> = {};
+ const apiClient = (await import("@/lib/api/client")).apiClient;
for (const columnName of categoryColumns) {
try {
+ // 🆕 엔티티 조인 컬럼 처리: "테이블명.컬럼명" 형태인지 확인
+ let targetTable = tableConfig.selectedTable;
+ let targetColumn = columnName;
+
+ if (columnName.includes(".")) {
+ const parts = columnName.split(".");
+ targetTable = parts[0]; // 조인된 테이블명 (예: item_info)
+ targetColumn = parts[1]; // 실제 컬럼명 (예: material)
+ console.log(`🔗 [TableList] 엔티티 조인 컬럼 감지:`, {
+ originalColumn: columnName,
+ targetTable,
+ targetColumn,
+ });
+ }
+
console.log(`📡 [TableList] API 호출 시작 [${columnName}]:`, {
- url: `/table-categories/${tableConfig.selectedTable}/${columnName}/values`,
+ url: `/table-categories/${targetTable}/${targetColumn}/values`,
});
- const apiClient = (await import("@/lib/api/client")).apiClient;
- const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values`);
+ const response = await apiClient.get(`/table-categories/${targetTable}/${targetColumn}/values`);
console.log(`📡 [TableList] API 응답 [${columnName}]:`, {
success: response.data.success,
@@ -1000,6 +1015,7 @@ export const TableListComponent: React.FC = ({
});
if (Object.keys(mapping).length > 0) {
+ // 🆕 원래 컬럼명(item_info.material)으로 매핑 저장
mappings[columnName] = mapping;
console.log(`✅ [TableList] 카테고리 매핑 로드 완료 [${columnName}]:`, {
columnName,
@@ -1028,6 +1044,59 @@ export const TableListComponent: React.FC = ({
}
}
+ // 🆕 엔티티 조인 컬럼 중 카테고리 타입이 아니지만 조인된 테이블의 카테고리 필드인 경우도 로드
+ // 화면 설정의 columns에서 "테이블명.컬럼명" 형태의 조인 컬럼 추출
+ const joinedColumns = tableConfig.columns
+ ?.filter((col) => col.columnName?.includes("."))
+ .map((col) => col.columnName) || [];
+
+ // 알려진 카테고리 필드 목록 (조인된 테이블에서 자주 사용되는 카테고리 컬럼)
+ const knownCategoryFields = ["material", "division", "status", "currency_code", "inbound_type", "outbound_type"];
+
+ for (const joinedColumn of joinedColumns) {
+ // 이미 로드된 컬럼은 스킵
+ if (mappings[joinedColumn]) continue;
+
+ const parts = joinedColumn.split(".");
+ if (parts.length !== 2) continue;
+
+ const joinedTable = parts[0];
+ const joinedColumnName = parts[1];
+
+ // 알려진 카테고리 필드인 경우만 로드 시도
+ if (!knownCategoryFields.includes(joinedColumnName)) continue;
+
+ try {
+ console.log(`📡 [TableList] 조인 테이블 카테고리 로드 시도 [${joinedColumn}]:`, {
+ url: `/table-categories/${joinedTable}/${joinedColumnName}/values`,
+ });
+
+ const response = await apiClient.get(`/table-categories/${joinedTable}/${joinedColumnName}/values`);
+
+ if (response.data.success && response.data.data && Array.isArray(response.data.data)) {
+ const mapping: Record = {};
+
+ response.data.data.forEach((item: any) => {
+ const key = String(item.valueCode);
+ mapping[key] = {
+ label: item.valueLabel,
+ color: item.color,
+ };
+ });
+
+ if (Object.keys(mapping).length > 0) {
+ mappings[joinedColumn] = mapping;
+ console.log(`✅ [TableList] 조인 테이블 카테고리 매핑 로드 완료 [${joinedColumn}]:`, {
+ mappingCount: Object.keys(mapping).length,
+ });
+ }
+ }
+ } catch (error) {
+ // 조인 테이블 카테고리 로드 실패는 무시 (카테고리가 아닌 필드일 수 있음)
+ console.log(`ℹ️ [TableList] 조인 테이블 카테고리 없음 (${joinedColumn})`);
+ }
+ }
+
console.log("📊 [TableList] 전체 카테고리 매핑 설정:", {
mappingsCount: Object.keys(mappings).length,
mappingsKeys: Object.keys(mappings),
@@ -1047,7 +1116,7 @@ export const TableListComponent: React.FC = ({
};
loadCategoryMappings();
- }, [tableConfig.selectedTable, categoryColumns.length, JSON.stringify(categoryColumns)]); // 더 명확한 의존성
+ }, [tableConfig.selectedTable, categoryColumns.length, JSON.stringify(categoryColumns), JSON.stringify(tableConfig.columns)]); // 더 명확한 의존성
// ========================================
// 데이터 가져오기
@@ -1885,7 +1954,18 @@ export const TableListComponent: React.FC = ({
if (inputType === "category") {
if (!value) return "";
- const mapping = categoryMappings[column.columnName];
+ // 🆕 엔티티 조인 컬럼의 경우 여러 형태로 매핑 찾기
+ // 1. 원래 컬럼명 (item_info.material)
+ // 2. 점(.) 뒤의 컬럼명만 (material)
+ let mapping = categoryMappings[column.columnName];
+
+ if (!mapping && column.columnName.includes(".")) {
+ const simpleColumnName = column.columnName.split(".").pop();
+ if (simpleColumnName) {
+ mapping = categoryMappings[simpleColumnName];
+ }
+ }
+
const { Badge } = require("@/components/ui/badge");
// 다중 값 처리: 콤마로 구분된 값들을 분리
diff --git a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx
index 209b3d2d..17ab3417 100644
--- a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx
+++ b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx
@@ -265,7 +265,7 @@ export const TableListConfigPanel: React.FC = ({
columnName: col.columnName || col.column_name,
dataType: col.dataType || col.data_type || "text",
label: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
- }))
+ })),
);
console.log("✅ 참조 테이블 컬럼 로드 완료:", columns.length, "개");
}
@@ -511,7 +511,7 @@ export const TableListConfigPanel: React.FC = ({
// 🎯 엔티티 컬럼의 표시 컬럼 정보 로드
const loadEntityDisplayConfig = async (column: ColumnConfig) => {
const configKey = `${column.columnName}`;
-
+
// 이미 로드된 경우 스킵
if (entityDisplayConfigs[configKey]) return;
@@ -609,7 +609,7 @@ export const TableListConfigPanel: React.FC = ({
// 기본 테이블 컬럼 정보는 항상 로드
const sourceResult = await entityJoinApi.getReferenceTableColumns(sourceTable);
const sourceColumns = sourceResult.columns || [];
-
+
// joinTable이 있으면 조인 테이블 컬럼도 로드
let joinColumns: Array<{ columnName: string; displayName: string; dataType: string }> = [];
if (joinTable) {
@@ -761,9 +761,7 @@ export const TableListConfigPanel: React.FC = ({
placeholder="테이블 제목 입력..."
className="h-8 text-xs"
/>
-
- 우선순위: 사용자 입력 제목 → 테이블 라벨명 → 테이블명
-
+ 우선순위: 사용자 입력 제목 → 테이블 라벨명 → 테이블명
@@ -782,7 +780,7 @@ export const TableListConfigPanel: React.FC = ({
/>
체크박스 표시
-
+
{config.checkbox?.enabled && (
<>
@@ -793,7 +791,7 @@ export const TableListConfigPanel: React.FC
= ({
/>
전체 선택 체크박스 표시
-
+
체크박스 위치
@@ -802,7 +800,7 @@ export const TableListConfigPanel: React.FC = ({
id="checkboxPosition"
value={config.checkbox?.position || "left"}
onChange={(e) => handleNestedChange("checkbox", "position", e.target.value)}
- className="w-full h-8 text-xs border rounded-md px-2"
+ className="h-8 w-full rounded-md border px-2 text-xs"
>
왼쪽
오른쪽
@@ -913,7 +911,9 @@ export const TableListConfigPanel: React.FC = ({
컬럼을 찾을 수 없습니다.
{entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
-
+
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
= ({
{/* 참조 테이블 미설정 안내 */}
- {!column.entityDisplayConfig?.joinTable && entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
-
- 현재 기본 테이블 컬럼만 표시됩니다. 테이블 타입 관리에서 참조 테이블을 설정하면 조인된 테이블의 컬럼도 선택할 수 있습니다.
-
- )}
+ {!column.entityDisplayConfig?.joinTable &&
+ entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
+
+ 현재 기본 테이블 컬럼만 표시됩니다. 테이블 타입 관리에서 참조 테이블을 설정하면 조인된
+ 테이블의 컬럼도 선택할 수 있습니다.
+
+ )}
{/* 선택된 컬럼 미리보기 */}
{entityDisplayConfigs[column.columnName].selectedColumns.length > 0 && (
@@ -1107,7 +1109,7 @@ export const TableListConfigPanel: React.FC = ({
// 해당 컬럼의 input_type 확인
const columnInfo = availableColumns.find((col) => col.columnName === column.columnName);
const isNumberType = columnInfo?.input_type === "number" || columnInfo?.input_type === "decimal";
-
+
return (
= ({
{columnInfo?.label || column.displayName || column.columnName}
-
+
{/* 숫자 타입인 경우 천단위 구분자 설정 */}
{isNumberType && (
@@ -1131,9 +1133,9 @@ export const TableListConfigPanel: React.FC
= ({
}}
className="h-3 w-3"
/>
-
천단위 구분자
@@ -1147,8 +1149,7 @@ export const TableListConfigPanel: React.FC = ({
checked={config.filter?.filters?.some((f) => f.columnName === column.columnName) || false}
onCheckedChange={(checked) => {
const currentFilters = config.filter?.filters || [];
- const columnLabel =
- columnInfo?.label || column.displayName || column.columnName;
+ const columnLabel = columnInfo?.label || column.displayName || column.columnName;
if (checked) {
// 필터 추가
@@ -1240,9 +1241,7 @@ export const TableListConfigPanel: React.FC = ({
placeholder="40"
className="h-8 text-xs"
/>
-
- 기본값: 40px (0-200px 범위, 10px 단위 권장)
-
+ 기본값: 40px (0-200px 범위, 10px 단위 권장)
)}
@@ -1251,19 +1250,20 @@ export const TableListConfigPanel: React.FC = ({
데이터 필터링
-
- 특정 컬럼 값으로 데이터를 필터링합니다
-
+
특정 컬럼 값으로 데이터를 필터링합니다
({
- columnName: col.columnName,
- columnLabel: col.label || col.columnName,
- dataType: col.dataType,
- input_type: col.input_type, // 🆕 실제 input_type 전달
- } as any))}
+ columns={availableColumns.map(
+ (col) =>
+ ({
+ columnName: col.columnName,
+ columnLabel: col.label || col.columnName,
+ dataType: col.dataType,
+ input_type: col.input_type, // 🆕 실제 input_type 전달
+ }) as any,
+ )}
config={config.dataFilter}
onConfigChange={(dataFilter) => handleChange("dataFilter", dataFilter)}
/>
@@ -1273,12 +1273,12 @@ export const TableListConfigPanel: React.FC = ({
연결된 필터
-
+
셀렉트박스 등 다른 컴포넌트의 값으로 테이블 데이터를 실시간 필터링합니다
-
+
{/* 연결된 필터 목록 */}
{(config.linkedFilters || []).map((filter, index) => (
@@ -1293,16 +1293,12 @@ export const TableListConfigPanel: React.FC
= ({
newFilters[index] = { ...filter, sourceComponentId: e.target.value };
handleChange("linkedFilters", newFilters);
}}
- className="h-7 text-xs flex-1"
+ className="h-7 flex-1 text-xs"
/>
- →
+ →
-
+
{filter.targetColumn || "필터링할 컬럼 선택"}
@@ -1311,7 +1307,7 @@ export const TableListConfigPanel: React.FC = ({
- 컬럼을 찾을 수 없습니다
+ 컬럼을 찾을 수 없습니다
{availableColumns.map((col) => (
= ({
{col.label || col.columnName}
@@ -1353,7 +1349,7 @@ export const TableListConfigPanel: React.FC = ({
))}
-
+
{/* 연결된 필터 추가 버튼 */}
= ({
onClick={() => {
const newFilters = [
...(config.linkedFilters || []),
- { sourceComponentId: "", targetColumn: "", operator: "equals" as const, enabled: true }
+ { sourceComponentId: "", targetColumn: "", operator: "equals" as const, enabled: true },
];
handleChange("linkedFilters", newFilters);
}}
@@ -1370,8 +1366,8 @@ export const TableListConfigPanel: React.FC = ({
연결된 필터 추가
-
-
+
+
예: 셀렉트박스(ID: select-basic-123)의 값으로 테이블의 inbound_type 컬럼을 필터링
@@ -1381,12 +1377,12 @@ export const TableListConfigPanel: React.FC = ({
제외 필터
-
+
다른 테이블에 이미 존재하는 데이터를 목록에서 제외합니다
-
+
{/* 제외 필터 활성화 */}
= ({
제외 필터 활성화
-
+
{config.excludeFilter?.enabled && (
{/* 참조 테이블 선택 */}
@@ -1411,11 +1407,7 @@ export const TableListConfigPanel: React.FC
= ({
참조 테이블 (매핑 테이블)
-
+
{config.excludeFilter?.referenceTable || "테이블 선택..."}
@@ -1424,7 +1416,7 @@ export const TableListConfigPanel: React.FC = ({
- 테이블을 찾을 수 없습니다
+ 테이블을 찾을 수 없습니다
{availableTables.map((table) => (
= ({
{table.displayName || table.tableName}
@@ -1457,7 +1451,7 @@ export const TableListConfigPanel: React.FC = ({
-
+
{config.excludeFilter?.referenceTable && (
<>
{/* 비교 컬럼 설정 - 한 줄에 두 개 */}
@@ -1473,9 +1467,7 @@ export const TableListConfigPanel: React.FC
= ({
disabled={loadingReferenceColumns}
className="h-8 w-full justify-between text-xs"
>
- {loadingReferenceColumns
- ? "..."
- : config.excludeFilter?.referenceColumn || "선택"}
+ {loadingReferenceColumns ? "..." : config.excludeFilter?.referenceColumn || "선택"}
@@ -1483,7 +1475,7 @@ export const TableListConfigPanel: React.FC = ({
- 없음
+ 없음
{referenceTableColumns.map((col) => (
= ({
{col.label || col.columnName}
@@ -1512,17 +1506,13 @@ export const TableListConfigPanel: React.FC = ({
-
+
{/* 소스 컬럼 (현재 테이블) */}
비교 컬럼 (현재)
-
+
{config.excludeFilter?.sourceColumn || "선택"}
@@ -1531,7 +1521,7 @@ export const TableListConfigPanel: React.FC = ({
- 없음
+ 없음
{availableColumns.map((col) => (
= ({
{col.label || col.columnName}
@@ -1561,11 +1553,11 @@ export const TableListConfigPanel: React.FC = ({
-
+
{/* 조건 필터 - 특정 조건의 데이터만 제외 */}
조건 필터 (선택사항)
-
+
특정 조건의 데이터만 제외하려면 설정하세요 (예: 특정 거래처의 품목만)
>
)}
-
+
{/* 설정 요약 */}
- {config.excludeFilter?.referenceTable && config.excludeFilter?.referenceColumn && config.excludeFilter?.sourceColumn && (
-
- 설정 요약: {config.selectedTable || screenTableName}.{config.excludeFilter.sourceColumn} 가
- {" "}{config.excludeFilter.referenceTable}.{config.excludeFilter.referenceColumn} 에
- {config.excludeFilter.filterColumn && config.excludeFilter.filterValueField && (
- <> ({config.excludeFilter.filterColumn}=URL의 {config.excludeFilter.filterValueField}일 때)>
- )}
- {" "}이미 있으면 제외
-
- )}
+ {config.excludeFilter?.referenceTable &&
+ config.excludeFilter?.referenceColumn &&
+ config.excludeFilter?.sourceColumn && (
+
+ 설정 요약: {config.selectedTable || screenTableName}.
+ {config.excludeFilter.sourceColumn} 가 {config.excludeFilter.referenceTable}.
+ {config.excludeFilter.referenceColumn} 에
+ {config.excludeFilter.filterColumn && config.excludeFilter.filterValueField && (
+ <>
+ {" "}
+ ({config.excludeFilter.filterColumn}=URL의 {config.excludeFilter.filterValueField}일 때)
+ >
+ )}{" "}
+ 이미 있으면 제외
+
+ )}
)}
diff --git a/화면_임베딩_및_데이터_전달_시스템_구현_계획서.md b/화면_임베딩_및_데이터_전달_시스템_구현_계획서.md
index 74d9d0ed..687896c1 100644
--- a/화면_임베딩_및_데이터_전달_시스템_구현_계획서.md
+++ b/화면_임베딩_및_데이터_전달_시스템_구현_계획서.md
@@ -1680,3 +1680,4 @@ const 출고등록_설정: ScreenSplitPanel = {
화면 임베딩 및 데이터 전달 시스템은 복잡한 업무 워크플로우를 효율적으로 처리할 수 있는 강력한 기능입니다. 단계별로 체계적으로 구현하면 약 3.5개월 내에 완성할 수 있으며, 이를 통해 사용자 경험을 크게 향상시킬 수 있습니다.
+
diff --git a/화면_임베딩_시스템_Phase1-4_구현_완료.md b/화면_임베딩_시스템_Phase1-4_구현_완료.md
index 47526bb1..bc025b41 100644
--- a/화면_임베딩_시스템_Phase1-4_구현_완료.md
+++ b/화면_임베딩_시스템_Phase1-4_구현_완료.md
@@ -527,3 +527,4 @@ const { data: config } = await getScreenSplitPanel(screenId);
이제 입고 등록과 같은 복잡한 워크플로우를 구현할 수 있습니다. 다음 단계는 각 컴포넌트 타입별 DataReceivable 인터페이스 구현과 설정 UI 개발입니다.
+
diff --git a/화면_임베딩_시스템_충돌_분석_보고서.md b/화면_임베딩_시스템_충돌_분석_보고서.md
index 135d36d8..cdd94d36 100644
--- a/화면_임베딩_시스템_충돌_분석_보고서.md
+++ b/화면_임베딩_시스템_충돌_분석_보고서.md
@@ -514,3 +514,4 @@ function ScreenViewPage() {
새로운 시스템은 기존 시스템과 **독립적으로 동작**하며, 최소한의 수정만으로 통합 가능합니다. 화면 페이지에 조건 분기만 추가하면 바로 사용할 수 있습니다.
+