multilang #2
|
|
@ -10,7 +10,7 @@ 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";
|
||||
import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang";
|
||||
|
||||
interface Company {
|
||||
company_code: string;
|
||||
|
|
@ -27,6 +27,8 @@ interface MenuFormModalProps {
|
|||
menuType?: string;
|
||||
level?: number;
|
||||
parentCompanyCode?: string;
|
||||
// 다국어 텍스트 props 추가
|
||||
uiTexts: Record<string, string>;
|
||||
}
|
||||
|
||||
export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||
|
|
@ -38,6 +40,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
menuType,
|
||||
level,
|
||||
parentCompanyCode,
|
||||
uiTexts,
|
||||
}) => {
|
||||
console.log("🎯 MenuFormModal 렌더링 - Props:", {
|
||||
isOpen,
|
||||
|
|
@ -48,6 +51,11 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
parentCompanyCode,
|
||||
});
|
||||
|
||||
// 다국어 텍스트 가져오기 함수
|
||||
const getText = (key: string, fallback?: string): string => {
|
||||
return uiTexts[key] || fallback || key;
|
||||
};
|
||||
|
||||
console.log("🔍 MenuFormModal 컴포넌트 마운트됨");
|
||||
|
||||
const [formData, setFormData] = useState<MenuFormData>({
|
||||
|
|
@ -149,7 +157,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
stack: error?.stack,
|
||||
response: error?.response,
|
||||
});
|
||||
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_INFO));
|
||||
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_INFO));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -254,7 +262,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
setCompanies(companyList);
|
||||
} catch (error) {
|
||||
console.error("회사 목록 로딩 오류:", error);
|
||||
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_COMPANY_LIST));
|
||||
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_COMPANY_LIST));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -273,7 +281,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 다국어 키 목록 로딩 오류:", error);
|
||||
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_LANG_KEY_LIST));
|
||||
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_LANG_KEY_LIST));
|
||||
setLangKeys([]);
|
||||
}
|
||||
};
|
||||
|
|
@ -282,12 +290,12 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
e.preventDefault();
|
||||
|
||||
if (!formData.menuNameKor.trim()) {
|
||||
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_MENU_NAME_REQUIRED));
|
||||
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_MENU_NAME_REQUIRED));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.companyCode) {
|
||||
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_COMPANY_REQUIRED));
|
||||
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_COMPANY_REQUIRED));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -324,7 +332,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("메뉴 저장/수정 실패:", error);
|
||||
toast.error(getMenuTextSync(MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_FAILED));
|
||||
toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_FAILED));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -356,68 +364,52 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isEdit
|
||||
? getMenuTextSync(MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE, getCurrentUserLang())
|
||||
: getMenuTextSync(MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE, getCurrentUserLang())}
|
||||
? getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE)
|
||||
: getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE)}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="menuType">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE, getCurrentUserLang())}
|
||||
</Label>
|
||||
<Label htmlFor="menuType">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE)}</Label>
|
||||
<Select value={formData.menuType} onValueChange={(value) => handleInputChange("menuType", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<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>
|
||||
<SelectItem value="0">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_ADMIN)}</SelectItem>
|
||||
<SelectItem value="1">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_TYPE_USER)}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="status">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS, getCurrentUserLang())}</Label>
|
||||
<Label htmlFor="status">{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_STATUS)}</Label>
|
||||
<Select value={formData.status} onValueChange={(value) => handleInputChange("status", value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ACTIVE">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS_ACTIVE, getCurrentUserLang())}
|
||||
</SelectItem>
|
||||
<SelectItem value="INACTIVE">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_STATUS_INACTIVE, getCurrentUserLang())}
|
||||
</SelectItem>
|
||||
<SelectItem value="ACTIVE">{getText(MENU_MANAGEMENT_KEYS.STATUS_ACTIVE)}</SelectItem>
|
||||
<SelectItem value="INACTIVE">{getText(MENU_MANAGEMENT_KEYS.STATUS_INACTIVE)}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="companyCode">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY, getCurrentUserLang())} *
|
||||
</Label>
|
||||
<Label htmlFor="companyCode">{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY)} *</Label>
|
||||
<Select
|
||||
value={formData.companyCode}
|
||||
onValueChange={(value) => handleInputChange("companyCode", value)}
|
||||
disabled={!isEdit && level !== 1} // 수정 모드가 아니고 최상위 메뉴가 아니면 비활성화
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SELECT, getCurrentUserLang())}
|
||||
/>
|
||||
<SelectValue placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SELECT)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_COMMON, getCurrentUserLang())}
|
||||
</SelectItem>
|
||||
<SelectItem value="none">{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_COMMON)}</SelectItem>
|
||||
{companies.map((company) => (
|
||||
<SelectItem key={company.company_code} value={company.company_code}>
|
||||
{company.company_name}
|
||||
|
|
@ -426,14 +418,12 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
</SelectContent>
|
||||
</Select>
|
||||
{!isEdit && level !== 1 && (
|
||||
<p className="text-xs text-gray-500">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE, getCurrentUserLang())}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE)}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="langKey">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY, getCurrentUserLang())}</Label>
|
||||
<Label htmlFor="langKey">{getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY)}</Label>
|
||||
<div className="langkey-dropdown relative">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -442,7 +432,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
disabled={!formData.companyCode}
|
||||
>
|
||||
<span className={!formData.langKey ? "text-muted-foreground" : ""}>
|
||||
{formData.langKey || getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECT, getCurrentUserLang())}
|
||||
{formData.langKey || getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECT)}
|
||||
</span>
|
||||
<svg
|
||||
className={`h-4 w-4 transition-transform ${isLangKeyDropdownOpen ? "rotate-180" : ""}`}
|
||||
|
|
@ -459,7 +449,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
{/* 검색 입력 */}
|
||||
<div className="border-b p-2">
|
||||
<Input
|
||||
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SEARCH, getCurrentUserLang())}
|
||||
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SEARCH)}
|
||||
value={langKeySearchText}
|
||||
onChange={(e) => setLangKeySearchText(e.target.value)}
|
||||
className="h-8 text-sm"
|
||||
|
|
@ -477,7 +467,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
setLangKeySearchText("");
|
||||
}}
|
||||
>
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_NONE, getCurrentUserLang())}
|
||||
{getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_NONE)}
|
||||
</div>
|
||||
|
||||
{langKeys
|
||||
|
|
@ -506,57 +496,47 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
</div>
|
||||
{selectedLangKeyInfo && (
|
||||
<p className="text-xs text-gray-500">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECTED, {
|
||||
key: selectedLangKeyInfo.langKey,
|
||||
description: selectedLangKeyInfo.description,
|
||||
})}
|
||||
{getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECTED)
|
||||
.replace("{key}", selectedLangKeyInfo.langKey)
|
||||
.replace("{description}", selectedLangKeyInfo.description)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="menuNameKor">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME, getCurrentUserLang())} *
|
||||
</Label>
|
||||
<Label htmlFor="menuNameKor">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME)} *</Label>
|
||||
<Input
|
||||
id="menuNameKor"
|
||||
value={formData.menuNameKor}
|
||||
onChange={(e) => handleInputChange("menuNameKor", e.target.value)}
|
||||
placeholder={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME_PLACEHOLDER, getCurrentUserLang())}
|
||||
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME_PLACEHOLDER)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="menuUrl">{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_URL, getCurrentUserLang())}</Label>
|
||||
<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={getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER, getCurrentUserLang())}
|
||||
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="menuDesc">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION, getCurrentUserLang())}
|
||||
</Label>
|
||||
<Label htmlFor="menuDesc">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION)}</Label>
|
||||
<Textarea
|
||||
id="menuDesc"
|
||||
value={formData.menuDesc}
|
||||
onChange={(e) => handleInputChange("menuDesc", e.target.value)}
|
||||
placeholder={getMenuTextSync(
|
||||
MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION_PLACEHOLDER,
|
||||
getCurrentUserLang(),
|
||||
)}
|
||||
placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_DESCRIPTION_PLACEHOLDER)}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="seq">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.FORM_MENU_SEQUENCE, getCurrentUserLang())}
|
||||
</Label>
|
||||
<Label htmlFor="seq">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_SEQUENCE)}</Label>
|
||||
<Input
|
||||
id="seq"
|
||||
type="number"
|
||||
|
|
@ -568,14 +548,14 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_CANCEL, getCurrentUserLang())}
|
||||
{getText(MENU_MANAGEMENT_KEYS.BUTTON_CANCEL)}
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading
|
||||
? getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_SAVE_PROCESSING, getCurrentUserLang())
|
||||
? getText(MENU_MANAGEMENT_KEYS.BUTTON_SAVE_PROCESSING)
|
||||
: isEdit
|
||||
? getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_MODIFY, getCurrentUserLang())
|
||||
: getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_REGISTER, getCurrentUserLang())}
|
||||
? getText(MENU_MANAGEMENT_KEYS.BUTTON_MODIFY)
|
||||
: getText(MENU_MANAGEMENT_KEYS.BUTTON_REGISTER)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@ export const MenuManagement: React.FC = () => {
|
|||
"form.menu.type",
|
||||
"form.menu.type.admin",
|
||||
"form.menu.type.user",
|
||||
"form.status",
|
||||
"form.company",
|
||||
"form.company.select",
|
||||
"form.company.common",
|
||||
|
|
@ -179,11 +178,123 @@ export const MenuManagement: React.FC = () => {
|
|||
// 초기 로딩
|
||||
useEffect(() => {
|
||||
loadCompanies();
|
||||
}, []); // 빈 의존성 배열로 한 번만 실행
|
||||
// 사용자 언어가 설정되지 않았을 때만 기본 텍스트 설정
|
||||
if (!userLang) {
|
||||
initializeDefaultTexts();
|
||||
}
|
||||
}, [userLang]); // userLang 변경 시마다 실행
|
||||
|
||||
// 초기 기본 텍스트 설정 함수
|
||||
const initializeDefaultTexts = () => {
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
// 기본 한국어 텍스트 제공
|
||||
const defaultText = getDefaultText(key);
|
||||
defaultTexts[key] = defaultText;
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
console.log("🌐 초기 기본 텍스트 설정 완료:", Object.keys(defaultTexts).length);
|
||||
};
|
||||
|
||||
// 기본 텍스트 반환 함수
|
||||
const getDefaultText = (key: string): string => {
|
||||
const defaultTexts: Record<string, string> = {
|
||||
"menu.management.title": "메뉴 관리",
|
||||
"menu.management.description": "시스템의 메뉴 구조와 권한을 관리합니다.",
|
||||
"menu.type.title": "메뉴 타입",
|
||||
"menu.type.admin": "관리자",
|
||||
"menu.type.user": "사용자",
|
||||
"menu.management.admin": "관리자 메뉴",
|
||||
"menu.management.user": "사용자 메뉴",
|
||||
"menu.management.admin.description": "시스템 관리 및 설정 메뉴",
|
||||
"menu.management.user.description": "일반 사용자 업무 메뉴",
|
||||
"button.add": "추가",
|
||||
"button.add.top.level": "최상위 메뉴 추가",
|
||||
"button.add.sub": "하위 메뉴 추가",
|
||||
"button.edit": "수정",
|
||||
"button.delete": "삭제",
|
||||
"button.delete.selected": "선택 삭제",
|
||||
"button.delete.selected.count": "선택 삭제 ({count})",
|
||||
"button.delete.processing": "삭제 중...",
|
||||
"button.cancel": "취소",
|
||||
"button.save": "저장",
|
||||
"button.register": "등록",
|
||||
"button.modify": "수정",
|
||||
"filter.company": "회사",
|
||||
"filter.company.all": "전체",
|
||||
"filter.company.common": "공통",
|
||||
"filter.company.search": "회사 검색",
|
||||
"filter.search": "검색",
|
||||
"filter.search.placeholder": "메뉴명 또는 URL로 검색...",
|
||||
"filter.reset": "초기화",
|
||||
"table.header.select": "선택",
|
||||
"table.header.menu.name": "메뉴명",
|
||||
"table.header.menu.url": "URL",
|
||||
"table.header.menu.type": "메뉴 타입",
|
||||
"table.header.status": "상태",
|
||||
"table.header.company": "회사",
|
||||
"table.header.sequence": "순서",
|
||||
"table.header.actions": "작업",
|
||||
"status.active": "활성화",
|
||||
"status.inactive": "비활성화",
|
||||
"status.unspecified": "미지정",
|
||||
"form.menu.type": "메뉴 타입",
|
||||
"form.menu.type.admin": "관리자",
|
||||
"form.menu.type.user": "사용자",
|
||||
"form.company": "회사",
|
||||
"form.company.select": "회사를 선택하세요",
|
||||
"form.company.common": "공통",
|
||||
"form.company.submenu.note": "하위 메뉴는 상위 메뉴와 동일한 회사를 가져야 합니다.",
|
||||
"form.lang.key": "다국어 키",
|
||||
"form.lang.key.select": "다국어 키를 선택하세요",
|
||||
"form.lang.key.none": "다국어 키 없음",
|
||||
"form.lang.key.search": "다국어 키 검색...",
|
||||
"form.lang.key.selected": "선택된 키: {key} - {description}",
|
||||
"form.menu.name": "메뉴명",
|
||||
"form.menu.name.placeholder": "메뉴명을 입력하세요",
|
||||
"form.menu.url": "URL",
|
||||
"form.menu.url.placeholder": "메뉴 URL을 입력하세요",
|
||||
"form.menu.description": "설명",
|
||||
"form.menu.description.placeholder": "메뉴 설명을 입력하세요",
|
||||
"form.menu.sequence": "순서",
|
||||
"modal.menu.register.title": "메뉴 등록",
|
||||
"modal.menu.modify.title": "메뉴 수정",
|
||||
"modal.delete.title": "메뉴 삭제",
|
||||
"modal.delete.description": "해당 메뉴를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
|
||||
"modal.delete.batch.description":
|
||||
"선택된 {count}개의 메뉴를 영구적으로 삭제하시겠습니까?\n\n⚠️ 주의: 상위 메뉴를 삭제하면 하위 메뉴들도 함께 삭제됩니다.\n이 작업은 되돌릴 수 없습니다.",
|
||||
"message.loading": "로딩 중...",
|
||||
"message.menu.delete.processing": "메뉴 삭제 중...",
|
||||
"message.menu.save.success": "메뉴가 성공적으로 저장되었습니다.",
|
||||
"message.menu.save.failed": "메뉴 저장에 실패했습니다.",
|
||||
"message.menu.delete.success": "메뉴가 성공적으로 삭제되었습니다.",
|
||||
"message.menu.delete.failed": "메뉴 삭제에 실패했습니다.",
|
||||
"message.menu.delete.batch.success": "선택된 메뉴들이 성공적으로 삭제되었습니다.",
|
||||
"message.menu.delete.batch.partial": "일부 메뉴 삭제에 실패했습니다.",
|
||||
"message.menu.status.toggle.success": "메뉴 상태가 변경되었습니다.",
|
||||
"message.menu.status.toggle.failed": "메뉴 상태 변경에 실패했습니다.",
|
||||
"message.validation.menu.name.required": "메뉴명을 입력해주세요.",
|
||||
"message.validation.company.required": "회사를 선택해주세요.",
|
||||
"message.validation.select.menu.delete": "삭제할 메뉴를 선택해주세요.",
|
||||
"message.error.load.menu.list": "메뉴 목록을 불러오는데 실패했습니다.",
|
||||
"message.error.load.menu.info": "메뉴 정보를 불러오는데 실패했습니다.",
|
||||
"message.error.load.company.list": "회사 목록을 불러오는데 실패했습니다.",
|
||||
"message.error.load.lang.key.list": "다국어 키 목록을 불러오는데 실패했습니다.",
|
||||
"menu.list.title": "메뉴 목록",
|
||||
"menu.list.total": "총 {count}개",
|
||||
"menu.list.search.result": "검색 결과: {count}개",
|
||||
"ui.expand": "펼치기",
|
||||
"ui.collapse": "접기",
|
||||
"ui.menu.collapse": "메뉴 접기",
|
||||
"ui.language": "언어",
|
||||
};
|
||||
|
||||
return defaultTexts[key] || key;
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 및 userLang 변경 시 다국어 텍스트 로드
|
||||
useEffect(() => {
|
||||
if (!uiTextsLoading) {
|
||||
if (userLang && !uiTextsLoading) {
|
||||
loadUITexts();
|
||||
}
|
||||
}, [userLang]); // userLang 변경 시마다 실행
|
||||
|
|
@ -199,17 +310,35 @@ export const MenuManagement: React.FC = () => {
|
|||
});
|
||||
}, [uiTexts]);
|
||||
|
||||
// 컴포넌트 마운트 시 강제로 번역 로드 (userLang이 아직 설정되지 않았을 수 있음)
|
||||
// 컴포넌트 마운트 후 다국어 텍스트 강제 로드 (userLang이 아직 설정되지 않았을 수 있음)
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!uiTextsLoading && Object.keys(uiTexts).length === 0) {
|
||||
console.log("🔄 컴포넌트 마운트 후 강제 번역 로드");
|
||||
if (userLang && !uiTextsLoading) {
|
||||
console.log("🔄 컴포넌트 마운트 후 다국어 텍스트 강제 로드");
|
||||
loadUITexts();
|
||||
}
|
||||
}, 100); // 100ms 후 실행
|
||||
}, 300); // 300ms 후 실행
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []); // 컴포넌트 마운트 시 한 번만 실행
|
||||
}, [userLang]); // userLang이 설정된 후 실행
|
||||
|
||||
// 추가 안전장치: 컴포넌트 마운트 후 일정 시간이 지나면 강제로 다국어 텍스트 로드
|
||||
useEffect(() => {
|
||||
const fallbackTimer = setTimeout(() => {
|
||||
if (!uiTextsLoading && Object.keys(uiTexts).length === 0) {
|
||||
console.log("🔄 안전장치: 컴포넌트 마운트 후 강제 다국어 텍스트 로드");
|
||||
// 사용자 언어가 설정되지 않았을 때만 기본 텍스트 설정
|
||||
if (!userLang) {
|
||||
initializeDefaultTexts();
|
||||
} else {
|
||||
// 사용자 언어가 설정된 경우 다국어 텍스트 로드
|
||||
loadUITexts();
|
||||
}
|
||||
}
|
||||
}, 1000); // 1초 후 실행
|
||||
|
||||
return () => clearTimeout(fallbackTimer);
|
||||
}, [userLang]); // userLang 변경 시마다 실행
|
||||
|
||||
// 번역 로드 이벤트 감지
|
||||
useEffect(() => {
|
||||
|
|
@ -293,12 +422,22 @@ export const MenuManagement: React.FC = () => {
|
|||
console.log("🌐 사용자 언어가 설정되지 않음, 기본값 설정");
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
defaultTexts[key] = key; // 키를 기본값으로 사용
|
||||
defaultTexts[key] = getDefaultText(key); // 기본 한국어 텍스트 사용
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
return;
|
||||
}
|
||||
|
||||
// 사용자 언어가 설정된 경우, 기존 uiTexts가 비어있으면 기본 텍스트로 초기화
|
||||
if (Object.keys(uiTexts).length === 0) {
|
||||
console.log("🌐 기존 uiTexts가 비어있음, 기본 텍스트로 초기화");
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
defaultTexts[key] = getDefaultText(key);
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
}
|
||||
|
||||
console.log("🌐 UI 다국어 텍스트 로드 시작", {
|
||||
userLang,
|
||||
apiParams: {
|
||||
|
|
@ -328,32 +467,26 @@ export const MenuManagement: React.FC = () => {
|
|||
const translations = response.data.data;
|
||||
console.log("🌐 배치 다국어 텍스트 응답:", translations);
|
||||
|
||||
// 번역 결과를 상태에 저장
|
||||
console.log("🔧 setUiTexts 호출 전:", { translationsCount: Object.keys(translations).length });
|
||||
setUiTexts(translations);
|
||||
console.log("🔧 setUiTexts 호출 후 - translations:", translations);
|
||||
// 번역 결과를 상태에 저장 (기존 uiTexts와 병합)
|
||||
const mergedTranslations = { ...uiTexts, ...translations };
|
||||
console.log("🔧 setUiTexts 호출 전:", {
|
||||
translationsCount: Object.keys(translations).length,
|
||||
mergedCount: Object.keys(mergedTranslations).length,
|
||||
});
|
||||
setUiTexts(mergedTranslations);
|
||||
console.log("🔧 setUiTexts 호출 후 - mergedTranslations:", mergedTranslations);
|
||||
|
||||
// 번역 캐시에 저장 (다른 컴포넌트에서도 사용할 수 있도록)
|
||||
setTranslationCache(userLang, translations);
|
||||
setTranslationCache(userLang, mergedTranslations);
|
||||
} else {
|
||||
console.error("❌ 다국어 텍스트 배치 조회 실패:", response.data.message);
|
||||
|
||||
// API 실패 시 기본 텍스트 사용
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
defaultTexts[key] = key; // 키를 기본값으로 사용
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
// API 실패 시에도 기존 uiTexts는 유지
|
||||
console.log("🔄 API 실패로 인해 기존 uiTexts 유지");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ UI 다국어 텍스트 로드 실패:", error);
|
||||
|
||||
// API 실패 시 기본 텍스트 사용
|
||||
const defaultTexts: Record<string, string> = {};
|
||||
MENU_MANAGEMENT_LANG_KEYS.forEach((key) => {
|
||||
defaultTexts[key] = key; // 키를 기본값으로 사용
|
||||
});
|
||||
setUiTexts(defaultTexts);
|
||||
// API 실패 시에도 기존 uiTexts는 유지
|
||||
console.log("🔄 API 실패로 인해 기존 uiTexts 유지");
|
||||
} finally {
|
||||
setUiTextsLoading(false);
|
||||
}
|
||||
|
|
@ -367,16 +500,9 @@ export const MenuManagement: React.FC = () => {
|
|||
// uiTexts에서 번역 텍스트 찾기
|
||||
let text = uiTexts[key];
|
||||
|
||||
// 디버깅: uiTexts 상태 확인
|
||||
// uiTexts에 없으면 fallback 또는 키 사용
|
||||
if (!text) {
|
||||
console.log(`🔍 getUITextSync - 키 "${key}"를 uiTexts에서 찾을 수 없음`);
|
||||
console.log("🔍 uiTexts 상태:", {
|
||||
count: Object.keys(uiTexts).length,
|
||||
sampleKeys: Object.keys(uiTexts).slice(0, 5),
|
||||
});
|
||||
text = fallback || key;
|
||||
} else {
|
||||
console.log(`✅ getUITextSync - 키 "${key}" 번역 텍스트 찾음: "${text}"`);
|
||||
}
|
||||
|
||||
// 파라미터 치환
|
||||
|
|
@ -900,6 +1026,7 @@ export const MenuManagement: React.FC = () => {
|
|||
onSelectAllMenus={handleSelectAllMenus}
|
||||
expandedMenus={expandedMenus}
|
||||
onToggleExpand={handleToggleExpand}
|
||||
uiTexts={uiTexts}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -915,6 +1042,7 @@ export const MenuManagement: React.FC = () => {
|
|||
menuType={formData.menuType}
|
||||
level={formData.level}
|
||||
parentCompanyCode={formData.parentCompanyCode}
|
||||
uiTexts={uiTexts}
|
||||
/>
|
||||
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { toast } from "sonner";
|
||||
import { useMultiLang } from "@/hooks/useMultiLang";
|
||||
import { getMenuTextSync, MENU_MANAGEMENT_KEYS, setTranslationCache } from "@/lib/utils/multilang";
|
||||
|
||||
import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang";
|
||||
|
||||
interface MenuTableProps {
|
||||
menus: MenuItem[];
|
||||
|
|
@ -20,6 +20,8 @@ interface MenuTableProps {
|
|||
onSelectAllMenus: (checked: boolean) => void;
|
||||
expandedMenus: Set<string>;
|
||||
onToggleExpand: (menuId: string) => void;
|
||||
// 다국어 텍스트 props 추가
|
||||
uiTexts: Record<string, string>;
|
||||
}
|
||||
|
||||
export const MenuTable: React.FC<MenuTableProps> = ({
|
||||
|
|
@ -33,8 +35,12 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
|||
onSelectAllMenus,
|
||||
expandedMenus,
|
||||
onToggleExpand,
|
||||
uiTexts,
|
||||
}) => {
|
||||
const { userLang } = useMultiLang();
|
||||
// 다국어 텍스트 가져오기 함수
|
||||
const getText = (key: string, fallback?: string): string => {
|
||||
return uiTexts[key] || fallback || key;
|
||||
};
|
||||
|
||||
// 다국어 텍스트 표시 함수 (기본값 처리)
|
||||
const getDisplayText = (menu: MenuItem) => {
|
||||
|
|
@ -130,8 +136,8 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
|||
}`}
|
||||
>
|
||||
{status === "active"
|
||||
? getMenuTextSync(MENU_MANAGEMENT_KEYS.STATUS_ACTIVE)
|
||||
: getMenuTextSync(MENU_MANAGEMENT_KEYS.STATUS_INACTIVE)}
|
||||
? getText(MENU_MANAGEMENT_KEYS.STATUS_ACTIVE)
|
||||
: getText(MENU_MANAGEMENT_KEYS.STATUS_INACTIVE)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
@ -156,22 +162,22 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
|||
/>
|
||||
</TableHead>
|
||||
<TableHead className="w-1/3 bg-gray-50 font-semibold text-gray-700">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_NAME)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_NAME)}
|
||||
</TableHead>
|
||||
<TableHead className="w-16 bg-gray-50 font-semibold text-gray-700">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_SEQUENCE)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_SEQUENCE)}
|
||||
</TableHead>
|
||||
<TableHead className="w-24 bg-gray-50 font-semibold text-gray-700">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_COMPANY)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_COMPANY)}
|
||||
</TableHead>
|
||||
<TableHead className="w-48 bg-gray-50 font-semibold text-gray-700">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_URL)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_MENU_URL)}
|
||||
</TableHead>
|
||||
<TableHead className="w-20 bg-gray-50 font-semibold text-gray-700">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_STATUS)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_STATUS)}
|
||||
</TableHead>
|
||||
<TableHead className="w-32 bg-gray-50 font-semibold text-gray-700">
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.TABLE_HEADER_ACTIONS)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.TABLE_HEADER_ACTIONS)}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
|
@ -236,11 +242,11 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
|||
<TableCell className="text-sm text-gray-600">
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className={`font-medium ${companyName && companyName !== getMenuTextSync(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED) ? "text-green-600" : "text-gray-500"}`}
|
||||
className={`font-medium ${companyName && companyName !== getText(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED) ? "text-green-600" : "text-gray-500"}`}
|
||||
>
|
||||
{companyCode === "*"
|
||||
? getMenuTextSync(MENU_MANAGEMENT_KEYS.FILTER_COMPANY_COMMON)
|
||||
: companyName || getMenuTextSync(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED)}
|
||||
? getText(MENU_MANAGEMENT_KEYS.FILTER_COMPANY_COMMON)
|
||||
: companyName || getText(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED)}
|
||||
</span>
|
||||
{companyCode && companyCode !== "" && (
|
||||
<span className="font-mono text-xs text-gray-400">{companyCode}</span>
|
||||
|
|
@ -285,7 +291,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
|||
className="min-w-[40px] px-1 py-1 text-xs"
|
||||
onClick={() => onAddMenu(objid, menuType, lev)}
|
||||
>
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_ADD)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.BUTTON_ADD)}
|
||||
</Button>
|
||||
)}
|
||||
{lev === 2 && (
|
||||
|
|
@ -296,7 +302,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
|||
className="min-w-[40px] px-1 py-1 text-xs"
|
||||
onClick={() => onAddMenu(objid, menuType, lev)}
|
||||
>
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_ADD_SUB)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.BUTTON_ADD_SUB)}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
|
|
@ -304,7 +310,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
|||
className="min-w-[40px] px-1 py-1 text-xs"
|
||||
onClick={() => onEditMenu(objid)}
|
||||
>
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_EDIT)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.BUTTON_EDIT)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -315,7 +321,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
|||
className="min-w-[40px] px-1 py-1 text-xs"
|
||||
onClick={() => onEditMenu(objid)}
|
||||
>
|
||||
{getMenuTextSync(MENU_MANAGEMENT_KEYS.BUTTON_EDIT)}
|
||||
{getText(MENU_MANAGEMENT_KEYS.BUTTON_EDIT)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
|
|||
// 전역 로케일이 아직 로드되지 않았으면 대기
|
||||
console.log("⏳ 전역 로케일 로드 대기 중...");
|
||||
|
||||
// 주기적으로 전역 로케일 확인
|
||||
// 주기적으로 전역 로케일 확인 (더 빠른 간격으로)
|
||||
const checkInterval = setInterval(() => {
|
||||
if ((window as any).__GLOBAL_USER_LOCALE_LOADED) {
|
||||
const globalLocale = (window as any).__GLOBAL_USER_LANG;
|
||||
|
|
@ -69,9 +69,9 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
|
|||
globalUserLang = globalLocale;
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
}, 100);
|
||||
}, 50); // 50ms로 단축
|
||||
|
||||
// 5초 후 타임아웃
|
||||
// 3초 후 타임아웃 (더 빠른 타임아웃)
|
||||
setTimeout(() => {
|
||||
clearInterval(checkInterval);
|
||||
if (!userLang) {
|
||||
|
|
@ -79,14 +79,14 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
|
|||
setUserLang("KR");
|
||||
globalUserLang = "KR";
|
||||
}
|
||||
}, 5000);
|
||||
}, 3000); // 3초로 단축
|
||||
|
||||
return () => clearInterval(checkInterval);
|
||||
}, []);
|
||||
|
||||
// 다국어 텍스트 가져오기 (배치 조회 방식)
|
||||
const getText = async (menuCode: string, langKey: string, fallback?: string): Promise<string> => {
|
||||
console.log(`🔍 다국어 텍스트 요청 (배치 방식):`, { menuCode, langKey, userLang, companyCode });
|
||||
console.log("🔍 다국어 텍스트 요청 (배치 방식):", { menuCode, langKey, userLang, companyCode });
|
||||
|
||||
try {
|
||||
// 배치 조회 API 사용
|
||||
|
|
@ -104,7 +104,7 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
|
|||
},
|
||||
);
|
||||
|
||||
console.log(`📡 배치 API 응답 상태:`, response.status, response.statusText);
|
||||
console.log("📡 배치 API 응답 상태:", response.status, response.statusText);
|
||||
|
||||
if (response.data.success && response.data.data && response.data.data[langKey]) {
|
||||
// 번역 텍스트를 캐시에 저장
|
||||
|
|
@ -117,11 +117,11 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
|
|||
}
|
||||
|
||||
// 실패 시 fallback 또는 키 반환
|
||||
console.log(`🔄 배치 API 성공했지만 데이터 없음, fallback 반환:`, fallback || langKey);
|
||||
console.log("🔄 배치 API 성공했지만 데이터 없음, fallback 반환:", fallback || langKey);
|
||||
return fallback || langKey;
|
||||
} catch (error) {
|
||||
console.error("❌ 다국어 텍스트 배치 조회 실패:", error);
|
||||
console.log(`🔄 에러 시 fallback 반환:`, fallback || langKey);
|
||||
console.log("🔄 에러 시 fallback 반환:", fallback || langKey);
|
||||
return fallback || langKey;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue