메뉴생성시 화면할당기능 구현
This commit is contained in:
@@ -3,14 +3,18 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { MenuItem, MenuFormData, menuApi, LangKey } from "@/lib/api/menu";
|
||||
import { companyAPI } from "@/lib/api/company";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { toast } from "sonner";
|
||||
import { ChevronDown, Search } from "lucide-react";
|
||||
import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang";
|
||||
import { ScreenDefinition } from "@/types/screen";
|
||||
|
||||
interface Company {
|
||||
company_code: string;
|
||||
@@ -70,6 +74,13 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||
langKey: "",
|
||||
});
|
||||
|
||||
// 화면 할당 관련 상태
|
||||
const [urlType, setUrlType] = useState<"direct" | "screen">("screen"); // URL 직접 입력 or 화면 할당 (기본값: 화면 할당)
|
||||
const [selectedScreen, setSelectedScreen] = useState<ScreenDefinition | null>(null);
|
||||
const [screens, setScreens] = useState<ScreenDefinition[]>([]);
|
||||
const [screenSearchText, setScreenSearchText] = useState("");
|
||||
const [isScreenDropdownOpen, setIsScreenDropdownOpen] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [companies, setCompanies] = useState<Company[]>([]);
|
||||
@@ -77,6 +88,132 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||
const [isLangKeyDropdownOpen, setIsLangKeyDropdownOpen] = useState(false);
|
||||
const [langKeySearchText, setLangKeySearchText] = useState("");
|
||||
|
||||
// 화면 목록 로드
|
||||
const loadScreens = async () => {
|
||||
try {
|
||||
const response = await screenApi.getScreens({ size: 1000 }); // 모든 화면 가져오기
|
||||
|
||||
console.log("🔍 화면 목록 로드 디버깅:", {
|
||||
totalScreens: response.data.length,
|
||||
firstScreen: response.data[0],
|
||||
firstScreenFields: response.data[0] ? Object.keys(response.data[0]) : [],
|
||||
firstScreenValues: response.data[0] ? Object.values(response.data[0]) : [],
|
||||
allScreenIds: response.data
|
||||
.map((s) => ({
|
||||
screenId: s.screenId,
|
||||
legacyId: s.id,
|
||||
name: s.screenName,
|
||||
code: s.screenCode,
|
||||
}))
|
||||
.slice(0, 5), // 처음 5개만 출력
|
||||
});
|
||||
|
||||
setScreens(response.data);
|
||||
console.log("✅ 화면 목록 로드 완료:", response.data.length);
|
||||
} catch (error) {
|
||||
console.error("❌ 화면 목록 로드 실패:", error);
|
||||
toast.error("화면 목록을 불러오는데 실패했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
// 화면 선택 시 URL 자동 설정
|
||||
const handleScreenSelect = (screen: ScreenDefinition) => {
|
||||
console.log("🖥️ 화면 선택 디버깅:", {
|
||||
screen,
|
||||
screenId: screen.screenId,
|
||||
screenIdType: typeof screen.screenId,
|
||||
legacyId: screen.id,
|
||||
allFields: Object.keys(screen),
|
||||
screenValues: Object.values(screen),
|
||||
});
|
||||
|
||||
// ScreenDefinition에서는 screenId 필드를 사용
|
||||
const actualScreenId = screen.screenId || screen.id;
|
||||
|
||||
if (!actualScreenId) {
|
||||
console.error("❌ 화면 ID를 찾을 수 없습니다:", screen);
|
||||
toast.error("화면 ID를 찾을 수 없습니다. 다른 화면을 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedScreen(screen);
|
||||
setIsScreenDropdownOpen(false);
|
||||
|
||||
// 실제 라우팅 패턴에 맞게 URL 생성: /screens/[screenId] (복수형)
|
||||
// 관리자 메뉴인 경우 mode=admin 파라미터 추가
|
||||
let screenUrl = `/screens/${actualScreenId}`;
|
||||
|
||||
// 현재 메뉴 타입이 관리자인지 확인 (0 또는 "admin")
|
||||
const isAdminMenu = menuType === "0" || menuType === "admin" || formData.menuType === "0";
|
||||
if (isAdminMenu) {
|
||||
screenUrl += "?mode=admin";
|
||||
}
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
menuUrl: screenUrl,
|
||||
}));
|
||||
|
||||
console.log("🖥️ 화면 선택 완료:", {
|
||||
screenId: screen.screenId,
|
||||
legacyId: screen.id,
|
||||
actualScreenId,
|
||||
screenName: screen.screenName,
|
||||
menuType: menuType,
|
||||
formDataMenuType: formData.menuType,
|
||||
isAdminMenu,
|
||||
generatedUrl: screenUrl,
|
||||
});
|
||||
};
|
||||
|
||||
// URL 타입 변경 시 처리
|
||||
const handleUrlTypeChange = (type: "direct" | "screen") => {
|
||||
console.log("🔄 URL 타입 변경:", {
|
||||
from: urlType,
|
||||
to: type,
|
||||
currentSelectedScreen: selectedScreen?.screenName,
|
||||
currentUrl: formData.menuUrl,
|
||||
});
|
||||
|
||||
setUrlType(type);
|
||||
|
||||
if (type === "direct") {
|
||||
// 직접 입력 모드로 변경 시 선택된 화면 초기화
|
||||
setSelectedScreen(null);
|
||||
// URL 필드도 초기화 (사용자가 직접 입력할 수 있도록)
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
menuUrl: "",
|
||||
}));
|
||||
} else {
|
||||
// 화면 할당 모드로 변경 시
|
||||
// 기존에 선택된 화면이 있고, 해당 화면의 URL이 있다면 유지
|
||||
if (selectedScreen) {
|
||||
console.log("📋 기존 선택된 화면 유지:", selectedScreen.screenName);
|
||||
// 현재 선택된 화면으로 URL 재생성
|
||||
const actualScreenId = selectedScreen.screenId || selectedScreen.id;
|
||||
let screenUrl = `/screens/${actualScreenId}`;
|
||||
|
||||
// 관리자 메뉴인 경우 mode=admin 파라미터 추가
|
||||
const isAdminMenu = menuType === "0" || menuType === "admin" || formData.menuType === "0";
|
||||
if (isAdminMenu) {
|
||||
screenUrl += "?mode=admin";
|
||||
}
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
menuUrl: screenUrl,
|
||||
}));
|
||||
} else {
|
||||
// 선택된 화면이 없으면 URL만 초기화
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
menuUrl: "",
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// loadMenuData 함수를 먼저 정의
|
||||
const loadMenuData = async () => {
|
||||
console.log("loadMenuData 호출됨 - menuId:", menuId);
|
||||
@@ -124,11 +261,16 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||
convertedStatus = "INACTIVE";
|
||||
}
|
||||
|
||||
const menuUrl = menu.menu_url || menu.MENU_URL || "";
|
||||
|
||||
// URL이 "/screens/"로 시작하면 화면 할당으로 판단 (실제 라우팅 패턴에 맞게 수정)
|
||||
const isScreenUrl = menuUrl.startsWith("/screens/");
|
||||
|
||||
setFormData({
|
||||
objid: menu.objid || menu.OBJID,
|
||||
parentObjId: menu.parent_obj_id || menu.PARENT_OBJ_ID || "0",
|
||||
menuNameKor: menu.menu_name_kor || menu.MENU_NAME_KOR || "",
|
||||
menuUrl: menu.menu_url || menu.MENU_URL || "",
|
||||
menuUrl: menuUrl,
|
||||
menuDesc: menu.menu_desc || menu.MENU_DESC || "",
|
||||
seq: menu.seq || menu.SEQ || 1,
|
||||
menuType: convertedMenuType,
|
||||
@@ -137,6 +279,57 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||
langKey: langKey, // 다국어 키 설정
|
||||
});
|
||||
|
||||
// URL 타입 설정
|
||||
if (isScreenUrl) {
|
||||
setUrlType("screen");
|
||||
// "/screens/123" 또는 "/screens/123?mode=admin" 형태에서 ID 추출
|
||||
const screenId = menuUrl.match(/\/screens\/(\d+)/)?.[1];
|
||||
if (screenId) {
|
||||
console.log("🔍 기존 메뉴에서 화면 ID 추출:", {
|
||||
menuUrl,
|
||||
screenId,
|
||||
hasAdminParam: menuUrl.includes("mode=admin"),
|
||||
currentScreensCount: screens.length,
|
||||
});
|
||||
|
||||
// 화면 설정 함수
|
||||
const setScreenFromId = () => {
|
||||
const screen = screens.find((s) => s.screenId.toString() === screenId || s.id?.toString() === screenId);
|
||||
if (screen) {
|
||||
setSelectedScreen(screen);
|
||||
console.log("🖥️ 기존 메뉴의 할당된 화면 설정:", {
|
||||
screen,
|
||||
originalUrl: menuUrl,
|
||||
hasAdminParam: menuUrl.includes("mode=admin"),
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
console.warn("⚠️ 해당 ID의 화면을 찾을 수 없음:", {
|
||||
screenId,
|
||||
availableScreens: screens.map((s) => ({ screenId: s.screenId, id: s.id, name: s.screenName })),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 화면 목록이 이미 있으면 즉시 설정, 없으면 로드 완료 대기
|
||||
if (screens.length > 0) {
|
||||
console.log("📋 화면 목록이 이미 로드됨 - 즉시 설정");
|
||||
setScreenFromId();
|
||||
} else {
|
||||
console.log("⏳ 화면 목록 로드 대기 중...");
|
||||
// 화면 ID를 저장해두고, 화면 목록 로드 완료 후 설정
|
||||
setTimeout(() => {
|
||||
console.log("🔄 재시도: 화면 목록 로드 후 설정");
|
||||
setScreenFromId();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setUrlType("direct");
|
||||
setSelectedScreen(null);
|
||||
}
|
||||
|
||||
console.log("설정된 폼 데이터:", {
|
||||
objid: menu.objid || menu.OBJID,
|
||||
parentObjId: menu.parent_obj_id || menu.PARENT_OBJ_ID || "0",
|
||||
@@ -237,6 +430,35 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||
}
|
||||
}, [isOpen, formData.companyCode]);
|
||||
|
||||
// 화면 목록 로드
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
loadScreens();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// 화면 목록 로드 완료 후 기존 메뉴의 할당된 화면 설정
|
||||
useEffect(() => {
|
||||
if (screens.length > 0 && isEdit && formData.menuUrl && urlType === "screen") {
|
||||
const menuUrl = formData.menuUrl;
|
||||
if (menuUrl.startsWith("/screens/")) {
|
||||
const screenId = menuUrl.match(/\/screens\/(\d+)/)?.[1];
|
||||
if (screenId && !selectedScreen) {
|
||||
console.log("🔄 화면 목록 로드 완료 - 기존 할당 화면 자동 설정");
|
||||
const screen = screens.find((s) => s.screenId.toString() === screenId || s.id?.toString() === screenId);
|
||||
if (screen) {
|
||||
setSelectedScreen(screen);
|
||||
console.log("✅ 기존 메뉴의 할당된 화면 자동 설정 완료:", {
|
||||
screenId,
|
||||
screenName: screen.screenName,
|
||||
menuUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [screens, isEdit, formData.menuUrl, urlType, selectedScreen]);
|
||||
|
||||
// 드롭다운 외부 클릭 시 닫기
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@@ -245,16 +467,20 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||
setIsLangKeyDropdownOpen(false);
|
||||
setLangKeySearchText("");
|
||||
}
|
||||
if (!target.closest(".screen-dropdown")) {
|
||||
setIsScreenDropdownOpen(false);
|
||||
setScreenSearchText("");
|
||||
}
|
||||
};
|
||||
|
||||
if (isLangKeyDropdownOpen) {
|
||||
if (isLangKeyDropdownOpen || isScreenDropdownOpen) {
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [isLangKeyDropdownOpen]);
|
||||
}, [isLangKeyDropdownOpen, isScreenDropdownOpen]);
|
||||
|
||||
const loadCompanies = async () => {
|
||||
try {
|
||||
@@ -516,12 +742,108 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="menuUrl">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL)}</Label>
|
||||
<Input
|
||||
id="menuUrl"
|
||||
value={formData.menuUrl}
|
||||
onChange={(e) => handleInputChange("menuUrl", e.target.value)}
|
||||
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER)}
|
||||
/>
|
||||
|
||||
{/* URL 타입 선택 */}
|
||||
<RadioGroup value={urlType} onValueChange={handleUrlTypeChange} className="mb-3 flex space-x-6">
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="screen" id="screen" />
|
||||
<Label htmlFor="screen" className="cursor-pointer">
|
||||
화면 할당
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="direct" id="direct" />
|
||||
<Label htmlFor="direct" className="cursor-pointer">
|
||||
URL 직접 입력
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
{/* 화면 할당 */}
|
||||
{urlType === "screen" && (
|
||||
<div className="space-y-2">
|
||||
{/* 화면 선택 드롭다운 */}
|
||||
<div className="relative">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setIsScreenDropdownOpen(!isScreenDropdownOpen)}
|
||||
className="w-full justify-between"
|
||||
>
|
||||
<span className="text-left">
|
||||
{selectedScreen ? selectedScreen.screenName : "화면을 선택하세요"}
|
||||
</span>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
{isScreenDropdownOpen && (
|
||||
<div className="screen-dropdown absolute top-full right-0 left-0 z-50 mt-1 max-h-60 overflow-y-auto rounded-md border bg-white shadow-lg">
|
||||
{/* 검색 입력 */}
|
||||
<div className="sticky top-0 border-b bg-white p-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute top-1/2 left-2 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
||||
<Input
|
||||
placeholder="화면 검색..."
|
||||
value={screenSearchText}
|
||||
onChange={(e) => setScreenSearchText(e.target.value)}
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 화면 목록 */}
|
||||
<div className="max-h-48 overflow-y-auto">
|
||||
{screens
|
||||
.filter(
|
||||
(screen) =>
|
||||
screen.screenName.toLowerCase().includes(screenSearchText.toLowerCase()) ||
|
||||
screen.screenCode.toLowerCase().includes(screenSearchText.toLowerCase()),
|
||||
)
|
||||
.map((screen, index) => (
|
||||
<div
|
||||
key={`screen-${screen.screenId || screen.id || index}-${screen.screenCode || index}`}
|
||||
onClick={() => handleScreenSelect(screen)}
|
||||
className="cursor-pointer border-b px-3 py-2 last:border-b-0 hover:bg-gray-100"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium">{screen.screenName}</div>
|
||||
<div className="text-xs text-gray-500">{screen.screenCode}</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">ID: {screen.screenId || screen.id || "N/A"}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{screens.filter(
|
||||
(screen) =>
|
||||
screen.screenName.toLowerCase().includes(screenSearchText.toLowerCase()) ||
|
||||
screen.screenCode.toLowerCase().includes(screenSearchText.toLowerCase()),
|
||||
).length === 0 && <div className="px-3 py-2 text-sm text-gray-500">검색 결과가 없습니다.</div>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 선택된 화면 정보 표시 */}
|
||||
{selectedScreen && (
|
||||
<div className="rounded-md border bg-blue-50 p-3">
|
||||
<div className="text-sm font-medium text-blue-900">{selectedScreen.screenName}</div>
|
||||
<div className="text-xs text-blue-600">코드: {selectedScreen.screenCode}</div>
|
||||
<div className="text-xs text-blue-600">생성된 URL: {formData.menuUrl}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* URL 직접 입력 */}
|
||||
{urlType === "direct" && (
|
||||
<Input
|
||||
id="menuUrl"
|
||||
value={formData.menuUrl}
|
||||
onChange={(e) => handleInputChange("menuUrl", e.target.value)}
|
||||
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user