'use client'; import { useState, useMemo, useRef, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import AdminSidebar from "@/app/components/AdminSidebar"; import ChevronDownSvg from "@/app/svgs/chevrondownsvg"; 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"; type Lesson = { id: string; courseName: string; // 교육과정명 lessonName: string; // 강좌명 attachments: string; // 첨부파일 (예: "강좌영상 3개, VR콘텐츠 2개, 평가문제 1개") questionCount: number; // 학습 평가 문제 수 createdBy: string; // 등록자 createdAt: string; // 등록일 }; export default function AdminLessonsPage() { const router = useRouter(); const [lessons, setLessons] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [isRegistrationMode, setIsRegistrationMode] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const dropdownRef = useRef(null); const [courses, setCourses] = useState([]); const [currentUser, setCurrentUser] = useState("관리자"); const [showToast, setShowToast] = useState(false); const rawLecturesRef = useRef([]); // 원본 강좌 데이터 저장 // 등록 폼 상태 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 [vrContentCount, setVrContentCount] = useState(0); const [vrContentFiles, setVrContentFiles] = useState([]); const [vrContentFileObjects, setVrContentFileObjects] = useState([]); const [vrContentFileKeys, setVrContentFileKeys] = useState([]); const [questionFileCount, setQuestionFileCount] = useState(0); const [questionFileObject, setQuestionFileObject] = useState(null); const [questionFileKey, setQuestionFileKey] = useState(null); const [csvData, setCsvData] = useState([]); const [csvHeaders, setCsvHeaders] = useState([]); const [csvRows, setCsvRows] = useState([]); // 파일 교체 확인 모달 상태 const [isFileReplaceModalOpen, setIsFileReplaceModalOpen] = useState(false); const [pendingFiles, setPendingFiles] = useState([]); const [pendingFileType, setPendingFileType] = useState<'video' | 'vr' | 'csv' | null>(null); // 에러 상태 const [errors, setErrors] = useState<{ selectedCourse?: string; lessonName?: string; learningGoal?: string; }>({}); // CSV 파싱 함수 const parseCsv = (csvText: string): string[][] => { const lines: string[][] = []; let currentLine: string[] = []; let currentField = ''; let inQuotes = false; for (let i = 0; i < csvText.length; i++) { const char = csvText[i]; const nextChar = csvText[i + 1]; if (char === '"') { if (inQuotes && nextChar === '"') { currentField += '"'; i++; } else { inQuotes = !inQuotes; } } else if (char === ',' && !inQuotes) { currentLine.push(currentField.trim()); currentField = ''; } else if ((char === '\n' || char === '\r') && !inQuotes) { if (char === '\r' && nextChar === '\n') { i++; } if (currentField || currentLine.length > 0) { currentLine.push(currentField.trim()); lines.push(currentLine); currentLine = []; currentField = ''; } } else { currentField += char; } } if (currentField || currentLine.length > 0) { currentLine.push(currentField.trim()); lines.push(currentLine); } return lines; }; // CSV 파일에서 행 개수 가져오기 const getCsvRowCount = async (lecture: any): Promise => { if (!lecture.csvUrl && !lecture.csvKey) { return 0; } try { const baseURL = process.env.NEXT_PUBLIC_API_BASE_URL || 'https://hrdi.coconutmeet.net'; const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null; let fileKey: string | null = null; // csvUrl에서 fileKey 추출 if (lecture.csvUrl) { // 전체 URL인 경우 fileKey 추출 if (lecture.csvUrl.startsWith('http://') || lecture.csvUrl.startsWith('https://')) { // URL에서 /api/files/ 이후 부분을 fileKey로 사용 const filesIndex = lecture.csvUrl.indexOf('/api/files/'); if (filesIndex !== -1) { fileKey = lecture.csvUrl.substring(filesIndex + '/api/files/'.length); // URL 디코딩 fileKey = decodeURIComponent(fileKey); } else { // URL에서 마지막 경로를 fileKey로 사용 const urlParts = lecture.csvUrl.split('/'); fileKey = urlParts[urlParts.length - 1]; fileKey = decodeURIComponent(fileKey); } } else if (lecture.csvUrl.startsWith('csv/')) { // "csv/" 접두사 제거 fileKey = lecture.csvUrl.substring(4); } else { // 그 외의 경우 fileKey로 사용 fileKey = lecture.csvUrl; } } else if (lecture.csvKey) { // csvKey가 있으면 fileKey로 사용 fileKey = lecture.csvKey; } if (!fileKey) { return 0; } // /api/files/{fileKey} 형태로 요청 const csvUrl = `${baseURL}/api/files/${encodeURIComponent(fileKey)}`; const csvResponse = await fetch(csvUrl, { method: 'GET', headers: { ...(token && { Authorization: `Bearer ${token}` }), }, }); if (csvResponse.ok) { const csvText = await csvResponse.text(); const parsed = parseCsv(csvText); // 헤더를 제외한 행 개수 반환 return parsed.length > 0 ? parsed.length - 1 : 0; } else { console.warn(`CSV 파일 다운로드 실패: ${csvResponse.status} - ${csvUrl}`); return 0; } } catch (error) { console.error('CSV 파일 파싱 실패:', error, lecture); return 0; } }; // 교육과정명 매핑 함수 const mapCourseNames = useCallback(async (lectures: any[]) => { // 먼저 기본 정보로 강좌 목록 생성 const initialLessons: Lesson[] = lectures.map((lecture: any) => { // 첨부파일 정보 구성 const attachmentParts: string[] = []; // 강좌 영상 개수 계산 if (lecture.videoUrl) { if (Array.isArray(lecture.videoUrl)) { const count = lecture.videoUrl.length; if (count > 0) { attachmentParts.push(`강좌영상 ${count}개`); } } else if (typeof lecture.videoUrl === 'string') { attachmentParts.push('강좌영상 1개'); } } else if (lecture.videoKeys && Array.isArray(lecture.videoKeys)) { const count = lecture.videoKeys.length; if (count > 0) { attachmentParts.push(`강좌영상 ${count}개`); } } else if (lecture.videoFileKeys && Array.isArray(lecture.videoFileKeys)) { const count = lecture.videoFileKeys.length; if (count > 0) { attachmentParts.push(`강좌영상 ${count}개`); } } else if (lecture.videoFiles && Array.isArray(lecture.videoFiles)) { const count = lecture.videoFiles.length; if (count > 0) { attachmentParts.push(`강좌영상 ${count}개`); } } else if (lecture.videoKey) { attachmentParts.push('강좌영상 1개'); } // VR 콘텐츠 개수 계산 if (lecture.webglUrl) { if (Array.isArray(lecture.webglUrl)) { const count = lecture.webglUrl.length; if (count > 0) { attachmentParts.push(`VR콘텐츠 ${count}개`); } } else if (typeof lecture.webglUrl === 'string') { attachmentParts.push('VR콘텐츠 1개'); } } else if (lecture.vrUrl) { if (Array.isArray(lecture.vrUrl)) { const count = lecture.vrUrl.length; if (count > 0) { attachmentParts.push(`VR콘텐츠 ${count}개`); } } else if (typeof lecture.vrUrl === 'string') { attachmentParts.push('VR콘텐츠 1개'); } } else if (lecture.webglKeys && Array.isArray(lecture.webglKeys)) { const count = lecture.webglKeys.length; if (count > 0) { attachmentParts.push(`VR콘텐츠 ${count}개`); } } else if (lecture.vrKeys && Array.isArray(lecture.vrKeys)) { const count = lecture.vrKeys.length; if (count > 0) { attachmentParts.push(`VR콘텐츠 ${count}개`); } } else if (lecture.webglFileKeys && Array.isArray(lecture.webglFileKeys)) { const count = lecture.webglFileKeys.length; if (count > 0) { attachmentParts.push(`VR콘텐츠 ${count}개`); } } else if (lecture.vrFileKeys && Array.isArray(lecture.vrFileKeys)) { const count = lecture.vrFileKeys.length; if (count > 0) { attachmentParts.push(`VR콘텐츠 ${count}개`); } } else if (lecture.webglFiles && Array.isArray(lecture.webglFiles)) { const count = lecture.webglFiles.length; if (count > 0) { attachmentParts.push(`VR콘텐츠 ${count}개`); } } else if (lecture.webglKey || lecture.vrKey) { attachmentParts.push('VR콘텐츠 1개'); } // 학습 평가 문제 개수는 나중에 업데이트 // csvUrl이 전체 URL이든 상대 경로든 모두 체크 if (lecture.csvUrl || lecture.csvKey) { attachmentParts.push('평가문제 확인 중...'); } const attachments = attachmentParts.length > 0 ? attachmentParts.join(', ') : '없음'; // subjectId로 교육과정명 찾기 const subjectId = lecture.subjectId || lecture.subject_id; let courseName = ''; if (subjectId && courses.length > 0) { const foundCourse = courses.find(course => course.id === String(subjectId)); courseName = foundCourse?.courseName || ''; } // 교육과정명을 찾지 못한 경우 fallback if (!courseName) { courseName = lecture.subjectName || lecture.courseName || ''; } return { id: String(lecture.id || lecture.lectureId || ''), courseName, lessonName: lecture.title || lecture.lessonName || '', attachments, questionCount: 0, // 초기값은 0, 나중에 업데이트 createdBy: lecture.createdBy || lecture.instructorName || '관리자', createdAt: lecture.createdAt ? new Date(lecture.createdAt).toISOString().split('T')[0] : new Date().toISOString().split('T')[0], }; }); // 초기 목록 설정 setLessons(initialLessons); // CSV 파일 행 개수 병렬로 가져오기 const csvRowCounts = await Promise.all( lectures.map(lecture => getCsvRowCount(lecture)) ); // CSV 행 개수로 업데이트 const updatedLessons = initialLessons.map((lesson, index) => { const lecture = lectures[index]; const rowCount = csvRowCounts[index]; // 첨부파일 정보 업데이트 let attachments = lesson.attachments; if (lecture.csvUrl || lecture.csvKey) { if (rowCount > 0) { attachments = attachments.replace('평가문제 확인 중...', `평가문제 ${rowCount}개`); } else { // CSV 파일이 있지만 다운로드 실패하거나 행이 없는 경우에도 표시 attachments = attachments.replace('평가문제 확인 중...', '평가문제 1개'); } } return { ...lesson, attachments, questionCount: rowCount, }; }); setLessons(updatedLessons); }, [courses]); // 교육과정 목록 가져오기 useEffect(() => { async function fetchCourses() { try { const data = await getCourses(); setCourses(data); } catch (error) { console.error('교육과정 목록 로드 오류:', error); setCourses([]); } } fetchCourses(); }, []); // 강좌 리스트 조회 함수 const fetchLectures = useCallback(async () => { try { const response = await apiService.getLectures(); if (response.data && Array.isArray(response.data)) { // 원본 데이터 저장 rawLecturesRef.current = response.data; // 교육과정명 매핑 함수 호출 await mapCourseNames(response.data); } } catch (error) { console.error('강좌 리스트 조회 오류:', error); setLessons([]); rawLecturesRef.current = []; } }, [mapCourseNames]); // 강좌 리스트 조회 useEffect(() => { fetchLectures(); }, [fetchLectures]); // 페이지 포커스 및 가시성 변경 시 리스트 새로고침 useEffect(() => { const handleFocus = () => { fetchLectures(); }; const handleVisibilityChange = () => { if (!document.hidden) { fetchLectures(); } }; window.addEventListener('focus', handleFocus); document.addEventListener('visibilitychange', handleVisibilityChange); return () => { window.removeEventListener('focus', handleFocus); document.removeEventListener('visibilitychange', handleVisibilityChange); }; }, [fetchLectures]); // 교육과정 목록이 로드되면 강좌 리스트의 교육과정명 업데이트 useEffect(() => { const updateLectures = async () => { if (rawLecturesRef.current.length > 0) { await mapCourseNames(rawLecturesRef.current); } }; updateLectures(); }, [mapCourseNames]); // 현재 사용자 정보 가져오기 useEffect(() => { async function fetchCurrentUser() { try { const token = typeof window !== 'undefined' ? (localStorage.getItem('token') || document.cookie .split('; ') .find(row => row.startsWith('token=')) ?.split('=')[1]) : null; if (!token) { return; } const response = await apiService.getCurrentUser(); if (response.data && response.data.name) { setCurrentUser(response.data.name); } } catch (error) { console.error('사용자 정보 조회 오류:', error); } } fetchCurrentUser(); }, []); const totalCount = useMemo(() => lessons.length, [lessons]); const ITEMS_PER_PAGE = 10; const sortedLessons = useMemo(() => { return [...lessons].sort((a, b) => { // 생성일 내림차순 정렬 (최신 날짜가 먼저) return b.createdAt.localeCompare(a.createdAt); }); }, [lessons]); const totalPages = Math.ceil(sortedLessons.length / ITEMS_PER_PAGE); const paginatedLessons = useMemo(() => { const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; const endIndex = startIndex + ITEMS_PER_PAGE; return sortedLessons.slice(startIndex, endIndex); }, [sortedLessons, currentPage]); // 교육과정 옵션 const courseOptions = useMemo(() => courses.map(course => ({ id: course.id, name: course.courseName })) , [courses]); // 외부 클릭 시 드롭다운 닫기 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]); const handleBackClick = () => { setIsRegistrationMode(false); // 폼 초기화 setSelectedCourse(""); setLessonName(""); setLearningGoal(""); setCourseVideoCount(0); setCourseVideoFiles([]); setCourseVideoFileObjects([]); setCourseVideoFileKeys([]); setVrContentCount(0); setVrContentFiles([]); setVrContentFileObjects([]); setVrContentFileKeys([]); setQuestionFileCount(0); setQuestionFileObject(null); setQuestionFileKey(null); setCsvData([]); setCsvHeaders([]); setCsvRows([]); setErrors({}); }; const handleRegisterClick = () => { setIsRegistrationMode(true); }; // 파일 업로드 함수 const handleVideoFileUpload = async (validFiles: File[]) => { try { let fileKeys: string[] = []; // 파일이 1개인 경우 uploadFile 사용 if (validFiles.length === 1) { const uploadResponse = await apiService.uploadFile(validFiles[0]); // 응답에서 fileKey 추출 const fileKey = uploadResponse.data?.fileKey || uploadResponse.data?.key || uploadResponse.data?.id || uploadResponse.data?.fileId; if (fileKey) { fileKeys = [fileKey]; } else { throw new Error('파일 업로드는 완료되었지만 fileKey를 받지 못했습니다.'); } } else { // 파일이 2개 이상인 경우 uploadFiles 사용 const uploadResponse = await apiService.uploadFiles(validFiles); // 응답에서 fileKey 배열 추출 if (uploadResponse.data?.results && Array.isArray(uploadResponse.data.results)) { uploadResponse.data.results.forEach((result: any) => { if (result.ok && result.fileKey) { fileKeys.push(result.fileKey); } else if (result.fileKey) { fileKeys.push(result.fileKey); } }); } else if (Array.isArray(uploadResponse.data)) { // 응답이 배열인 경우 fileKeys = uploadResponse.data.map((item: any) => item.fileKey || item.key || item.id || item.fileId ).filter(Boolean); } else if (uploadResponse.data?.fileKeys && Array.isArray(uploadResponse.data.fileKeys)) { fileKeys = uploadResponse.data.fileKeys; } } if (fileKeys.length > 0) { // 이전 파일 삭제하고 새 파일로 교체 setCourseVideoFiles(validFiles.map(f => f.name)); setCourseVideoFileObjects(validFiles); setCourseVideoFileKeys(fileKeys); setCourseVideoCount(fileKeys.length); } else { throw new Error('파일 업로드는 완료되었지만 fileKey를 받지 못했습니다.'); } } catch (error) { console.error('강좌 영상 업로드 실패:', error); alert('파일 업로드에 실패했습니다. 다시 시도해주세요.'); } }; const handleVrFileUpload = async (validFiles: File[]) => { try { let fileKeys: string[] = []; // 파일이 1개인 경우 uploadFile 사용 if (validFiles.length === 1) { const uploadResponse = await apiService.uploadFile(validFiles[0]); // 응답에서 fileKey 추출 const fileKey = uploadResponse.data?.fileKey || uploadResponse.data?.key || uploadResponse.data?.id || uploadResponse.data?.fileId; if (fileKey) { fileKeys = [fileKey]; } else { throw new Error('파일 업로드는 완료되었지만 fileKey를 받지 못했습니다.'); } } else { // 파일이 2개 이상인 경우 uploadFiles 사용 const uploadResponse = await apiService.uploadFiles(validFiles); // 응답에서 fileKey 배열 추출 if (uploadResponse.data?.results && Array.isArray(uploadResponse.data.results)) { uploadResponse.data.results.forEach((result: any) => { if (result.ok && result.fileKey) { fileKeys.push(result.fileKey); } else if (result.fileKey) { fileKeys.push(result.fileKey); } }); } else if (Array.isArray(uploadResponse.data)) { // 응답이 배열인 경우 fileKeys = uploadResponse.data.map((item: any) => item.fileKey || item.key || item.id || item.fileId ).filter(Boolean); } else if (uploadResponse.data?.fileKeys && Array.isArray(uploadResponse.data.fileKeys)) { fileKeys = uploadResponse.data.fileKeys; } } if (fileKeys.length > 0) { // 이전 파일 삭제하고 새 파일로 교체 setVrContentFiles(validFiles.map(f => f.name)); setVrContentFileObjects(validFiles); setVrContentFileKeys(fileKeys); setVrContentCount(fileKeys.length); } else { throw new Error('파일 업로드는 완료되었지만 fileKey를 받지 못했습니다.'); } } catch (error) { console.error('VR 콘텐츠 업로드 실패:', error); alert('파일 업로드에 실패했습니다. 다시 시도해주세요.'); } }; const handleCsvFileUpload = async (file: File) => { try { // CSV 파일 파싱 const reader = new FileReader(); reader.onload = (event) => { const text = event.target?.result as string; if (!text) return; try { // CSV 파싱 함수 const parseCsv = (csvText: string): string[][] => { const lines: string[][] = []; let currentLine: string[] = []; let currentField = ''; let inQuotes = false; for (let i = 0; i < csvText.length; i++) { const char = csvText[i]; const nextChar = csvText[i + 1]; if (char === '"') { if (inQuotes && nextChar === '"') { currentField += '"'; i++; } else { inQuotes = !inQuotes; } } else if (char === ',' && !inQuotes) { currentLine.push(currentField.trim()); currentField = ''; } else if ((char === '\n' || char === '\r') && !inQuotes) { if (char === '\r' && nextChar === '\n') { i++; } if (currentField || currentLine.length > 0) { currentLine.push(currentField.trim()); lines.push(currentLine); currentLine = []; currentField = ''; } } else { currentField += char; } } if (currentField || currentLine.length > 0) { currentLine.push(currentField.trim()); lines.push(currentLine); } return lines; }; const parsed = parseCsv(text); if (parsed.length === 0) { alert('CSV 파일이 비어있습니다.'); return; } // 첫 번째 줄을 헤더로 사용 const headers = parsed[0]; const rows = parsed.slice(1); setCsvHeaders(headers); setCsvRows(rows); setCsvData(parsed); } catch (parseError) { console.error('CSV 파싱 오류:', parseError); alert('CSV 파일을 읽는 중 오류가 발생했습니다.'); } }; reader.onerror = () => { alert('파일을 읽는 중 오류가 발생했습니다.'); }; reader.readAsText(file, 'UTF-8'); // 단일 파일 업로드 const uploadResponse = await apiService.uploadFile(file); // 응답에서 fileKey 추출 const fileKey = uploadResponse.data?.fileKey || uploadResponse.data?.key || uploadResponse.data?.id || uploadResponse.data?.fileId || uploadResponse.data?.imageKey || (uploadResponse.data?.data && (uploadResponse.data.data.key || uploadResponse.data.data.fileKey)) || null; if (fileKey) { // 이전 파일 삭제하고 새 파일로 교체 setQuestionFileObject(file); setQuestionFileKey(fileKey); setQuestionFileCount(1); } else { throw new Error('파일 업로드는 완료되었지만 fileKey를 받지 못했습니다.'); } } catch (error) { console.error('학습 평가 문제 업로드 실패:', error); alert('파일 업로드에 실패했습니다. 다시 시도해주세요.'); } }; // 파일 교체 확인 모달 핸들러 const handleFileReplaceConfirm = async () => { if (!pendingFiles.length || !pendingFileType) return; setIsFileReplaceModalOpen(false); if (pendingFileType === 'video') { await handleVideoFileUpload(pendingFiles); } else if (pendingFileType === 'vr') { await handleVrFileUpload(pendingFiles); } else if (pendingFileType === 'csv' && pendingFiles.length > 0) { await handleCsvFileUpload(pendingFiles[0]); } setPendingFiles([]); setPendingFileType(null); }; const handleFileReplaceCancel = () => { setIsFileReplaceModalOpen(false); setPendingFiles([]); setPendingFileType(null); }; 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 = "내용을 입력해 주세요."; } // 에러가 있으면 표시하고 중단 if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } // 에러 초기화 setErrors({}); try { // 이미 업로드된 fileKey 배열 사용 const videoFileKeys = courseVideoFileKeys; const vrFileKeys = vrContentFileKeys; const csvFileKey = questionFileKey; // 강좌 영상 fileKey (단일 파일은 문자열, 다중 파일은 배열) let videoUrl: string | string[] | undefined; if (videoFileKeys.length === 1) { videoUrl = videoFileKeys[0]; // 단일 파일은 문자열로 } else if (videoFileKeys.length > 1) { videoUrl = videoFileKeys; // 다중 파일은 배열로 } // VR 콘텐츠 fileKey (단일 파일은 문자열, 다중 파일은 배열) let webglUrl: string | string[] | undefined; if (vrFileKeys.length === 1) { webglUrl = vrFileKeys[0]; // 단일 파일은 문자열로 } else if (vrFileKeys.length > 1) { webglUrl = vrFileKeys; // 다중 파일은 배열로 } // 학습 평가 문제 fileKey let csvKey: string | undefined; if (csvFileKey) { csvKey = csvFileKey; } // API 요청 body 구성 const requestBody: { subjectId: number; title: string; objective: string; videoUrl?: string | string[]; webglUrl?: string | string[]; csvKey?: string; } = { subjectId: Number(selectedCourse), title: lessonName.trim(), objective: learningGoal.trim(), }; // 선택적 필드 추가 if (videoUrl) { requestBody.videoUrl = videoUrl; } if (webglUrl) { requestBody.webglUrl = webglUrl; } if (csvKey) { requestBody.csvKey = csvKey; } // 강좌 등록 API 호출 const response = await apiService.createLecture(requestBody); // 응답에서 id 추출 및 저장 const lectureId = response.data?.id; if (!lectureId) { throw new Error('강좌 등록 응답에서 ID를 받지 못했습니다.'); } // 첨부파일 정보 문자열 생성 const attachmentParts: string[] = []; if (courseVideoCount > 0) { attachmentParts.push(`강좌영상 ${courseVideoCount}개`); } if (vrContentCount > 0) { attachmentParts.push(`VR콘텐츠 ${vrContentCount}개`); } if (questionFileCount > 0) { attachmentParts.push(`평가문제 ${questionFileCount}개`); } const attachments = attachmentParts.length > 0 ? attachmentParts.join(', ') : '없음'; // 교육과정명 가져오기 const courseName = courseOptions.find(c => c.id === selectedCourse)?.name || ''; // 새 강좌 생성 (API 응답의 id 사용) const newLesson: Lesson = { id: String(lectureId), courseName, lessonName, attachments, questionCount: questionFileCount, createdBy: currentUser, createdAt: new Date().toISOString().split('T')[0], }; // 강좌 목록에 추가 setLessons(prev => [...prev, newLesson]); // 등록 모드 종료 및 폼 초기화 setIsRegistrationMode(false); setSelectedCourse(""); setLessonName(""); setLearningGoal(""); setCourseVideoCount(0); setCourseVideoFiles([]); setCourseVideoFileObjects([]); setCourseVideoFileKeys([]); setVrContentCount(0); setVrContentFiles([]); setVrContentFileObjects([]); setVrContentFileKeys([]); setQuestionFileCount(0); setQuestionFileObject(null); setQuestionFileKey(null); setCsvData([]); setCsvHeaders([]); setCsvRows([]); // 토스트 팝업 표시 setShowToast(true); } catch (error) { console.error('강좌 등록 실패:', error); const errorMessage = error instanceof Error ? error.message : '강좌 등록 중 오류가 발생했습니다.'; alert(errorMessage); } }; // 토스트 자동 닫기 useEffect(() => { if (showToast) { const timer = setTimeout(() => { setShowToast(false); }, 3000); // 3초 후 자동 닫기 return () => clearTimeout(timer); } }, [showToast]); return (
{/* 메인 레이아웃 */}
{/* 사이드바 */}
{/* 메인 콘텐츠 */}
{isRegistrationMode ? ( /* 등록 모드 */
{/* 헤더 */}

강좌 등록

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

강좌 정보

{/* 교육 과정 */}
{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}

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