메뉴 삭제

This commit is contained in:
kjs
2025-09-09 16:14:21 +09:00
parent 49c8f9a2dd
commit 85a1e0c68a
6 changed files with 144 additions and 42 deletions

View File

@@ -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,

View File

@@ -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<any[]>`

View File

@@ -1010,5 +1010,3 @@ After (새로운):
- **다중 플랫폼**: 모바일/데스크톱 앱에서도 동일한 시스템 사용
**이제 미래 지향적이고 확장 가능한 화면관리 시스템을 구축할 준비가 완료되었습니다!** 🚀

View File

@@ -199,7 +199,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
const parentObjId = menu.parent_obj_id || menu.PARENT_OBJ_ID || "";
return (
<TableRow key={objid} className="hover:bg-gray-50">
<TableRow key={`${objid}-${lev}-${parentObjId}`} className="hover:bg-gray-50">
<TableCell>
<input
type="checkbox"

View File

@@ -643,45 +643,144 @@ Response: {
## 9. 개발 가이드
### 9.1 새로운 웹타입 추가
### 9.1 새로운 웹타입 추가 (간편한 3단계)
1. **타입 정의 추가**
새로운 웹타입 추가가 대폭 간편해졌습니다! 이제 **데이터베이스 기반 동적 웹타입 시스템**을 사용합니다.
```typescript
// types/screen.ts
type WebType = "text" | "number" | "date" | "새로운타입";
#### **1단계: 데이터베이스에 웹타입 등록**
interface TypeConfig {
// 설정 속성들
}
type WebTypeConfig = TextTypeConfig | NumberTypeConfig | TypeConfig;
```sql
-- web_type_standard 테이블에 새 웹타입 추가
INSERT INTO web_type_standard (
web_type,
type_name,
config_panel,
active
) VALUES (
'my_new_type', -- 웹타입 코드 (영문)
'새로운 입력 타입', -- 한글 표시명
'MyNewTypeConfigPanel', -- 설정 패널 컴포넌트명 (선택사항)
'Y' -- 활성화 여부
);
```
2. **설정 패널 생성**
#### **2단계: 설정 패널 컴포넌트 생성 (선택사항)**
웹타입에 특별한 설정이 필요한 경우만 생성:
```typescript
// panels/webtype-configs/새로운타입TypeConfigPanel.tsx
export const 새로운타입ConfigPanel: React.FC<Props> = ({ component, onUpdateComponent }) => {
// 설정 UI 구현
// frontend/components/screen/config-panels/MyNewTypeConfigPanel.tsx
export const MyNewTypeConfigPanel = ({ config, onConfigChange }) => {
return (
<div className="space-y-4">
<h3 className="text-sm font-medium"> </h3>
{/* 설정 UI */}
<div>
<label className="text-sm"> 1</label>
<input
value={config.option1 || ""}
onChange={(e) => onConfigChange({ ...config, option1: e.target.value })}
/>
</div>
<div>
<label className="text-sm"> 2</label>
<select
value={config.option2 || "default"}
onChange={(e) => onConfigChange({ ...config, option2: e.target.value })}
>
<option value="default"></option>
<option value="custom"> </option>
</select>
</div>
</div>
);
};
```
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 (
<MyNewInputComponent
value={currentValue}
onChange={handleInputChange}
placeholder={finalPlaceholder}
config={widget.webTypeConfig}
style={{ height: "100%" }}
/>
);
```
#### **타입 정의 추가 (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에 템플릿 정의 추가**

View File

@@ -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,