From 9e956999c56860d40da08bcefc7109d07afc7e9c Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 5 Dec 2025 10:46:10 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=ED=81=AC=EA=B8=B0=20?= =?UTF-8?q?=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/admin/AddColumnModal.tsx | 34 +- .../components/admin/AdvancedBatchModal.tsx | 2 +- frontend/components/admin/BatchJobModal.tsx | 22 +- .../admin/CodeCategoryFormModal.tsx | 16 +- frontend/components/admin/CodeFormModal.tsx | 16 +- .../admin/CollectionConfigModal.tsx | 22 +- .../components/admin/CompanyFormModal.tsx | 32 +- .../components/admin/CreateTableModal.tsx | 38 +- frontend/components/admin/DDLLogViewer.tsx | 28 +- .../admin/ExternalCallConfigModal.tsx | 34 +- .../admin/ExternalDbConnectionModal.tsx | 34 +- frontend/components/admin/LangKeyModal.tsx | 26 +- frontend/components/admin/LanguageModal.tsx | 26 +- frontend/components/admin/LayoutFormModal.tsx | 24 +- frontend/components/admin/MenuFormModal.tsx | 26 +- .../admin/RestApiConnectionModal.tsx | 32 +- frontend/components/admin/RoleDeleteModal.tsx | 32 +- frontend/components/admin/RoleFormModal.tsx | 32 +- frontend/components/admin/SqlQueryModal.tsx | 36 +- frontend/components/admin/TableLogViewer.tsx | 28 +- .../components/admin/TemplateImportExport.tsx | 2 +- frontend/components/admin/UserFormModal.tsx | 30 +- .../components/admin/UserHistoryModal.tsx | 26 +- .../admin/UserPasswordResetModal.tsx | 16 +- .../admin/dashboard/DashboardDesigner.tsx | 30 +- .../admin/dashboard/DashboardSaveModal.tsx | 32 +- .../admin/dashboard/MenuAssignmentModal.tsx | 34 +- .../widgets/yard-3d/MaterialAddModal.tsx | 30 +- .../widgets/yard-3d/MaterialLibrary.tsx | 2 +- .../widgets/yard-3d/YardLayoutCreateModal.tsx | 34 +- .../components/common/BarcodeScanModal.tsx | 47 +- .../components/common/ExcelUploadModal.tsx | 54 +- frontend/components/common/ScreenModal.tsx | 58 +- .../components/common/TableHistoryModal.tsx | 20 +- .../components/common/TableOptionsModal.tsx | 47 +- .../dataflow/ConnectionSetupModal.tsx | 34 +- .../components/dataflow/SaveDiagramModal.tsx | 34 +- .../node-editor/dialogs/LoadFlowDialog.tsx | 2 +- .../components/flow/FlowDataListModal.tsx | 8 +- frontend/components/layout/ProfileModal.tsx | 68 +- frontend/components/mail/MailDetailModal.tsx | 18 +- .../components/multilang/LangKeyModal.tsx | 6 +- .../order/OrderRegistrationModal.tsx | 2 +- .../components/report/ReportCreateModal.tsx | 15 +- .../components/screen/CopyScreenModal.tsx | 2 +- .../components/screen/CreateScreenModal.tsx | 40 +- frontend/components/screen/EditModal.tsx | 55 +- .../screen/FileAttachmentDetailModal.tsx | 6 +- .../screen/InteractiveDataTable.tsx | 6 +- .../screen/InteractiveScreenViewer.tsx | 2 +- .../screen/InteractiveScreenViewerDynamic.tsx | 27 +- .../components/screen/MenuAssignmentModal.tsx | 76 +-- .../screen/ResponsivePreviewModal.tsx | 4 +- frontend/components/screen/SaveModal.tsx | 25 +- .../screen/templates/DataTableTemplate.tsx | 2 +- frontend/components/ui/dialog.tsx | 6 +- frontend/components/ui/resizable-dialog.tsx | 601 ------------------ .../components/webtypes/RepeaterInput.tsx | 91 ++- .../card-display/CardDisplayComponent.tsx | 4 +- .../entity-search-input/EntitySearchModal.tsx | 2 +- .../file-upload/FileViewerModal.tsx | 4 +- .../ItemSelectionModal.tsx | 6 +- .../table-list/TableListComponent.tsx | 90 ++- .../table-list/TableListConfigPanel.tsx | 193 +++--- ..._임베딩_및_데이터_전달_시스템_구현_계획서.md | 1 + 화면_임베딩_시스템_Phase1-4_구현_완료.md | 1 + 화면_임베딩_시스템_충돌_분석_보고서.md | 1 + 67 files changed, 969 insertions(+), 1465 deletions(-) delete mode 100644 frontend/components/ui/resizable-dialog.tsx diff --git a/frontend/components/admin/AddColumnModal.tsx b/frontend/components/admin/AddColumnModal.tsx index 6b9467d9..550c46fc 100644 --- a/frontend/components/admin/AddColumnModal.tsx +++ b/frontend/components/admin/AddColumnModal.tsx @@ -7,13 +7,13 @@ import { 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 { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -197,14 +197,14 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol const inputTypeOption = INPUT_TYPE_OPTIONS.find((opt) => opt.value === column.inputType); return ( - - - - + + + + 컬럼 추가 - {tableName} - - + +
{/* 검증 오류 표시 */} @@ -346,7 +346,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol
- + @@ -365,8 +365,8 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol "컬럼 추가" )} - -
-
+ + + ); } diff --git a/frontend/components/admin/AdvancedBatchModal.tsx b/frontend/components/admin/AdvancedBatchModal.tsx index b1667c36..1276bcad 100644 --- a/frontend/components/admin/AdvancedBatchModal.tsx +++ b/frontend/components/admin/AdvancedBatchModal.tsx @@ -198,7 +198,7 @@ export default function AdvancedBatchModal({ return ( - + 고급 배치 생성 diff --git a/frontend/components/admin/BatchJobModal.tsx b/frontend/components/admin/BatchJobModal.tsx index cc9ca22f..b3fbb0e9 100644 --- a/frontend/components/admin/BatchJobModal.tsx +++ b/frontend/components/admin/BatchJobModal.tsx @@ -7,7 +7,7 @@ import { DialogHeader, -} from "@/components/ui/resizable-dialog"; +} from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -169,13 +169,13 @@ export default function BatchJobModal({ // 상태 제거 - 필요없음 return ( - - - - + + + + {job ? "배치 작업 수정" : "새 배치 작업"} - - + +
{/* 기본 정보 */} @@ -344,7 +344,7 @@ export default function BatchJobModal({ - + - +
-
-
+
+
); } diff --git a/frontend/components/admin/CodeCategoryFormModal.tsx b/frontend/components/admin/CodeCategoryFormModal.tsx index c7c62818..0ba970e0 100644 --- a/frontend/components/admin/CodeCategoryFormModal.tsx +++ b/frontend/components/admin/CodeCategoryFormModal.tsx @@ -3,7 +3,7 @@ import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -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 { Textarea } from "@/components/ui/textarea"; @@ -164,11 +164,11 @@ export function CodeCategoryFormModal({ const isLoading = createCategoryMutation.isPending || updateCategoryMutation.isPending; return ( - - - - {isEditing ? "카테고리 수정" : "새 카테고리"} - + + + + {isEditing ? "카테고리 수정" : "새 카테고리"} +
{/* 카테고리 코드 */} @@ -383,7 +383,7 @@ export function CodeCategoryFormModal({
-
-
+ + ); } diff --git a/frontend/components/admin/CodeFormModal.tsx b/frontend/components/admin/CodeFormModal.tsx index 2d6c7d39..977e9e84 100644 --- a/frontend/components/admin/CodeFormModal.tsx +++ b/frontend/components/admin/CodeFormModal.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -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 { Textarea } from "@/components/ui/textarea"; @@ -153,11 +153,11 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code const isLoading = createCodeMutation.isPending || updateCodeMutation.isPending; return ( - - - - {isEditing ? "코드 수정" : "새 코드"} - + + + + {isEditing ? "코드 수정" : "새 코드"} +
{/* 코드값 */} @@ -328,7 +328,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
-
-
+ + ); } diff --git a/frontend/components/admin/CollectionConfigModal.tsx b/frontend/components/admin/CollectionConfigModal.tsx index ef5e4998..ea099bfa 100644 --- a/frontend/components/admin/CollectionConfigModal.tsx +++ b/frontend/components/admin/CollectionConfigModal.tsx @@ -7,7 +7,7 @@ import { DialogHeader, -} from "@/components/ui/resizable-dialog"; +} from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -164,13 +164,13 @@ export default function CollectionConfigModal({ ]; return ( - - - - + + + + {config ? "수집 설정 수정" : "새 수집 설정"} - - + +
{/* 기본 정보 */} @@ -331,16 +331,16 @@ export default function CollectionConfigModal({ - + - +
-
-
+ + ); } diff --git a/frontend/components/admin/CompanyFormModal.tsx b/frontend/components/admin/CompanyFormModal.tsx index 56b79294..91cff911 100644 --- a/frontend/components/admin/CompanyFormModal.tsx +++ b/frontend/components/admin/CompanyFormModal.tsx @@ -4,13 +4,13 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle, - ResizableDialogDescription, - ResizableDialogFooter, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; import { LoadingSpinner } from "@/components/common/LoadingSpinner"; import { validateBusinessNumber, formatBusinessNumber } from "@/lib/validation/businessNumber"; @@ -111,8 +111,8 @@ export function CompanyFormModal({ }; return ( - - + - - {isEditMode ? "회사 정보 수정" : "새 회사 등록"} - + + {isEditMode ? "회사 정보 수정" : "새 회사 등록"} +
{/* 회사명 입력 (필수) */} @@ -255,7 +255,7 @@ export function CompanyFormModal({ )}
- + @@ -273,8 +273,8 @@ export function CompanyFormModal({ {(isLoading || isSaving) && } {isEditMode ? "수정" : "등록"} - -
-
+ + + ); } diff --git a/frontend/components/admin/CreateTableModal.tsx b/frontend/components/admin/CreateTableModal.tsx index 8de74c32..ecb6b03f 100644 --- a/frontend/components/admin/CreateTableModal.tsx +++ b/frontend/components/admin/CreateTableModal.tsx @@ -7,13 +7,13 @@ import { 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 { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -321,20 +321,20 @@ export function CreateTableModal({ const isFormValid = !tableNameError && tableName && columns.some((col) => col.name && col.inputType); return ( - - - - + + + + {isDuplicateMode ? "테이블 복제" : "새 테이블 생성"} - - + + {isDuplicateMode ? `${sourceTableName} 테이블을 복제하여 새 테이블을 생성합니다. 테이블명을 입력하고 필요시 컬럼을 수정하세요.` : "최고 관리자만 새로운 테이블을 생성할 수 있습니다. 테이블명과 컬럼 정의를 입력하고 검증 후 생성하세요." } - - + +
{/* 테이블 기본 정보 */} @@ -452,7 +452,7 @@ export function CreateTableModal({ )}
- + @@ -482,8 +482,8 @@ export function CreateTableModal({ isDuplicateMode ? "복제 생성" : "테이블 생성" )} - -
-
+ + + ); } diff --git a/frontend/components/admin/DDLLogViewer.tsx b/frontend/components/admin/DDLLogViewer.tsx index d4441056..f707511b 100644 --- a/frontend/components/admin/DDLLogViewer.tsx +++ b/frontend/components/admin/DDLLogViewer.tsx @@ -7,12 +7,12 @@ import { useState, useEffect } 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 { Badge } from "@/components/ui/badge"; @@ -148,14 +148,14 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) { }; return ( - - - - + + + + DDL 실행 로그 및 통계 - - + + @@ -407,7 +407,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) { )} - - + + ); } diff --git a/frontend/components/admin/ExternalCallConfigModal.tsx b/frontend/components/admin/ExternalCallConfigModal.tsx index 0217977a..30694034 100644 --- a/frontend/components/admin/ExternalCallConfigModal.tsx +++ b/frontend/components/admin/ExternalCallConfigModal.tsx @@ -6,13 +6,13 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle, - ResizableDialogDescription, - ResizableDialogFooter, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { toast } from "sonner"; import { @@ -266,13 +266,13 @@ export function ExternalCallConfigModal({ isOpen, onClose, onSave, editingConfig }; return ( - - - - + + + + {editingConfig ? "외부 호출 설정 편집" : "새 외부 호출 설정"} - - + +
{/* 기본 정보 */} @@ -564,7 +564,7 @@ export function ExternalCallConfigModal({ isOpen, onClose, onSave, editingConfig )}
- + - -
-
+ + + ); } diff --git a/frontend/components/admin/ExternalDbConnectionModal.tsx b/frontend/components/admin/ExternalDbConnectionModal.tsx index 1d0c046f..f5631297 100644 --- a/frontend/components/admin/ExternalDbConnectionModal.tsx +++ b/frontend/components/admin/ExternalDbConnectionModal.tsx @@ -8,13 +8,13 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle, - ResizableDialogDescription, - ResizableDialogFooter, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; import { useToast } from "@/hooks/use-toast"; import { ExternalDbConnectionAPI, @@ -311,13 +311,13 @@ export const ExternalDbConnectionModal: React.FC }; return ( - - - - + + + + {isEditMode ? "연결 정보 수정" : "새 외부 DB 연결 추가"} - - + +
{/* 기본 정보 */} @@ -607,7 +607,7 @@ export const ExternalDbConnectionModal: React.FC
- + - -
-
+ + + ); }; diff --git a/frontend/components/admin/LangKeyModal.tsx b/frontend/components/admin/LangKeyModal.tsx index 034ca213..6801e873 100644 --- a/frontend/components/admin/LangKeyModal.tsx +++ b/frontend/components/admin/LangKeyModal.tsx @@ -2,12 +2,12 @@ import { useState, useEffect } 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"; @@ -66,11 +66,11 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani }; return ( - - - - {keyData ? "언어 키 수정" : "새 언어 키 추가"} - + + + + {keyData ? "언어 키 수정" : "새 언어 키 추가"} +
@@ -131,7 +131,7 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
-
-
+ + ); } diff --git a/frontend/components/admin/LanguageModal.tsx b/frontend/components/admin/LanguageModal.tsx index a50f12ef..908ebf0a 100644 --- a/frontend/components/admin/LanguageModal.tsx +++ b/frontend/components/admin/LanguageModal.tsx @@ -2,12 +2,12 @@ import { useState, useEffect } 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"; @@ -68,11 +68,11 @@ export default function LanguageModal({ isOpen, onClose, onSave, languageData }: }; return ( - - - - {languageData ? "언어 수정" : "새 언어 추가"} - + + + + {languageData ? "언어 수정" : "새 언어 추가"} +
@@ -141,8 +141,8 @@ export default function LanguageModal({ isOpen, onClose, onSave, languageData }:
- - + +
); } diff --git a/frontend/components/admin/LayoutFormModal.tsx b/frontend/components/admin/LayoutFormModal.tsx index da6b0f3a..a4bcdf4f 100644 --- a/frontend/components/admin/LayoutFormModal.tsx +++ b/frontend/components/admin/LayoutFormModal.tsx @@ -13,7 +13,7 @@ import { DialogHeader, -} from "@/components/ui/resizable-dialog"; +} from "@/components/ui/dialog"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; @@ -225,14 +225,14 @@ export const LayoutFormModal: React.FC = ({ open, onOpenCh }; return ( - - - - + + + + 새 레이아웃 생성 - - GUI를 통해 새로운 레이아웃을 쉽게 생성할 수 있습니다. - + + GUI를 통해 새로운 레이아웃을 쉽게 생성할 수 있습니다. + {/* 단계 표시기 */}
@@ -499,7 +499,7 @@ export const LayoutFormModal: React.FC = ({ open, onOpenCh )}
- + {step !== "basic" && !generationResult && ( - -
-
+ + + ); }; diff --git a/frontend/components/admin/MenuFormModal.tsx b/frontend/components/admin/MenuFormModal.tsx index 33d2447e..43f17b52 100644 --- a/frontend/components/admin/MenuFormModal.tsx +++ b/frontend/components/admin/MenuFormModal.tsx @@ -9,11 +9,11 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle +} from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { toast } from "sonner"; @@ -684,15 +684,15 @@ export const MenuFormModal: React.FC = ({ }; return ( - - - - + + + + {isEdit ? getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE) : getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE)} - - + +
@@ -1067,7 +1067,7 @@ export const MenuFormModal: React.FC = ({
-
-
+ + ); }; diff --git a/frontend/components/admin/RestApiConnectionModal.tsx b/frontend/components/admin/RestApiConnectionModal.tsx index 3de34800..95ac6e76 100644 --- a/frontend/components/admin/RestApiConnectionModal.tsx +++ b/frontend/components/admin/RestApiConnectionModal.tsx @@ -8,13 +8,13 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle, - ResizableDialogDescription, - ResizableDialogFooter, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; import { useToast } from "@/hooks/use-toast"; import { ExternalRestApiConnectionAPI, @@ -271,11 +271,11 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }: }; return ( - - - - {connection ? "REST API 연결 수정" : "새 REST API 연결 추가"} - + + + + {connection ? "REST API 연결 수정" : "새 REST API 연결 추가"} +
{/* 기본 정보 */} @@ -574,7 +574,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
- + - -
-
+ + + ); } diff --git a/frontend/components/admin/RoleDeleteModal.tsx b/frontend/components/admin/RoleDeleteModal.tsx index 9d178351..9f3cf75a 100644 --- a/frontend/components/admin/RoleDeleteModal.tsx +++ b/frontend/components/admin/RoleDeleteModal.tsx @@ -2,13 +2,13 @@ import React, { useState, 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 { Button } from "@/components/ui/button"; import { roleAPI, RoleGroup } from "@/lib/api/role"; import { AlertTriangle } from "lucide-react"; @@ -71,11 +71,11 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete if (!role) return null; return ( - - - - 권한 그룹 삭제 - + + + + 권한 그룹 삭제 +
{/* 경고 메시지 */} @@ -133,7 +133,7 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete )}
- + - -
-
+ + + ); } diff --git a/frontend/components/admin/RoleFormModal.tsx b/frontend/components/admin/RoleFormModal.tsx index 492a463c..cdeeccc5 100644 --- a/frontend/components/admin/RoleFormModal.tsx +++ b/frontend/components/admin/RoleFormModal.tsx @@ -2,13 +2,13 @@ import React, { useState, useCallback, useEffect, useMemo } 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 { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -184,11 +184,11 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF ); return ( - - - - {isEditMode ? "권한 그룹 수정" : "권한 그룹 생성"} - + + + + {isEditMode ? "권한 그룹 수정" : "권한 그룹 생성"} +
{/* 권한 그룹명 */} @@ -359,7 +359,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF )}
- + - -
-
+ + + ); } diff --git a/frontend/components/admin/SqlQueryModal.tsx b/frontend/components/admin/SqlQueryModal.tsx index 4c01f472..6f83d78f 100644 --- a/frontend/components/admin/SqlQueryModal.tsx +++ b/frontend/components/admin/SqlQueryModal.tsx @@ -3,12 +3,12 @@ import { useState, useEffect, ChangeEvent } from "react"; import { Button } from "@/components/ui/button"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle, - ResizableDialogDescription, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; @@ -179,14 +179,14 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c }; return ( - - - - {connectionName} - SQL 쿼리 실행 - + + + + {connectionName} - SQL 쿼리 실행 + 데이터베이스에 대해 SQL SELECT 쿼리를 실행하고 결과를 확인할 수 있습니다. - - + + {/* 쿼리 입력 영역 */}
@@ -228,7 +228,7 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c

사용 가능한 테이블

-
+
{tables.map((table) => (
@@ -263,7 +263,7 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c {loadingColumns ? (
컬럼 정보 로딩 중...
) : selectedTableColumns.length > 0 ? ( -
+
@@ -332,7 +332,7 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c {/* 결과 그리드 */}
-
+
@@ -378,7 +378,7 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c - - + + ); }; diff --git a/frontend/components/admin/TableLogViewer.tsx b/frontend/components/admin/TableLogViewer.tsx index 181c6e4b..147229df 100644 --- a/frontend/components/admin/TableLogViewer.tsx +++ b/frontend/components/admin/TableLogViewer.tsx @@ -2,12 +2,12 @@ import { useState, useEffect } 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; @@ -126,14 +126,14 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer }; return ( - - - - + + + + {tableName} - 변경 이력 - - + + {/* 필터 영역 */}
@@ -261,7 +261,7 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer
-
-
+ + ); } diff --git a/frontend/components/admin/TemplateImportExport.tsx b/frontend/components/admin/TemplateImportExport.tsx index a72bb468..e11dada6 100644 --- a/frontend/components/admin/TemplateImportExport.tsx +++ b/frontend/components/admin/TemplateImportExport.tsx @@ -7,7 +7,7 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Alert, AlertDescription } from "@/components/ui/alert"; -import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Upload, Download, FileText, AlertCircle, CheckCircle } from "lucide-react"; import { toast } from "sonner"; import { useTemplates } from "@/hooks/admin/useTemplates"; diff --git a/frontend/components/admin/UserFormModal.tsx b/frontend/components/admin/UserFormModal.tsx index b3095e67..a70e82b9 100644 --- a/frontend/components/admin/UserFormModal.tsx +++ b/frontend/components/admin/UserFormModal.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useState, useCallback, useEffect, useMemo } 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 { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -32,11 +32,11 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod }; return ( - - - - {title} - + + + + {title} +

{message}

@@ -45,8 +45,8 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod 확인 -
-
+ + ); } @@ -441,11 +441,11 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF return ( <> - - - - {isEditMode ? "사용자 정보 수정" : "사용자 등록"} - + + + + {isEditMode ? "사용자 정보 수정" : "사용자 등록"} +
{/* 기본 정보 */} @@ -684,8 +684,8 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF {isLoading ? "처리중..." : isEditMode ? "수정" : "등록"}
-
-
+ + {/* 알림 모달 */} - - - + + + + 사용자 관리 이력 - +
{userName} ({userId})의 변경이력을 조회합니다.
-
+
{/* 로딩 상태 */} @@ -254,7 +254,7 @@ export function UserHistoryModal({ isOpen, onClose, userId, userName }: UserHist 닫기
-
- + + ); } diff --git a/frontend/components/admin/UserPasswordResetModal.tsx b/frontend/components/admin/UserPasswordResetModal.tsx index dc9fd206..086b1556 100644 --- a/frontend/components/admin/UserPasswordResetModal.tsx +++ b/frontend/components/admin/UserPasswordResetModal.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useState, useCallback } 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"; @@ -127,11 +127,11 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu if (!userId) return null; return ( - - - - 비밀번호 초기화 - + + + + 비밀번호 초기화 +
{/* 대상 사용자 정보 */} @@ -215,7 +215,7 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu {isLoading ? "처리중..." : "초기화"}
-
+ {/* 알림 모달 */} -
+ ); } diff --git a/frontend/components/admin/dashboard/DashboardDesigner.tsx b/frontend/components/admin/dashboard/DashboardDesigner.tsx index 4c70419e..08296fd1 100644 --- a/frontend/components/admin/dashboard/DashboardDesigner.tsx +++ b/frontend/components/admin/dashboard/DashboardDesigner.tsx @@ -13,12 +13,12 @@ import { DashboardProvider } from "@/contexts/DashboardContext"; import { useMenu } from "@/contexts/MenuContext"; import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogDescription, - ResizableDialogHeader, - ResizableDialogTitle, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, @@ -639,23 +639,23 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D /> {/* 저장 성공 모달 */} - { setSuccessModalOpen(false); router.push("/admin/dashboard"); }} > - - + +
- 저장 완료 - + 저장 완료 + 대시보드가 성공적으로 저장되었습니다. - -
+ +
-
-
+ + {/* 초기화 확인 모달 */} diff --git a/frontend/components/admin/dashboard/DashboardSaveModal.tsx b/frontend/components/admin/dashboard/DashboardSaveModal.tsx index 2c9ff4d6..f99984e8 100644 --- a/frontend/components/admin/dashboard/DashboardSaveModal.tsx +++ b/frontend/components/admin/dashboard/DashboardSaveModal.tsx @@ -2,13 +2,13 @@ import { 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 { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -174,11 +174,11 @@ export function DashboardSaveModal({ const flatMenus = flattenMenus(currentMenus); return ( - - - - {isEditing ? "대시보드 수정" : "대시보드 저장"} - + + + + {isEditing ? "대시보드 수정" : "대시보드 저장"} +
{/* 대시보드 이름 */} @@ -312,7 +312,7 @@ export function DashboardSaveModal({
- + @@ -329,8 +329,8 @@ export function DashboardSaveModal({ )} - -
-
+ + + ); } diff --git a/frontend/components/admin/dashboard/MenuAssignmentModal.tsx b/frontend/components/admin/dashboard/MenuAssignmentModal.tsx index 5e8869a9..a6f01c8b 100644 --- a/frontend/components/admin/dashboard/MenuAssignmentModal.tsx +++ b/frontend/components/admin/dashboard/MenuAssignmentModal.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 { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; @@ -116,14 +116,14 @@ export const MenuAssignmentModal: React.FC = ({ }; return ( - - - + + +
- 대시보드 저장 완료 - '{dashboardTitle}' 대시보드가 저장되었습니다. + 대시보드 저장 완료 + '{dashboardTitle}' 대시보드가 저장되었습니다.
-
+
@@ -200,13 +200,13 @@ export const MenuAssignmentModal: React.FC = ({ )}
- + - - - + + + ); }; diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/MaterialAddModal.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/MaterialAddModal.tsx index b5831d27..3f64c2b2 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/MaterialAddModal.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/MaterialAddModal.tsx @@ -3,13 +3,13 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle, - ResizableDialogDescription, - ResizableDialogFooter, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Loader2 } from "lucide-react"; @@ -94,10 +94,10 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M if (!open) onClose(); }} > - - - 자재 배치 설정 - + + + 자재 배치 설정 +
{/* 자재 정보 */} @@ -233,7 +233,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
- + @@ -247,8 +247,8 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M "배치" )} - -
-
+ + + ); } diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/MaterialLibrary.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/MaterialLibrary.tsx index 15b6db93..79909658 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/MaterialLibrary.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/MaterialLibrary.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Search, Loader2 } from "lucide-react"; import { materialApi } from "@/lib/api/yardLayoutApi"; diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/YardLayoutCreateModal.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/YardLayoutCreateModal.tsx index e6c8a3b8..feac0f22 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/YardLayoutCreateModal.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/YardLayoutCreateModal.tsx @@ -3,13 +3,13 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle, - ResizableDialogDescription, - ResizableDialogFooter, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Alert, AlertDescription } from "@/components/ui/alert"; @@ -64,14 +64,14 @@ export default function YardLayoutCreateModal({ isOpen, onClose, onCreate }: Yar }; return ( - - e.stopPropagation()}> - + + e.stopPropagation()}> +
- 새로운 3D필드 생성 - 필드 이름을 입력하세요 + 새로운 3D필드 생성 + 필드 이름을 입력하세요
-
+
@@ -100,7 +100,7 @@ export default function YardLayoutCreateModal({ isOpen, onClose, onCreate }: Yar )}
- + @@ -114,8 +114,8 @@ export default function YardLayoutCreateModal({ isOpen, onClose, onCreate }: Yar "생성" )} - - - + + + ); } diff --git a/frontend/components/common/BarcodeScanModal.tsx b/frontend/components/common/BarcodeScanModal.tsx index 7c615941..34706b8c 100644 --- a/frontend/components/common/BarcodeScanModal.tsx +++ b/frontend/components/common/BarcodeScanModal.tsx @@ -2,13 +2,13 @@ import React, { useState, useRef, 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 { toast } from "sonner"; import { Camera, CameraOff, CheckCircle2, AlertCircle, Scan } from "lucide-react"; @@ -179,26 +179,15 @@ export const BarcodeScanModal: React.FC = ({ }; return ( - - - - 바코드 스캔 - + + + + 바코드 스캔 + 카메라로 바코드를 스캔하세요. {targetField && ` (대상 필드: ${targetField})`} - 모달 테두리를 드래그하여 크기를 조절할 수 있습니다. - - + +
{/* 카메라 권한 요청 대기 중 */} @@ -337,7 +326,7 @@ export const BarcodeScanModal: React.FC = ({ )}
- + )} - -
-
+ + + ); }; diff --git a/frontend/components/common/ExcelUploadModal.tsx b/frontend/components/common/ExcelUploadModal.tsx index c5a25a65..0f080bcc 100644 --- a/frontend/components/common/ExcelUploadModal.tsx +++ b/frontend/components/common/ExcelUploadModal.tsx @@ -2,13 +2,13 @@ import React, { useState, useRef, 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 { @@ -385,27 +385,27 @@ export const ExcelUploadModal: React.FC = ({ }, [open]); return ( - - + - - + + 엑셀 데이터 업로드 - - + + 엑셀 파일을 선택하고 컬럼을 매핑하여 데이터를 업로드하세요. 모달 테두리를 드래그하여 크기를 조절할 수 있습니다. - - + + {/* 스텝 인디케이터 */}
@@ -863,7 +863,7 @@ export const ExcelUploadModal: React.FC = ({ )}
- + )} - -
-
+ + + ); }; diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index a048bbe4..4da781e6 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -2,12 +2,12 @@ import React, { useState, useEffect, useRef } from "react"; import { - ResizableDialog, - ResizableDialogContent, - ResizableDialogHeader, - ResizableDialogTitle, - ResizableDialogDescription, -} from "@/components/ui/resizable-dialog"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic"; @@ -514,16 +514,18 @@ export const ScreenModal: React.FC = ({ className }) => { } // 화면관리에서 설정한 크기 = 컨텐츠 영역 크기 - // 실제 모달 크기 = 컨텐츠 + 헤더 + 연속등록 체크박스 - const headerHeight = 60; // DialogHeader (타이틀 + 패딩) + // 실제 모달 크기 = 컨텐츠 + 헤더 + 연속등록 체크박스 + gap + padding + const headerHeight = 52; // DialogHeader (타이틀 + border-b + py-3) const footerHeight = 52; // 연속 등록 모드 체크박스 영역 + const dialogGap = 16; // DialogContent gap-4 + const extraPadding = 24; // 추가 여백 (안전 마진) - const totalHeight = screenDimensions.height + headerHeight + footerHeight; + const totalHeight = screenDimensions.height + headerHeight + footerHeight + 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", @@ -593,36 +595,28 @@ export const ScreenModal: React.FC = ({ className }) => { ]); return ( - - + - +
- {modalState.title} + {modalState.title} {modalState.description && !loading && ( - + {modalState.description} - + )} {loading && ( - + {loading ? "화면을 불러오는 중입니다..." : ""} - + )}
-
+ -
+
{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} +

{message}

@@ -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({ )}
- + - -
-
+ + + {/* 알림 모달 */} - - - 새 차량 등록 - + + + + 새 차량 등록 + 새로운 차량 정보를 입력해주세요. - - + +
@@ -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 )}
- + - - - + + +
); } diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index cde9086c..dafe07a5 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"; @@ -661,14 +661,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", @@ -679,32 +682,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 ? (
@@ -795,8 +790,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 = ({
- + - + ) : ( // 기본 할당 화면 <> - - + + 메뉴에 화면 할당 - - + + 저장된 화면을 메뉴에 할당하여 사용자가 접근할 수 있도록 설정합니다. - + {screenInfo && (
@@ -432,7 +432,7 @@ export const MenuAssignmentModal: React.FC = ({ {screenInfo.description &&

{screenInfo.description}

}
)} - +
{/* 메뉴 선택 (검색 기능 포함) */} @@ -550,7 +550,7 @@ export const MenuAssignmentModal: React.FC = ({ )}
- + - + )} - - + +
{/* 화면 교체 확인 대화상자 */} - - - - + + + + 화면 교체 확인 - - 선택한 메뉴에 이미 할당된 화면이 있습니다. - + + 선택한 메뉴에 이미 할당된 화면이 있습니다. +
{/* 기존 화면 목록 */} @@ -628,7 +628,7 @@ export const MenuAssignmentModal: React.FC = ({
- + @@ -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 ? "데이터 수정" : "데이터 등록"}
-
+
{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 && ( - - )} - - - - 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 = ({ />
- +
{/* 참조 테이블 미설정 안내 */} - {!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" /> - + - @@ -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 = ({
))} - + {/* 연결된 필터 추가 버튼 */} - -

+ +

예: 셀렉트박스(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 = ({ - @@ -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 = ({
- + {/* 소스 컬럼 (현재 테이블) */}
- @@ -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 = ({
- + {/* 조건 필터 - 특정 조건의 데이터만 제외 */}
-

+

특정 조건의 데이터만 제외하려면 설정하세요 (예: 특정 거래처의 품목만)

@@ -1578,9 +1570,9 @@ export const TableListConfigPanel: React.FC = ({ disabled={loadingReferenceColumns} className="h-8 w-full justify-between text-xs" > - {loadingReferenceColumns - ? "..." - : config.excludeFilter?.filterColumn + {loadingReferenceColumns + ? "..." + : config.excludeFilter?.filterColumn ? `매핑: ${config.excludeFilter.filterColumn}` : "매핑 테이블 컬럼"} @@ -1590,7 +1582,7 @@ export const TableListConfigPanel: React.FC = ({ - 없음 + 없음 = ({ filterValueField: undefined, }); }} - className="text-xs text-muted-foreground" + className="text-muted-foreground text-xs" > - + 사용 안함 {referenceTableColumns.map((col) => ( @@ -1624,7 +1621,9 @@ export const TableListConfigPanel: React.FC = ({ {col.label || col.columnName} @@ -1635,7 +1634,7 @@ 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() { 새로운 시스템은 기존 시스템과 **독립적으로 동작**하며, 최소한의 수정만으로 통합 가능합니다. 화면 페이지에 조건 분기만 추가하면 바로 사용할 수 있습니다. +