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/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..dd48479d 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 ? ( // 캔버스 컴포넌트들을 정확한 해상도로 표시
{ - 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/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index 5ac4c6cb..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) => { @@ -451,8 +452,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
- {/* 가운데 컨텐츠 영역 */} -
{children}
+ {/* 가운데 컨텐츠 영역 - overflow 문제 해결 */} +
{children}
{/* 프로필 수정 모달 */} 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 000ebc44..a0f6ad09 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, // 웹타입 정보 추가 @@ -1803,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; } @@ -1998,22 +2023,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, @@ -3144,9 +3159,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD isSaving={isSaving} /> - {/* 메인 캔버스 영역 (스크롤 가능한 컨테이너) */} -
- {/* 해상도 정보 표시 */} + {/* 메인 캔버스 영역 (스크롤 가능한 컨테이너) - 좌우 최소화, 위아래 넉넉한 여유 */} +
+ {/* 해상도 정보 표시 - 적당한 여백 */}
@@ -3155,11 +3170,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
- {/* 실제 작업 캔버스 (해상도 크기) */} + {/* 실제 작업 캔버스 (해상도 크기) - 반응형 개선 */}
{ - 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/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/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/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/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)} +
+
+ ); + })} +
+ ); +}; diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index a3d63041..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; @@ -390,7 +391,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 +464,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 +643,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 +662,7 @@ export const TableListComponent: React.FC = ({ // 상세설정에 현재 페이지 정보 알림 (필요한 경우) if (onConfigChange && tableConfig.pagination) { - console.log("📤 테이블에서 페이지 변경을 상세설정에 알림:", newPage); + // console.log("📤 테이블에서 페이지 변경을 상세설정에 알림:", newPage); onConfigChange({ ...tableConfig, pagination: { @@ -670,7 +671,7 @@ export const TableListComponent: React.FC = ({ }, }); } else if (!onConfigChange) { - console.warn("⚠️ onConfigChange가 정의되지 않음 - 페이지 변경 상세설정과 연동 불가"); + // console.warn("⚠️ onConfigChange가 정의되지 않음 - 페이지 변경 상세설정과 연동 불가"); } }; @@ -839,14 +840,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 +891,7 @@ export const TableListComponent: React.FC = ({ }) .sort((a, b) => a.order - b.order); } else { - console.log("🎯 사용할 컬럼이 없음"); + // console.log("🎯 사용할 컬럼이 없음"); return []; } @@ -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 컬럼 사용
@@ -1522,15 +1546,15 @@ export const TableListComponent: React.FC = ({ + handleNestedChange("cardConfig", "cardsPerRow", parseInt(value)) + } + > + + + + + 1개 + 2개 + 3개 + 4개 + 5개 + 6개 + + +
+ +
+ + + handleNestedChange("cardConfig", "cardSpacing", parseInt(e.target.value)) + } + min="0" + max="50" + /> +
+
+ +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ + handleNestedChange("cardConfig", "showActions", checked as boolean) + } + /> + +
+
+
+ )} + + + 연결된 테이블 @@ -870,8 +1063,16 @@ export const TableListConfigPanel: React.FC = ({