메뉴 삭제
This commit is contained in:
parent
49c8f9a2dd
commit
85a1e0c68a
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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[]>`
|
||||
|
|
|
|||
|
|
@ -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 || "";
|
||||
|
||||
return (
|
||||
<TableRow key={objid} className="hover:bg-gray-50">
|
||||
<TableRow key={`${objid}-${lev}-${parentObjId}`} className="hover:bg-gray-50">
|
||||
<TableCell>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
|
|
|||
|
|
@ -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에 템플릿 정의 추가**
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue