"use client"; import { useState, useEffect, useCallback } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; 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 { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Loader2, AlertCircle, CheckCircle2, Info, Check, ChevronsUpDown } from "lucide-react"; import { cn } from "@/lib/utils"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { LangCategory, Language, generateKey, previewKey, createOverrideKey, getLanguages, getCategoryPath, KeyPreview, } from "@/lib/api/multilang"; import { apiClient } from "@/lib/api/client"; interface Company { companyCode: string; companyName: string; } interface KeyGenerateModalProps { isOpen: boolean; onClose: () => void; selectedCategory: LangCategory | null; companyCode: string; isSuperAdmin: boolean; onSuccess: () => void; } export function KeyGenerateModal({ isOpen, onClose, selectedCategory, companyCode, isSuperAdmin, onSuccess, }: KeyGenerateModalProps) { // 상태 const [keyMeaning, setKeyMeaning] = useState(""); const [usageNote, setUsageNote] = useState(""); const [targetCompanyCode, setTargetCompanyCode] = useState(companyCode); const [languages, setLanguages] = useState([]); const [texts, setTexts] = useState>({}); const [categoryPath, setCategoryPath] = useState([]); const [preview, setPreview] = useState(null); const [loading, setLoading] = useState(false); const [previewLoading, setPreviewLoading] = useState(false); const [error, setError] = useState(null); const [companies, setCompanies] = useState([]); const [companySearchOpen, setCompanySearchOpen] = useState(false); // 초기화 useEffect(() => { if (isOpen) { setKeyMeaning(""); setUsageNote(""); setTargetCompanyCode(isSuperAdmin ? "*" : companyCode); setTexts({}); setPreview(null); setError(null); loadLanguages(); if (isSuperAdmin) { loadCompanies(); } if (selectedCategory) { loadCategoryPath(selectedCategory.categoryId); } else { setCategoryPath([]); } } }, [isOpen, selectedCategory, companyCode, isSuperAdmin]); // 회사 목록 로드 (최고관리자 전용) const loadCompanies = async () => { try { const response = await apiClient.get("/admin/companies"); if (response.data.success && response.data.data) { // snake_case를 camelCase로 변환하고 공통(*)은 제외 const companyList = response.data.data .filter((c: any) => c.company_code !== "*") .map((c: any) => ({ companyCode: c.company_code, companyName: c.company_name, })); setCompanies(companyList); } } catch (err) { console.error("회사 목록 로드 실패:", err); } }; // 언어 목록 로드 const loadLanguages = async () => { const response = await getLanguages(); if (response.success && response.data) { const activeLanguages = response.data.filter((l) => l.isActive === "Y"); setLanguages(activeLanguages); // 초기 텍스트 상태 설정 const initialTexts: Record = {}; activeLanguages.forEach((lang) => { initialTexts[lang.langCode] = ""; }); setTexts(initialTexts); } }; // 카테고리 경로 로드 const loadCategoryPath = async (categoryId: number) => { const response = await getCategoryPath(categoryId); if (response.success && response.data) { setCategoryPath(response.data); } }; // 키 미리보기 (디바운스) const loadPreview = useCallback(async () => { if (!selectedCategory || !keyMeaning.trim()) { setPreview(null); return; } setPreviewLoading(true); try { const response = await previewKey( selectedCategory.categoryId, keyMeaning.trim().toLowerCase().replace(/\s+/g, "_"), targetCompanyCode ); if (response.success && response.data) { setPreview(response.data); } } catch (err) { console.error("키 미리보기 실패:", err); } finally { setPreviewLoading(false); } }, [selectedCategory, keyMeaning, targetCompanyCode]); // keyMeaning 변경 시 디바운스로 미리보기 로드 useEffect(() => { const timer = setTimeout(loadPreview, 500); return () => clearTimeout(timer); }, [loadPreview]); // 텍스트 변경 핸들러 const handleTextChange = (langCode: string, value: string) => { setTexts((prev) => ({ ...prev, [langCode]: value })); }; // 저장 핸들러 const handleSave = async () => { if (!selectedCategory) { setError("카테고리를 선택해주세요"); return; } if (!keyMeaning.trim()) { setError("키 의미를 입력해주세요"); return; } // 최소 하나의 텍스트 입력 검증 const hasText = Object.values(texts).some((t) => t.trim()); if (!hasText) { setError("최소 하나의 언어에 대한 텍스트를 입력해주세요"); return; } setLoading(true); setError(null); try { // 오버라이드 모드인지 확인 if (preview?.isOverride && preview.baseKeyId) { // 오버라이드 키 생성 const response = await createOverrideKey({ companyCode: targetCompanyCode, baseKeyId: preview.baseKeyId, texts: Object.entries(texts) .filter(([_, text]) => text.trim()) .map(([langCode, langText]) => ({ langCode, langText })), }); if (response.success) { onSuccess(); onClose(); } else { setError(response.error?.details || "오버라이드 키 생성 실패"); } } else { // 새 키 생성 const response = await generateKey({ companyCode: targetCompanyCode, categoryId: selectedCategory.categoryId, keyMeaning: keyMeaning.trim().toLowerCase().replace(/\s+/g, "_"), usageNote: usageNote.trim() || undefined, texts: Object.entries(texts) .filter(([_, text]) => text.trim()) .map(([langCode, langText]) => ({ langCode, langText })), }); if (response.success) { onSuccess(); onClose(); } else { setError(response.error?.details || "키 생성 실패"); } } } catch (err: any) { setError(err.message || "키 생성 중 오류 발생"); } finally { setLoading(false); } }; // 생성될 키 미리보기 const generatedKeyPreview = categoryPath.length > 0 && keyMeaning.trim() ? [...categoryPath.map((c) => c.keyPrefix), keyMeaning.trim().toLowerCase().replace(/\s+/g, "_")].join(".") : ""; return ( !open && onClose()}> {preview?.isOverride ? "오버라이드 키 생성" : "다국어 키 생성"} {preview?.isOverride ? "공통 키에 대한 회사별 오버라이드를 생성합니다" : "새로운 다국어 키를 자동으로 생성합니다"}
{/* 카테고리 경로 표시 */}
{categoryPath.length > 0 ? ( categoryPath.map((cat, idx) => ( {cat.categoryName} {idx < categoryPath.length - 1 && ( / )} )) ) : ( 카테고리를 선택해주세요 )}
{/* 키 의미 입력 */}
setKeyMeaning(e.target.value)} placeholder="예: add_new_item, search_button, save_success" className="h-8 text-xs sm:h-10 sm:text-sm" />

영문 소문자와 밑줄(_)을 사용하세요

{/* 생성될 키 미리보기 */} {generatedKeyPreview && (
{previewLoading ? ( ) : preview?.exists ? ( ) : preview?.isOverride ? ( ) : ( )} {generatedKeyPreview}
{preview?.exists && (

이미 존재하는 키입니다

)} {preview?.isOverride && !preview?.exists && (

공통 키가 존재합니다. 회사별 오버라이드로 생성됩니다.

)}
)} {/* 대상 회사 선택 (최고 관리자만) */} {isSuperAdmin && (
검색 결과가 없습니다 { setTargetCompanyCode("*"); setCompanySearchOpen(false); }} className="text-xs sm:text-sm" > 공통 (*) - 모든 회사 적용 {companies.map((company) => ( { setTargetCompanyCode(company.companyCode); setCompanySearchOpen(false); }} className="text-xs sm:text-sm" > {company.companyName} ({company.companyCode}) ))}
)} {/* 사용 메모 */}