'use client'; import { useState, useRef, useEffect, useMemo } from "react"; import { useParams, useRouter } from "next/navigation"; import AdminSidebar from "@/app/components/AdminSidebar"; import DropdownIcon from "@/app/svgs/dropdownicon"; import BackArrowSvg from "@/app/svgs/backarrow"; import { getCourses, type Course } from "@/app/admin/courses/mockData"; import CloseXOSvg from "@/app/svgs/closexo"; import apiService from "@/app/lib/apiService"; export default function LessonEditPage() { const params = useParams(); const router = useRouter(); const [loading, setLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const dropdownRef = useRef(null); const [courses, setCourses] = useState([]); const [showToast, setShowToast] = useState(false); // 폼 상태 const [selectedCourse, setSelectedCourse] = useState(""); const [lessonName, setLessonName] = useState(""); const [learningGoal, setLearningGoal] = useState(""); const [courseVideoCount, setCourseVideoCount] = useState(0); const [courseVideoFiles, setCourseVideoFiles] = useState([]); const [courseVideoFileObjects, setCourseVideoFileObjects] = useState([]); const [courseVideoFileKeys, setCourseVideoFileKeys] = useState([]); const [existingVideoFiles, setExistingVideoFiles] = useState>([]); const [vrContentCount, setVrContentCount] = useState(0); const [vrContentFiles, setVrContentFiles] = useState([]); const [vrContentFileObjects, setVrContentFileObjects] = useState([]); const [vrContentFileKeys, setVrContentFileKeys] = useState([]); const [existingVrFiles, setExistingVrFiles] = useState>([]); const [questionFileCount, setQuestionFileCount] = useState(0); const [questionFileObject, setQuestionFileObject] = useState(null); const [questionFileKey, setQuestionFileKey] = useState(null); const [existingQuestionFile, setExistingQuestionFile] = useState<{fileName: string, fileKey?: string} | null>(null); // 원본 데이터 저장 (변경사항 비교용) const [originalData, setOriginalData] = useState<{ title?: string; objective?: string; videoUrl?: string; webglUrl?: string; csvUrl?: string; } | null>(null); // 에러 상태 const [errors, setErrors] = useState<{ selectedCourse?: string; lessonName?: string; learningGoal?: string; }>({}); // 교육과정 옵션 const courseOptions = useMemo(() => { return courses.map(course => ({ id: course.id, name: course.courseName, })); }, [courses]); // 교육과정 목록 로드 useEffect(() => { const loadCourses = async () => { try { const data = await getCourses(); setCourses(data); } catch (error) { console.error('교육과정 목록 로드 오류:', error); } }; loadCourses(); }, []); // 강좌 데이터 로드 useEffect(() => { const loadLecture = async () => { if (!params?.id) return; try { setLoading(true); // sessionStorage에서 저장된 강좌 데이터 가져오기 let data: any = null; const storedData = typeof window !== 'undefined' ? sessionStorage.getItem('selectedLecture') : null; if (storedData) { try { data = JSON.parse(storedData); } catch (e) { console.error('저장된 데이터 파싱 실패:', e); } } // sessionStorage에 데이터가 없으면 API에서 직접 가져오기 if (!data) { try { const response = await apiService.getLecture(params.id as string); data = response.data; } catch (err) { console.error('강좌 조회 실패:', err); // getLecture 실패 시 getLectures로 재시도 try { const listResponse = await apiService.getLectures(); const lectures = Array.isArray(listResponse.data) ? listResponse.data : listResponse.data?.items || listResponse.data?.lectures || listResponse.data?.data || []; data = lectures.find((l: any) => String(l.id || l.lectureId) === params.id); } catch (listErr) { console.error('강좌 리스트 조회 실패:', listErr); } } } if (!data) { throw new Error('강좌를 찾을 수 없습니다.'); } // 원본 데이터 저장 const original = { title: data.title || data.lectureName || '', objective: data.objective || data.goal || '', videoUrl: data.videoUrl || undefined, webglUrl: data.webglUrl || data.vrUrl || undefined, csvUrl: data.csvUrl || (data.csvKey ? `csv/${data.csvKey}` : undefined), }; setOriginalData(original); // 폼에 데이터 채우기 if (data.subjectId || data.subject_id) { setSelectedCourse(String(data.subjectId || data.subject_id)); } setLessonName(original.title); setLearningGoal(original.objective); // 기존 비디오 파일 if (data.videoUrl || data.videoKey) { const videoFiles = []; if (data.videoUrl) { videoFiles.push({ fileName: data.videoFileName || '강좌영상.mp4', fileKey: data.videoKey, url: data.videoUrl, }); } if (data.videoFiles && Array.isArray(data.videoFiles)) { data.videoFiles.forEach((vf: any) => { videoFiles.push({ fileName: vf.fileName || vf.name || '강좌영상.mp4', fileKey: vf.fileKey || vf.key, url: vf.url || vf.videoUrl, }); }); } setExistingVideoFiles(videoFiles); setCourseVideoCount(videoFiles.length); } // 기존 VR 파일 if (data.webglUrl || data.vrUrl || data.webglKey || data.vrKey) { const vrFiles = []; if (data.webglUrl || data.vrUrl) { vrFiles.push({ fileName: data.vrFileName || data.webglFileName || 'VR_콘텐츠.zip', fileKey: data.webglKey || data.vrKey, url: data.webglUrl || data.vrUrl, }); } setExistingVrFiles(vrFiles); setVrContentCount(vrFiles.length); } // 기존 평가 문제 파일 if (data.csvKey || data.csvUrl) { setExistingQuestionFile({ fileName: '평가문제.csv', fileKey: data.csvKey, }); setQuestionFileCount(1); } } catch (err) { console.error('강좌 로드 실패:', err); // 기본값 설정 (에러 발생 시에도 폼이 작동하도록) const defaultOriginal = { title: '', objective: '', videoUrl: undefined, webglUrl: undefined, csvUrl: undefined, }; setOriginalData(defaultOriginal); alert('강좌를 불러오는데 실패했습니다. 페이지를 새로고침하거나 다시 시도해주세요.'); // 에러 발생 시에도 페이지는 유지 (사용자가 수동으로 데이터 입력 가능) } finally { setLoading(false); } }; loadLecture(); }, [params?.id, router]); // 외부 클릭 시 드롭다운 닫기 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setIsDropdownOpen(false); } }; if (isDropdownOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isDropdownOpen]); // 토스트 자동 닫기 useEffect(() => { if (showToast) { const timer = setTimeout(() => { setShowToast(false); }, 3000); // 3초 후 자동 닫기 return () => clearTimeout(timer); } }, [showToast]); const handleBackClick = () => { router.push(`/admin/lessons/${params.id}`); }; const handleSaveClick = async () => { // 유효성 검사 const newErrors: { selectedCourse?: string; lessonName?: string; learningGoal?: string; } = {}; if (!selectedCourse) { newErrors.selectedCourse = "교육과정을 선택해 주세요."; } if (!lessonName.trim()) { newErrors.lessonName = "강좌명을 입력해 주세요."; } if (!learningGoal.trim()) { newErrors.learningGoal = "학습 목표를 입력해 주세요."; } setErrors(newErrors); if (Object.keys(newErrors).length > 0) { return; } if (isSaving) return; setIsSaving(true); try { if (!originalData) { alert('데이터를 불러오는 중입니다. 잠시 후 다시 시도해주세요.'); return; } // 이미 업로드된 fileKey 배열 사용 let videoUrl: string | undefined; let webglUrl: string | undefined; let csvUrl: string | undefined; // 강좌 영상 fileKey (새로 업로드된 파일이 있는 경우) if (courseVideoFileKeys.length > 0) { videoUrl = courseVideoFileKeys[0]; // 첫 번째 fileKey 사용 // TODO: API가 배열을 받는 경우 courseVideoFileKeys 배열 전체를 저장해야 함 } else if (existingVideoFiles.length > 0 && existingVideoFiles[0].url) { // 기존 파일 URL 유지 videoUrl = existingVideoFiles[0].url; } else if (existingVideoFiles.length > 0 && existingVideoFiles[0].fileKey) { // 기존 파일 fileKey 사용 videoUrl = existingVideoFiles[0].fileKey; } // VR 콘텐츠 fileKey (새로 업로드된 파일이 있는 경우) if (vrContentFileKeys.length > 0) { webglUrl = vrContentFileKeys[0]; // 첫 번째 fileKey 사용 // TODO: API가 배열을 받는 경우 vrContentFileKeys 배열 전체를 저장해야 함 } else if (existingVrFiles.length > 0 && existingVrFiles[0].url) { // 기존 파일 URL 유지 webglUrl = existingVrFiles[0].url; } else if (existingVrFiles.length > 0 && existingVrFiles[0].fileKey) { // 기존 파일 fileKey 사용 webglUrl = existingVrFiles[0].fileKey; } // 학습 평가 문제 fileKey (새로 업로드된 파일이 있는 경우) if (questionFileKey) { csvUrl = questionFileKey; } else if (existingQuestionFile?.fileKey) { // 기존 파일 fileKey 사용 csvUrl = existingQuestionFile.fileKey; } else if (originalData.csvUrl) { // 원본 데이터의 csvUrl 유지 csvUrl = originalData.csvUrl; } // 현재 값들 const currentTitle = lessonName.trim(); const currentObjective = learningGoal.trim(); // 변경된 항목만 포함하는 request body 생성 const requestBody: { title?: string; objective?: string; videoUrl?: string; webglUrl?: string; csvUrl?: string; } = {}; // 변경된 항목만 추가 if (currentTitle !== originalData.title) { requestBody.title = currentTitle; } if (currentObjective !== originalData.objective) { requestBody.objective = currentObjective; } // videoUrl 변경사항 체크 (원본에 있었는데 삭제된 경우도 포함) const originalVideoUrl = originalData.videoUrl; if (videoUrl !== originalVideoUrl) { // 원본에 있었는데 현재 없는 경우 (삭제된 경우) 빈 문자열로 처리 if (originalVideoUrl && !videoUrl) { requestBody.videoUrl = ''; } else if (videoUrl) { requestBody.videoUrl = videoUrl; } } // webglUrl 변경사항 체크 const originalWebglUrl = originalData.webglUrl; if (webglUrl !== originalWebglUrl) { // 원본에 있었는데 현재 없는 경우 (삭제된 경우) 빈 문자열로 처리 if (originalWebglUrl && !webglUrl) { requestBody.webglUrl = ''; } else if (webglUrl) { requestBody.webglUrl = webglUrl; } } // csvUrl 변경사항 체크 const originalCsvUrl = originalData.csvUrl; if (csvUrl !== originalCsvUrl) { // 원본에 있었는데 현재 없는 경우 (삭제된 경우) 빈 문자열로 처리 if (originalCsvUrl && !csvUrl) { requestBody.csvUrl = ''; } else if (csvUrl) { requestBody.csvUrl = csvUrl; } } // 변경사항이 없으면 알림 if (Object.keys(requestBody).length === 0) { alert('변경된 내용이 없습니다.'); setIsSaving(false); return; } // 강좌 수정 API 호출 (PATCH /lectures/{id}) await apiService.updateLecture(params.id as string, requestBody); // 성공 시 토스트 표시 setShowToast(true); // 토스트 표시 후 상세 페이지로 이동 setTimeout(() => { router.push(`/admin/lessons/${params.id}`); }, 1500); } catch (error) { console.error('강좌 수정 실패:', error); const errorMessage = error instanceof Error ? error.message : '강좌 수정 중 오류가 발생했습니다.'; alert(errorMessage); } finally { setIsSaving(false); } }; if (loading) { return (

강좌 수정

로딩 중...

); } return (
{/* 헤더 */}

강좌 수정

{/* 폼 콘텐츠 */}
{/* 강좌 정보 */}

강좌 정보

{/* 교육 과정 */}
{isDropdownOpen && (
{courseOptions.map((course, index) => ( ))}
)}
{errors.selectedCourse && (

{errors.selectedCourse}

)}
{/* 강좌명 */}
{ setLessonName(e.target.value); if (errors.lessonName) { setErrors(prev => ({ ...prev, lessonName: undefined })); } }} placeholder="강좌명을 입력해 주세요." className={`h-[40px] px-[12px] py-[8px] border rounded-[8px] bg-white text-[16px] font-normal leading-[1.5] text-[#1b2027] placeholder:text-[#b1b8c0] focus:outline-none focus:shadow-[inset_0_0_0_1px_#333c47] ${ errors.lessonName ? 'border-[#e63946]' : 'border-[#dee1e6]' }`} /> {errors.lessonName && (

{errors.lessonName}

)}
{/* 학습 목표 */}