feat: 화면 복사 기능 개선 및 버튼 모달 설정 수정
## 주요 변경사항 ### 1. 화면 복사 기능 강화 - 최고 관리자가 다른 회사로 화면 복사 가능하도록 개선 - 메인 화면과 연결된 모달 화면 자동 감지 및 일괄 복사 - 복사 시 버튼의 targetScreenId 자동 업데이트 - 일괄 이름 변경 기능 추가 (복사본 텍스트 제거) - 중복 화면명 체크 기능 추가 #### 백엔드 (screenManagementService.ts) - generateMultipleScreenCodes: 여러 화면 코드 일괄 생성 (Advisory Lock 사용) - detectLinkedModalScreens: edit 액션도 모달로 감지하도록 개선 - checkDuplicateScreenName: 중복 화면명 체크 API 추가 - copyScreenWithModals: 메인+모달 일괄 복사 및 버튼 업데이트 - updateButtonTargetScreenIds: 복사된 모달로 버튼 targetScreenId 업데이트 - updated_date 컬럼 제거 (screen_layouts 테이블에 존재하지 않음) #### 프론트엔드 (CopyScreenModal.tsx) - 회사 선택 UI 추가 (최고 관리자 전용) - 연결된 모달 화면 자동 감지 및 표시 - 일괄 이름 변경 기능 (텍스트 제거/추가) - 실시간 미리보기 - 중복 화면명 체크 ### 2. 버튼 설정 모달 화면 선택 개선 - 편집 중인 화면의 company_code 기준으로 화면 목록 조회 - 최고 관리자가 다른 회사 화면 편집 시 해당 회사의 모달 화면만 표시 - targetScreenId 문자열/숫자 타입 불일치 수정 #### 백엔드 (screenManagementController.ts) - getScreens API에 companyCode 쿼리 파라미터 추가 - 최고 관리자는 다른 회사의 화면 목록 조회 가능 #### 프론트엔드 - ButtonConfigPanel: currentScreenCompanyCode props 추가 - DetailSettingsPanel: currentScreenCompanyCode 전달 - UnifiedPropertiesPanel: currentScreenCompanyCode 전달 - ScreenDesigner: selectedScreen.companyCode 전달 - targetScreenId 비교 시 parseInt 처리 (문자열→숫자) ### 3. 카테고리 메뉴별 컬럼 분리 기능 - 메뉴별로 카테고리 컬럼을 독립적으로 관리 - 카테고리 컬럼 추가/삭제 시 메뉴 스코프 적용 ## 수정된 파일 - backend-node/src/services/screenManagementService.ts - backend-node/src/controllers/screenManagementController.ts - backend-node/src/routes/screenManagementRoutes.ts - frontend/components/screen/CopyScreenModal.tsx - frontend/components/screen/config-panels/ButtonConfigPanel.tsx - frontend/components/screen/panels/DetailSettingsPanel.tsx - frontend/components/screen/panels/UnifiedPropertiesPanel.tsx - frontend/components/screen/ScreenDesigner.tsx - frontend/lib/api/screen.ts
This commit is contained in:
@@ -5,11 +5,23 @@ import { AuthenticatedRequest } from "../types/auth";
|
||||
// 화면 목록 조회
|
||||
export const getScreens = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { companyCode } = req.user as any;
|
||||
const { page = 1, size = 20, searchTerm } = req.query;
|
||||
const userCompanyCode = (req.user as any).companyCode;
|
||||
const { page = 1, size = 20, searchTerm, companyCode } = req.query;
|
||||
|
||||
// 쿼리 파라미터로 companyCode가 전달되면 해당 회사의 화면 조회 (최고 관리자 전용)
|
||||
// 아니면 현재 사용자의 companyCode 사용
|
||||
const targetCompanyCode = (companyCode as string) || userCompanyCode;
|
||||
|
||||
// 최고 관리자가 아닌 경우 자신의 회사 코드만 사용 가능
|
||||
if (userCompanyCode !== "*" && targetCompanyCode !== userCompanyCode) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: "다른 회사의 화면을 조회할 권한이 없습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await screenManagementService.getScreensByCompany(
|
||||
companyCode,
|
||||
targetCompanyCode,
|
||||
parseInt(page as string),
|
||||
parseInt(size as string)
|
||||
);
|
||||
@@ -325,7 +337,118 @@ export const bulkPermanentDeleteScreens = async (
|
||||
}
|
||||
};
|
||||
|
||||
// 화면 복사
|
||||
// 연결된 모달 화면 감지 (화면 복사 전 확인)
|
||||
export const detectLinkedScreens = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const linkedScreens = await screenManagementService.detectLinkedModalScreens(
|
||||
parseInt(id)
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: linkedScreens,
|
||||
message: linkedScreens.length > 0
|
||||
? `${linkedScreens.length}개의 연결된 모달 화면을 감지했습니다.`
|
||||
: "연결된 모달 화면이 없습니다.",
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("연결된 화면 감지 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "연결된 화면 감지에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 화면명 중복 체크
|
||||
export const checkDuplicateScreenName = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode, screenName } = req.body;
|
||||
|
||||
if (!companyCode || !screenName) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "companyCode와 screenName은 필수입니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const isDuplicate =
|
||||
await screenManagementService.checkDuplicateScreenName(
|
||||
companyCode,
|
||||
screenName
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { isDuplicate },
|
||||
message: isDuplicate
|
||||
? "이미 존재하는 화면명입니다."
|
||||
: "사용 가능한 화면명입니다.",
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("화면명 중복 체크 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "화면명 중복 체크에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 화면 일괄 복사 (메인 + 모달 화면들)
|
||||
export const copyScreenWithModals = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { mainScreen, modalScreens, targetCompanyCode } = req.body;
|
||||
const { companyCode, userId } = req.user as any;
|
||||
|
||||
if (!mainScreen || !mainScreen.screenName || !mainScreen.screenCode) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "메인 화면 정보(screenName, screenCode)가 필요합니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await screenManagementService.copyScreenWithModals({
|
||||
sourceScreenId: parseInt(id),
|
||||
companyCode,
|
||||
userId,
|
||||
targetCompanyCode, // 최고 관리자가 다른 회사로 복사할 때 사용
|
||||
mainScreen: {
|
||||
screenName: mainScreen.screenName,
|
||||
screenCode: mainScreen.screenCode,
|
||||
description: mainScreen.description,
|
||||
},
|
||||
modalScreens: modalScreens || [],
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: `화면 복사가 완료되었습니다. (메인 1개 + 모달 ${result.modalScreens.length}개)`,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("화면 일괄 복사 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "화면 일괄 복사에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 화면 복사 (단일 - 하위 호환용)
|
||||
export const copyScreen = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
@@ -495,6 +618,50 @@ export const generateScreenCode = async (
|
||||
}
|
||||
};
|
||||
|
||||
// 여러 개의 화면 코드 일괄 생성
|
||||
export const generateMultipleScreenCodes = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
) => {
|
||||
try {
|
||||
const { companyCode, count } = req.body;
|
||||
|
||||
if (!companyCode || typeof companyCode !== "string") {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "회사 코드(companyCode)는 필수입니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!count || typeof count !== "number" || count < 1 || count > 100) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "count는 1~100 사이의 숫자여야 합니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const screenCodes =
|
||||
await screenManagementService.generateMultipleScreenCodes(
|
||||
companyCode,
|
||||
count
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { screenCodes },
|
||||
message: `${count}개의 화면 코드가 생성되었습니다.`,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("화면 코드 일괄 생성 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "화면 코드 일괄 생성에 실패했습니다.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 화면-메뉴 할당
|
||||
export const assignScreenToMenu = async (
|
||||
req: AuthenticatedRequest,
|
||||
|
||||
Reference in New Issue
Block a user