From 1a60177fe4fcbe6314cf646372a1ca78eb239741 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Wed, 24 Sep 2025 18:07:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=ED=86=B5=EC=9D=BC=20=EB=B0=8F=20JSX=20=EA=B5=AC?= =?UTF-8?q?=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - admin/screenMng, dataflow 페이지에 tableMng 레퍼런스 레이아웃 적용 - admin/standards 페이지 JSX 괄호 문제 수정 - 전체 관리자 페이지 UI 일관성 향상 - bg-gray-50 배경, container 구조, 통일된 제목 스타일 적용 --- .../(main)/admin/batch-management/page.tsx | 12 +- .../admin/collection-management/page.tsx | 12 +- frontend/app/(main)/admin/commonCode/page.tsx | 16 +- frontend/app/(main)/admin/company/page.tsx | 15 +- frontend/app/(main)/admin/dataflow/page.tsx | 71 +- .../admin/external-call-configs/page.tsx | 4 +- .../admin/external-connections/page.tsx | 15 +- frontend/app/(main)/admin/i18n/page.tsx | 8 +- frontend/app/(main)/admin/layouts/page.tsx | 15 +- frontend/app/(main)/admin/menu/page.tsx | 13 +- frontend/app/(main)/admin/monitoring/page.tsx | 22 +- frontend/app/(main)/admin/page.tsx | 4 +- frontend/app/(main)/admin/screenMng/page.tsx | 60 +- .../admin/standards/[webType]/edit/page.tsx | 4 +- .../(main)/admin/standards/[webType]/page.tsx | 4 +- .../app/(main)/admin/standards/new/page.tsx | 4 +- frontend/app/(main)/admin/standards/page.tsx | 26 +- frontend/app/(main)/admin/tableMng/page.tsx | 2 +- frontend/app/(main)/admin/templates/page.tsx | 16 +- frontend/app/(main)/admin/userMng/page.tsx | 13 +- .../app/(main)/admin/validation-demo/page.tsx | 12 +- .../app/(main)/screens/[screenId]/page.tsx | 2 +- .../EnhancedInteractiveScreenViewer.tsx | 2 +- .../screen/InteractiveScreenViewer.tsx | 2 +- .../screen/RealtimePreviewDynamic.tsx | 15 +- frontend/components/screen/ScreenDesigner.tsx | 38 +- .../config-panels/ButtonConfigPanel.tsx | 20 +- .../screen/filters/AdvancedSearchFilters.tsx | 6 - .../screen/panels/DetailSettingsPanel.tsx | 7 +- .../screen/panels/PropertiesPanel.tsx | 82 +- .../lib/hooks/useEntityJoinOptimization.ts | 49 +- .../AutoRegisteringComponentRenderer.ts | 2 +- .../lib/registry/DynamicComponentRenderer.tsx | 5 + .../AccordionBasicComponent.tsx | 2 +- .../button-primary/ButtonPrimaryComponent.tsx | 92 +- .../checkbox-basic/CheckboxBasicComponent.tsx | 4 +- .../date-input/DateInputComponent.tsx | 2 +- .../divider-line/DividerLineComponent.tsx | 2 +- .../file-upload/FileUploadComponent.tsx | 2 +- .../image-display/ImageDisplayComponent.tsx | 2 +- .../number-input/NumberInputComponent.tsx | 2 +- .../radio-basic/RadioBasicComponent.tsx | 4 +- .../select-basic/SelectBasicComponent.tsx | 2 +- .../slider-basic/SliderBasicComponent.tsx | 4 +- .../table-list/SingleTableWithSticky.tsx | 59 +- .../table-list/TableListComponent.tsx | 958 ++++++++++-------- .../table-list/TableListConfigPanel.tsx | 24 +- .../table-list/TableListRenderer.tsx | 20 +- .../test-input/TestInputComponent.tsx | 2 +- .../text-display/TextDisplayComponent.tsx | 4 +- .../text-display/TextDisplayConfigPanel.tsx | 2 +- .../registry/components/text-display/index.ts | 2 +- .../text-input/TextInputComponent.tsx | 2 +- .../textarea-basic/TextareaBasicComponent.tsx | 2 +- .../toggle-switch/ToggleSwitchComponent.tsx | 4 +- .../layouts/AutoRegisteringLayoutRenderer.tsx | 2 +- .../registry/layouts/BaseLayoutRenderer.tsx | 33 +- .../layouts/accordion/AccordionLayout.tsx | 2 +- frontend/lib/registry/utils/hotReload.ts | 12 +- .../lib/utils/getComponentConfigPanel.tsx | 13 + frontend/scripts/create-component.js | 6 +- frontend/types/component.ts | 7 +- 62 files changed, 1173 insertions(+), 677 deletions(-) diff --git a/frontend/app/(main)/admin/batch-management/page.tsx b/frontend/app/(main)/admin/batch-management/page.tsx index 9b23cf70..9e48a097 100644 --- a/frontend/app/(main)/admin/batch-management/page.tsx +++ b/frontend/app/(main)/admin/batch-management/page.tsx @@ -185,11 +185,12 @@ export default function BatchManagementPage() { }; return ( -
- {/* 헤더 */} -
-
-

배치 관리

+
+
+ {/* 헤더 */} +
+
+

배치 관리

스케줄된 배치 작업을 관리하고 실행 상태를 모니터링합니다.

@@ -428,6 +429,7 @@ export default function BatchManagementPage() { onSave={handleModalSave} job={selectedJob} /> +
); } diff --git a/frontend/app/(main)/admin/collection-management/page.tsx b/frontend/app/(main)/admin/collection-management/page.tsx index 4edbcaec..8320caac 100644 --- a/frontend/app/(main)/admin/collection-management/page.tsx +++ b/frontend/app/(main)/admin/collection-management/page.tsx @@ -162,11 +162,12 @@ export default function CollectionManagementPage() { }; return ( -
- {/* 헤더 */} -
-
-

수집 관리

+
+
+ {/* 헤더 */} +
+
+

수집 관리

외부 데이터베이스에서 데이터를 수집하는 설정을 관리합니다.

@@ -332,6 +333,7 @@ export default function CollectionManagementPage() { onSave={handleModalSave} config={selectedConfig} /> +
); } diff --git a/frontend/app/(main)/admin/commonCode/page.tsx b/frontend/app/(main)/admin/commonCode/page.tsx index be946e05..3868ec40 100644 --- a/frontend/app/(main)/admin/commonCode/page.tsx +++ b/frontend/app/(main)/admin/commonCode/page.tsx @@ -11,14 +11,15 @@ export default function CommonCodeManagementPage() { const { selectedCategoryCode, selectCategory } = useSelectedCategory(); return ( -
- {/* 페이지 헤더 */} -
-
-

공통코드 관리

-

시스템에서 사용하는 공통코드를 관리합니다

+
+
+ {/* 페이지 제목 */} +
+
+

공통코드 관리

+

시스템에서 사용하는 공통코드를 관리합니다

+
-
{/* 메인 콘텐츠 */} {/* 반응형 레이아웃: PC는 가로, 모바일은 세로 */} @@ -52,6 +53,7 @@ export default function CommonCodeManagementPage() {
+
); } diff --git a/frontend/app/(main)/admin/company/page.tsx b/frontend/app/(main)/admin/company/page.tsx index 79e92516..7e222aa8 100644 --- a/frontend/app/(main)/admin/company/page.tsx +++ b/frontend/app/(main)/admin/company/page.tsx @@ -4,5 +4,18 @@ import { CompanyManagement } from "@/components/admin/CompanyManagement"; * 회사 관리 페이지 */ export default function CompanyPage() { - return ; + return ( +
+
+ {/* 페이지 제목 */} +
+
+

회사 관리

+

시스템에서 사용하는 회사 정보를 관리합니다

+
+
+ +
+
+ ); } diff --git a/frontend/app/(main)/admin/dataflow/page.tsx b/frontend/app/(main)/admin/dataflow/page.tsx index 19914665..cf57b3cb 100644 --- a/frontend/app/(main)/admin/dataflow/page.tsx +++ b/frontend/app/(main)/admin/dataflow/page.tsx @@ -76,48 +76,49 @@ export default function DataFlowPage() { }; return ( -
- {/* 헤더 */} -
+
+
+ {/* 페이지 제목 */}
-
- {currentStep !== "list" && ( - - )} -
-

- {stepConfig[currentStep].icon} - {stepConfig[currentStep].title} -

-

{stepConfig[currentStep].description}

-
+
+

데이터 흐름 관리

+

테이블 간 데이터 관계를 시각적으로 설계하고 관리합니다

+ {currentStep !== "list" && ( + + )}
-
- {/* 단계별 내용 */} -
- {/* 관계도 목록 단계 */} - {currentStep === "list" && ( -
- + {/* 단계별 내용 */} +
+ {/* 관계도 목록 단계 */} + {currentStep === "list" && ( +
+
+

{stepConfig.list.title}

+
+
)} - {/* 관계도 설계 단계 */} - {currentStep === "design" && ( -
- goToStep("list")} - /> -
- )} + {/* 관계도 설계 단계 */} + {currentStep === "design" && ( +
+
+

{stepConfig.design.title}

+
+ goToStep("list")} + /> +
+ )} +
); diff --git a/frontend/app/(main)/admin/external-call-configs/page.tsx b/frontend/app/(main)/admin/external-call-configs/page.tsx index dbdd4aeb..e3755083 100644 --- a/frontend/app/(main)/admin/external-call-configs/page.tsx +++ b/frontend/app/(main)/admin/external-call-configs/page.tsx @@ -161,7 +161,8 @@ export default function ExternalCallConfigsPage() { }; return ( -
+
+
{/* 페이지 헤더 */}
@@ -396,6 +397,7 @@ export default function ExternalCallConfigsPage() { +
); } diff --git a/frontend/app/(main)/admin/external-connections/page.tsx b/frontend/app/(main)/admin/external-connections/page.tsx index 85e7911f..e1ab62d7 100644 --- a/frontend/app/(main)/admin/external-connections/page.tsx +++ b/frontend/app/(main)/admin/external-connections/page.tsx @@ -220,11 +220,15 @@ export default function ExternalConnectionsPage() { }; return ( -
-
-

외부 커넥션 관리

-

외부 데이터베이스 연결 정보를 관리합니다.

-
+
+
+ {/* 페이지 제목 */} +
+
+

외부 커넥션 관리

+

외부 데이터베이스 연결 정보를 관리합니다

+
+
{/* 검색 및 필터 */} @@ -446,6 +450,7 @@ export default function ExternalConnectionsPage() { connectionName={selectedConnection.connection_name} /> )} +
); } diff --git a/frontend/app/(main)/admin/i18n/page.tsx b/frontend/app/(main)/admin/i18n/page.tsx index f1fa7ef4..bb7308e2 100644 --- a/frontend/app/(main)/admin/i18n/page.tsx +++ b/frontend/app/(main)/admin/i18n/page.tsx @@ -3,6 +3,12 @@ import MultiLang from "@/components/admin/MultiLang"; export default function I18nPage() { - return ; + return ( +
+
+ +
+
+ ); } diff --git a/frontend/app/(main)/admin/layouts/page.tsx b/frontend/app/(main)/admin/layouts/page.tsx index c5215057..f22bd3d8 100644 --- a/frontend/app/(main)/admin/layouts/page.tsx +++ b/frontend/app/(main)/admin/layouts/page.tsx @@ -220,12 +220,14 @@ export default function LayoutManagementPage() { }; return ( -
-
-
-

레이아웃 관리

-

화면 레이아웃을 생성하고 관리합니다.

-
+
+
+ {/* 페이지 제목 */} +
+
+

레이아웃 관리

+

화면 레이아웃을 생성하고 관리합니다

+
@@ -411,6 +413,7 @@ export default function LayoutManagementPage() { loadCategoryCounts(); }} /> +
); } diff --git a/frontend/app/(main)/admin/menu/page.tsx b/frontend/app/(main)/admin/menu/page.tsx index 301e0321..fcf0b965 100644 --- a/frontend/app/(main)/admin/menu/page.tsx +++ b/frontend/app/(main)/admin/menu/page.tsx @@ -4,8 +4,17 @@ import { MenuManagement } from "@/components/admin/MenuManagement"; export default function MenuPage() { return ( -
- +
+
+ {/* 페이지 제목 */} +
+
+

메뉴 관리

+

시스템 메뉴를 관리하고 화면을 할당합니다

+
+
+ +
); } diff --git a/frontend/app/(main)/admin/monitoring/page.tsx b/frontend/app/(main)/admin/monitoring/page.tsx index 6161c387..2f028639 100644 --- a/frontend/app/(main)/admin/monitoring/page.tsx +++ b/frontend/app/(main)/admin/monitoring/page.tsx @@ -5,17 +5,19 @@ import MonitoringDashboard from "@/components/admin/MonitoringDashboard"; export default function MonitoringPage() { return ( -
- {/* 헤더 */} -
-

모니터링

-

- 배치 작업 실행 상태를 실시간으로 모니터링합니다. -

-
+
+
+ {/* 헤더 */} +
+

모니터링

+

+ 배치 작업 실행 상태를 실시간으로 모니터링합니다. +

+
- {/* 모니터링 대시보드 */} - + {/* 모니터링 대시보드 */} + +
); } diff --git a/frontend/app/(main)/admin/page.tsx b/frontend/app/(main)/admin/page.tsx index b320ab45..8735d7f6 100644 --- a/frontend/app/(main)/admin/page.tsx +++ b/frontend/app/(main)/admin/page.tsx @@ -5,7 +5,8 @@ import Link from "next/link"; */ export default function AdminPage() { return ( -
+
+
{/* 관리자 기능 카드들 */}
@@ -162,6 +163,7 @@ export default function AdminPage() {
+
); } diff --git a/frontend/app/(main)/admin/screenMng/page.tsx b/frontend/app/(main)/admin/screenMng/page.tsx index bf90f2d7..8918e936 100644 --- a/frontend/app/(main)/admin/screenMng/page.tsx +++ b/frontend/app/(main)/admin/screenMng/page.tsx @@ -66,14 +66,23 @@ export default function ScreenManagementPage() { const isLastStep = currentStep === "template"; return ( -
- {/* 단계별 내용 */} -
- {/* 화면 목록 단계 */} - {currentStep === "list" && ( -
-
-

{stepConfig.list.title}

+
+
+ {/* 페이지 제목 */} +
+
+

화면 관리

+

화면을 설계하고 템플릿을 관리합니다

+
+
+ + {/* 단계별 내용 */} +
+ {/* 화면 목록 단계 */} + {currentStep === "list" && ( +
+
+

{stepConfig.list.title}

@@ -89,18 +98,24 @@ export default function ScreenManagementPage() {
)} - {/* 화면 설계 단계 */} - {currentStep === "design" && ( -
- goToStep("list")} /> -
- )} + {/* 화면 설계 단계 */} + {currentStep === "design" && ( +
+
+

{stepConfig.design.title}

+ +
+ goToStep("list")} /> +
+ )} - {/* 템플릿 관리 단계 */} - {currentStep === "template" && ( -
-
-

{stepConfig.template.title}

+ {/* 템플릿 관리 단계 */} + {currentStep === "template" && ( +
+
+

{stepConfig.template.title}

- goToStep("list")} /> -
- )} + goToStep("list")} /> +
+ )} +
); diff --git a/frontend/app/(main)/admin/standards/[webType]/edit/page.tsx b/frontend/app/(main)/admin/standards/[webType]/edit/page.tsx index be7dd3f5..ff24db7f 100644 --- a/frontend/app/(main)/admin/standards/[webType]/edit/page.tsx +++ b/frontend/app/(main)/admin/standards/[webType]/edit/page.tsx @@ -203,7 +203,8 @@ export default function EditWebTypePage() { } return ( -
+
+
{/* 헤더 */}
@@ -502,6 +503,7 @@ export default function EditWebTypePage() {

)} +
); } diff --git a/frontend/app/(main)/admin/standards/[webType]/page.tsx b/frontend/app/(main)/admin/standards/[webType]/page.tsx index c11999ff..f44a8447 100644 --- a/frontend/app/(main)/admin/standards/[webType]/page.tsx +++ b/frontend/app/(main)/admin/standards/[webType]/page.tsx @@ -80,7 +80,8 @@ export default function WebTypeDetailPage() { } return ( -
+
+
{/* 헤더 */}
@@ -280,6 +281,7 @@ export default function WebTypeDetailPage() { +
); } diff --git a/frontend/app/(main)/admin/standards/new/page.tsx b/frontend/app/(main)/admin/standards/new/page.tsx index 77df8a74..aa60ed45 100644 --- a/frontend/app/(main)/admin/standards/new/page.tsx +++ b/frontend/app/(main)/admin/standards/new/page.tsx @@ -159,7 +159,8 @@ export default function NewWebTypePage() { }; return ( -
+
+
{/* 헤더 */}
@@ -453,6 +454,7 @@ export default function NewWebTypePage() {

)} +
); } diff --git a/frontend/app/(main)/admin/standards/page.tsx b/frontend/app/(main)/admin/standards/page.tsx index c21266ab..e00ddfa1 100644 --- a/frontend/app/(main)/admin/standards/page.tsx +++ b/frontend/app/(main)/admin/standards/page.tsx @@ -127,19 +127,20 @@ export default function WebTypesManagePage() { } return ( -
- {/* 헤더 */} -
-
-

웹타입 관리

-

화면관리에서 사용할 웹타입들을 관리합니다.

+
+
+ {/* 페이지 제목 */} +
+
+

웹타입 관리

+

화면관리에서 사용할 웹타입들을 관리합니다

+
+ + +
- - - -
{/* 필터 및 검색 */} @@ -364,6 +365,7 @@ export default function WebTypesManagePage() {

)} +
); } diff --git a/frontend/app/(main)/admin/tableMng/page.tsx b/frontend/app/(main)/admin/tableMng/page.tsx index 3c95f4df..311e20db 100644 --- a/frontend/app/(main)/admin/tableMng/page.tsx +++ b/frontend/app/(main)/admin/tableMng/page.tsx @@ -541,7 +541,7 @@ export default function TableManagementPage() { }, [selectedTable, columns.length, totalColumns, columnsLoading, pageSize, loadColumnTypes]); return ( -
+
{/* 페이지 제목 */}
diff --git a/frontend/app/(main)/admin/templates/page.tsx b/frontend/app/(main)/admin/templates/page.tsx index 800c84ac..e964d85c 100644 --- a/frontend/app/(main)/admin/templates/page.tsx +++ b/frontend/app/(main)/admin/templates/page.tsx @@ -145,13 +145,14 @@ export default function TemplatesManagePage() { } return ( -
- {/* 헤더 */} -
-
-

템플릿 관리

-

화면 디자이너에서 사용할 템플릿을 관리합니다.

-
+
+
+ {/* 페이지 제목 */} +
+
+

템플릿 관리

+

화면 디자이너에서 사용할 템플릿을 관리합니다

+
+
); } diff --git a/frontend/app/(main)/admin/userMng/page.tsx b/frontend/app/(main)/admin/userMng/page.tsx index 94f861cc..0d0df171 100644 --- a/frontend/app/(main)/admin/userMng/page.tsx +++ b/frontend/app/(main)/admin/userMng/page.tsx @@ -8,8 +8,17 @@ import { UserManagement } from "@/components/admin/UserManagement"; */ export default function UserMngPage() { return ( -
- +
+
+ {/* 페이지 제목 */} +
+
+

사용자 관리

+

시스템 사용자 계정 및 권한을 관리합니다

+
+
+ +
); } diff --git a/frontend/app/(main)/admin/validation-demo/page.tsx b/frontend/app/(main)/admin/validation-demo/page.tsx index bb567d63..e903bb4e 100644 --- a/frontend/app/(main)/admin/validation-demo/page.tsx +++ b/frontend/app/(main)/admin/validation-demo/page.tsx @@ -54,7 +54,7 @@ const TEST_COMPONENTS: ComponentData[] = [ required: true, style: { labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", }, } as WidgetComponent, @@ -72,7 +72,7 @@ const TEST_COMPONENTS: ComponentData[] = [ required: true, style: { labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", }, } as WidgetComponent, @@ -94,7 +94,7 @@ const TEST_COMPONENTS: ComponentData[] = [ }, style: { labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", }, } as WidgetComponent, @@ -112,7 +112,7 @@ const TEST_COMPONENTS: ComponentData[] = [ required: false, style: { labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", }, } as WidgetComponent, @@ -130,7 +130,7 @@ const TEST_COMPONENTS: ComponentData[] = [ required: false, style: { labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", }, } as WidgetComponent, @@ -152,7 +152,7 @@ const TEST_COMPONENTS: ComponentData[] = [ }, style: { labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", }, } as WidgetComponent, diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index 90195801..e5b622a6 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -237,7 +237,7 @@ export default function ScreenViewPage() { const labelText = component.style?.labelText || component.label || ""; const labelStyle = { fontSize: component.style?.labelFontSize || "14px", - color: component.style?.labelColor || "#374151", + color: component.style?.labelColor || "#3b83f6", fontWeight: component.style?.labelFontWeight || "500", backgroundColor: component.style?.labelBackgroundColor || "transparent", padding: component.style?.labelPadding || "0", diff --git a/frontend/components/screen/EnhancedInteractiveScreenViewer.tsx b/frontend/components/screen/EnhancedInteractiveScreenViewer.tsx index 93475d3f..8c553b3c 100644 --- a/frontend/components/screen/EnhancedInteractiveScreenViewer.tsx +++ b/frontend/components/screen/EnhancedInteractiveScreenViewer.tsx @@ -232,7 +232,7 @@ export const EnhancedInteractiveScreenViewer: React.FC = ( // 라벨 스타일 적용 const labelStyle = { fontSize: component.style?.labelFontSize || "14px", - color: component.style?.labelColor || "#374151", + color: component.style?.labelColor || "#3b83f6", fontWeight: component.style?.labelFontWeight || "500", backgroundColor: component.style?.labelBackgroundColor || "transparent", padding: component.style?.labelPadding || "0", diff --git a/frontend/components/screen/RealtimePreviewDynamic.tsx b/frontend/components/screen/RealtimePreviewDynamic.tsx index 409c6056..5260b3e5 100644 --- a/frontend/components/screen/RealtimePreviewDynamic.tsx +++ b/frontend/components/screen/RealtimePreviewDynamic.tsx @@ -32,6 +32,7 @@ interface RealtimePreviewProps { selectedScreen?: any; onZoneComponentDrop?: (e: React.DragEvent, zoneId: string, layoutId: string) => void; // 존별 드롭 핸들러 onZoneClick?: (zoneId: string) => void; // 존 클릭 핸들러 + onConfigChange?: (config: any) => void; // 설정 변경 핸들러 } // 동적 위젯 타입 아이콘 (레지스트리에서 조회) @@ -73,6 +74,7 @@ export const RealtimePreviewDynamic: React.FC = ({ selectedScreen, onZoneComponentDrop, onZoneClick, + onConfigChange, }) => { const { id, type, position, size, style: componentStyle } = component; @@ -89,8 +91,12 @@ export const RealtimePreviewDynamic: React.FC = ({ const baseStyle = { left: `${position.x}px`, top: `${position.y}px`, - width: `${size?.width || 100}px`, - height: `${size?.height || 36}px`, + width: component.componentConfig?.type === "table-list" + ? `${Math.max(size?.width || 400, 400)}px` // table-list는 최소 400px + : `${size?.width || 100}px`, + height: component.componentConfig?.type === "table-list" + ? `${Math.max(size?.height || 300, 300)}px` // table-list는 최소 300px + : `${size?.height || 36}px`, zIndex: component.type === "layout" ? 1 : position.z || 2, // 레이아웃은 z-index 1, 다른 컴포넌트는 2 이상 ...componentStyle, }; @@ -120,7 +126,9 @@ export const RealtimePreviewDynamic: React.FC = ({ onDragEnd={handleDragEnd} > {/* 동적 컴포넌트 렌더링 */} -
+
= ({ selectedScreen={selectedScreen} onZoneComponentDrop={onZoneComponentDrop} onZoneClick={onZoneClick} + onConfigChange={onConfigChange} />
diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 5e89166d..000ebc44 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -1004,7 +1004,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: true, labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "600", labelMarginBottom: "8px", ...templateComp.style, @@ -1083,7 +1083,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: true, labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "600", labelMarginBottom: "8px", ...templateComp.style, @@ -1134,7 +1134,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: true, labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "600", labelMarginBottom: "8px", ...templateComp.style, @@ -1185,7 +1185,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: true, labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "600", labelMarginBottom: "8px", ...templateComp.style, @@ -1274,7 +1274,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: true, labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "600", labelMarginBottom: "8px", ...templateComp.style, @@ -1564,7 +1564,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: false, // 모든 컴포넌트의 기본 라벨 표시를 false로 설정 labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", labelMarginBottom: "4px", }, @@ -1653,7 +1653,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: true, labelFontSize: "14px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "600", labelMarginBottom: "8px", }, @@ -1844,7 +1844,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: false, // 모든 컴포넌트의 기본 라벨 표시를 false로 설정 labelFontSize: "12px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", labelMarginBottom: "6px", }, @@ -1887,7 +1887,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD style: { labelDisplay: true, // 테이블 패널에서 드래그한 컴포넌트는 라벨을 기본적으로 표시 labelFontSize: "12px", - labelColor: "#374151", + labelColor: "#3b83f6", labelFontWeight: "500", labelMarginBottom: "6px", }, @@ -3158,11 +3158,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD {/* 실제 작업 캔버스 (해상도 크기) */}
{ if (e.target === e.currentTarget && !selectionDrag.wasSelecting) { setSelectedComponent(null); @@ -3271,6 +3275,13 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD selectedScreen={selectedScreen} // onZoneComponentDrop 제거 onZoneClick={handleZoneClick} + // 설정 변경 핸들러 (테이블 페이지 크기 등 설정을 상세설정에 반영) + onConfigChange={(config) => { + console.log("📤 테이블 설정 변경을 상세설정에 알림:", config); + // 여기서 DetailSettingsPanel의 상태를 업데이트하거나 + // 컴포넌트의 componentConfig를 업데이트할 수 있습니다 + // TODO: 실제 구현은 DetailSettingsPanel과의 연동 필요 + }} > {/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 (레이아웃은 독립적으로 렌더링) */} {(component.type === "group" || component.type === "container" || component.type === "area") && @@ -3351,6 +3362,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD selectedScreen={selectedScreen} // onZoneComponentDrop 제거 onZoneClick={handleZoneClick} + // 설정 변경 핸들러 (자식 컴포넌트용) + onConfigChange={(config) => { + console.log("📤 자식 컴포넌트 설정 변경을 상세설정에 알림:", config); + // TODO: 실제 구현은 DetailSettingsPanel과의 연동 필요 + }} /> ); })} diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx index 7c819eb2..10cabc52 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx @@ -134,7 +134,25 @@ export const ButtonConfigPanel: React.FC = ({ component, handleSearch(e.target.value)} - className="w-64 pl-8" - /> + + {loading && ( +
+ )}
- {tableConfig.filter?.showColumnSelector && ( - - )} + + {loading ? "새로고침 중..." : "새로고침"} +
- )} */} - - {/* 새로고침 */} -
@@ -1246,168 +1230,255 @@ export const TableListComponent: React.FC = ({ {/* 고급 검색 필터 - 항상 표시 (컬럼 정보 기반 자동 생성) */} {tableConfig.filter?.enabled && visibleColumns && visibleColumns.length > 0 && ( <> - -
+
+ ({ columnName: col.columnName, - webType: (columnMeta[col.columnName]?.webType as any) || "text", + widgetType: (columnMeta[col.columnName]?.webType || "text") as WebType, displayName: columnLabels[col.columnName] || col.displayName || col.columnName, codeCategory: columnMeta[col.columnName]?.codeCategory, isVisible: col.visible, // 추가 메타데이터 전달 (필터 자동 생성용) - web_type: columnMeta[col.columnName]?.webType || "text", + web_type: (columnMeta[col.columnName]?.webType || "text") as WebType, column_name: col.columnName, column_label: columnLabels[col.columnName] || col.displayName || col.columnName, code_category: columnMeta[col.columnName]?.codeCategory, }))} - tableName={tableConfig.selectedTable} - /> + tableName={tableConfig.selectedTable} + /> +
)} {/* 테이블 컨텐츠 */} -
= 50 ? "flex-1 overflow-auto" : ""}`}> +
= 50 ? "flex-1" : ""}`} + style={{ + width: "100%", + maxWidth: "100%", + boxSizing: "border-box" + }} + > {loading ? ( -
-
- -
데이터를 불러오는 중...
+
+
+
+
+ +
+
+
+
데이터를 불러오는 중...
+
잠시만 기다려주세요
) : error ? ( -
-
-
오류가 발생했습니다
-
{error}
+
+
+
+
+ ! +
+
+
오류가 발생했습니다
+
{error}
) : needsHorizontalScroll ? ( // 가로 스크롤이 필요한 경우 - 단일 테이블에서 sticky 컬럼 사용 - +
+ +
) : ( // 기존 테이블 (가로 스크롤이 필요 없는 경우) - - - - {visibleColumns.map((column, colIndex) => ( - column.sortable && handleSort(column.columnName)} - > - {column.columnName === "__checkbox__" ? ( - renderCheckboxHeader() - ) : ( -
- {columnLabels[column.columnName] || column.displayName} - {column.sortable && ( -
- {sortColumn === column.columnName ? ( - sortDirection === "asc" ? ( - +
+
+ + + {visibleColumns.map((column, colIndex) => ( + column.sortable && handleSort(column.columnName)} + > + {column.columnName === "__checkbox__" ? ( + renderCheckboxHeader() + ) : ( +
+ + {columnLabels[column.columnName] || column.displayName} + + {column.sortable && ( +
+ {sortColumn === column.columnName ? ( + sortDirection === "asc" ? ( + + ) : ( + + ) ) : ( - - ) - ) : ( - - )} -
- )} -
- )} -
- ))} -
-
+ + )} + + )} + + )} + + ))} + + {data.length === 0 ? ( - - 데이터가 없습니다 + +
+
+ +
+
데이터가 없습니다
+
조건을 변경하거나 새로운 데이터를 추가해보세요
+
) : ( data.map((row, index) => ( handleRowDragStart(e, row, index)} + onDragEnd={handleRowDragEnd} className={cn( - "h-10 cursor-pointer leading-none", - tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50", - tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-50/50", + "group relative h-12 cursor-pointer transition-all duration-200 border-b border-gray-100", + // 기본 스타일 + tableConfig.tableStyle?.hoverEffect && "hover:bg-gradient-to-r hover:from-orange-200 hover:to-orange-300/90 hover:shadow-sm", + tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-100/80", + // 드래그 상태 스타일 (미묘하게) + draggedRowIndex === index && "bg-gradient-to-r from-blue-50 to-blue-100/40 shadow-sm border-blue-200", + isDragging && draggedRowIndex !== index && "opacity-70", + // 드래그 가능 표시 + !isDesignMode && "hover:cursor-grab active:cursor-grabbing" )} - style={{ minHeight: "40px", height: "40px", lineHeight: "1" }} + style={{ + minHeight: "48px", + height: "48px", + lineHeight: "1", + width: "100%", + maxWidth: "100%" + }} onClick={() => handleRowClick(row)} > {visibleColumns.map((column, colIndex) => ( {column.columnName === "__checkbox__" ? renderCheckboxCell(row, index) : (() => { // 🎯 매핑된 컬럼명으로 데이터 찾기 const mappedColumnName = joinColumnMapping[column.columnName] || column.columnName; - - // 조인 컬럼 매핑 정보 로깅 - if (column.columnName !== mappedColumnName && index === 0) { - console.log(`🔗 조인 컬럼 매핑: ${column.columnName} → ${mappedColumnName}`); - } - const cellValue = row[mappedColumnName]; if (index === 0) { - // 첫 번째 행만 로그 출력 - console.log(`🔍 셀 데이터 [${column.columnName} → ${mappedColumnName}]:`, cellValue); - - // 🚨 조인된 컬럼인 경우 추가 디버깅 - if (column.columnName !== mappedColumnName) { - console.log(" 🔗 조인 컬럼 분석:"); - console.log(` 👤 사용자 설정 컬럼: "${column.columnName}"`); - console.log(` 📡 매핑된 API 컬럼: "${mappedColumnName}"`); - console.log(` 📋 컬럼 라벨: "${column.displayName}"`); - console.log(` 💾 실제 데이터: "${cellValue}"`); - console.log( - ` 🔄 원본 컬럼 데이터 (${column.columnName}): "${row[column.columnName]}"`, - ); - } + // 디버깅 로그 제거 (성능상 이유로) } - return formatCellValue(cellValue, column.format, column.columnName) || "\u00A0"; + const formattedValue = formatCellValue(cellValue, column.format, column.columnName) || "\u00A0"; + + // 첫 번째 컬럼에 드래그 핸들과 아바타 추가 + const isFirstColumn = colIndex === (visibleColumns[0]?.columnName === "__checkbox__" ? 1 : 0); + + return ( +
+ {isFirstColumn && !isDesignMode && ( +
+ {/* 그리드 스냅 가이드 아이콘 */} +
+
+
+
+
+
+
+
+
+
+
+ )} + + {formattedValue} + +
+ ); })()}
))} @@ -1416,40 +1487,76 @@ export const TableListComponent: React.FC = ({ )}
+
)}
{/* 푸터/페이지네이션 */} {tableConfig.showFooter && tableConfig.pagination?.enabled && ( -
-
- {tableConfig.pagination?.showPageInfo && ( - - 전체 {totalItems.toLocaleString()}건 중 {(currentPage - 1) * localPageSize + 1}- - {Math.min(currentPage * localPageSize, totalItems)} 표시 +
+ {/* 페이지 정보 - 가운데 정렬 */} + {tableConfig.pagination?.showPageInfo && ( +
+
+ + 전체 {totalItems.toLocaleString()}건 중{" "} + + {(currentPage - 1) * localPageSize + 1}-{Math.min(currentPage * localPageSize, totalItems)} + {" "} + 표시 - )} -
+
+ )} -
- {/* 페이지 크기 선택 */} - {tableConfig.pagination?.showSizeSelector && ( + {/* 페이지 크기 선택과 페이지네이션 버튼 - 가운데 정렬 */} +
+ {/* 페이지 크기 선택 - 임시로 항상 표시 (테스트용) */} + {true && ( handleChange("color", e.target.value)} />
diff --git a/frontend/lib/registry/components/text-display/index.ts b/frontend/lib/registry/components/text-display/index.ts index c86255f1..9280aa0b 100644 --- a/frontend/lib/registry/components/text-display/index.ts +++ b/frontend/lib/registry/components/text-display/index.ts @@ -24,7 +24,7 @@ export const TextDisplayDefinition = createComponentDefinition({ text: "텍스트를 입력하세요", fontSize: "14px", fontWeight: "normal", - color: "#374151", + color: "#3b83f6", textAlign: "left", }, defaultSize: { width: 150, height: 24 }, diff --git a/frontend/lib/registry/components/text-input/TextInputComponent.tsx b/frontend/lib/registry/components/text-input/TextInputComponent.tsx index 4a5aabf6..f4fe7a9e 100644 --- a/frontend/lib/registry/components/text-input/TextInputComponent.tsx +++ b/frontend/lib/registry/components/text-input/TextInputComponent.tsx @@ -190,7 +190,7 @@ export const TextInputComponent: React.FC = ({ top: "-25px", left: "0px", fontSize: component.style?.labelFontSize || "14px", - color: component.style?.labelColor || "#374151", + color: component.style?.labelColor || "#3b83f6", fontWeight: "500", }} > diff --git a/frontend/lib/registry/components/textarea-basic/TextareaBasicComponent.tsx b/frontend/lib/registry/components/textarea-basic/TextareaBasicComponent.tsx index 04128d74..482280b0 100644 --- a/frontend/lib/registry/components/textarea-basic/TextareaBasicComponent.tsx +++ b/frontend/lib/registry/components/textarea-basic/TextareaBasicComponent.tsx @@ -84,7 +84,7 @@ export const TextareaBasicComponent: React.FC = ({ top: "-25px", left: "0px", fontSize: component.style?.labelFontSize || "14px", - color: component.style?.labelColor || "#374151", + color: component.style?.labelColor || "#3b83f6", fontWeight: "500", // isInteractive 모드에서는 사용자 스타일 우선 적용 ...(isInteractive && component.style ? component.style : {}), diff --git a/frontend/lib/registry/components/toggle-switch/ToggleSwitchComponent.tsx b/frontend/lib/registry/components/toggle-switch/ToggleSwitchComponent.tsx index 8183e1c0..f71a4127 100644 --- a/frontend/lib/registry/components/toggle-switch/ToggleSwitchComponent.tsx +++ b/frontend/lib/registry/components/toggle-switch/ToggleSwitchComponent.tsx @@ -84,7 +84,7 @@ export const ToggleSwitchComponent: React.FC = ({ top: "-25px", left: "0px", fontSize: component.style?.labelFontSize || "14px", - color: component.style?.labelColor || "#374151", + color: component.style?.labelColor || "#3b83f6", fontWeight: "500", // isInteractive 모드에서는 사용자 스타일 우선 적용 ...(isInteractive && component.style ? component.style : {}), @@ -173,7 +173,7 @@ export const ToggleSwitchComponent: React.FC = ({
{ const element = e.currentTarget; - element.style.borderColor = "#3b82f6"; - element.style.backgroundColor = "rgba(59, 130, 246, 0.02)"; - element.style.boxShadow = "0 1px 3px rgba(0, 0, 0, 0.1)"; + // 🎯 컴포넌트가 있는 존은 호버 효과 최소화 + if (zoneChildren.length > 0) { + element.style.backgroundColor = "rgba(59, 130, 246, 0.01)"; + } else { + element.style.borderColor = "#3b82f6"; + element.style.backgroundColor = "rgba(59, 130, 246, 0.02)"; + element.style.boxShadow = "0 1px 3px rgba(0, 0, 0, 0.1)"; + } }} onMouseLeave={(e) => { const element = e.currentTarget; - element.style.borderColor = isDesignMode ? "#cbd5e1" : "#e2e8f0"; - element.style.backgroundColor = isDesignMode ? "rgba(241, 245, 249, 0.8)" : "rgba(248, 250, 252, 0.5)"; + if (zoneChildren.length > 0) { + // 컴포넌트가 있는 존 복원 + element.style.borderColor = "transparent"; + element.style.backgroundColor = isDesignMode ? "rgba(248, 250, 252, 0.3)" : "rgba(248, 250, 252, 0.5)"; + } else { + // 빈 존 복원 + element.style.borderColor = isDesignMode ? "#cbd5e1" : "#e2e8f0"; + element.style.backgroundColor = isDesignMode ? "rgba(241, 245, 249, 0.8)" : "rgba(248, 250, 252, 0.5)"; + } element.style.boxShadow = "none"; }} onDrop={this.handleDrop(zone.id)} diff --git a/frontend/lib/registry/layouts/accordion/AccordionLayout.tsx b/frontend/lib/registry/layouts/accordion/AccordionLayout.tsx index 43488c51..e108b7c7 100644 --- a/frontend/lib/registry/layouts/accordion/AccordionLayout.tsx +++ b/frontend/lib/registry/layouts/accordion/AccordionLayout.tsx @@ -148,7 +148,7 @@ const AccordionSection: React.FC<{ const headerStyle: React.CSSProperties = { padding: "12px 16px", backgroundColor: isDesignMode ? "#3b82f6" : "#f8fafc", - color: isDesignMode ? "white" : "#374151", + color: isDesignMode ? "white" : "#3b83f6", border: "1px solid #e2e8f0", borderBottom: isExpanded ? "none" : "1px solid #e2e8f0", cursor: "pointer", diff --git a/frontend/lib/registry/utils/hotReload.ts b/frontend/lib/registry/utils/hotReload.ts index c4688100..a2f61a29 100644 --- a/frontend/lib/registry/utils/hotReload.ts +++ b/frontend/lib/registry/utils/hotReload.ts @@ -14,6 +14,10 @@ let hotReloadListeners: Array<() => void> = []; * Hot Reload 시스템 초기화 */ export function initializeHotReload(): void { + // 핫 리로드 시스템 임시 비활성화 (디버깅 목적) + console.log("🔥 컴포넌트 Hot Reload 시스템 비활성화됨 (디버깅 모드)"); + return; + if (process.env.NODE_ENV !== "development" || typeof window === "undefined") { return; } @@ -55,11 +59,15 @@ function setupDevServerEventListener(): void { const originalLog = console.log; let reloadPending = false; - // console.log 메시지를 감지하여 Hot Reload 트리거 + // console.log 메시지를 감지하여 Hot Reload 트리거 (특정 메시지만) console.log = (...args: any[]) => { const message = args.join(" "); - if (message.includes("compiled") || message.includes("Fast Refresh") || message.includes("component")) { + // 핫 리로드를 트리거할 특정 메시지만 감지 (디버깅 로그는 제외) + if ((message.includes("compiled") || message.includes("Fast Refresh")) && + !message.includes("🔍") && !message.includes("🎯") && !message.includes("📤") && + !message.includes("📥") && !message.includes("⚠️") && !message.includes("🔄") && + !message.includes("✅") && !message.includes("🔧") && !message.includes("📋")) { if (!reloadPending) { reloadPending = true; setTimeout(() => { diff --git a/frontend/lib/utils/getComponentConfigPanel.tsx b/frontend/lib/utils/getComponentConfigPanel.tsx index 8ff6fd55..fa464377 100644 --- a/frontend/lib/utils/getComponentConfigPanel.tsx +++ b/frontend/lib/utils/getComponentConfigPanel.tsx @@ -110,6 +110,8 @@ export const DynamicComponentConfigPanel: React.FC = screenTableName, tableColumns, }) => { + console.log(`🔥 DynamicComponentConfigPanel 렌더링 시작: ${componentId}`); + const [ConfigPanelComponent, setConfigPanelComponent] = React.useState | null>(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); @@ -180,10 +182,21 @@ export const DynamicComponentConfigPanel: React.FC = ); } + console.log(`🔧 DynamicComponentConfigPanel 렌더링:`, { + componentId, + ConfigPanelComponent: ConfigPanelComponent?.name, + config, + configType: typeof config, + configKeys: typeof config === 'object' ? Object.keys(config || {}) : 'not object', + screenTableName, + tableColumns: Array.isArray(tableColumns) ? tableColumns.length : tableColumns + }); + return ( diff --git a/frontend/scripts/create-component.js b/frontend/scripts/create-component.js index 674f48d7..83d5c852 100755 --- a/frontend/scripts/create-component.js +++ b/frontend/scripts/create-component.js @@ -661,7 +661,7 @@ function getComponentJSXByWebType(webType) { top: "-25px", left: "0px", fontSize: component.style?.labelFontSize || "14px", - color: component.style?.labelColor || "#374151", + color: component.style?.labelColor || "#3b83f6", fontWeight: "500", }} > @@ -709,7 +709,7 @@ function getComponentJSXByWebType(webType) { top: "-25px", left: "0px", fontSize: component.style?.labelFontSize || "14px", - color: component.style?.labelColor || "#374151", + color: component.style?.labelColor || "#3b83f6", fontWeight: "500", }} > @@ -785,7 +785,7 @@ function getComponentJSXByWebType(webType) { top: "-25px", left: "0px", fontSize: component.style?.labelFontSize || "14px", - color: component.style?.labelColor || "#374151", + color: component.style?.labelColor || "#3b83f6", fontWeight: "500", }} > diff --git a/frontend/types/component.ts b/frontend/types/component.ts index 34a8cd92..ea29cc5f 100644 --- a/frontend/types/component.ts +++ b/frontend/types/component.ts @@ -68,6 +68,9 @@ export interface ComponentRendererProps { // 새로운 기능들 autoGeneration?: AutoGenerationConfig; // 자동생성 설정 hidden?: boolean; // 숨김 기능 (편집기에서는 연하게, 실제 화면에서는 숨김) + + // 설정 변경 핸들러 + onConfigChange?: (config: any) => void; [key: string]: any; } @@ -317,7 +320,7 @@ export const COMPONENT_CATEGORIES_INFO = { [ComponentCategory.CHART]: { name: "차트", description: "데이터 시각화 컴포넌트", - color: "#06b6d4", + color: "#3b83f6", }, [ComponentCategory.FORM]: { name: "폼", @@ -347,7 +350,7 @@ export const COMPONENT_CATEGORIES_INFO = { [ComponentCategory.CONTAINER]: { name: "컨테이너", description: "다른 컴포넌트를 담는 컨테이너", - color: "#374151", + color: "#3b83f6", }, [ComponentCategory.SYSTEM]: { name: "시스템", From e3cd6dc3a0c5beccfd48ba3236136800f5c06476 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Thu, 25 Sep 2025 09:29:56 +0900 Subject: [PATCH 2/2] =?UTF-8?q?UI/UX=20=EA=B0=9C=EC=84=A0:=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=EB=B0=94=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=95=88=EC=A0=95=ED=99=94=20=EB=B0=8F=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20hover=20=ED=9A=A8=EA=B3=BC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사이드바 고정 너비 설정으로 메뉴 클릭 시 너비 변화 방지 - 메뉴 항목 hover 효과 일관성 개선 (고정 높이, 부드러운 색상 전환) - 디버깅 로그 제거로 성능 최적화 - 관리자 페이지 카드 디자인 개선 (그라데이션 배경, 아이콘 색상 조정) --- frontend/app/(main)/admin/commonCode/page.tsx | 10 +- frontend/app/(main)/admin/company/page.tsx | 2 +- frontend/app/(main)/admin/dataflow/page.tsx | 8 +- .../admin/external-connections/page.tsx | 8 +- frontend/app/(main)/admin/layouts/page.tsx | 14 +- frontend/app/(main)/admin/menu/page.tsx | 2 +- frontend/app/(main)/admin/screenMng/page.tsx | 36 +-- frontend/app/(main)/admin/standards/page.tsx | 258 +++++++++--------- frontend/app/(main)/admin/tableMng/page.tsx | 14 +- frontend/app/(main)/admin/templates/page.tsx | 26 +- frontend/app/(main)/admin/userMng/page.tsx | 2 +- frontend/components/admin/MenuManagement.tsx | 126 +++++---- frontend/components/layout/AppLayout.tsx | 70 +++-- 13 files changed, 307 insertions(+), 269 deletions(-) diff --git a/frontend/app/(main)/admin/commonCode/page.tsx b/frontend/app/(main)/admin/commonCode/page.tsx index 3868ec40..6d5eba31 100644 --- a/frontend/app/(main)/admin/commonCode/page.tsx +++ b/frontend/app/(main)/admin/commonCode/page.tsx @@ -14,7 +14,7 @@ export default function CommonCodeManagementPage() {
{/* 페이지 제목 */} -
+

공통코드 관리

시스템에서 사용하는 공통코드를 관리합니다

@@ -26,8 +26,8 @@ export default function CommonCodeManagementPage() {
{/* 카테고리 패널 - PC에서 좌측 고정 너비, 모바일에서 전체 너비 */}
- - + + 📂 코드 카테고리 @@ -38,8 +38,8 @@ export default function CommonCodeManagementPage() { {/* 코드 상세 패널 - PC에서 나머지 공간, 모바일에서 전체 너비 */}
- - + + 📋 코드 상세 정보 {selectedCategoryCode && ( diff --git a/frontend/app/(main)/admin/company/page.tsx b/frontend/app/(main)/admin/company/page.tsx index 7e222aa8..645470eb 100644 --- a/frontend/app/(main)/admin/company/page.tsx +++ b/frontend/app/(main)/admin/company/page.tsx @@ -8,7 +8,7 @@ export default function CompanyPage() {
{/* 페이지 제목 */} -
+

회사 관리

시스템에서 사용하는 회사 정보를 관리합니다

diff --git a/frontend/app/(main)/admin/dataflow/page.tsx b/frontend/app/(main)/admin/dataflow/page.tsx index cf57b3cb..de70ff1a 100644 --- a/frontend/app/(main)/admin/dataflow/page.tsx +++ b/frontend/app/(main)/admin/dataflow/page.tsx @@ -79,13 +79,13 @@ export default function DataFlowPage() {
{/* 페이지 제목 */} -
+

데이터 흐름 관리

테이블 간 데이터 관계를 시각적으로 설계하고 관리합니다

{currentStep !== "list" && ( - @@ -97,7 +97,7 @@ export default function DataFlowPage() { {/* 관계도 목록 단계 */} {currentStep === "list" && (
-
+

{stepConfig.list.title}

@@ -107,7 +107,7 @@ export default function DataFlowPage() { {/* 관계도 설계 단계 */} {currentStep === "design" && (
-
+

{stepConfig.design.title}

{/* 페이지 제목 */} -
+

외부 커넥션 관리

외부 데이터베이스 연결 정보를 관리합니다

@@ -231,7 +231,7 @@ export default function ExternalConnectionsPage() {
{/* 검색 및 필터 */} - +
@@ -289,7 +289,7 @@ export default function ExternalConnectionsPage() {
로딩 중...
) : connections.length === 0 ? ( - +
@@ -302,7 +302,7 @@ export default function ExternalConnectionsPage() { ) : ( - + diff --git a/frontend/app/(main)/admin/layouts/page.tsx b/frontend/app/(main)/admin/layouts/page.tsx index f22bd3d8..eb5b2aff 100644 --- a/frontend/app/(main)/admin/layouts/page.tsx +++ b/frontend/app/(main)/admin/layouts/page.tsx @@ -223,18 +223,18 @@ export default function LayoutManagementPage() {
{/* 페이지 제목 */} -
+

레이아웃 관리

화면 레이아웃을 생성하고 관리합니다

- -
+ +
{/* 검색 및 필터 */} - +
@@ -284,7 +284,7 @@ export default function LayoutManagementPage() { {layouts.map((layout) => { const CategoryIcon = CATEGORY_ICONS[layout.category as keyof typeof CATEGORY_ICONS]; return ( - +
diff --git a/frontend/app/(main)/admin/menu/page.tsx b/frontend/app/(main)/admin/menu/page.tsx index fcf0b965..3d5548cc 100644 --- a/frontend/app/(main)/admin/menu/page.tsx +++ b/frontend/app/(main)/admin/menu/page.tsx @@ -7,7 +7,7 @@ export default function MenuPage() {
{/* 페이지 제목 */} -
+

메뉴 관리

시스템 메뉴를 관리하고 화면을 할당합니다

diff --git a/frontend/app/(main)/admin/screenMng/page.tsx b/frontend/app/(main)/admin/screenMng/page.tsx index 8918e936..2002d364 100644 --- a/frontend/app/(main)/admin/screenMng/page.tsx +++ b/frontend/app/(main)/admin/screenMng/page.tsx @@ -69,7 +69,7 @@ export default function ScreenManagementPage() {
{/* 페이지 제목 */} -
+

화면 관리

화면을 설계하고 템플릿을 관리합니다

@@ -81,12 +81,12 @@ export default function ScreenManagementPage() { {/* 화면 목록 단계 */} {currentStep === "list" && (
-
+

{stepConfig.list.title}

- -
+ +
-
+

{stepConfig.design.title}

-
@@ -114,18 +114,18 @@ export default function ScreenManagementPage() { {/* 템플릿 관리 단계 */} {currentStep === "template" && (
-
+

{stepConfig.template.title}

-
- - +
+ + +
-
goToStep("list")} />
)} diff --git a/frontend/app/(main)/admin/standards/page.tsx b/frontend/app/(main)/admin/standards/page.tsx index e00ddfa1..ce1170e9 100644 --- a/frontend/app/(main)/admin/standards/page.tsx +++ b/frontend/app/(main)/admin/standards/page.tsx @@ -130,44 +130,44 @@ export default function WebTypesManagePage() {
{/* 페이지 제목 */} -
+

웹타입 관리

화면관리에서 사용할 웹타입들을 관리합니다

-
- {/* 필터 및 검색 */} - - - - - 필터 및 검색 - - - -
- {/* 검색 */} -
- - setSearchTerm(e.target.value)} - className="pl-10" - /> -
+ {/* 필터 및 검색 */} + + + + + 필터 및 검색 + + + +
+ {/* 검색 */} +
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
- {/* 카테고리 필터 */} - + + + 전체 카테고리 {categories.map((category) => ( @@ -178,96 +178,96 @@ export default function WebTypesManagePage() { - {/* 활성화 상태 필터 */} - + {/* 활성화 상태 필터 */} + - {/* 초기화 버튼 */} - + {/* 초기화 버튼 */} +
- {/* 결과 통계 */} -
-

총 {filteredAndSortedWebTypes.length}개의 웹타입이 있습니다.

-
+ {/* 결과 통계 */} +
+

총 {filteredAndSortedWebTypes.length}개의 웹타입이 있습니다.

+
- {/* 웹타입 목록 테이블 */} - - -
- - - handleSort("sort_order")}> -
- 순서 - {sortField === "sort_order" && - (sortDirection === "asc" ? : )} -
-
- handleSort("web_type")}> -
- 웹타입 코드 - {sortField === "web_type" && - (sortDirection === "asc" ? : )} -
-
- handleSort("type_name")}> -
- 웹타입명 - {sortField === "type_name" && - (sortDirection === "asc" ? : )} -
-
- handleSort("category")}> -
- 카테고리 - {sortField === "category" && - (sortDirection === "asc" ? : )} -
-
- 설명 - handleSort("component_name")}> -
- 연결된 컴포넌트 - {sortField === "component_name" && - (sortDirection === "asc" ? : )} -
-
- handleSort("config_panel")}> -
- 설정 패널 - {sortField === "config_panel" && - (sortDirection === "asc" ? : )} -
-
- handleSort("is_active")}> -
- 상태 - {sortField === "is_active" && - (sortDirection === "asc" ? : )} -
-
- handleSort("updated_date")}> -
- 최종 수정일 - {sortField === "updated_date" && - (sortDirection === "asc" ? : )} -
-
- 작업 + {/* 웹타입 목록 테이블 */} + + +
+ + + handleSort("sort_order")}> +
+ 순서 + {sortField === "sort_order" && + (sortDirection === "asc" ? : )} +
+
+ handleSort("web_type")}> +
+ 웹타입 코드 + {sortField === "web_type" && + (sortDirection === "asc" ? : )} +
+
+ handleSort("type_name")}> +
+ 웹타입명 + {sortField === "type_name" && + (sortDirection === "asc" ? : )} +
+
+ handleSort("category")}> +
+ 카테고리 + {sortField === "category" && + (sortDirection === "asc" ? : )} +
+
+ 설명 + handleSort("component_name")}> +
+ 연결된 컴포넌트 + {sortField === "component_name" && + (sortDirection === "asc" ? : )} +
+
+ handleSort("config_panel")}> +
+ 설정 패널 + {sortField === "config_panel" && + (sortDirection === "asc" ? : )} +
+
+ handleSort("is_active")}> +
+ 상태 + {sortField === "is_active" && + (sortDirection === "asc" ? : )} +
+
+ handleSort("updated_date")}> +
+ 최종 수정일 + {sortField === "updated_date" && + (sortDirection === "asc" ? : )} +
+
+ 작업
@@ -310,24 +310,24 @@ export default function WebTypesManagePage() { {webType.updated_date ? new Date(webType.updated_date).toLocaleDateString("ko-KR") : "-"} - -
- - - - - - - - - - + +
+ + + + + + + + + + 웹타입 삭제 diff --git a/frontend/app/(main)/admin/tableMng/page.tsx b/frontend/app/(main)/admin/tableMng/page.tsx index 311e20db..9fbaaed5 100644 --- a/frontend/app/(main)/admin/tableMng/page.tsx +++ b/frontend/app/(main)/admin/tableMng/page.tsx @@ -543,7 +543,7 @@ export default function TableManagementPage() { return (
{/* 페이지 제목 */} -
+

{getTextFromUI(TABLE_MANAGEMENT_KEYS.PAGE_TITLE, "테이블 타입 관리")} @@ -593,10 +593,10 @@ export default function TableManagementPage() {
{/* 테이블 목록 */} - - + + - + {getTextFromUI(TABLE_MANAGEMENT_KEYS.TABLE_NAME, "테이블 목록")} @@ -663,10 +663,10 @@ export default function TableManagementPage() { {/* 컬럼 타입 관리 */} - - + + - + {selectedTable ? <>테이블 설정 - {selectedTable} : "테이블 타입 관리"} diff --git a/frontend/app/(main)/admin/templates/page.tsx b/frontend/app/(main)/admin/templates/page.tsx index e964d85c..c06fda1d 100644 --- a/frontend/app/(main)/admin/templates/page.tsx +++ b/frontend/app/(main)/admin/templates/page.tsx @@ -148,25 +148,25 @@ export default function TemplatesManagePage() {
{/* 페이지 제목 */} -
+

템플릿 관리

화면 디자이너에서 사용할 템플릿을 관리합니다

-
- +
+ +
-
{/* 필터 및 검색 */} - - + + - + 필터 및 검색 @@ -231,8 +231,8 @@ export default function TemplatesManagePage() { {/* 템플릿 목록 테이블 */} - - + + 템플릿 목록 ({filteredAndSortedTemplates.length}개) diff --git a/frontend/app/(main)/admin/userMng/page.tsx b/frontend/app/(main)/admin/userMng/page.tsx index 0d0df171..3348148a 100644 --- a/frontend/app/(main)/admin/userMng/page.tsx +++ b/frontend/app/(main)/admin/userMng/page.tsx @@ -11,7 +11,7 @@ export default function UserMngPage() {
{/* 페이지 제목 */} -
+

사용자 관리

시스템 사용자 계정 및 권한을 관리합니다

diff --git a/frontend/components/admin/MenuManagement.tsx b/frontend/components/admin/MenuManagement.tsx index eb6d72de..c92f0a2d 100644 --- a/frontend/components/admin/MenuManagement.tsx +++ b/frontend/components/admin/MenuManagement.tsx @@ -821,8 +821,11 @@ export const MenuManagement: React.FC = () => { {/* 좌측 사이드바 - 메뉴 타입 선택 (20%) */}
-

{getUITextSync("menu.type.title")}

-
+ + + {getUITextSync("menu.type.title")} + + {
-
+ +
{/* 우측 메인 영역 - 메뉴 목록 (80%) */}
-
-

- {getMenuTypeString()} {getUITextSync("menu.list.title")} -

-
- - {/* 검색 및 필터 영역 */} -
+ + + + {getMenuTypeString()} {getUITextSync("menu.list.title")} + + + + {/* 검색 및 필터 영역 */} +
@@ -997,52 +1002,54 @@ export const MenuManagement: React.FC = () => {
-
+
-
-
-
- {getUITextSync("menu.list.total", { count: getCurrentMenus().length })} -
-
- - {selectedMenus.size > 0 && ( - + {selectedMenus.size > 0 && ( + )} - - )} +
+
+
-
- -
+ +
@@ -1050,8 +1057,15 @@ export const MenuManagement: React.FC = () => { {/* 화면 할당 탭 */} - - + + + + 화면 할당 + + + + + diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index 7d537098..5ac4c6cb 100644 --- a/frontend/components/layout/AppLayout.tsx +++ b/frontend/components/layout/AppLayout.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, Suspense } from "react"; +import { useState, Suspense, useEffect } from "react"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { Button } from "@/components/ui/button"; import { @@ -197,8 +197,27 @@ function AppLayoutInner({ children }: AppLayoutProps) { const searchParams = useSearchParams(); const { user, logout, refreshUserData } = useAuth(); const { userMenus, adminMenus, loading, refreshMenus } = useMenu(); - const [sidebarOpen, setSidebarOpen] = useState(false); + const [sidebarOpen, setSidebarOpen] = useState(true); const [expandedMenus, setExpandedMenus] = useState>(new Set()); + const [isMobile, setIsMobile] = useState(false); + + // 화면 크기 감지 및 사이드바 초기 상태 설정 + useEffect(() => { + const checkIsMobile = () => { + const mobile = window.innerWidth < 1024; // lg 브레이크포인트 + setIsMobile(mobile); + // 모바일에서만 사이드바를 닫음 + if (mobile) { + setSidebarOpen(false); + } else { + setSidebarOpen(true); + } + }; + + checkIsMobile(); + window.addEventListener('resize', checkIsMobile); + return () => window.removeEventListener('resize', checkIsMobile); + }, []); // 프로필 관련 로직 const { @@ -253,15 +272,10 @@ function AppLayoutInner({ children }: AppLayoutProps) { ? `/screens/${firstScreen.screenId}?mode=admin` : `/screens/${firstScreen.screenId}`; - console.log("🎯 메뉴에서 화면으로 이동:", { - menuName: menu.name, - screenId: firstScreen.screenId, - isAdminMode, - targetPath: screenPath, - }); - router.push(screenPath); - setSidebarOpen(false); + if (isMobile) { + setSidebarOpen(false); + } return; } } catch (error) { @@ -271,10 +285,11 @@ function AppLayoutInner({ children }: AppLayoutProps) { // 할당된 화면이 없고 URL이 있으면 기존 URL로 이동 if (menu.url && menu.url !== "#") { router.push(menu.url); - setSidebarOpen(false); + if (isMobile) { + setSidebarOpen(false); + } } else { // URL도 없고 할당된 화면도 없으면 경고 메시지 - console.warn("메뉴에 URL이나 할당된 화면이 없습니다:", menu); toast.warning("이 메뉴에는 연결된 페이지나 화면이 없습니다."); } } @@ -295,7 +310,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { await logout(); router.push("/login"); } catch (error) { - console.error("로그아웃 실패:", error); + // 로그아웃 실패 시 처리 } }; @@ -306,7 +321,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { return (
0 ? "ml-6" : ""}`} onClick={() => handleMenuClick(menu)} > -
+
{menu.icon} - {menu.name} + {menu.name}
{menu.hasChildren && (
@@ -339,8 +354,10 @@ function AppLayoutInner({ children }: AppLayoutProps) { }`} onClick={() => handleMenuClick(child)} > - {child.icon} - {child.name} +
+ {child.icon} + {child.name} +
))}
@@ -369,22 +386,29 @@ function AppLayoutInner({ children }: AppLayoutProps) { {/* MainHeader 컴포넌트 사용 */} setSidebarOpen(!sidebarOpen)} + onSidebarToggle={() => { + // 모바일에서만 토글 동작 + if (isMobile) { + setSidebarOpen(!sidebarOpen); + } + }} onProfileClick={openProfileModal} onLogout={handleLogout} />
{/* 모바일 사이드바 오버레이 */} - {sidebarOpen && ( + {sidebarOpen && isMobile && (
setSidebarOpen(false)} /> )} {/* 왼쪽 사이드바 */} {/* 가운데 컨텐츠 영역 */} -
{children}
+
{children}
{/* 프로필 수정 모달 */}