공지사항 삭제등록 등11

This commit is contained in:
2025-11-29 15:40:39 +09:00
parent 872a88866e
commit eb7871133d
10 changed files with 1663 additions and 412 deletions

View File

@@ -63,20 +63,209 @@ export default function AdminLessonsPage() {
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<number> => {
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((lectures: any[]) => {
const fetchedLessons: Lesson[] = lectures.map((lecture: any) => {
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개');
}
if (lecture.csvKey) {
attachmentParts.push('평가문제 1개');
// 학습 평가 문제 개수는 나중에 업데이트
// csvUrl이 전체 URL이든 상대 경로든 모두 체크
if (lecture.csvUrl || lecture.csvKey) {
attachmentParts.push('평가문제 확인 중...');
}
const attachments = attachmentParts.length > 0
? attachmentParts.join(', ')
: '없음';
@@ -99,14 +288,46 @@ export default function AdminLessonsPage() {
courseName,
lessonName: lecture.title || lecture.lessonName || '',
attachments,
questionCount: lecture.csvKey ? 1 : 0,
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(fetchedLessons);
// 초기 목록 설정
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]);
// 교육과정 목록 가져오기
@@ -123,32 +344,57 @@ export default function AdminLessonsPage() {
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(() => {
async function fetchLectures() {
try {
const response = await apiService.getLectures();
if (response.data && Array.isArray(response.data)) {
// 원본 데이터 저장
rawLecturesRef.current = response.data;
// 교육과정명 매핑 함수 호출
mapCourseNames(response.data);
}
} catch (error) {
console.error('강좌 리스트 조회 오류:', error);
setLessons([]);
rawLecturesRef.current = [];
}
}
fetchLectures();
}, [mapCourseNames]);
}, [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(() => {
if (rawLecturesRef.current.length > 0) {
mapCourseNames(rawLecturesRef.current);
}
const updateLectures = async () => {
if (rawLecturesRef.current.length > 0) {
await mapCourseNames(rawLecturesRef.current);
}
};
updateLectures();
}, [mapCourseNames]);
// 현재 사용자 정보 가져오기
@@ -527,16 +773,20 @@ export default function AdminLessonsPage() {
const vrFileKeys = vrContentFileKeys;
const csvFileKey = questionFileKey;
// 강좌 영상 fileKey 배열 (모든 fileKey를 배열로 저장)
let videoUrl: string[] | undefined;
if (videoFileKeys.length > 0) {
videoUrl = videoFileKeys; // 모든 fileKey를 배열로 저장
// 강좌 영상 fileKey (단일 파일은 문자열, 다중 파일은 배열)
let videoUrl: string | string[] | undefined;
if (videoFileKeys.length === 1) {
videoUrl = videoFileKeys[0]; // 단일 파일은 문자열로
} else if (videoFileKeys.length > 1) {
videoUrl = videoFileKeys; // 다중 파일은 배열로
}
// VR 콘텐츠 fileKey 배열 (모든 fileKey를 배열로 저장)
let webglUrl: string[] | undefined;
if (vrFileKeys.length > 0) {
webglUrl = vrFileKeys; // 모든 fileKey를 배열로 저장
// VR 콘텐츠 fileKey (단일 파일은 문자열, 다중 파일은 배열)
let webglUrl: string | string[] | undefined;
if (vrFileKeys.length === 1) {
webglUrl = vrFileKeys[0]; // 단일 파일은 문자열로
} else if (vrFileKeys.length > 1) {
webglUrl = vrFileKeys; // 다중 파일은 배열로
}
// 학습 평가 문제 fileKey
@@ -560,10 +810,10 @@ export default function AdminLessonsPage() {
};
// 선택적 필드 추가
if (videoUrl && videoUrl.length > 0) {
if (videoUrl) {
requestBody.videoUrl = videoUrl;
}
if (webglUrl && webglUrl.length > 0) {
if (webglUrl) {
requestBody.webglUrl = webglUrl;
}
if (csvKey) {