feat: 메뉴 복사 기능 - 2단계 복사 방식으로 화면 참조 매핑 문제 해결

- 문제: 화면 복사 시 참조되는 화면이 아직 복사되지 않아 screenIdMap에 매핑 정보가 없었음
- 해결: 2단계 복사 방식 도입
  1단계: 모든 screen_definitions 먼저 복사하여 screenIdMap 완성
  2단계: screen_layouts 복사하면서 완성된 screenIdMap으로 참조 업데이트
- 결과: targetScreenId가 올바르게 새 회사의 화면 ID로 매핑됨 (예: 149 → 517)
- 추가: 화면 수집 시 문자열 타입 ID도 올바르게 파싱하도록 개선
- 추가: 참조 화면 발견 및 업데이트 로그 추가

관련 파일:
- backend-node/src/services/menuCopyService.ts
- db/migrations/1003_add_source_menu_objid_to_menu_info.sql
- db/scripts/cleanup_company_11_*.sql
This commit is contained in:
kjs
2025-11-21 14:37:09 +09:00
parent bb49073bf7
commit c70998fa4f
11 changed files with 4021 additions and 16 deletions

View File

@@ -5,6 +5,7 @@ import { menuApi } from "@/lib/api/menu";
import type { MenuItem } from "@/lib/api/menu";
import { MenuTable } from "./MenuTable";
import { MenuFormModal } from "./MenuFormModal";
import { MenuCopyDialog } from "./MenuCopyDialog";
import { Button } from "@/components/ui/button";
import { LoadingSpinner, LoadingOverlay } from "@/components/common/LoadingSpinner";
import { toast } from "sonner";
@@ -25,17 +26,21 @@ import { useMenu } from "@/contexts/MenuContext";
import { useMenuManagementText, setTranslationCache, getMenuTextSync } from "@/lib/utils/multilang";
import { useMultiLang } from "@/hooks/useMultiLang";
import { apiClient } from "@/lib/api/client";
import { useAuth } from "@/hooks/useAuth"; // useAuth 추가
type MenuType = "admin" | "user";
export const MenuManagement: React.FC = () => {
const { adminMenus, userMenus, refreshMenus } = useMenu();
const { user } = useAuth(); // 현재 사용자 정보 가져오기
const [selectedMenuType, setSelectedMenuType] = useState<MenuType>("admin");
const [loading, setLoading] = useState(false);
const [deleting, setDeleting] = useState(false);
const [formModalOpen, setFormModalOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [copyDialogOpen, setCopyDialogOpen] = useState(false);
const [selectedMenuId, setSelectedMenuId] = useState<string>("");
const [selectedMenuName, setSelectedMenuName] = useState<string>("");
const [selectedMenus, setSelectedMenus] = useState<Set<string>>(new Set());
// 메뉴 관리 화면용 로컬 상태 (모든 상태의 메뉴 표시)
@@ -46,6 +51,9 @@ export const MenuManagement: React.FC = () => {
// getMenuText는 더 이상 사용하지 않음 - getUITextSync만 사용
const { userLang } = useMultiLang({ companyCode: "*" });
// SUPER_ADMIN 여부 확인
const isSuperAdmin = user?.userType === "SUPER_ADMIN";
// 다국어 텍스트 상태
const [uiTexts, setUiTexts] = useState<Record<string, string>>({});
const [uiTextsLoading, setUiTextsLoading] = useState(false);
@@ -749,6 +757,18 @@ export const MenuManagement: React.FC = () => {
}
};
const handleCopyMenu = (menuId: string, menuName: string) => {
setSelectedMenuId(menuId);
setSelectedMenuName(menuName);
setCopyDialogOpen(true);
};
const handleCopyComplete = async () => {
// 복사 완료 후 메뉴 목록 새로고침
await loadMenus(false);
toast.success("메뉴 복사가 완료되었습니다");
};
const handleToggleStatus = async (menuId: string) => {
try {
const response = await menuApi.toggleMenuStatus(menuId);
@@ -1062,6 +1082,7 @@ export const MenuManagement: React.FC = () => {
title=""
onAddMenu={handleAddMenu}
onEditMenu={handleEditMenu}
onCopyMenu={handleCopyMenu}
onToggleStatus={handleToggleStatus}
selectedMenus={selectedMenus}
onMenuSelectionChange={handleMenuSelectionChange}
@@ -1069,6 +1090,7 @@ export const MenuManagement: React.FC = () => {
expandedMenus={expandedMenus}
onToggleExpand={handleToggleExpand}
uiTexts={uiTexts}
isSuperAdmin={isSuperAdmin} // SUPER_ADMIN 여부 전달
/>
</div>
</div>
@@ -1101,6 +1123,14 @@ export const MenuManagement: React.FC = () => {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<MenuCopyDialog
menuObjid={selectedMenuId ? parseInt(selectedMenuId, 10) : null}
menuName={selectedMenuName}
open={copyDialogOpen}
onOpenChange={setCopyDialogOpen}
onCopyComplete={handleCopyComplete}
/>
</LoadingOverlay>
);
};