2025-08-21 09:41:46 +09:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
|
|
import { MenuItem, MenuFormData, menuApi, LangKey } from "@/lib/api/menu";
|
|
|
|
|
|
import { companyAPI } from "@/lib/api/company";
|
|
|
|
|
|
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 { toast } from "sonner";
|
|
|
|
|
|
import { getMenuTextSync, MENU_MANAGEMENT_KEYS, setTranslationCache } from "@/lib/utils/multilang";
|
|
|
|
|
|
|
|
|
|
|
|
interface Company {
|
|
|
|
|
|
company_code: string;
|
|
|
|
|
|
company_name: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface MenuFormModalProps {
|
|
|
|
|
|
isOpen: boolean;
|
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
|
onSuccess: () => void;
|
|
|
|
|
|
menuId?: string;
|
|
|
|
|
|
parentId?: string;
|
|
|
|
|
|
menuType?: string;
|
|
|
|
|
|
level?: number;
|
|
|
|
|
|
parentCompanyCode?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|
|
|
|
|
isOpen,
|
|
|
|
|
|
onClose,
|
|
|
|
|
|
onSuccess,
|
|
|
|
|
|
menuId,
|
|
|
|
|
|
parentId,
|
|
|
|
|
|
menuType,
|
|
|
|
|
|
level,
|
|
|
|
|
|
parentCompanyCode,
|
|
|
|
|
|
}) => {
|
2025-08-25 11:07:39 +09:00
|
|
|
|
console.log("🎯 MenuFormModal 렌더링 - Props:", {
|
|
|
|
|
|
isOpen,
|
|
|
|
|
|
menuId,
|
|
|
|
|
|
parentId,
|
|
|
|
|
|
menuType,
|
|
|
|
|
|
level,
|
|
|
|
|
|
parentCompanyCode,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log("🔍 MenuFormModal 컴포넌트 마운트됨");
|
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
|
const [formData, setFormData] = useState<MenuFormData>({
|
|
|
|
|
|
parentObjId: parentId || "0",
|
|
|
|
|
|
menuNameKor: "",
|
|
|
|
|
|
menuUrl: "",
|
|
|
|
|
|
menuDesc: "",
|
|
|
|
|
|
seq: 1,
|
2025-08-25 11:07:39 +09:00
|
|
|
|
menuType: "1",
|
|
|
|
|
|
status: "ACTIVE",
|
|
|
|
|
|
companyCode: parentCompanyCode || "none",
|
|
|
|
|
|
langKey: "",
|
2025-08-21 09:41:46 +09:00
|
|
|
|
});
|
2025-08-25 11:07:39 +09:00
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [isEdit, setIsEdit] = useState(false);
|
|
|
|
|
|
const [companies, setCompanies] = useState<Company[]>([]);
|
|
|
|
|
|
const [langKeys, setLangKeys] = useState<LangKey[]>([]);
|
|
|
|
|
|
const [isLangKeyDropdownOpen, setIsLangKeyDropdownOpen] = useState(false);
|
|
|
|
|
|
const [langKeySearchText, setLangKeySearchText] = useState("");
|
|
|
|
|
|
|
2025-08-25 11:07:39 +09:00
|
|
|
|
// loadMenuData 함수를 먼저 정의
|
2025-08-21 09:41:46 +09:00
|
|
|
|
const loadMenuData = async () => {
|
|
|
|
|
|
console.log("loadMenuData 호출됨 - menuId:", menuId);
|
|
|
|
|
|
if (!menuId) {
|
|
|
|
|
|
console.log("menuId가 없어서 loadMenuData 종료");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
console.log("API 호출 시작 - menuId:", menuId);
|
|
|
|
|
|
console.log("API URL:", `/admin/menus/${menuId}`);
|
|
|
|
|
|
|
|
|
|
|
|
const response = await menuApi.getMenuInfo(menuId);
|
|
|
|
|
|
console.log("메뉴 정보 조회 응답:", response);
|
|
|
|
|
|
console.log("응답 success:", response.success);
|
|
|
|
|
|
console.log("응답 data:", response.data);
|
|
|
|
|
|
console.log("응답 message:", response.message);
|
|
|
|
|
|
console.log("응답 errorCode:", response.errorCode);
|
|
|
|
|
|
|
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
|
const menu = response.data;
|
|
|
|
|
|
console.log("메뉴 데이터:", menu);
|
|
|
|
|
|
console.log("메뉴 데이터 키들:", Object.keys(menu));
|
|
|
|
|
|
|
|
|
|
|
|
// 대문자 키와 소문자 키 모두 처리
|
|
|
|
|
|
const menuType = menu.menu_type || menu.MENU_TYPE || "1";
|
|
|
|
|
|
const status = menu.status || menu.STATUS || "active";
|
|
|
|
|
|
const companyCode = menu.company_code || menu.COMPANY_CODE || "";
|
|
|
|
|
|
const langKey = menu.lang_key || menu.LANG_KEY || "";
|
|
|
|
|
|
|
|
|
|
|
|
// 메뉴 타입 변환 (admin/user -> 0/1)
|
|
|
|
|
|
let convertedMenuType = menuType;
|
|
|
|
|
|
if (menuType === "admin" || menuType === "0") {
|
|
|
|
|
|
convertedMenuType = "0";
|
|
|
|
|
|
} else if (menuType === "user" || menuType === "1") {
|
|
|
|
|
|
convertedMenuType = "1";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 상태 변환 (active/inactive/inActive -> ACTIVE/INACTIVE)
|
|
|
|
|
|
let convertedStatus = status;
|
|
|
|
|
|
if (status === "active") {
|
|
|
|
|
|
convertedStatus = "ACTIVE";
|
|
|
|
|
|
} else if (status === "inactive" || status === "inActive") {
|
|
|
|
|
|
convertedStatus = "INACTIVE";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 || "",
|
|
|
|
|
|
menuDesc: menu.menu_desc || menu.MENU_DESC || "",
|
|
|
|
|
|
seq: menu.seq || menu.SEQ || 1,
|
|
|
|
|
|
menuType: convertedMenuType,
|
|
|
|
|
|
status: convertedStatus,
|
|
|
|
|
|
companyCode: companyCode,
|
|
|
|
|
|
langKey: langKey, // 다국어 키 설정
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log("설정된 폼 데이터:", {
|
|
|
|
|
|
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 || "",
|
|
|
|
|
|
menuDesc: menu.menu_desc || menu.MENU_DESC || "",
|
|
|
|
|
|
seq: menu.seq || menu.SEQ || 1,
|
|
|
|
|
|
menuType: convertedMenuType,
|
|
|
|
|
|
status: convertedStatus,
|
|
|
|
|
|
companyCode: companyCode,
|
|
|
|
|
|
langKey: langKey,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error("메뉴 정보 로딩 오류:", error);
|
|
|
|
|
|
console.error("오류 상세 정보:", {
|
|
|
|
|
|
message: error?.message,
|
|
|
|
|
|
stack: error?.stack,
|
|
|
|
|
|
response: error?.response,
|
|
|
|
|
|
});
|
|
|
|
|
|
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_INFO));
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-25 11:07:39 +09:00
|
|
|
|
// useEffect를 loadMenuData 함수 정의 후로 이동
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
console.log("🚀 MenuFormModal useEffect 실행됨!");
|
|
|
|
|
|
console.log("📋 useEffect 파라미터:", { menuId, parentId, menuType });
|
|
|
|
|
|
console.log("MenuFormModal useEffect - menuId:", menuId, "parentId:", parentId, "menuType:", menuType);
|
|
|
|
|
|
|
|
|
|
|
|
if (menuId) {
|
|
|
|
|
|
console.log("메뉴 수정 모드 - menuId:", menuId);
|
|
|
|
|
|
setIsEdit(true);
|
|
|
|
|
|
loadMenuData();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log("메뉴 등록 모드 - parentId:", parentId, "menuType:", menuType);
|
|
|
|
|
|
setIsEdit(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 메뉴 타입 변환 (0 -> 0, 1 -> 1, admin -> 0, user -> 1)
|
|
|
|
|
|
let defaultMenuType = "1"; // 기본값은 사용자
|
|
|
|
|
|
if (menuType === "0" || menuType === "admin") {
|
|
|
|
|
|
defaultMenuType = "0"; // 관리자
|
|
|
|
|
|
} else if (menuType === "1" || menuType === "user") {
|
|
|
|
|
|
defaultMenuType = "1"; // 사용자
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
parentObjId: parentId || "0",
|
|
|
|
|
|
menuNameKor: "",
|
|
|
|
|
|
menuUrl: "",
|
|
|
|
|
|
menuDesc: "",
|
|
|
|
|
|
seq: 1,
|
|
|
|
|
|
menuType: defaultMenuType,
|
|
|
|
|
|
status: "ACTIVE", // 기본값은 활성화
|
|
|
|
|
|
companyCode: parentCompanyCode || "none", // 상위 메뉴의 회사 코드를 기본값으로 설정
|
|
|
|
|
|
langKey: "", // 다국어 키 초기화
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log("메뉴 등록 기본값 설정:", {
|
|
|
|
|
|
parentObjId: parentId || "0",
|
|
|
|
|
|
menuType: defaultMenuType,
|
|
|
|
|
|
status: "ACTIVE",
|
|
|
|
|
|
companyCode: "",
|
|
|
|
|
|
langKey: "",
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [menuId, parentId, menuType]);
|
|
|
|
|
|
|
|
|
|
|
|
// 강제로 useEffect 실행시키기 위한 별도 useEffect
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
console.log("🔧 강제 useEffect 실행 - 컴포넌트 마운트됨");
|
|
|
|
|
|
console.log("🔧 현재 props:", { isOpen, menuId, parentId, menuType });
|
|
|
|
|
|
|
|
|
|
|
|
// isOpen이 true일 때만 실행
|
|
|
|
|
|
if (isOpen && menuId) {
|
|
|
|
|
|
console.log("🔧 모달이 열렸고 menuId가 있음 - 강제 실행");
|
|
|
|
|
|
// 약간의 지연 후 실행
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
console.log("🔧 setTimeout으로 loadMenuData 실행");
|
|
|
|
|
|
loadMenuData();
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [isOpen]); // isOpen만 의존성으로 설정
|
|
|
|
|
|
|
|
|
|
|
|
// 회사 목록 로드
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (isOpen) {
|
|
|
|
|
|
loadCompanies();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [isOpen]);
|
|
|
|
|
|
|
|
|
|
|
|
// 다국어 키 목록 로드
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (isOpen && formData.companyCode) {
|
|
|
|
|
|
loadLangKeys();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [isOpen, formData.companyCode]);
|
|
|
|
|
|
|
|
|
|
|
|
// 드롭다운 외부 클릭 시 닫기
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
|
|
|
|
const target = event.target as Element;
|
|
|
|
|
|
if (!target.closest(".langkey-dropdown")) {
|
|
|
|
|
|
setIsLangKeyDropdownOpen(false);
|
|
|
|
|
|
setLangKeySearchText("");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (isLangKeyDropdownOpen) {
|
|
|
|
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [isLangKeyDropdownOpen]);
|
|
|
|
|
|
|
|
|
|
|
|
const loadCompanies = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const companyList = await companyAPI.getList({ status: "active" });
|
|
|
|
|
|
setCompanies(companyList);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("회사 목록 로딩 오류:", error);
|
|
|
|
|
|
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_COMPANY_LIST));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const loadLangKeys = async () => {
|
|
|
|
|
|
console.log("🔤 다국어 키 목록 조회 시작 - companyCode:", formData.companyCode);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await menuApi.getLangKeys({
|
|
|
|
|
|
companyCode: formData.companyCode === "none" ? "*" : formData.companyCode,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
|
// 활성화된 다국어 키만 필터링
|
|
|
|
|
|
const activeKeys = response.data.filter((key) => key.isActive === "Y");
|
|
|
|
|
|
console.log("🔤 다국어 키 목록 조회 성공:", activeKeys.length, "개 (활성화된 키)");
|
|
|
|
|
|
setLangKeys(activeKeys);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("❌ 다국어 키 목록 로딩 오류:", error);
|
|
|
|
|
|
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_LANG_KEY_LIST));
|
|
|
|
|
|
setLangKeys([]);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
|
|
if (!formData.menuNameKor.trim()) {
|
|
|
|
|
|
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_MENU_NAME_REQUIRED));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!formData.companyCode) {
|
|
|
|
|
|
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_COMPANY_REQUIRED));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
|
|
|
|
// 백엔드에 전송할 데이터 변환
|
|
|
|
|
|
const submitData = {
|
|
|
|
|
|
...formData,
|
|
|
|
|
|
// 상태를 소문자로 변환 (백엔드에서 소문자 기대)
|
|
|
|
|
|
status: formData.status.toLowerCase(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
console.log("저장할 데이터:", submitData);
|
2025-08-25 11:07:39 +09:00
|
|
|
|
|
|
|
|
|
|
let response;
|
|
|
|
|
|
|
|
|
|
|
|
if (isEdit && menuId) {
|
|
|
|
|
|
// 수정 모드: updateMenu API 호출
|
|
|
|
|
|
console.log("🔧 메뉴 수정 API 호출:", menuId);
|
|
|
|
|
|
response = await menuApi.updateMenu(menuId, submitData);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 추가 모드: saveMenu API 호출
|
|
|
|
|
|
console.log("➕ 메뉴 추가 API 호출");
|
|
|
|
|
|
response = await menuApi.saveMenu(submitData);
|
|
|
|
|
|
}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
|
|
if (response.success) {
|
|
|
|
|
|
toast.success(response.message);
|
|
|
|
|
|
onSuccess();
|
|
|
|
|
|
onClose();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
toast.error(response.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2025-08-25 11:07:39 +09:00
|
|
|
|
console.error("메뉴 저장/수정 실패:", error);
|
2025-08-21 09:41:46 +09:00
|
|
|
|
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_FAILED));
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleInputChange = (field: keyof MenuFormData, value: string | number) => {
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[field]: value,
|
|
|
|
|
|
}));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 선택된 다국어 키 정보 가져오기
|
|
|
|
|
|
const getSelectedLangKeyInfo = () => {
|
|
|
|
|
|
if (!formData.langKey) return null;
|
|
|
|
|
|
return langKeys.find((key) => key.langKey === formData.langKey);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const selectedLangKeyInfo = getSelectedLangKeyInfo();
|
|
|
|
|
|
|
2025-08-25 17:22:20 +09:00
|
|
|
|
// 전역 사용자 로케일 가져오기
|
|
|
|
|
|
const getCurrentUserLang = () => {
|
|
|
|
|
|
return (window as any).__GLOBAL_USER_LANG || localStorage.getItem("userLocale") || "KR";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
|
return (
|
|
|
|
|
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
|
|
|
|
<DialogContent className="sm:max-w-[600px]">
|
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
|
<DialogTitle>
|
|
|
|
|
|
{isEdit
|
2025-08-25 17:22:20 +09:00
|
|
|
|
? getMenuTextSync(MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE, getCurrentUserLang())
|
|
|
|
|
|
: getMenuTextSync(MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE, getCurrentUserLang())}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
</DialogTitle>
|
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
|
|
|
<div className="space-y-2">
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<Label htmlFor="menuType">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE, getCurrentUserLang())}
|
|
|
|
|
|
</Label>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
<Select value={formData.menuType} onValueChange={(value) => handleInputChange("menuType", value)}>
|
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<SelectItem value="0">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_ADMIN, getCurrentUserLang())}
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
<SelectItem value="1">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_USER, getCurrentUserLang())}
|
|
|
|
|
|
</SelectItem>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<Label htmlFor="status">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS, getCurrentUserLang())}</Label>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
<Select value={formData.status} onValueChange={(value) => handleInputChange("status", value)}>
|
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<SelectItem value="ACTIVE">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS_ACTIVE, getCurrentUserLang())}
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
<SelectItem value="INACTIVE">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS_INACTIVE, getCurrentUserLang())}
|
|
|
|
|
|
</SelectItem>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<Label htmlFor="companyCode">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY, getCurrentUserLang())} *
|
|
|
|
|
|
</Label>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
<Select
|
|
|
|
|
|
value={formData.companyCode}
|
|
|
|
|
|
onValueChange={(value) => handleInputChange("companyCode", value)}
|
|
|
|
|
|
disabled={!isEdit && level !== 1} // 수정 모드가 아니고 최상위 메뉴가 아니면 비활성화
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger>
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<SelectValue
|
|
|
|
|
|
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SELECT, getCurrentUserLang())}
|
|
|
|
|
|
/>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<SelectItem value="none">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_COMMON, getCurrentUserLang())}
|
|
|
|
|
|
</SelectItem>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
{companies.map((company) => (
|
|
|
|
|
|
<SelectItem key={company.company_code} value={company.company_code}>
|
|
|
|
|
|
{company.company_name}
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
{!isEdit && level !== 1 && (
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<p className="text-xs text-gray-500">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE, getCurrentUserLang())}
|
|
|
|
|
|
</p>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<Label htmlFor="langKey">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY, getCurrentUserLang())}</Label>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
<div className="langkey-dropdown relative">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setIsLangKeyDropdownOpen(!isLangKeyDropdownOpen)}
|
|
|
|
|
|
className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
|
|
|
|
disabled={!formData.companyCode}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span className={!formData.langKey ? "text-muted-foreground" : ""}>
|
2025-08-25 17:22:20 +09:00
|
|
|
|
{formData.langKey || getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECT, getCurrentUserLang())}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
</span>
|
|
|
|
|
|
<svg
|
|
|
|
|
|
className={`h-4 w-4 transition-transform ${isLangKeyDropdownOpen ? "rotate-180" : ""}`}
|
|
|
|
|
|
fill="none"
|
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
|
>
|
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
{isLangKeyDropdownOpen && (
|
|
|
|
|
|
<div className="bg-popover text-popover-foreground absolute top-full left-0 z-50 mt-1 w-full rounded-md border shadow-md">
|
|
|
|
|
|
{/* 검색 입력 */}
|
|
|
|
|
|
<div className="border-b p-2">
|
|
|
|
|
|
<Input
|
2025-08-25 17:22:20 +09:00
|
|
|
|
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SEARCH, getCurrentUserLang())}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
value={langKeySearchText}
|
|
|
|
|
|
onChange={(e) => setLangKeySearchText(e.target.value)}
|
|
|
|
|
|
className="h-8 text-sm"
|
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 다국어 키 목록 */}
|
|
|
|
|
|
<div className="max-h-48 overflow-y-auto">
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="hover:bg-accent hover:text-accent-foreground flex cursor-pointer items-center px-2 py-1.5 text-sm"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
handleInputChange("langKey", "");
|
|
|
|
|
|
setIsLangKeyDropdownOpen(false);
|
|
|
|
|
|
setLangKeySearchText("");
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2025-08-25 17:22:20 +09:00
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_NONE, getCurrentUserLang())}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{langKeys
|
|
|
|
|
|
.filter(
|
|
|
|
|
|
(key) =>
|
|
|
|
|
|
key.langKey.toLowerCase().includes(langKeySearchText.toLowerCase()) ||
|
|
|
|
|
|
key.description.toLowerCase().includes(langKeySearchText.toLowerCase()),
|
|
|
|
|
|
)
|
|
|
|
|
|
.map((key) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={key.keyId}
|
|
|
|
|
|
className="hover:bg-accent hover:text-accent-foreground flex cursor-pointer flex-col px-2 py-1.5 text-sm"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
handleInputChange("langKey", key.langKey);
|
|
|
|
|
|
setIsLangKeyDropdownOpen(false);
|
|
|
|
|
|
setLangKeySearchText("");
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="font-medium">{key.langKey}</div>
|
|
|
|
|
|
{key.description && <div className="text-xs text-gray-500">{key.description}</div>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{selectedLangKeyInfo && (
|
|
|
|
|
|
<p className="text-xs text-gray-500">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECTED, {
|
|
|
|
|
|
key: selectedLangKeyInfo.langKey,
|
|
|
|
|
|
description: selectedLangKeyInfo.description,
|
|
|
|
|
|
})}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<Label htmlFor="menuNameKor">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME, getCurrentUserLang())} *
|
|
|
|
|
|
</Label>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
<Input
|
|
|
|
|
|
id="menuNameKor"
|
|
|
|
|
|
value={formData.menuNameKor}
|
|
|
|
|
|
onChange={(e) => handleInputChange("menuNameKor", e.target.value)}
|
2025-08-25 17:22:20 +09:00
|
|
|
|
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME_PLACEHOLDER, getCurrentUserLang())}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
required
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<Label htmlFor="menuUrl">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_URL, getCurrentUserLang())}</Label>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
<Input
|
|
|
|
|
|
id="menuUrl"
|
|
|
|
|
|
value={formData.menuUrl}
|
|
|
|
|
|
onChange={(e) => handleInputChange("menuUrl", e.target.value)}
|
2025-08-25 17:22:20 +09:00
|
|
|
|
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER, getCurrentUserLang())}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<Label htmlFor="menuDesc">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION, getCurrentUserLang())}
|
|
|
|
|
|
</Label>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
<Textarea
|
|
|
|
|
|
id="menuDesc"
|
|
|
|
|
|
value={formData.menuDesc}
|
|
|
|
|
|
onChange={(e) => handleInputChange("menuDesc", e.target.value)}
|
2025-08-25 17:22:20 +09:00
|
|
|
|
placeholder={getMenuTextSync(
|
|
|
|
|
|
MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION_PLACEHOLDER,
|
|
|
|
|
|
getCurrentUserLang(),
|
|
|
|
|
|
)}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
rows={3}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
2025-08-25 17:22:20 +09:00
|
|
|
|
<Label htmlFor="seq">
|
|
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_SEQUENCE, getCurrentUserLang())}
|
|
|
|
|
|
</Label>
|
2025-08-21 09:41:46 +09:00
|
|
|
|
<Input
|
|
|
|
|
|
id="seq"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={formData.seq}
|
|
|
|
|
|
onChange={(e) => handleInputChange("seq", parseInt(e.target.value) || 1)}
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-end space-x-2 pt-4">
|
|
|
|
|
|
<Button type="button" variant="outline" onClick={onClose}>
|
2025-08-25 17:22:20 +09:00
|
|
|
|
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_CANCEL, getCurrentUserLang())}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
</Button>
|
|
|
|
|
|
<Button type="submit" disabled={loading}>
|
|
|
|
|
|
{loading
|
2025-08-25 17:22:20 +09:00
|
|
|
|
? getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_SAVE_PROCESSING, getCurrentUserLang())
|
2025-08-21 09:41:46 +09:00
|
|
|
|
: isEdit
|
2025-08-25 17:22:20 +09:00
|
|
|
|
? getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_MODIFY, getCurrentUserLang())
|
|
|
|
|
|
: getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_REGISTER, getCurrentUserLang())}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|