"use client"; import { useState, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Plus } from "lucide-react"; import { DataTable } from "@/components/common/DataTable"; import { LoadingSpinner } from "@/components/common/LoadingSpinner"; import { useAuth } from "@/hooks/useAuth"; import LangKeyModal from "@/components/admin/LangKeyModal"; import LanguageModal from "@/components/admin/LanguageModal"; import { CategoryTree } from "@/components/admin/multilang/CategoryTree"; import { KeyGenerateModal } from "@/components/admin/multilang/KeyGenerateModal"; import { apiClient } from "@/lib/api/client"; import { LangCategory } from "@/lib/api/multilang"; interface Language { langCode: string; langName: string; langNative: string; isActive: string; } interface LangKey { keyId: number; companyCode: string; menuName: string; langKey: string; description: string; isActive: string; } interface LangText { textId: number; keyId: number; langCode: string; langText: string; isActive: string; } export default function I18nPage() { const { user } = useAuth(); const [loading, setLoading] = useState(true); const [languages, setLanguages] = useState([]); const [langKeys, setLangKeys] = useState([]); const [selectedKey, setSelectedKey] = useState(null); const [langTexts, setLangTexts] = useState([]); const [editingTexts, setEditingTexts] = useState([]); const [selectedCompany, setSelectedCompany] = useState("all"); const [searchText, setSearchText] = useState(""); const [isModalOpen, setIsModalOpen] = useState(false); const [editingKey, setEditingKey] = useState(null); const [selectedKeys, setSelectedKeys] = useState>(new Set()); // 언어 관리 관련 상태 const [isLanguageModalOpen, setIsLanguageModalOpen] = useState(false); const [editingLanguage, setEditingLanguage] = useState(null); const [selectedLanguages, setSelectedLanguages] = useState>(new Set()); const [activeTab, setActiveTab] = useState<"keys" | "languages">("keys"); // 카테고리 관련 상태 const [selectedCategory, setSelectedCategory] = useState(null); const [isGenerateModalOpen, setIsGenerateModalOpen] = useState(false); const [companies, setCompanies] = useState>([]); // 회사 목록 조회 const fetchCompanies = async () => { try { const response = await apiClient.get("/admin/companies"); const data = response.data; if (data.success) { const companyList = data.data.map((company: any) => ({ code: company.company_code, name: company.company_name, })); setCompanies(companyList); } } catch (error) { // console.error("회사 목록 조회 실패:", error); } }; // 언어 목록 조회 const fetchLanguages = async () => { try { const response = await apiClient.get("/multilang/languages"); const data = response.data; if (data.success) { setLanguages(data.data); } } catch (error) { // console.error("언어 목록 조회 실패:", error); } }; // 다국어 키 목록 조회 const fetchLangKeys = async () => { try { const response = await apiClient.get("/multilang/keys"); const data = response.data; if (data.success) { setLangKeys(data.data); } } catch (error) { // console.error("다국어 키 목록 조회 실패:", error); } }; // 필터링된 데이터 계산 const getFilteredLangKeys = () => { let filteredKeys = langKeys; // 회사 필터링 if (selectedCompany && selectedCompany !== "all") { filteredKeys = filteredKeys.filter((key) => key.companyCode === selectedCompany); } // 텍스트 검색 필터링 if (searchText.trim()) { const searchLower = searchText.toLowerCase(); filteredKeys = filteredKeys.filter((key) => { const langKey = (key.langKey || "").toLowerCase(); const description = (key.description || "").toLowerCase(); const menuName = (key.menuName || "").toLowerCase(); const companyName = companies.find((c) => c.code === key.companyCode)?.name?.toLowerCase() || ""; return ( langKey.includes(searchLower) || description.includes(searchLower) || menuName.includes(searchLower) || companyName.includes(searchLower) ); }); } return filteredKeys; }; // 선택된 키의 다국어 텍스트 조회 const fetchLangTexts = async (keyId: number) => { try { const response = await apiClient.get(`/multilang/keys/${keyId}/texts`); const data = response.data; if (data.success) { setLangTexts(data.data); const editingData = data.data.map((text: LangText) => ({ ...text })); setEditingTexts(editingData); } } catch (error) { // console.error("다국어 텍스트 조회 실패:", error); } }; // 언어 키 선택 처리 const handleKeySelect = (key: LangKey) => { setSelectedKey(key); fetchLangTexts(key.keyId); }; // 텍스트 변경 처리 const handleTextChange = (langCode: string, value: string) => { const newEditingTexts = [...editingTexts]; const existingIndex = newEditingTexts.findIndex((t) => t.langCode === langCode); if (existingIndex >= 0) { newEditingTexts[existingIndex].langText = value; } else { newEditingTexts.push({ textId: 0, keyId: selectedKey!.keyId, langCode: langCode, langText: value, isActive: "Y", }); } setEditingTexts(newEditingTexts); }; // 텍스트 저장 const handleSave = async () => { if (!selectedKey) return; try { const requestData = { texts: editingTexts.map((text) => ({ langCode: text.langCode, langText: text.langText, isActive: text.isActive || "Y", createdBy: user?.userId || "system", updatedBy: user?.userId || "system", })), }; const response = await apiClient.post(`/multilang/keys/${selectedKey.keyId}/texts`, requestData); const data = response.data; if (data.success) { alert("저장되었습니다."); fetchLangTexts(selectedKey.keyId); } } catch (error) { alert("저장에 실패했습니다."); } }; // 언어 키 추가/수정 모달 열기 const handleAddKey = () => { setEditingKey(null); setIsModalOpen(true); }; // 언어 추가/수정 모달 열기 const handleAddLanguage = () => { setEditingLanguage(null); setIsLanguageModalOpen(true); }; // 언어 수정 const handleEditLanguage = (language: Language) => { setEditingLanguage(language); setIsLanguageModalOpen(true); }; // 언어 저장 (추가/수정) const handleSaveLanguage = async (languageData: any) => { try { const requestData = { ...languageData, createdBy: user?.userId || "admin", updatedBy: user?.userId || "admin", }; let response; if (editingLanguage) { response = await apiClient.put(`/multilang/languages/${editingLanguage.langCode}`, requestData); } else { response = await apiClient.post("/multilang/languages", requestData); } const result = response.data; if (result.success) { alert(editingLanguage ? "언어가 수정되었습니다." : "언어가 추가되었습니다."); setIsLanguageModalOpen(false); fetchLanguages(); } else { alert(`오류: ${result.message}`); } } catch (error) { alert("언어 저장 중 오류가 발생했습니다."); } }; // 언어 삭제 const handleDeleteLanguages = async () => { if (selectedLanguages.size === 0) { alert("삭제할 언어를 선택해주세요."); return; } if ( !confirm( `선택된 ${selectedLanguages.size}개의 언어를 영구적으로 삭제하시겠습니까?\n이 작업은 되돌릴 수 없습니다.`, ) ) { return; } try { const deletePromises = Array.from(selectedLanguages).map((langCode) => apiClient.delete(`/multilang/languages/${langCode}`), ); const responses = await Promise.all(deletePromises); const failedDeletes = responses.filter((response) => !response.data.success); if (failedDeletes.length === 0) { alert("선택된 언어가 삭제되었습니다."); setSelectedLanguages(new Set()); fetchLanguages(); } else { alert(`${failedDeletes.length}개의 언어 삭제에 실패했습니다.`); } } catch (error) { alert("언어 삭제 중 오류가 발생했습니다."); } }; // 언어 선택 체크박스 처리 const handleLanguageCheckboxChange = (langCode: string, checked: boolean) => { const newSelected = new Set(selectedLanguages); if (checked) { newSelected.add(langCode); } else { newSelected.delete(langCode); } setSelectedLanguages(newSelected); }; // 언어 전체 선택/해제 const handleSelectAllLanguages = (checked: boolean) => { if (checked) { setSelectedLanguages(new Set(languages.map((lang) => lang.langCode))); } else { setSelectedLanguages(new Set()); } }; // 언어 키 수정 모달 열기 const handleEditKey = (key: LangKey) => { setEditingKey(key); setIsModalOpen(true); }; // 언어 키 저장 (추가/수정) const handleSaveKey = async (keyData: any) => { try { const requestData = { ...keyData, createdBy: user?.userId || "admin", updatedBy: user?.userId || "admin", }; let response; if (editingKey) { response = await apiClient.put(`/multilang/keys/${editingKey.keyId}`, requestData); } else { response = await apiClient.post("/multilang/keys", requestData); } const data = response.data; if (data.success) { alert(editingKey ? "언어 키가 수정되었습니다." : "언어 키가 추가되었습니다."); fetchLangKeys(); setIsModalOpen(false); } else { if (data.message && data.message.includes("이미 존재하는 언어키")) { alert(data.message); } else { alert(data.message || "언어 키 저장에 실패했습니다."); } } } catch (error) { alert("언어 키 저장에 실패했습니다."); } }; // 체크박스 선택/해제 const handleCheckboxChange = (keyId: number, checked: boolean) => { const newSelectedKeys = new Set(selectedKeys); if (checked) { newSelectedKeys.add(keyId); } else { newSelectedKeys.delete(keyId); } setSelectedKeys(newSelectedKeys); }; // 키 상태 토글 const handleToggleStatus = async (keyId: number) => { try { const response = await apiClient.put(`/multilang/keys/${keyId}/toggle`); const data = response.data; if (data.success) { alert(`키가 ${data.data}되었습니다.`); fetchLangKeys(); } else { alert("상태 변경 중 오류가 발생했습니다."); } } catch (error) { alert("키 상태 변경 중 오류가 발생했습니다."); } }; // 언어 상태 토글 const handleToggleLanguageStatus = async (langCode: string) => { try { const response = await apiClient.put(`/multilang/languages/${langCode}/toggle`); const data = response.data; if (data.success) { alert(`언어가 ${data.data}되었습니다.`); fetchLanguages(); } else { alert("언어 상태 변경 중 오류가 발생했습니다."); } } catch (error) { alert("언어 상태 변경 중 오류가 발생했습니다."); } }; // 전체 선택/해제 const handleSelectAll = (checked: boolean) => { if (checked) { const allKeyIds = getFilteredLangKeys().map((key) => key.keyId); setSelectedKeys(new Set(allKeyIds)); } else { setSelectedKeys(new Set()); } }; // 선택된 키들 일괄 삭제 const handleDeleteSelectedKeys = async () => { if (selectedKeys.size === 0) { alert("삭제할 키를 선택해주세요."); return; } if ( !confirm( `선택된 ${selectedKeys.size}개의 언어 키를 영구적으로 삭제하시겠습니까?\n\n⚠️ 이 작업은 되돌릴 수 없습니다.`, ) ) { return; } try { const deletePromises = Array.from(selectedKeys).map((keyId) => apiClient.delete(`/multilang/keys/${keyId}`)); const responses = await Promise.all(deletePromises); const allSuccess = responses.every((response) => response.data.success); if (allSuccess) { alert(`${selectedKeys.size}개의 언어 키가 영구적으로 삭제되었습니다.`); setSelectedKeys(new Set()); fetchLangKeys(); if (selectedKey && selectedKeys.has(selectedKey.keyId)) { handleCancel(); } } else { alert("일부 키 삭제에 실패했습니다."); } } catch (error) { alert("선택된 키 삭제에 실패했습니다."); } }; // 개별 키 삭제 const handleDeleteKey = async (keyId: number) => { if (!confirm("정말로 이 언어 키를 영구적으로 삭제하시겠습니까?\n\n⚠️ 이 작업은 되돌릴 수 없습니다.")) { return; } try { const response = await apiClient.delete(`/multilang/keys/${keyId}`); const data = response.data; if (data.success) { alert("언어 키가 영구적으로 삭제되었습니다."); fetchLangKeys(); if (selectedKey && selectedKey.keyId === keyId) { handleCancel(); } } } catch (error) { alert("언어 키 삭제에 실패했습니다."); } }; // 취소 처리 const handleCancel = () => { setSelectedKey(null); setLangTexts([]); setEditingTexts([]); }; useEffect(() => { const initializeData = async () => { setLoading(true); await Promise.all([fetchCompanies(), fetchLanguages(), fetchLangKeys()]); setLoading(false); }; initializeData(); }, []); const columns = [ { id: "select", header: () => { const filteredKeys = getFilteredLangKeys(); return ( 0} onChange={(e) => handleSelectAll(e.target.checked)} className="h-4 w-4" /> ); }, cell: ({ row }: any) => ( handleCheckboxChange(row.original.keyId, e.target.checked)} onClick={(e) => e.stopPropagation()} className="h-4 w-4" disabled={row.original.isActive === "N"} /> ), }, { accessorKey: "companyCode", header: "회사", cell: ({ row }: any) => { const companyName = row.original.companyCode === "*" ? "공통" : companies.find((c) => c.code === row.original.companyCode)?.name || row.original.companyCode; return {companyName}; }, }, { accessorKey: "menuName", header: "메뉴명", cell: ({ row }: any) => ( {row.original.menuName} ), }, { accessorKey: "langKey", header: "언어 키", cell: ({ row }: any) => (
handleEditKey(row.original)} > {row.original.langKey}
), }, { accessorKey: "description", header: "설명", cell: ({ row }: any) => ( {row.original.description} ), }, { accessorKey: "isActive", header: "상태", cell: ({ row }: any) => ( ), }, ]; // 언어 테이블 컬럼 정의 const languageColumns = [ { id: "select", header: () => ( 0} onChange={(e) => handleSelectAllLanguages(e.target.checked)} className="h-4 w-4" /> ), cell: ({ row }: any) => ( handleLanguageCheckboxChange(row.original.langCode, e.target.checked)} onClick={(e) => e.stopPropagation()} className="h-4 w-4" disabled={row.original.isActive === "N"} /> ), }, { accessorKey: "langCode", header: "언어 코드", cell: ({ row }: any) => (
handleEditLanguage(row.original)} > {row.original.langCode}
), }, { accessorKey: "langName", header: "언어명 (영문)", cell: ({ row }: any) => ( {row.original.langName} ), }, { accessorKey: "langNative", header: "언어명 (원어)", cell: ({ row }: any) => ( {row.original.langNative} ), }, { accessorKey: "isActive", header: "상태", cell: ({ row }: any) => ( ), }, ]; if (loading) { return ; } return (
{/* 탭 네비게이션 */}
{/* 메인 콘텐츠 영역 */}
{/* 언어 관리 탭 */} {activeTab === "languages" && ( 언어 관리
총 {languages.length}개의 언어가 등록되어 있습니다.
{selectedLanguages.size > 0 && ( )}
)} {/* 다국어 키 관리 탭 */} {activeTab === "keys" && (
{/* 좌측: 카테고리 트리 (2/12) */}
카테고리
setSelectedCategory(cat)} onDoubleClickCategory={(cat) => { setSelectedCategory(cat); setIsGenerateModalOpen(true); }} />
{/* 중앙: 언어 키 목록 (6/12) */}
언어 키 목록 {selectedCategory && ( {selectedCategory.categoryName} )}
{/* 검색 필터 영역 */}
setSearchText(e.target.value)} className="h-8 text-xs" />
결과: {getFilteredLangKeys().length}건
{/* 테이블 영역 */}
{/* 우측: 선택된 키의 다국어 관리 (4/12) */} {selectedKey ? ( <> 선택된 키:{" "} {selectedKey.companyCode}.{selectedKey.menuName}.{selectedKey.langKey} ) : ( "다국어 편집" )} {selectedKey ? (
{/* 스크롤 가능한 텍스트 영역 */}
{languages .filter((lang) => lang.isActive === "Y") .map((lang) => { const text = editingTexts.find((t) => t.langCode === lang.langCode); return (
{lang.langName} handleTextChange(lang.langCode, e.target.value)} className="flex-1" />
); })}
{/* 저장 버튼 - 고정 위치 */}
) : (
언어 키를 선택하세요
좌측 목록에서 편집할 언어 키를 클릭하세요
)}
)}
{/* 언어 키 추가/수정 모달 */} setIsModalOpen(false)} onSave={handleSaveKey} keyData={editingKey} companies={companies} /> {/* 언어 추가/수정 모달 */} setIsLanguageModalOpen(false)} onSave={handleSaveLanguage} languageData={editingLanguage} /> {/* 키 자동 생성 모달 */} setIsGenerateModalOpen(false)} selectedCategory={selectedCategory} companyCode={user?.companyCode || ""} isSuperAdmin={user?.companyCode === "*"} onSuccess={() => { fetchLangKeys(); }} />
); }