From cd84d9033c01f11b7a8bb74a2de212805d08e0f3 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Thu, 25 Sep 2025 14:22:30 +0900 Subject: [PATCH 1/4] =?UTF-8?q?uiux=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/admin/batch-management/page.tsx | 2 +- .../admin/collection-management/page.tsx | 2 +- frontend/app/(main)/admin/commonCode/page.tsx | 2 +- frontend/app/(main)/admin/company/page.tsx | 2 +- .../admin/dataflow/edit/[diagramId]/page.tsx | 2 +- frontend/app/(main)/admin/dataflow/page.tsx | 8 +- .../admin/external-call-configs/page.tsx | 2 +- .../admin/external-connections/page.tsx | 2 +- frontend/app/(main)/admin/i18n/page.tsx | 2 +- frontend/app/(main)/admin/layouts/page.tsx | 2 +- frontend/app/(main)/admin/menu/page.tsx | 2 +- frontend/app/(main)/admin/monitoring/page.tsx | 2 +- frontend/app/(main)/admin/page.tsx | 140 +++++++++++------- frontend/app/(main)/admin/screenMng/page.tsx | 8 +- .../admin/standards/[webType]/edit/page.tsx | 4 +- .../(main)/admin/standards/[webType]/page.tsx | 10 +- .../app/(main)/admin/standards/new/page.tsx | 4 +- frontend/app/(main)/admin/standards/page.tsx | 2 +- frontend/app/(main)/admin/tableMng/page.tsx | 2 +- frontend/app/(main)/admin/templates/page.tsx | 4 +- frontend/app/(main)/admin/userMng/page.tsx | 2 +- .../app/(main)/admin/validation-demo/page.tsx | 2 +- frontend/app/(main)/main/page.tsx | 2 +- frontend/app/(main)/multilang/page.tsx | 2 +- frontend/app/(main)/page.tsx | 2 +- .../app/(main)/screens/[screenId]/page.tsx | 2 +- frontend/components/layout/AppLayout.tsx | 4 +- frontend/components/screen/ScreenDesigner.tsx | 10 +- 28 files changed, 133 insertions(+), 97 deletions(-) diff --git a/frontend/app/(main)/admin/batch-management/page.tsx b/frontend/app/(main)/admin/batch-management/page.tsx index 9e48a097..cf067d14 100644 --- a/frontend/app/(main)/admin/batch-management/page.tsx +++ b/frontend/app/(main)/admin/batch-management/page.tsx @@ -186,7 +186,7 @@ export default function BatchManagementPage() { return (
-
+
{/* 헤더 */}
diff --git a/frontend/app/(main)/admin/collection-management/page.tsx b/frontend/app/(main)/admin/collection-management/page.tsx index 8320caac..6523b1d0 100644 --- a/frontend/app/(main)/admin/collection-management/page.tsx +++ b/frontend/app/(main)/admin/collection-management/page.tsx @@ -163,7 +163,7 @@ export default function CollectionManagementPage() { return (
-
+
{/* 헤더 */}
diff --git a/frontend/app/(main)/admin/commonCode/page.tsx b/frontend/app/(main)/admin/commonCode/page.tsx index 6d5eba31..bdb435f7 100644 --- a/frontend/app/(main)/admin/commonCode/page.tsx +++ b/frontend/app/(main)/admin/commonCode/page.tsx @@ -12,7 +12,7 @@ export default function CommonCodeManagementPage() { return (
-
+
{/* 페이지 제목 */}
diff --git a/frontend/app/(main)/admin/company/page.tsx b/frontend/app/(main)/admin/company/page.tsx index 645470eb..c24a3e10 100644 --- a/frontend/app/(main)/admin/company/page.tsx +++ b/frontend/app/(main)/admin/company/page.tsx @@ -6,7 +6,7 @@ import { CompanyManagement } from "@/components/admin/CompanyManagement"; export default function CompanyPage() { return (
-
+
{/* 페이지 제목 */}
diff --git a/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx b/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx index fd25cb96..94ea23a8 100644 --- a/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx +++ b/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx @@ -59,7 +59,7 @@ export default function DataFlowEditPage() { } return ( -
+
{/* 헤더 */}
diff --git a/frontend/app/(main)/admin/dataflow/page.tsx b/frontend/app/(main)/admin/dataflow/page.tsx index de70ff1a..a9b3cde6 100644 --- a/frontend/app/(main)/admin/dataflow/page.tsx +++ b/frontend/app/(main)/admin/dataflow/page.tsx @@ -77,7 +77,7 @@ export default function DataFlowPage() { return (
-
+
{/* 페이지 제목 */}
@@ -93,10 +93,10 @@ export default function DataFlowPage() {
{/* 단계별 내용 */} -
+
{/* 관계도 목록 단계 */} {currentStep === "list" && ( -
+

{stepConfig.list.title}

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

{stepConfig.design.title}

diff --git a/frontend/app/(main)/admin/external-call-configs/page.tsx b/frontend/app/(main)/admin/external-call-configs/page.tsx index e3755083..805220ca 100644 --- a/frontend/app/(main)/admin/external-call-configs/page.tsx +++ b/frontend/app/(main)/admin/external-call-configs/page.tsx @@ -162,7 +162,7 @@ export default function ExternalCallConfigsPage() { return (
-
+
{/* 페이지 헤더 */}
diff --git a/frontend/app/(main)/admin/external-connections/page.tsx b/frontend/app/(main)/admin/external-connections/page.tsx index 96dd64c4..802a2fea 100644 --- a/frontend/app/(main)/admin/external-connections/page.tsx +++ b/frontend/app/(main)/admin/external-connections/page.tsx @@ -221,7 +221,7 @@ export default function ExternalConnectionsPage() { return (
-
+
{/* 페이지 제목 */}
diff --git a/frontend/app/(main)/admin/i18n/page.tsx b/frontend/app/(main)/admin/i18n/page.tsx index bb7308e2..48655de7 100644 --- a/frontend/app/(main)/admin/i18n/page.tsx +++ b/frontend/app/(main)/admin/i18n/page.tsx @@ -5,7 +5,7 @@ import MultiLang from "@/components/admin/MultiLang"; export default function I18nPage() { return (
-
+
diff --git a/frontend/app/(main)/admin/layouts/page.tsx b/frontend/app/(main)/admin/layouts/page.tsx index eb5b2aff..6f7da62a 100644 --- a/frontend/app/(main)/admin/layouts/page.tsx +++ b/frontend/app/(main)/admin/layouts/page.tsx @@ -221,7 +221,7 @@ export default function LayoutManagementPage() { return (
-
+
{/* 페이지 제목 */}
diff --git a/frontend/app/(main)/admin/menu/page.tsx b/frontend/app/(main)/admin/menu/page.tsx index 3d5548cc..9c2c17c8 100644 --- a/frontend/app/(main)/admin/menu/page.tsx +++ b/frontend/app/(main)/admin/menu/page.tsx @@ -5,7 +5,7 @@ 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 2f028639..ac70e9a4 100644 --- a/frontend/app/(main)/admin/monitoring/page.tsx +++ b/frontend/app/(main)/admin/monitoring/page.tsx @@ -6,7 +6,7 @@ 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 8735d7f6..7914f412 100644 --- a/frontend/app/(main)/admin/page.tsx +++ b/frontend/app/(main)/admin/page.tsx @@ -1,18 +1,27 @@ -import { Users, Shield, Settings, BarChart3, Palette, Layout, Database, Package } from "lucide-react"; +import { + Users, Shield, Settings, BarChart3, Palette, Layout, Database, Package +} from "lucide-react"; import Link from "next/link"; + /** * 관리자 메인 페이지 */ export default function AdminPage() { return (
-
- {/* 관리자 기능 카드들 */} -
+
+ + {/* 주요 관리 기능 */} +
+
+

주요 관리 기능

+

시스템의 핵심 관리 기능들을 제공합니다

+
+
-
+
@@ -25,8 +34,8 @@ export default function AdminPage() {
-
- +
+

권한 관리

@@ -37,8 +46,8 @@ export default function AdminPage() {
-
- +
+

시스템 설정

@@ -49,8 +58,8 @@ export default function AdminPage() {
-
- +
+

통계 및 리포트

@@ -62,7 +71,7 @@ export default function AdminPage() {
-
+
@@ -72,17 +81,21 @@ export default function AdminPage() {
+
{/* 표준 관리 섹션 */} -
-

표준 관리

+
+
+

표준 관리

+

시스템 표준 및 컴포넌트를 통합 관리합니다

+
- -
+ +
-
- +
+

웹타입 관리

@@ -92,11 +105,11 @@ export default function AdminPage() {
- -
+ +
-
- +
+

템플릿 관리

@@ -106,11 +119,11 @@ export default function AdminPage() {
- -
+ +
-
- +
+

테이블 관리

@@ -120,11 +133,11 @@ export default function AdminPage() {
- -
+ +
-
- +
+

컴포넌트 관리

@@ -136,31 +149,54 @@ export default function AdminPage() {
- {/* 최근 활동 */} -
-

최근 관리자 활동

-
-
-
-

새로운 사용자 추가

-

김철수 사용자가 생성되었습니다.

+ {/* 빠른 액세스 */} +
+
+

빠른 액세스

+

자주 사용하는 관리 기능에 빠르게 접근할 수 있습니다

+
+
+ +
+
+
+ +
+
+

메뉴 관리

+

시스템 메뉴 및 네비게이션 설정

+
+
- 2분 전 -
-
-
-

권한 변경

-

이영희 사용자의 권한이 수정되었습니다.

+ + + +
+
+
+ +
+
+

외부 연결 관리

+

외부 데이터베이스 연결 설정

+
+
- 15분 전 -
-
-
-

시스템 설정 변경

-

비밀번호 정책이 업데이트되었습니다.

+ + + +
+
+
+ +
+
+

공통 코드 관리

+

시스템 공통 코드 및 설정

+
+
- 1시간 전 -
+
diff --git a/frontend/app/(main)/admin/screenMng/page.tsx b/frontend/app/(main)/admin/screenMng/page.tsx index 2002d364..88dff27f 100644 --- a/frontend/app/(main)/admin/screenMng/page.tsx +++ b/frontend/app/(main)/admin/screenMng/page.tsx @@ -67,7 +67,7 @@ export default function ScreenManagementPage() { return (
-
+
{/* 페이지 제목 */}
@@ -80,7 +80,7 @@ export default function ScreenManagementPage() {
{/* 화면 목록 단계 */} {currentStep === "list" && ( -
+

{stepConfig.list.title}

diff --git a/frontend/app/(main)/page.tsx b/frontend/app/(main)/page.tsx index 2ec94790..ccc4bba3 100644 --- a/frontend/app/(main)/page.tsx +++ b/frontend/app/(main)/page.tsx @@ -1,6 +1,6 @@ export default function MainHomePage() { return ( -
+
{/* 대시보드 컨텐츠 */}

WACE 솔루션에 오신 것을 환영합니다!

diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index e5b622a6..86dd77df 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -147,7 +147,7 @@ export default function ScreenViewPage() { const screenHeight = layout?.screenResolution?.height || 800; return ( -
+
{layout && layout.components.length > 0 ? ( // 캔버스 컴포넌트들을 정확한 해상도로 표시
- {/* 가운데 컨텐츠 영역 */} -
{children}
+ {/* 가운데 컨텐츠 영역 - overflow 문제 해결 */} +
{children}
{/* 프로필 수정 모달 */} diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 000ebc44..79d6daae 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -3144,9 +3144,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD isSaving={isSaving} /> - {/* 메인 캔버스 영역 (스크롤 가능한 컨테이너) */} -
- {/* 해상도 정보 표시 */} + {/* 메인 캔버스 영역 (스크롤 가능한 컨테이너) - 좌우 최소화, 위아래 넉넉한 여유 */} +
+ {/* 해상도 정보 표시 - 적당한 여백 */}
@@ -3155,11 +3155,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
- {/* 실제 작업 캔버스 (해상도 크기) */} + {/* 실제 작업 캔버스 (해상도 크기) - 반응형 개선 */}
Date: Thu, 25 Sep 2025 16:22:02 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=EC=BD=98=EC=86=94=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=EC=B2=98=EB=A6=AC,=20=ED=99=94=EB=A9=B4=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=AA=BD=20=EC=BB=AC=EB=9F=BC=EC=88=98,=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/layout/AppLayout.tsx | 3 +- .../screen/RealtimePreviewDynamic.tsx | 4 +- frontend/components/screen/ScreenDesigner.tsx | 64 ++++++++++++------- frontend/hooks/usePanelState.ts | 5 +- .../button-primary/ButtonPrimaryComponent.tsx | 31 ++++----- .../table-list/TableListComponent.tsx | 44 ++++++------- .../table-list/TableListConfigPanel.tsx | 62 +++++++++++------- .../registry/components/table-list/index.ts | 2 +- .../text-input/TextInputComponent.tsx | 51 +++++++-------- frontend/lib/utils/gridUtils.ts | 8 +-- frontend/types/screen-management.ts | 25 +++++++- frontend/types/unified-core.ts | 2 + 12 files changed, 179 insertions(+), 122 deletions(-) diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index 9e484905..959ba363 100644 --- a/frontend/components/layout/AppLayout.tsx +++ b/frontend/components/layout/AppLayout.tsx @@ -240,7 +240,8 @@ function AppLayoutInner({ children }: AppLayoutProps) { const isAdminMode = pathname.startsWith("/admin") || searchParams.get("mode") === "admin"; // 현재 모드에 따라 표시할 메뉴 결정 - const currentMenus = isAdminMode ? adminMenus : userMenus; + // 관리자 모드에서는 관리자 메뉴 + 사용자 메뉴(툴 생성 메뉴 포함)를 모두 표시 + const currentMenus = isAdminMode ? [...adminMenus, ...userMenus] : userMenus; // 메뉴 토글 함수 const toggleMenu = (menuId: string) => { diff --git a/frontend/components/screen/RealtimePreviewDynamic.tsx b/frontend/components/screen/RealtimePreviewDynamic.tsx index 5260b3e5..6ebd8b5a 100644 --- a/frontend/components/screen/RealtimePreviewDynamic.tsx +++ b/frontend/components/screen/RealtimePreviewDynamic.tsx @@ -92,10 +92,10 @@ export const RealtimePreviewDynamic: React.FC = ({ left: `${position.x}px`, top: `${position.y}px`, width: component.componentConfig?.type === "table-list" - ? `${Math.max(size?.width || 400, 400)}px` // table-list는 최소 400px + ? `${Math.max(size?.width || 120, 120)}px` // table-list 디폴트를 그리드 1컬럼 크기로 축소 (120px) : `${size?.width || 100}px`, height: component.componentConfig?.type === "table-list" - ? `${Math.max(size?.height || 300, 300)}px` // table-list는 최소 300px + ? `${Math.max(size?.height || 200, 200)}px` // table-list 디폴트 높이도 축소 (200px) : `${size?.height || 36}px`, zIndex: component.type === "layout" ? 1 : position.z || 2, // 레이아웃은 z-index 1, 다른 컴포넌트는 2 이상 ...componentStyle, diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 79d6daae..39c8e34e 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -1518,12 +1518,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD defaultConfig: component.defaultConfig, }); - // 카드 디스플레이 컴포넌트의 경우 gridColumns에 맞는 width 계산 + // 컴포넌트별 gridColumns 설정 및 크기 계산 let componentSize = component.defaultSize; const isCardDisplay = component.id === "card-display"; - const gridColumns = isCardDisplay ? 8 : 1; + const isTableList = component.id === "table-list"; + + // 컴포넌트별 기본 그리드 컬럼 수 설정 + const gridColumns = isCardDisplay ? 8 : isTableList ? 1 : 1; - if (isCardDisplay && layout.gridSettings?.snapToGrid && gridInfo) { + if ((isCardDisplay || isTableList) && layout.gridSettings?.snapToGrid && gridInfo) { // gridColumns에 맞는 정확한 너비 계산 const calculatedWidth = calculateWidthFromColumns( gridColumns, @@ -1531,12 +1534,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD layout.gridSettings as GridUtilSettings, ); + // 컴포넌트별 최소 크기 보장 + const minWidth = isTableList ? 120 : isCardDisplay ? 400 : 100; + componentSize = { ...component.defaultSize, - width: calculatedWidth, + width: Math.max(calculatedWidth, minWidth), }; - console.log("📐 카드 디스플레이 초기 크기 자동 조정:", { + console.log(`📐 ${component.name} 초기 크기 자동 조정:`, { componentId: component.id, gridColumns, defaultWidth: component.defaultSize.width, @@ -1554,7 +1560,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD componentType: component.id, // 새 컴포넌트 시스템의 ID (DynamicComponentRenderer용) position: snappedPosition, size: componentSize, - gridColumns: gridColumns, // 카드 디스플레이 컴포넌트는 기본 8그리드 + gridColumns: gridColumns, // 컴포넌트별 그리드 컬럼 수 적용 componentConfig: { type: component.id, // 새 컴포넌트 시스템의 ID 사용 webType: component.webType, // 웹타입 정보 추가 @@ -1998,22 +2004,12 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // 마지막 선택된 컴포넌트를 selectedComponent로 설정 if (!isSelected) { - console.log("🎯 컴포넌트 선택 (다중 모드):", { - componentId: component.id, - componentType: component.type, - webTypeConfig: component.type === "widget" ? (component as any).webTypeConfig : null, - timestamp: new Date().toISOString(), - }); + // console.log("🎯 컴포넌트 선택 (다중 모드):", component.id); setSelectedComponent(component); } } else { // 단일 선택 모드 - console.log("🎯 컴포넌트 선택 (단일 모드):", { - componentId: component.id, - componentType: component.type, - webTypeConfig: component.type === "widget" ? (component as any).webTypeConfig : null, - timestamp: new Date().toISOString(), - }); + // console.log("🎯 컴포넌트 선택 (단일 모드):", component.id); setSelectedComponent(component); setGroupState((prev) => ({ ...prev, @@ -3277,10 +3273,34 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD onZoneClick={handleZoneClick} // 설정 변경 핸들러 (테이블 페이지 크기 등 설정을 상세설정에 반영) onConfigChange={(config) => { - console.log("📤 테이블 설정 변경을 상세설정에 알림:", config); - // 여기서 DetailSettingsPanel의 상태를 업데이트하거나 - // 컴포넌트의 componentConfig를 업데이트할 수 있습니다 - // TODO: 실제 구현은 DetailSettingsPanel과의 연동 필요 + console.log("📤 테이블 설정 변경을 상세설정에 반영:", config); + + // 컴포넌트의 componentConfig 업데이트 + const updatedComponents = layout.components.map(comp => { + if (comp.id === component.id) { + return { + ...comp, + componentConfig: { + ...comp.componentConfig, + ...config + } + }; + } + return comp; + }); + + const newLayout = { + ...layout, + components: updatedComponents + }; + + setLayout(newLayout); + saveToHistory(newLayout); + + console.log("✅ 컴포넌트 설정 업데이트 완료:", { + componentId: component.id, + updatedConfig: config + }); }} > {/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 (레이아웃은 독립적으로 렌더링) */} diff --git a/frontend/hooks/usePanelState.ts b/frontend/hooks/usePanelState.ts index a2e08a2a..407a6953 100644 --- a/frontend/hooks/usePanelState.ts +++ b/frontend/hooks/usePanelState.ts @@ -71,10 +71,7 @@ export const usePanelState = (panels: PanelConfig[]) => { // 패널 열기 const openPanel = useCallback((panelId: string) => { - console.log("📂 패널 열기:", { - panelId, - timestamp: new Date().toISOString(), - }); + // console.log("📂 패널 열기:", panelId); setPanelStates((prev) => ({ ...prev, [panelId]: { diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index caf2d5f5..b9eab149 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -169,20 +169,21 @@ export const ButtonPrimaryComponent: React.FC = ({ }; } - console.log("🔧 버튼 컴포넌트 설정:", { - originalConfig: componentConfig, - processedConfig, - actionConfig: processedConfig.action, - webTypeConfig: component.webTypeConfig, - enableDataflowControl: component.webTypeConfig?.enableDataflowControl, - dataflowConfig: component.webTypeConfig?.dataflowConfig, - screenId, - tableName, - onRefresh, - onClose, - selectedRows, - selectedRowsData, - }); + // 디버그 로그 (필요시 주석 해제) + // console.log("🔧 버튼 컴포넌트 설정:", { + // originalConfig: componentConfig, + // processedConfig, + // actionConfig: processedConfig.action, + // webTypeConfig: component.webTypeConfig, + // enableDataflowControl: component.webTypeConfig?.enableDataflowControl, + // dataflowConfig: component.webTypeConfig?.dataflowConfig, + // screenId, + // tableName, + // onRefresh, + // onClose, + // selectedRows, + // selectedRowsData, + // }); // 스타일 계산 (위치는 RealtimePreviewDynamic에서 처리하므로 제외) const componentStyle: React.CSSProperties = { @@ -203,7 +204,7 @@ export const ButtonPrimaryComponent: React.FC = ({ // 실제 액션 실행 함수 const executeAction = async (actionConfig: any, context: ButtonActionContext) => { - console.log("🚀 executeAction 시작:", { actionConfig, context }); + // console.log("🚀 executeAction 시작:", { actionConfig, context }); try { // 기존 토스트가 있다면 먼저 제거 diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index a3d63041..e78d5c21 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -390,7 +390,7 @@ export const TableListComponent: React.FC = ({ console.log("🔗 Entity 조인 컬럼:", entityJoinColumns); console.log("🔗 조인 탭 컬럼:", joinTabColumns); console.log("🔗 추가 Entity 조인 컬럼:", additionalJoinColumns); - console.log("🎯 화면별 엔티티 설정:", screenEntityConfigs); + // console.log("🎯 화면별 엔티티 설정:", screenEntityConfigs); const result = await entityJoinApi.getTableDataWithJoins(tableConfig.selectedTable, { page: currentPage, @@ -463,10 +463,10 @@ export const TableListComponent: React.FC = ({ }); if (result) { - console.log("🎯 API 응답 결과:", result); - console.log("🎯 데이터 개수:", result.data?.length || 0); - console.log("🎯 전체 페이지:", result.totalPages); - console.log("🎯 총 아이템:", result.total); + // console.log("🎯 API 응답 결과:", result); + // console.log("🎯 데이터 개수:", result.data?.length || 0); + // console.log("🎯 전체 페이지:", result.totalPages); + // console.log("🎯 총 아이템:", result.total); setData(result.data || []); setTotalPages(result.totalPages || 1); setTotalItems(result.total || 0); @@ -642,9 +642,9 @@ export const TableListComponent: React.FC = ({ // 🎯 표시할 컬럼 상태 업데이트 setDisplayColumns(processedColumns); - console.log("🎯 displayColumns 업데이트됨:", processedColumns); - console.log("🎯 데이터 개수:", result.data?.length || 0); - console.log("🎯 전체 데이터:", result.data); + // console.log("🎯 displayColumns 업데이트됨:", processedColumns); + // console.log("🎯 데이터 개수:", result.data?.length || 0); + // console.log("🎯 전체 데이터:", result.data); } } catch (err) { console.error("테이블 데이터 로딩 오류:", err); @@ -661,7 +661,7 @@ export const TableListComponent: React.FC = ({ // 상세설정에 현재 페이지 정보 알림 (필요한 경우) if (onConfigChange && tableConfig.pagination) { - console.log("📤 테이블에서 페이지 변경을 상세설정에 알림:", newPage); + // console.log("📤 테이블에서 페이지 변경을 상세설정에 알림:", newPage); onConfigChange({ ...tableConfig, pagination: { @@ -670,7 +670,7 @@ export const TableListComponent: React.FC = ({ }, }); } else if (!onConfigChange) { - console.warn("⚠️ onConfigChange가 정의되지 않음 - 페이지 변경 상세설정과 연동 불가"); + // console.warn("⚠️ onConfigChange가 정의되지 않음 - 페이지 변경 상세설정과 연동 불가"); } }; @@ -839,14 +839,14 @@ export const TableListComponent: React.FC = ({ useEffect(() => { // 페이지 크기 동기화 if (tableConfig.pagination?.pageSize && tableConfig.pagination.pageSize !== localPageSize) { - console.log("🔄 상세설정에서 페이지 크기 변경 감지:", tableConfig.pagination.pageSize); + // console.log("🔄 상세설정에서 페이지 크기 변경 감지:", tableConfig.pagination.pageSize); setLocalPageSize(tableConfig.pagination.pageSize); setCurrentPage(1); // 페이지를 1로 리셋 } // 현재 페이지 동기화 (상세설정에서 페이지를 직접 변경한 경우) if (tableConfig.pagination?.currentPage && tableConfig.pagination.currentPage !== currentPage) { - console.log("🔄 상세설정에서 현재 페이지 변경 감지:", tableConfig.pagination.currentPage); + // console.log("🔄 상세설정에서 현재 페이지 변경 감지:", tableConfig.pagination.currentPage); setCurrentPage(tableConfig.pagination.currentPage); } }, [tableConfig.pagination?.pageSize, tableConfig.pagination?.currentPage]); @@ -890,7 +890,7 @@ export const TableListComponent: React.FC = ({ }) .sort((a, b) => a.order - b.order); } else { - console.log("🎯 사용할 컬럼이 없음"); + // console.log("🎯 사용할 컬럼이 없음"); return []; } @@ -1522,15 +1522,15 @@ export const TableListComponent: React.FC = ({ handleNestedChange("pagination", "pageSize", parseInt(value))} + onValueChange={(value) => { + // console.log("🎯 상세설정에서 페이지 크기 변경:", { + // from: config.pagination?.pageSize, + // to: parseInt(value), + // currentConfigPageSize: config.pagination?.pageSize + // }); + handleNestedChange("pagination", "pageSize", parseInt(value)); + }} > diff --git a/frontend/lib/registry/components/table-list/index.ts b/frontend/lib/registry/components/table-list/index.ts index fe3eb7ff..5236b80f 100644 --- a/frontend/lib/registry/components/table-list/index.ts +++ b/frontend/lib/registry/components/table-list/index.ts @@ -83,7 +83,7 @@ export const TableListDefinition = createComponentDefinition({ // 데이터 로딩 autoLoad: true, }, - defaultSize: { width: 800, height: 960 }, + defaultSize: { width: 120, height: 200 }, // 그리드 1컬럼 크기로 축소 configPanel: TableListConfigPanel, icon: "Table", tags: ["테이블", "데이터", "목록", "그리드"], diff --git a/frontend/lib/registry/components/text-input/TextInputComponent.tsx b/frontend/lib/registry/components/text-input/TextInputComponent.tsx index f4fe7a9e..54caec16 100644 --- a/frontend/lib/registry/components/text-input/TextInputComponent.tsx +++ b/frontend/lib/registry/components/text-input/TextInputComponent.tsx @@ -58,21 +58,22 @@ export const TextInputComponent: React.FC = ({ } : autoGeneration; - console.log("🔧 텍스트 입력 컴포넌트 설정:", { - config, - componentConfig, - component: component, - autoGeneration, - testAutoGeneration, - isTestMode: component.label?.toLowerCase().includes("test"), - isHidden, - isInteractive, - formData, - columnName: component.columnName, - currentFormValue: formData?.[component.columnName], - componentValue: component.value, - autoGeneratedValue, - }); + // 디버그 로그 (필요시 주석 해제) + // console.log("🔧 텍스트 입력 컴포넌트 설정:", { + // config, + // componentConfig, + // component: component, + // autoGeneration, + // testAutoGeneration, + // isTestMode: component.label?.toLowerCase().includes("test"), + // isHidden, + // isInteractive, + // formData, + // columnName: component.columnName, + // currentFormValue: formData?.[component.columnName], + // componentValue: component.value, + // autoGeneratedValue, + // }); // 자동생성 값 생성 (컴포넌트 마운트 시 또는 폼 데이터 변경 시) useEffect(() => { @@ -248,18 +249,18 @@ export const TextInputComponent: React.FC = ({ onDragEnd={onDragEnd} onChange={(e) => { const newValue = e.target.value; - console.log("🎯 TextInputComponent onChange 호출:", { - componentId: component.id, - columnName: component.columnName, - newValue, - isInteractive, - hasOnFormDataChange: !!onFormDataChange, - hasOnChange: !!props.onChange, - }); + // console.log("🎯 TextInputComponent onChange 호출:", { + // componentId: component.id, + // columnName: component.columnName, + // newValue, + // isInteractive, + // hasOnFormDataChange: !!onFormDataChange, + // hasOnChange: !!props.onChange, + // }); // isInteractive 모드에서는 formData 업데이트 if (isInteractive && onFormDataChange && component.columnName) { - console.log(`📤 TextInputComponent -> onFormDataChange 호출: ${component.columnName} = "${newValue}"`); + // console.log(`📤 TextInputComponent -> onFormDataChange 호출: ${component.columnName} = "${newValue}"`); console.log("🔍 onFormDataChange 함수 정보:", { functionName: onFormDataChange.name, functionString: onFormDataChange.toString().substring(0, 200), @@ -275,7 +276,7 @@ export const TextInputComponent: React.FC = ({ // 기존 onChange 핸들러도 호출 if (props.onChange) { - console.log(`📤 TextInputComponent -> props.onChange 호출: "${newValue}"`); + // console.log(`📤 TextInputComponent -> props.onChange 호출: "${newValue}"`); props.onChange(newValue); } }} diff --git a/frontend/lib/utils/gridUtils.ts b/frontend/lib/utils/gridUtils.ts index 29c1ffab..b23c0ec0 100644 --- a/frontend/lib/utils/gridUtils.ts +++ b/frontend/lib/utils/gridUtils.ts @@ -1,11 +1,5 @@ import { Position, Size } from "@/types/screen"; - -export interface GridSettings { - columns: number; - gap: number; - padding: number; - snapToGrid: boolean; -} +import { GridSettings } from "@/types/screen-management"; export interface GridInfo { columnWidth: number; diff --git a/frontend/types/screen-management.ts b/frontend/types/screen-management.ts index 80aa6e72..1d6734bb 100644 --- a/frontend/types/screen-management.ts +++ b/frontend/types/screen-management.ts @@ -34,6 +34,12 @@ export interface BaseComponent { readonly?: boolean; style?: ComponentStyle; className?: string; + // 새 컴포넌트 시스템에서 필요한 속성들 + gridColumns?: number; // 그리드에서 차지할 컬럼 수 (1-12) + zoneId?: string; // 레이아웃 존 ID + componentConfig?: any; // 컴포넌트별 설정 + componentType?: string; // 새 컴포넌트 시스템의 ID + webTypeConfig?: WebTypeConfig; // 웹타입별 설정 } /** @@ -110,10 +116,20 @@ export interface FileComponent extends BaseComponent { uploadedFiles?: UploadedFile[]; } +/** + * 새로운 컴포넌트 시스템 컴포넌트 + */ +export interface ComponentComponent extends BaseComponent { + type: "component"; + widgetType: WebType; // 웹타입 (기존 호환성) + componentType: string; // 새 컴포넌트 시스템의 ID + componentConfig: any; // 컴포넌트별 설정 +} + /** * 통합 컴포넌트 데이터 타입 */ -export type ComponentData = WidgetComponent | ContainerComponent | GroupComponent | DataTableComponent | FileComponent; +export type ComponentData = WidgetComponent | ContainerComponent | GroupComponent | DataTableComponent | FileComponent | ComponentComponent; // ===== 웹타입별 설정 인터페이스 ===== @@ -370,6 +386,13 @@ export interface GridSettings { color: string; opacity: number; snapToGrid: boolean; + // gridUtils에서 필요한 속성들 추가 + columns: number; + gap: number; + padding: number; + showGrid?: boolean; + gridColor?: string; + gridOpacity?: number; } /** diff --git a/frontend/types/unified-core.ts b/frontend/types/unified-core.ts index bf9d3b18..291c3a5d 100644 --- a/frontend/types/unified-core.ts +++ b/frontend/types/unified-core.ts @@ -136,6 +136,8 @@ export interface CommonStyle { lineHeight?: string; // 라벨 스타일 + labelDisplay?: boolean; // 라벨 표시 여부 + labelText?: string; // 라벨 텍스트 labelFontSize?: string; labelColor?: string; labelFontWeight?: string; From d69feef9da2a6c18ba7933c1a06446d83f9acb6e Mon Sep 17 00:00:00 2001 From: leeheejin Date: Thu, 25 Sep 2025 18:54:12 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=ED=99=94=EB=A9=B4=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9D=98=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8,=20=EC=B9=B4=EB=93=9C=20=EB=94=94=EC=8A=A4=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table-list/CardModeRenderer.tsx | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 frontend/lib/registry/components/table-list/CardModeRenderer.tsx diff --git a/frontend/lib/registry/components/table-list/CardModeRenderer.tsx b/frontend/lib/registry/components/table-list/CardModeRenderer.tsx new file mode 100644 index 00000000..2988f864 --- /dev/null +++ b/frontend/lib/registry/components/table-list/CardModeRenderer.tsx @@ -0,0 +1,224 @@ +"use client"; + +import React from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Eye, Edit, Trash2, MoreHorizontal } from "lucide-react"; +import { CardDisplayConfig, ColumnConfig } from "./types"; + +interface CardModeRendererProps { + data: Record[]; + cardConfig: CardDisplayConfig; + visibleColumns: ColumnConfig[]; + onRowClick?: (row: Record) => void; + onRowSelect?: (row: Record, selected: boolean) => void; + selectedRows?: string[]; + showActions?: boolean; +} + +/** + * 카드 모드 렌더러 + * 테이블 데이터를 카드 형태로 표시 + */ +export const CardModeRenderer: React.FC = ({ + data, + cardConfig, + visibleColumns, + onRowClick, + onRowSelect, + selectedRows = [], + showActions = true, +}) => { + // 기본값 설정 + const config = { + cardsPerRow: 3, + cardSpacing: 16, + showActions: true, + cardHeight: "auto", + ...cardConfig, + }; + + // 카드 그리드 스타일 계산 + const gridStyle: React.CSSProperties = { + display: "grid", + gridTemplateColumns: `repeat(${config.cardsPerRow}, 1fr)`, + gap: `${config.cardSpacing}px`, + padding: `${config.cardSpacing}px`, + }; + + // 카드 높이 스타일 + const cardStyle: React.CSSProperties = { + height: config.cardHeight === "auto" ? "auto" : `${config.cardHeight}px`, + cursor: onRowClick ? "pointer" : "default", + }; + + // 컬럼 값 가져오기 함수 + const getColumnValue = (row: Record, columnName?: string): string => { + if (!columnName || !row) return ""; + return String(row[columnName] || ""); + }; + + // 액션 버튼 렌더링 + const renderActions = (row: Record) => { + if (!showActions || !config.showActions) return null; + + return ( +
+ + + + +
+ ); + }; + + // 데이터가 없는 경우 + if (!data || data.length === 0) { + return ( +
+
+
+
+
표시할 데이터가 없습니다
+
조건을 변경하거나 새로운 데이터를 추가해보세요
+
+ ); + } + + return ( +
+ {data.map((row, index) => { + const idValue = getColumnValue(row, config.idColumn); + const titleValue = getColumnValue(row, config.titleColumn); + const subtitleValue = getColumnValue(row, config.subtitleColumn); + const descriptionValue = getColumnValue(row, config.descriptionColumn); + const imageValue = getColumnValue(row, config.imageColumn); + + const isSelected = selectedRows.includes(idValue); + + return ( + onRowClick?.(row)} + > + +
+
+ + {titleValue || "제목 없음"} + + {subtitleValue && ( +
+ {subtitleValue} +
+ )} +
+ + {/* ID 뱃지 */} + {idValue && ( + + {idValue} + + )} +
+
+ + + {/* 이미지 표시 */} + {imageValue && ( +
+ {titleValue} { + const target = e.target as HTMLImageElement; + target.style.display = "none"; + }} + /> +
+ )} + + {/* 설명 표시 */} + {descriptionValue && ( +
+ {descriptionValue} +
+ )} + + {/* 추가 필드들 표시 (선택적) */} +
+ {visibleColumns + .filter(col => + col.columnName !== config.idColumn && + col.columnName !== config.titleColumn && + col.columnName !== config.subtitleColumn && + col.columnName !== config.descriptionColumn && + col.columnName !== config.imageColumn && + col.columnName !== "__checkbox__" && + col.visible + ) + .slice(0, 3) // 최대 3개 추가 필드만 표시 + .map((col) => { + const value = getColumnValue(row, col.columnName); + if (!value) return null; + + return ( +
+ {col.displayName}: + {value} +
+ ); + })} +
+ + {/* 액션 버튼들 */} + {renderActions(row)} +
+
+ ); + })} +
+ ); +}; From c28e27f3e83422c04f1df1b84a1ba95fef8b68c6 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Thu, 25 Sep 2025 18:54:25 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=ED=99=94=EB=A9=B4=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9D=98=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8,=20=EC=B9=B4=EB=93=9C=20=EB=94=94=EC=8A=A4=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(main)/screens/[screenId]/page.tsx | 20 +- frontend/components/screen/ScreenDesigner.tsx | 19 ++ .../screen/panels/ComponentsPanel.tsx | 25 +- .../card-display/CardDisplayComponent.tsx | 245 +++++++++++++++--- .../table-list/TableListComponent.tsx | 24 ++ .../table-list/TableListConfigPanel.tsx | 183 +++++++++++++ .../table-list/TableListRenderer.tsx | 13 + .../registry/components/table-list/index.ts | 14 +- .../registry/components/table-list/types.ts | 25 +- 9 files changed, 512 insertions(+), 56 deletions(-) diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index 86dd77df..dd48479d 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -275,16 +275,16 @@ export default function ScreenViewPage() { zIndex: component.position.z || 1, }} onMouseEnter={() => { - console.log("🎯 할당된 화면 컴포넌트:", { - id: component.id, - type: component.type, - position: component.position, - size: component.size, - styleWidth: component.style?.width, - styleHeight: component.style?.height, - finalWidth: `${component.size.width}px`, - finalHeight: `${component.size.height}px`, - }); + // console.log("🎯 할당된 화면 컴포넌트:", { + // id: component.id, + // type: component.type, + // position: component.position, + // size: component.size, + // styleWidth: component.style?.width, + // styleHeight: component.style?.height, + // finalWidth: `${component.size.width}px`, + // finalHeight: `${component.size.height}px`, + // }); }} > {/* 위젯 컴포넌트가 아닌 경우 DynamicComponentRenderer 사용 */} diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 39c8e34e..a0f6ad09 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -1809,6 +1809,25 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD filters: [], displayFormat: "simple" as const, }; + case "table": + return { + tableName: "", + displayMode: "table" as const, + showHeader: true, + showFooter: true, + pagination: { + enabled: true, + pageSize: 10, + showPageSizeSelector: true, + showPageInfo: true, + showFirstLast: true, + }, + columns: [], + searchable: true, + sortable: true, + filterable: true, + exportable: true, + }; default: return undefined; } diff --git a/frontend/components/screen/panels/ComponentsPanel.tsx b/frontend/components/screen/panels/ComponentsPanel.tsx index d6daef4e..a779d484 100644 --- a/frontend/components/screen/panels/ComponentsPanel.tsx +++ b/frontend/components/screen/panels/ComponentsPanel.tsx @@ -20,7 +20,30 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) { // 레지스트리에서 모든 컴포넌트 조회 const allComponents = useMemo(() => { - return ComponentRegistry.getAllComponents(); + const components = ComponentRegistry.getAllComponents(); + console.log("🔍 ComponentsPanel - 로드된 컴포넌트:", components.map(c => ({ id: c.id, name: c.name, category: c.category }))); + + // 수동으로 table-list 컴포넌트 추가 (임시) + const hasTableList = components.some(c => c.id === 'table-list'); + if (!hasTableList) { + console.log("⚠️ table-list 컴포넌트가 없어서 수동 추가"); + components.push({ + id: "table-list", + name: "테이블 리스트", + nameEng: "TableList Component", + description: "데이터베이스 테이블의 데이터를 목록으로 표시하는 컴포넌트", + category: "display", + webType: "text", + defaultConfig: {}, + defaultSize: { width: 800, height: 400 }, + icon: "Table", + tags: ["테이블", "데이터", "목록", "그리드"], + version: "1.0.0", + author: "개발팀", + }); + } + + return components; }, []); // 카테고리별 분류 (input 카테고리 제외) diff --git a/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx b/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx index 2cd69c54..318ca587 100644 --- a/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx +++ b/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx @@ -4,6 +4,10 @@ import React, { useEffect, useState, useMemo } from "react"; import { ComponentRendererProps } from "@/types/component"; import { CardDisplayConfig } from "./types"; import { tableTypeApi } from "@/lib/api/screen"; +import { filterDOMProps } from "@/lib/utils/domPropsFilter"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; export interface CardDisplayComponentProps extends ComponentRendererProps { config?: CardDisplayConfig; @@ -39,6 +43,59 @@ export const CardDisplayComponent: React.FC = ({ const [loadedTableColumns, setLoadedTableColumns] = useState([]); const [loading, setLoading] = useState(false); + // 상세보기 모달 상태 + const [viewModalOpen, setViewModalOpen] = useState(false); + const [selectedData, setSelectedData] = useState(null); + + // 편집 모달 상태 + const [editModalOpen, setEditModalOpen] = useState(false); + const [editData, setEditData] = useState(null); + + // 카드 액션 핸들러 + const handleCardView = (data: any) => { + // console.log("👀 상세보기 클릭:", data); + setSelectedData(data); + setViewModalOpen(true); + }; + + const handleCardEdit = (data: any) => { + // console.log("✏️ 편집 클릭:", data); + setEditData({ ...data }); // 복사본 생성 + setEditModalOpen(true); + }; + + // 편집 폼 데이터 변경 핸들러 + const handleEditFormChange = (key: string, value: string) => { + setEditData((prev: any) => ({ + ...prev, + [key]: value + })); + }; + + // 편집 저장 핸들러 + const handleEditSave = async () => { + // console.log("💾 편집 저장:", editData); + + try { + // TODO: 실제 API 호출로 데이터 업데이트 + // await tableTypeApi.updateTableData(tableName, editData); + + // console.log("✅ 편집 저장 완료"); + alert("✅ 저장되었습니다!"); + + // 모달 닫기 + setEditModalOpen(false); + setEditData(null); + + // 데이터 새로고침 (필요시) + // loadTableData(); + + } catch (error) { + console.error("❌ 편집 저장 실패:", error); + alert("❌ 저장에 실패했습니다."); + } + }; + // 테이블 데이터 로딩 useEffect(() => { const loadTableData = async () => { @@ -48,19 +105,25 @@ export const CardDisplayComponent: React.FC = ({ } // tableName 확인 (props에서 전달받은 tableName 사용) - const tableNameToUse = tableName || component.componentConfig?.tableName; + const tableNameToUse = tableName || component.componentConfig?.tableName || 'user_info'; // 기본 테이블명 설정 if (!tableNameToUse) { - console.log("📋 CardDisplay: 테이블명이 설정되지 않음", { - tableName, - componentTableName: component.componentConfig?.tableName, - }); + // console.log("📋 CardDisplay: 테이블명이 설정되지 않음", { + // tableName, + // componentTableName: component.componentConfig?.tableName, + // }); return; } + // console.log("📋 CardDisplay: 사용할 테이블명", { + // tableName, + // componentTableName: component.componentConfig?.tableName, + // finalTableName: tableNameToUse, + // }); + try { setLoading(true); - console.log(`📋 CardDisplay: ${tableNameToUse} 테이블 데이터 로딩 시작`); + // console.log(`📋 CardDisplay: ${tableNameToUse} 테이블 데이터 로딩 시작`); // 테이블 데이터와 컬럼 정보를 병렬로 로드 const [dataResponse, columnsResponse] = await Promise.all([ @@ -71,13 +134,13 @@ export const CardDisplayComponent: React.FC = ({ tableTypeApi.getColumns(tableNameToUse), ]); - console.log(`📋 CardDisplay: ${tableNameToUse} 데이터 로딩 완료`, { - total: dataResponse.total, - dataLength: dataResponse.data.length, - columnsLength: columnsResponse.length, - sampleData: dataResponse.data.slice(0, 2), - sampleColumns: columnsResponse.slice(0, 3), - }); + // console.log(`📋 CardDisplay: ${tableNameToUse} 데이터 로딩 완료`, { + // total: dataResponse.total, + // dataLength: dataResponse.data.length, + // columnsLength: columnsResponse.length, + // sampleData: dataResponse.data.slice(0, 2), + // sampleColumns: columnsResponse.slice(0, 3), + // }); setLoadedTableData(dataResponse.data); setLoadedTableColumns(columnsResponse); @@ -130,32 +193,32 @@ export const CardDisplayComponent: React.FC = ({ // 표시할 데이터 결정 (로드된 테이블 데이터 우선 사용) const displayData = useMemo(() => { - console.log("📋 CardDisplay: displayData 결정 중", { - dataSource: componentConfig.dataSource, - loadedTableDataLength: loadedTableData.length, - tableDataLength: tableData.length, - staticDataLength: componentConfig.staticData?.length || 0, - }); + // console.log("📋 CardDisplay: displayData 결정 중", { + // dataSource: componentConfig.dataSource, + // loadedTableDataLength: loadedTableData.length, + // tableDataLength: tableData.length, + // staticDataLength: componentConfig.staticData?.length || 0, + // }); // 로드된 테이블 데이터가 있으면 항상 우선 사용 (dataSource 설정 무시) if (loadedTableData.length > 0) { - console.log("📋 CardDisplay: 로드된 테이블 데이터 사용", loadedTableData.slice(0, 2)); + // console.log("📋 CardDisplay: 로드된 테이블 데이터 사용", loadedTableData.slice(0, 2)); return loadedTableData; } // props로 전달받은 테이블 데이터가 있으면 사용 if (tableData.length > 0) { - console.log("📋 CardDisplay: props 테이블 데이터 사용", tableData.slice(0, 2)); + // console.log("📋 CardDisplay: props 테이블 데이터 사용", tableData.slice(0, 2)); return tableData; } if (componentConfig.staticData && componentConfig.staticData.length > 0) { - console.log("📋 CardDisplay: 정적 데이터 사용", componentConfig.staticData.slice(0, 2)); + // console.log("📋 CardDisplay: 정적 데이터 사용", componentConfig.staticData.slice(0, 2)); return componentConfig.staticData; } // 데이터가 없으면 빈 배열 반환 - console.log("📋 CardDisplay: 표시할 데이터가 없음"); + // console.log("📋 CardDisplay: 표시할 데이터가 없음"); return []; }, [componentConfig.dataSource, loadedTableData, tableData, componentConfig.staticData]); @@ -260,23 +323,8 @@ export const CardDisplayComponent: React.FC = ({ } }; - // DOM에 전달하면 안 되는 React-specific props 필터링 - const { - selectedScreen, - onZoneComponentDrop, - onZoneClick, - componentConfig: _componentConfig, - component: _component, - isSelected: _isSelected, - onClick: _onClick, - onDragStart: _onDragStart, - onDragEnd: _onDragEnd, - size: _size, - position: _position, - style: _style, - onRefresh: _onRefresh, // React DOM 속성이 아니므로 필터링 - ...domProps - } = props; + // DOM 안전한 props만 필터링 (filterDOMProps 유틸리티 사용) + const safeDomProps = filterDOMProps(props); return ( <> @@ -301,7 +349,7 @@ export const CardDisplayComponent: React.FC = ({ onClick={handleClick} onDragStart={onDragStart} onDragEnd={onDragEnd} - {...domProps} + {...safeDomProps} >
{displayData.length === 0 ? ( @@ -393,8 +441,24 @@ export const CardDisplayComponent: React.FC = ({ {/* 카드 액션 (선택사항) */}
- - + +
); @@ -402,6 +466,101 @@ export const CardDisplayComponent: React.FC = ({ )}
+ + {/* 상세보기 모달 */} + + + + + 📋 + 상세 정보 + + + + {selectedData && ( +
+
+ {Object.entries(selectedData) + .filter(([key, value]) => value !== null && value !== undefined && value !== '') + .map(([key, value]) => ( +
+
+ {key.replace(/_/g, ' ')} +
+
+ {String(value)} +
+
+ )) + } +
+ +
+ +
+
+ )} +
+
+ + {/* 편집 모달 */} + + + + + ✏️ + 데이터 편집 + + + + {editData && ( +
+
+ {Object.entries(editData) + .filter(([key, value]) => value !== null && value !== undefined) + .map(([key, value]) => ( +
+ + handleEditFormChange(key, e.target.value)} + className="w-full" + placeholder={`${key} 입력`} + /> +
+ )) + } +
+ +
+ + +
+
+ )} +
+
); }; diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index e78d5c21..03754e7f 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -24,6 +24,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import { cn } from "@/lib/utils"; import { AdvancedSearchFilters } from "@/components/screen/filters/AdvancedSearchFilters"; import { SingleTableWithSticky } from "./SingleTableWithSticky"; +import { CardModeRenderer } from "./CardModeRenderer"; export interface TableListComponentProps { component: any; @@ -1290,6 +1291,29 @@ export const TableListComponent: React.FC = ({
{error}
+ ) : tableConfig.displayMode === "card" ? ( + // 카드 모드 렌더링 +
+ { + const rowIndex = data.findIndex(d => d === row); + const rowKey = getRowKey(row, rowIndex); + handleRowSelection(rowKey, selected); + }} + selectedRows={Array.from(selectedRows)} + showActions={tableConfig.actions?.showActions} + /> +
) : needsHorizontalScroll ? ( // 가로 스크롤이 필요한 경우 - 단일 테이블에서 sticky 컬럼 사용
diff --git a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx index e78dcce5..34bf0e2f 100644 --- a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx +++ b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx @@ -10,6 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { TableListConfig, ColumnConfig } from "./types"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { tableTypeApi } from "@/lib/api/screen"; @@ -755,6 +756,188 @@ export const TableListConfigPanel: React.FC = ({ {/* 기본 설정 탭 */} + {/* 표시 모드 설정 */} + + + 표시 모드 + 데이터를 어떤 형태로 표시할지 선택하세요 + + +
+ + handleChange("displayMode", value)} + > +
+ + +
+
+ + +
+
+
+ + {/* 카드 모드 설정 */} + {config.displayMode === "card" && ( +
+
+ + +
+
+ + +
+ +
+ + + handleNestedChange("cardConfig", "cardSpacing", parseInt(e.target.value)) + } + min="0" + max="50" + /> +
+
+ +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ + handleNestedChange("cardConfig", "showActions", checked as boolean) + } + /> + +
+
+
+ )} +
+
+ 연결된 테이블 diff --git a/frontend/lib/registry/components/table-list/TableListRenderer.tsx b/frontend/lib/registry/components/table-list/TableListRenderer.tsx index 84569200..05f75319 100644 --- a/frontend/lib/registry/components/table-list/TableListRenderer.tsx +++ b/frontend/lib/registry/components/table-list/TableListRenderer.tsx @@ -67,3 +67,16 @@ export class TableListRenderer extends AutoRegisteringComponentRenderer { // 자동 등록 실행 TableListRenderer.registerSelf(); + +// 강제 등록 (디버깅용) +if (typeof window !== "undefined") { + setTimeout(() => { + try { + console.log("🔄 TableList 강제 등록 시도..."); + TableListRenderer.registerSelf(); + console.log("✅ TableList 강제 등록 완료"); + } catch (error) { + console.error("❌ TableList 강제 등록 실패:", error); + } + }, 1000); +} diff --git a/frontend/lib/registry/components/table-list/index.ts b/frontend/lib/registry/components/table-list/index.ts index 5236b80f..b846e9f6 100644 --- a/frontend/lib/registry/components/table-list/index.ts +++ b/frontend/lib/registry/components/table-list/index.ts @@ -18,9 +18,21 @@ export const TableListDefinition = createComponentDefinition({ nameEng: "TableList Component", description: "데이터베이스 테이블의 데이터를 목록으로 표시하는 컴포넌트", category: ComponentCategory.DISPLAY, - webType: "table", + webType: "text", component: TableListWrapper, defaultConfig: { + // 표시 모드 설정 + displayMode: "table" as const, + + // 카드 모드 기본 설정 + cardConfig: { + idColumn: "id", + titleColumn: "name", + cardsPerRow: 3, + cardSpacing: 16, + showActions: true, + }, + // 테이블 기본 설정 showHeader: true, showFooter: true, diff --git a/frontend/lib/registry/components/table-list/types.ts b/frontend/lib/registry/components/table-list/types.ts index 1067bade..fc120b7c 100644 --- a/frontend/lib/registry/components/table-list/types.ts +++ b/frontend/lib/registry/components/table-list/types.ts @@ -74,6 +74,21 @@ export interface ColumnConfig { autoGeneration?: AutoGenerationConfig; // 자동생성 설정 } +/** + * 카드 디스플레이 설정 + */ +export interface CardDisplayConfig { + idColumn: string; // ID 컬럼 (사번 등) + titleColumn: string; // 제목 컬럼 (이름 등) + subtitleColumn?: string; // 부제목 컬럼 (부서 등) + descriptionColumn?: string; // 설명 컬럼 + imageColumn?: string; // 이미지 컬럼 + cardsPerRow: number; // 한 행당 카드 수 (기본: 3) + cardSpacing: number; // 카드 간격 (기본: 16px) + showActions: boolean; // 액션 버튼 표시 여부 + cardHeight?: number; // 카드 높이 (기본: auto) +} + /** * 필터 설정 */ @@ -147,6 +162,12 @@ export interface CheckboxConfig { * TableList 컴포넌트 설정 타입 */ export interface TableListConfig extends ComponentConfig { + // 표시 모드 설정 + displayMode?: "table" | "card"; // 기본: "table" + + // 카드 디스플레이 설정 (displayMode가 "card"일 때 사용) + cardConfig?: CardDisplayConfig; + // 테이블 기본 설정 selectedTable?: string; tableName?: string; @@ -175,7 +196,9 @@ export interface TableListConfig extends ComponentConfig { }; // 페이지네이션 - pagination: PaginationConfig; + pagination: PaginationConfig & { + currentPage?: number; // 현재 페이지 (추가) + }; // 필터 설정 filter: FilterConfig;