메뉴 삭제
This commit is contained in:
parent
49c8f9a2dd
commit
85a1e0c68a
|
|
@ -31,7 +31,6 @@ export async function getAdminMenus(
|
||||||
const paramMap = {
|
const paramMap = {
|
||||||
userCompanyCode,
|
userCompanyCode,
|
||||||
userLang,
|
userLang,
|
||||||
SYSTEM_NAME: "PLM",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuList = await AdminService.getAdminMenuList(paramMap);
|
const menuList = await AdminService.getAdminMenuList(paramMap);
|
||||||
|
|
@ -84,7 +83,6 @@ export async function getUserMenus(
|
||||||
const paramMap = {
|
const paramMap = {
|
||||||
userCompanyCode,
|
userCompanyCode,
|
||||||
userLang,
|
userLang,
|
||||||
SYSTEM_NAME: "PLM",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuList = await AdminService.getUserMenuList(paramMap);
|
const menuList = await AdminService.getUserMenuList(paramMap);
|
||||||
|
|
@ -1035,7 +1033,7 @@ export async function saveMenu(
|
||||||
writer: req.user?.userId || "admin",
|
writer: req.user?.userId || "admin",
|
||||||
regdate: new Date(),
|
regdate: new Date(),
|
||||||
status: menuData.status || "active",
|
status: menuData.status || "active",
|
||||||
system_name: menuData.systemName || "PLM",
|
system_name: menuData.systemName || null,
|
||||||
company_code: menuData.companyCode || "*",
|
company_code: menuData.companyCode || "*",
|
||||||
lang_key: menuData.langKey || null,
|
lang_key: menuData.langKey || null,
|
||||||
lang_key_desc: menuData.langKeyDesc || null,
|
lang_key_desc: menuData.langKeyDesc || null,
|
||||||
|
|
@ -1101,7 +1099,7 @@ export async function updateMenu(
|
||||||
menu_url: menuData.menuUrl || null,
|
menu_url: menuData.menuUrl || null,
|
||||||
menu_desc: menuData.menuDesc || null,
|
menu_desc: menuData.menuDesc || null,
|
||||||
status: menuData.status || "active",
|
status: menuData.status || "active",
|
||||||
system_name: menuData.systemName || "PLM",
|
system_name: menuData.systemName || null,
|
||||||
company_code: menuData.companyCode || "*",
|
company_code: menuData.companyCode || "*",
|
||||||
lang_key: menuData.langKey || null,
|
lang_key: menuData.langKey || null,
|
||||||
lang_key_desc: menuData.langKeyDesc || null,
|
lang_key_desc: menuData.langKeyDesc || null,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export class AdminService {
|
||||||
try {
|
try {
|
||||||
logger.info("AdminService.getAdminMenuList 시작 - 파라미터:", paramMap);
|
logger.info("AdminService.getAdminMenuList 시작 - 파라미터:", paramMap);
|
||||||
|
|
||||||
const { userLang = "ko", SYSTEM_NAME = "PLM" } = paramMap;
|
const { userLang = "ko" } = paramMap;
|
||||||
|
|
||||||
// 기존 Java의 selectAdminMenuList 쿼리를 Prisma로 포팅
|
// 기존 Java의 selectAdminMenuList 쿼리를 Prisma로 포팅
|
||||||
// WITH RECURSIVE 쿼리를 Prisma의 $queryRaw로 구현
|
// WITH RECURSIVE 쿼리를 Prisma의 $queryRaw로 구현
|
||||||
|
|
@ -92,8 +92,11 @@ export class AdminService {
|
||||||
MENU.MENU_DESC
|
MENU.MENU_DESC
|
||||||
)
|
)
|
||||||
FROM MENU_INFO MENU
|
FROM MENU_INFO MENU
|
||||||
WHERE PARENT_OBJ_ID = 0
|
WHERE MENU_TYPE = 0
|
||||||
AND MENU_TYPE = 0
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM MENU_INFO parent_menu
|
||||||
|
WHERE parent_menu.OBJID = MENU.PARENT_OBJ_ID
|
||||||
|
)
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
|
|
||||||
|
|
@ -208,7 +211,7 @@ export class AdminService {
|
||||||
try {
|
try {
|
||||||
logger.info("AdminService.getUserMenuList 시작 - 파라미터:", paramMap);
|
logger.info("AdminService.getUserMenuList 시작 - 파라미터:", paramMap);
|
||||||
|
|
||||||
const { userLang = "ko", SYSTEM_NAME = "PLM" } = paramMap;
|
const { userLang = "ko" } = paramMap;
|
||||||
|
|
||||||
// 기존 Java의 selectUserMenuList 쿼리를 Prisma로 포팅
|
// 기존 Java의 selectUserMenuList 쿼리를 Prisma로 포팅
|
||||||
const menuList = await prisma.$queryRaw<any[]>`
|
const menuList = await prisma.$queryRaw<any[]>`
|
||||||
|
|
|
||||||
|
|
@ -1010,5 +1010,3 @@ After (새로운):
|
||||||
- **다중 플랫폼**: 모바일/데스크톱 앱에서도 동일한 시스템 사용
|
- **다중 플랫폼**: 모바일/데스크톱 앱에서도 동일한 시스템 사용
|
||||||
|
|
||||||
**이제 미래 지향적이고 확장 가능한 화면관리 시스템을 구축할 준비가 완료되었습니다!** 🚀
|
**이제 미래 지향적이고 확장 가능한 화면관리 시스템을 구축할 준비가 완료되었습니다!** 🚀
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
||||||
const parentObjId = menu.parent_obj_id || menu.PARENT_OBJ_ID || "";
|
const parentObjId = menu.parent_obj_id || menu.PARENT_OBJ_ID || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={objid} className="hover:bg-gray-50">
|
<TableRow key={`${objid}-${lev}-${parentObjId}`} className="hover:bg-gray-50">
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
|
||||||
|
|
@ -643,45 +643,144 @@ Response: {
|
||||||
|
|
||||||
## 9. 개발 가이드
|
## 9. 개발 가이드
|
||||||
|
|
||||||
### 9.1 새로운 웹타입 추가
|
### 9.1 새로운 웹타입 추가 (간편한 3단계)
|
||||||
|
|
||||||
1. **타입 정의 추가**
|
새로운 웹타입 추가가 대폭 간편해졌습니다! 이제 **데이터베이스 기반 동적 웹타입 시스템**을 사용합니다.
|
||||||
|
|
||||||
```typescript
|
#### **1단계: 데이터베이스에 웹타입 등록**
|
||||||
// types/screen.ts
|
|
||||||
type WebType = "text" | "number" | "date" | "새로운타입";
|
|
||||||
|
|
||||||
interface 새로운타입TypeConfig {
|
```sql
|
||||||
// 설정 속성들
|
-- web_type_standard 테이블에 새 웹타입 추가
|
||||||
}
|
INSERT INTO web_type_standard (
|
||||||
|
web_type,
|
||||||
type WebTypeConfig = TextTypeConfig | NumberTypeConfig | 새로운타입TypeConfig;
|
type_name,
|
||||||
|
config_panel,
|
||||||
|
active
|
||||||
|
) VALUES (
|
||||||
|
'my_new_type', -- 웹타입 코드 (영문)
|
||||||
|
'새로운 입력 타입', -- 한글 표시명
|
||||||
|
'MyNewTypeConfigPanel', -- 설정 패널 컴포넌트명 (선택사항)
|
||||||
|
'Y' -- 활성화 여부
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **설정 패널 생성**
|
#### **2단계: 설정 패널 컴포넌트 생성 (선택사항)**
|
||||||
|
|
||||||
|
웹타입에 특별한 설정이 필요한 경우만 생성:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// panels/webtype-configs/새로운타입TypeConfigPanel.tsx
|
// frontend/components/screen/config-panels/MyNewTypeConfigPanel.tsx
|
||||||
export const 새로운타입ConfigPanel: React.FC<Props> = ({ component, onUpdateComponent }) => {
|
export const MyNewTypeConfigPanel = ({ config, onConfigChange }) => {
|
||||||
// 설정 UI 구현
|
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
|
```typescript
|
||||||
// RealtimePreview.tsx, InteractiveScreenViewer.tsx
|
// frontend/lib/utils/availableConfigPanels.ts
|
||||||
case "새로운타입":
|
import { MyNewTypeConfigPanel } from "@/components/screen/config-panels/MyNewTypeConfigPanel";
|
||||||
return renderNewWidget(component);
|
|
||||||
|
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
|
```typescript
|
||||||
case "새로운타입":
|
// RealtimePreviewDynamic.tsx 또는 InteractiveScreenViewer.tsx
|
||||||
return <새로운타입ConfigPanel component={widget} onUpdateComponent={handleUpdate} />;
|
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 새로운 템플릿 추가
|
### 9.2 새로운 템플릿 추가
|
||||||
|
|
||||||
1. **TemplatesPanel에 템플릿 정의 추가**
|
1. **TemplatesPanel에 템플릿 정의 추가**
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,12 @@ export function snapToGrid(position: Position, gridInfo: GridInfo, gridSettings:
|
||||||
|
|
||||||
// 격자 기준으로 위치 계산
|
// 격자 기준으로 위치 계산
|
||||||
const gridX = Math.round((position.x - padding) / (columnWidth + gap));
|
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 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 {
|
return {
|
||||||
x: snappedX,
|
x: snappedX,
|
||||||
|
|
@ -90,8 +91,9 @@ export function snapSizeToGrid(size: Size, gridInfo: GridInfo, gridSettings: Gri
|
||||||
|
|
||||||
const snappedWidth = gridColumns * columnWidth + (gridColumns - 1) * gap;
|
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(
|
console.log(
|
||||||
`📏 크기 스냅: ${size.width}px → ${snappedWidth}px (${gridColumns}컬럼, 컬럼너비:${columnWidth}px, 간격:${gap}px)`,
|
`📏 크기 스냅: ${size.width}px → ${snappedWidth}px (${gridColumns}컬럼, 컬럼너비:${columnWidth}px, 간격:${gap}px)`,
|
||||||
|
|
@ -185,9 +187,10 @@ export function generateGridLines(
|
||||||
// 우측 경계선
|
// 우측 경계선
|
||||||
verticalLines.push(containerWidth - padding);
|
verticalLines.push(containerWidth - padding);
|
||||||
|
|
||||||
// 가로 격자선 (20px 단위)
|
// 가로 격자선 (동적 행 높이 단위)
|
||||||
|
const rowHeight = Math.max(20, gap);
|
||||||
const horizontalLines: number[] = [];
|
const horizontalLines: number[] = [];
|
||||||
for (let y = padding; y < containerHeight; y += 20) {
|
for (let y = padding; y < containerHeight; y += rowHeight) {
|
||||||
horizontalLines.push(y);
|
horizontalLines.push(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,16 +256,17 @@ export function alignGroupChildrenToGrid(
|
||||||
const columnIndex = Math.round(effectiveX / (columnWidth + gap));
|
const columnIndex = Math.round(effectiveX / (columnWidth + gap));
|
||||||
const snappedX = padding + columnIndex * (columnWidth + gap);
|
const snappedX = padding + columnIndex * (columnWidth + gap);
|
||||||
|
|
||||||
// Y 좌표는 20px 단위로 스냅
|
// Y 좌표는 동적 행 높이 단위로 스냅
|
||||||
|
const rowHeight = Math.max(20, gap);
|
||||||
const effectiveY = child.position.y - padding;
|
const effectiveY = child.position.y - padding;
|
||||||
const rowIndex = Math.round(effectiveY / 20);
|
const rowIndex = Math.round(effectiveY / rowHeight);
|
||||||
const snappedY = padding + rowIndex * 20;
|
const snappedY = padding + rowIndex * rowHeight;
|
||||||
|
|
||||||
// 크기는 외부 격자와 동일하게 스냅 (columnWidth + gap 사용)
|
// 크기는 외부 격자와 동일하게 스냅 (columnWidth + gap 사용)
|
||||||
const fullColumnWidth = columnWidth + gap; // 외부 격자와 동일한 크기
|
const fullColumnWidth = columnWidth + gap; // 외부 격자와 동일한 크기
|
||||||
const widthInColumns = Math.max(1, Math.round(child.size.width / fullColumnWidth));
|
const widthInColumns = Math.max(1, Math.round(child.size.width / fullColumnWidth));
|
||||||
const snappedWidth = widthInColumns * fullColumnWidth - gap; // gap 제거하여 실제 컴포넌트 크기
|
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 = {
|
const snappedChild = {
|
||||||
...child,
|
...child,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue