From 85a1e0c68aa6248611f504cc06c7a2c2c7c64c78 Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 9 Sep 2025 16:14:21 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/controllers/adminController.ts | 6 +- backend-node/src/services/adminService.ts | 11 +- docs/screen-management-dynamic-system-plan.md | 2 - frontend/components/admin/MenuTable.tsx | 2 +- frontend/docs/screen-management-design.md | 141 +++++++++++++++--- frontend/lib/utils/gridUtils.ts | 24 +-- 6 files changed, 144 insertions(+), 42 deletions(-) diff --git a/backend-node/src/controllers/adminController.ts b/backend-node/src/controllers/adminController.ts index fee6205c..cc6b751e 100644 --- a/backend-node/src/controllers/adminController.ts +++ b/backend-node/src/controllers/adminController.ts @@ -31,7 +31,6 @@ export async function getAdminMenus( const paramMap = { userCompanyCode, userLang, - SYSTEM_NAME: "PLM", }; const menuList = await AdminService.getAdminMenuList(paramMap); @@ -84,7 +83,6 @@ export async function getUserMenus( const paramMap = { userCompanyCode, userLang, - SYSTEM_NAME: "PLM", }; const menuList = await AdminService.getUserMenuList(paramMap); @@ -1035,7 +1033,7 @@ export async function saveMenu( writer: req.user?.userId || "admin", regdate: new Date(), status: menuData.status || "active", - system_name: menuData.systemName || "PLM", + system_name: menuData.systemName || null, company_code: menuData.companyCode || "*", lang_key: menuData.langKey || null, lang_key_desc: menuData.langKeyDesc || null, @@ -1101,7 +1099,7 @@ export async function updateMenu( menu_url: menuData.menuUrl || null, menu_desc: menuData.menuDesc || null, status: menuData.status || "active", - system_name: menuData.systemName || "PLM", + system_name: menuData.systemName || null, company_code: menuData.companyCode || "*", lang_key: menuData.langKey || null, lang_key_desc: menuData.langKeyDesc || null, diff --git a/backend-node/src/services/adminService.ts b/backend-node/src/services/adminService.ts index cf827872..d5f8c46a 100644 --- a/backend-node/src/services/adminService.ts +++ b/backend-node/src/services/adminService.ts @@ -11,7 +11,7 @@ export class AdminService { try { logger.info("AdminService.getAdminMenuList 시작 - 파라미터:", paramMap); - const { userLang = "ko", SYSTEM_NAME = "PLM" } = paramMap; + const { userLang = "ko" } = paramMap; // 기존 Java의 selectAdminMenuList 쿼리를 Prisma로 포팅 // WITH RECURSIVE 쿼리를 Prisma의 $queryRaw로 구현 @@ -92,8 +92,11 @@ export class AdminService { MENU.MENU_DESC ) FROM MENU_INFO MENU - WHERE PARENT_OBJ_ID = 0 - AND MENU_TYPE = 0 + WHERE MENU_TYPE = 0 + AND NOT EXISTS ( + SELECT 1 FROM MENU_INFO parent_menu + WHERE parent_menu.OBJID = MENU.PARENT_OBJ_ID + ) UNION ALL @@ -208,7 +211,7 @@ export class AdminService { try { logger.info("AdminService.getUserMenuList 시작 - 파라미터:", paramMap); - const { userLang = "ko", SYSTEM_NAME = "PLM" } = paramMap; + const { userLang = "ko" } = paramMap; // 기존 Java의 selectUserMenuList 쿼리를 Prisma로 포팅 const menuList = await prisma.$queryRaw` diff --git a/docs/screen-management-dynamic-system-plan.md b/docs/screen-management-dynamic-system-plan.md index 0b96e362..5e6c1071 100644 --- a/docs/screen-management-dynamic-system-plan.md +++ b/docs/screen-management-dynamic-system-plan.md @@ -1010,5 +1010,3 @@ After (새로운): - **다중 플랫폼**: 모바일/데스크톱 앱에서도 동일한 시스템 사용 **이제 미래 지향적이고 확장 가능한 화면관리 시스템을 구축할 준비가 완료되었습니다!** 🚀 - - diff --git a/frontend/components/admin/MenuTable.tsx b/frontend/components/admin/MenuTable.tsx index 3b01690a..40b01fa3 100644 --- a/frontend/components/admin/MenuTable.tsx +++ b/frontend/components/admin/MenuTable.tsx @@ -199,7 +199,7 @@ export const MenuTable: React.FC = ({ const parentObjId = menu.parent_obj_id || menu.PARENT_OBJ_ID || ""; return ( - + = ({ component, onUpdateComponent }) => { - // 설정 UI 구현 +// frontend/components/screen/config-panels/MyNewTypeConfigPanel.tsx +export const MyNewTypeConfigPanel = ({ config, onConfigChange }) => { + return ( +
+

새로운 타입 설정

+ + {/* 설정 UI */} +
+ + onConfigChange({ ...config, option1: e.target.value })} + /> +
+ +
+ + +
+
+ ); }; ``` -3. **렌더링 로직 추가** +#### **3단계: 레지스트리에 등록** ```typescript -// RealtimePreview.tsx, InteractiveScreenViewer.tsx -case "새로운타입": - return renderNewWidget(component); +// frontend/lib/utils/availableConfigPanels.ts +import { MyNewTypeConfigPanel } from "@/components/screen/config-panels/MyNewTypeConfigPanel"; + +export const availableConfigPanels = { + // 기존 패널들... + ButtonConfigPanel, + TextTypeConfigPanel, + NumberTypeConfigPanel, + + // 새 패널 추가 + MyNewTypeConfigPanel, // ← 이 한 줄만 추가! +}; ``` -4. **DetailSettingsPanel에 연결** +#### **완료! 🎉** + +- ✅ **PropertiesPanel**: 자동으로 드롭다운에 "새로운 입력 타입" 표시 +- ✅ **DetailSettingsPanel**: 위젯 타입 변경 시 자동으로 설정 패널 표시 +- ✅ **실시간 업데이트**: React key props로 즉시 반영 + +#### **설정 패널이 없는 경우** + +config_panel을 NULL로 설정하거나 레지스트리에 등록하지 않으면 "기본 설정" 메시지가 표시됩니다. + +```sql +-- 설정 패널 없는 간단한 웹타입 +INSERT INTO web_type_standard (web_type, type_name, active) +VALUES ('simple_type', '간단한 타입', 'Y'); +``` + +#### **렌더링 로직 추가 (필요한 경우)** + +새 웹타입이 특별한 렌더링이 필요한 경우에만 추가: ```typescript -case "새로운타입": - return <새로운타입ConfigPanel component={widget} onUpdateComponent={handleUpdate} />; +// RealtimePreviewDynamic.tsx 또는 InteractiveScreenViewer.tsx +case "my_new_type": + return ( + + ); ``` +#### **타입 정의 추가 (TypeScript 지원)** + +```typescript +// types/screen.ts +export type WebType = + | "text" + | "number" + | "date" + | "my_new_type" // ← 새 타입 추가 + | /* 기타 타입들 */; + +export interface MyNewTypeConfig { + option1?: string; + option2?: "default" | "custom"; + // 기타 설정 옵션들 +} + +export type WebTypeConfig = + | TextTypeConfig + | NumberTypeConfig + | MyNewTypeConfig // ← 새 설정 타입 추가 + | /* 기타 설정 타입들 */; +``` + +### 🎯 **핵심 장점** + +- **플러그 앤 플레이**: 코드 수정 최소화 +- **데이터베이스 기반**: 개발자 도구나 어드민에서 웹타입 관리 가능 +- **자동 감지**: 별도 등록 로직 없이 자동으로 시스템에 반영 +- **실시간 업데이트**: React key props로 즉시 설정 변경 반영 + ### 9.2 새로운 템플릿 추가 1. **TemplatesPanel에 템플릿 정의 추가** diff --git a/frontend/lib/utils/gridUtils.ts b/frontend/lib/utils/gridUtils.ts index 49e19d24..20cce383 100644 --- a/frontend/lib/utils/gridUtils.ts +++ b/frontend/lib/utils/gridUtils.ts @@ -50,11 +50,12 @@ export function snapToGrid(position: Position, gridInfo: GridInfo, gridSettings: // 격자 기준으로 위치 계산 const gridX = Math.round((position.x - padding) / (columnWidth + gap)); - const gridY = Math.round((position.y - padding) / 20); // 20px 단위로 세로 스냅 + const rowHeight = Math.max(20, gap); // gap과 최소 20px 중 큰 값으로 행 높이 설정 + const gridY = Math.round((position.y - padding) / rowHeight); // 동적 행 높이로 세로 스냅 // 실제 픽셀 위치로 변환 const snappedX = Math.max(padding, padding + gridX * (columnWidth + gap)); - const snappedY = Math.max(padding, padding + gridY * 20); + const snappedY = Math.max(padding, padding + gridY * rowHeight); return { x: snappedX, @@ -90,8 +91,9 @@ export function snapSizeToGrid(size: Size, gridInfo: GridInfo, gridSettings: Gri const snappedWidth = gridColumns * columnWidth + (gridColumns - 1) * gap; - // 높이는 20px 단위로 스냅 - const snappedHeight = Math.max(40, Math.round(size.height / 20) * 20); + // 높이는 동적 행 높이 단위로 스냅 + const rowHeight = Math.max(20, gap); + const snappedHeight = Math.max(40, Math.round(size.height / rowHeight) * rowHeight); console.log( `📏 크기 스냅: ${size.width}px → ${snappedWidth}px (${gridColumns}컬럼, 컬럼너비:${columnWidth}px, 간격:${gap}px)`, @@ -185,9 +187,10 @@ export function generateGridLines( // 우측 경계선 verticalLines.push(containerWidth - padding); - // 가로 격자선 (20px 단위) + // 가로 격자선 (동적 행 높이 단위) + const rowHeight = Math.max(20, gap); const horizontalLines: number[] = []; - for (let y = padding; y < containerHeight; y += 20) { + for (let y = padding; y < containerHeight; y += rowHeight) { horizontalLines.push(y); } @@ -253,16 +256,17 @@ export function alignGroupChildrenToGrid( const columnIndex = Math.round(effectiveX / (columnWidth + gap)); const snappedX = padding + columnIndex * (columnWidth + gap); - // Y 좌표는 20px 단위로 스냅 + // Y 좌표는 동적 행 높이 단위로 스냅 + const rowHeight = Math.max(20, gap); const effectiveY = child.position.y - padding; - const rowIndex = Math.round(effectiveY / 20); - const snappedY = padding + rowIndex * 20; + const rowIndex = Math.round(effectiveY / rowHeight); + const snappedY = padding + rowIndex * rowHeight; // 크기는 외부 격자와 동일하게 스냅 (columnWidth + gap 사용) const fullColumnWidth = columnWidth + gap; // 외부 격자와 동일한 크기 const widthInColumns = Math.max(1, Math.round(child.size.width / fullColumnWidth)); const snappedWidth = widthInColumns * fullColumnWidth - gap; // gap 제거하여 실제 컴포넌트 크기 - const snappedHeight = Math.max(40, Math.round(child.size.height / 20) * 20); + const snappedHeight = Math.max(40, Math.round(child.size.height / rowHeight) * rowHeight); const snappedChild = { ...child,