강좌 삭제 구현1
This commit is contained in:
@@ -39,11 +39,14 @@ export default function AdminLessonsPage() {
|
||||
const [courseVideoCount, setCourseVideoCount] = useState(0);
|
||||
const [courseVideoFiles, setCourseVideoFiles] = useState<string[]>([]);
|
||||
const [courseVideoFileObjects, setCourseVideoFileObjects] = useState<File[]>([]);
|
||||
const [courseVideoFileKeys, setCourseVideoFileKeys] = useState<string[]>([]);
|
||||
const [vrContentCount, setVrContentCount] = useState(0);
|
||||
const [vrContentFiles, setVrContentFiles] = useState<string[]>([]);
|
||||
const [vrContentFileObjects, setVrContentFileObjects] = useState<File[]>([]);
|
||||
const [vrContentFileKeys, setVrContentFileKeys] = useState<string[]>([]);
|
||||
const [questionFileCount, setQuestionFileCount] = useState(0);
|
||||
const [questionFileObject, setQuestionFileObject] = useState<File | null>(null);
|
||||
const [questionFileKey, setQuestionFileKey] = useState<string | null>(null);
|
||||
|
||||
// 에러 상태
|
||||
const [errors, setErrors] = useState<{
|
||||
@@ -234,11 +237,14 @@ export default function AdminLessonsPage() {
|
||||
setCourseVideoCount(0);
|
||||
setCourseVideoFiles([]);
|
||||
setCourseVideoFileObjects([]);
|
||||
setCourseVideoFileKeys([]);
|
||||
setVrContentCount(0);
|
||||
setVrContentFiles([]);
|
||||
setVrContentFileObjects([]);
|
||||
setVrContentFileKeys([]);
|
||||
setQuestionFileCount(0);
|
||||
setQuestionFileObject(null);
|
||||
setQuestionFileKey(null);
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
@@ -274,72 +280,27 @@ export default function AdminLessonsPage() {
|
||||
setErrors({});
|
||||
|
||||
try {
|
||||
// 파일 업로드 및 키 추출
|
||||
let videoUrl: string | undefined;
|
||||
let webglUrl: string | undefined;
|
||||
// 이미 업로드된 fileKey 배열 사용
|
||||
const videoFileKeys = courseVideoFileKeys;
|
||||
const vrFileKeys = vrContentFileKeys;
|
||||
const csvFileKey = questionFileKey;
|
||||
|
||||
// 강좌 영상 fileKey 배열 (모든 fileKey를 배열로 저장)
|
||||
let videoUrl: string[] | undefined;
|
||||
if (videoFileKeys.length > 0) {
|
||||
videoUrl = videoFileKeys; // 모든 fileKey를 배열로 저장
|
||||
}
|
||||
|
||||
// VR 콘텐츠 fileKey 배열 (모든 fileKey를 배열로 저장)
|
||||
let webglUrl: string[] | undefined;
|
||||
if (vrFileKeys.length > 0) {
|
||||
webglUrl = vrFileKeys; // 모든 fileKey를 배열로 저장
|
||||
}
|
||||
|
||||
// 학습 평가 문제 fileKey
|
||||
let csvKey: string | undefined;
|
||||
|
||||
// 강좌 영상 업로드 (첫 번째 파일만 사용)
|
||||
if (courseVideoFileObjects.length > 0) {
|
||||
try {
|
||||
const uploadResponse = await apiService.uploadFile(courseVideoFileObjects[0]);
|
||||
if (uploadResponse.data) {
|
||||
const fileKey = uploadResponse.data.key
|
||||
|| uploadResponse.data.fileKey
|
||||
|| uploadResponse.data.id
|
||||
|| uploadResponse.data.imageKey
|
||||
|| uploadResponse.data.fileId
|
||||
|| (uploadResponse.data.data && (uploadResponse.data.data.key || uploadResponse.data.data.fileKey))
|
||||
|| null;
|
||||
if (fileKey) {
|
||||
videoUrl = fileKey;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('강좌 영상 업로드 실패:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// VR 콘텐츠 업로드 (첫 번째 파일만 사용)
|
||||
if (vrContentFileObjects.length > 0) {
|
||||
try {
|
||||
const uploadResponse = await apiService.uploadFile(vrContentFileObjects[0]);
|
||||
if (uploadResponse.data) {
|
||||
const fileKey = uploadResponse.data.key
|
||||
|| uploadResponse.data.fileKey
|
||||
|| uploadResponse.data.id
|
||||
|| uploadResponse.data.imageKey
|
||||
|| uploadResponse.data.fileId
|
||||
|| (uploadResponse.data.data && (uploadResponse.data.data.key || uploadResponse.data.data.fileKey))
|
||||
|| null;
|
||||
if (fileKey) {
|
||||
webglUrl = fileKey;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('VR 콘텐츠 업로드 실패:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 학습 평가 문제 업로드
|
||||
if (questionFileObject) {
|
||||
try {
|
||||
const uploadResponse = await apiService.uploadFile(questionFileObject);
|
||||
if (uploadResponse.data) {
|
||||
const fileKey = uploadResponse.data.key
|
||||
|| uploadResponse.data.fileKey
|
||||
|| uploadResponse.data.id
|
||||
|| uploadResponse.data.imageKey
|
||||
|| uploadResponse.data.fileId
|
||||
|| (uploadResponse.data.data && (uploadResponse.data.data.key || uploadResponse.data.data.fileKey))
|
||||
|| null;
|
||||
if (fileKey) {
|
||||
csvKey = fileKey;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('학습 평가 문제 업로드 실패:', error);
|
||||
}
|
||||
if (csvFileKey) {
|
||||
csvKey = csvFileKey;
|
||||
}
|
||||
|
||||
// API 요청 body 구성
|
||||
@@ -347,8 +308,8 @@ export default function AdminLessonsPage() {
|
||||
subjectId: number;
|
||||
title: string;
|
||||
objective: string;
|
||||
videoUrl?: string;
|
||||
webglUrl?: string;
|
||||
videoUrl?: string | string[];
|
||||
webglUrl?: string | string[];
|
||||
csvKey?: string;
|
||||
} = {
|
||||
subjectId: Number(selectedCourse),
|
||||
@@ -357,10 +318,10 @@ export default function AdminLessonsPage() {
|
||||
};
|
||||
|
||||
// 선택적 필드 추가
|
||||
if (videoUrl) {
|
||||
if (videoUrl && videoUrl.length > 0) {
|
||||
requestBody.videoUrl = videoUrl;
|
||||
}
|
||||
if (webglUrl) {
|
||||
if (webglUrl && webglUrl.length > 0) {
|
||||
requestBody.webglUrl = webglUrl;
|
||||
}
|
||||
if (csvKey) {
|
||||
@@ -416,11 +377,14 @@ export default function AdminLessonsPage() {
|
||||
setCourseVideoCount(0);
|
||||
setCourseVideoFiles([]);
|
||||
setCourseVideoFileObjects([]);
|
||||
setCourseVideoFileKeys([]);
|
||||
setVrContentCount(0);
|
||||
setVrContentFiles([]);
|
||||
setVrContentFileObjects([]);
|
||||
setVrContentFileKeys([]);
|
||||
setQuestionFileCount(0);
|
||||
setQuestionFileObject(null);
|
||||
setQuestionFileKey(null);
|
||||
|
||||
// 토스트 팝업 표시
|
||||
setShowToast(true);
|
||||
@@ -628,37 +592,56 @@ export default function AdminLessonsPage() {
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
accept=".mp4,video/mp4"
|
||||
accept=".mp4,.avi,.mov,.wmv,.flv,.webm,.mkv,video/mp4,video/avi,video/quicktime,video/x-ms-wmv,video/x-flv,video/webm,video/x-matroska"
|
||||
className="hidden"
|
||||
onChange={async (e) => {
|
||||
const files = e.target.files;
|
||||
if (!files) return;
|
||||
|
||||
const MAX_SIZE = 30 * 1024 * 1024; // 30MB
|
||||
const MAX_COUNT = 10; // 최대 10개
|
||||
const validFiles: File[] = [];
|
||||
const oversizedFiles: string[] = [];
|
||||
const invalidTypeFiles: string[] = [];
|
||||
|
||||
// 영상 파일 확장자 확인
|
||||
const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv'];
|
||||
|
||||
Array.from(files).forEach((file) => {
|
||||
// mp4 파일인지 확인
|
||||
if (!file.name.toLowerCase().endsWith('.mp4')) {
|
||||
const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
|
||||
if (!videoExtensions.includes(fileExtension)) {
|
||||
invalidTypeFiles.push(file.name);
|
||||
return;
|
||||
}
|
||||
// 각 파일이 30MB 미만인지 검사
|
||||
if (file.size < MAX_SIZE) {
|
||||
// 각 파일이 30MB 이하인지 검사
|
||||
if (file.size <= MAX_SIZE) {
|
||||
validFiles.push(file);
|
||||
} else {
|
||||
oversizedFiles.push(file.name);
|
||||
}
|
||||
});
|
||||
|
||||
// 30MB 이상인 파일이 있으면 알림
|
||||
// 파일 타입 오류
|
||||
if (invalidTypeFiles.length > 0) {
|
||||
alert(`다음 파일은 영상 파일 형식만 가능합니다 (MP4, AVI, MOV, WMV, FLV, WEBM, MKV):\n${invalidTypeFiles.join('\n')}`);
|
||||
}
|
||||
|
||||
// 30MB 초과 파일이 있으면 알림
|
||||
if (oversizedFiles.length > 0) {
|
||||
alert(`다음 파일은 30MB 미만이어야 합니다:\n${oversizedFiles.join('\n')}`);
|
||||
alert(`다음 파일은 30MB 이하여야 합니다:\n${oversizedFiles.join('\n')}`);
|
||||
}
|
||||
|
||||
// 파일 개수 제한 확인
|
||||
if (courseVideoCount + validFiles.length > 10) {
|
||||
alert('강좌 영상은 최대 10개까지 첨부할 수 있습니다.');
|
||||
const totalCount = courseVideoCount + validFiles.length;
|
||||
if (totalCount > MAX_COUNT) {
|
||||
const availableCount = MAX_COUNT - courseVideoCount;
|
||||
alert(`강좌 영상은 최대 ${MAX_COUNT}개까지 첨부할 수 있습니다. (현재 ${courseVideoCount}개, 추가 가능 ${availableCount > 0 ? availableCount : 0}개)`);
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 30MB 초과 파일이나 잘못된 타입 파일만 있는 경우 중단
|
||||
if (validFiles.length === 0 && (oversizedFiles.length > 0 || invalidTypeFiles.length > 0)) {
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
@@ -666,10 +649,26 @@ export default function AdminLessonsPage() {
|
||||
if (validFiles.length > 0) {
|
||||
try {
|
||||
// 다중 파일 업로드
|
||||
await apiService.uploadFiles(validFiles);
|
||||
setCourseVideoFiles(prev => [...prev, ...validFiles.map(f => f.name)]);
|
||||
setCourseVideoFileObjects(prev => [...prev, ...validFiles]);
|
||||
setCourseVideoCount(prev => prev + validFiles.length);
|
||||
const uploadResponse = await apiService.uploadFiles(validFiles);
|
||||
|
||||
// 응답에서 fileKey 배열 추출
|
||||
const fileKeys: string[] = [];
|
||||
if (uploadResponse.data?.results && Array.isArray(uploadResponse.data.results)) {
|
||||
uploadResponse.data.results.forEach((result: any) => {
|
||||
if (result.ok && result.fileKey) {
|
||||
fileKeys.push(result.fileKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (fileKeys.length > 0) {
|
||||
setCourseVideoFiles(prev => [...prev, ...validFiles.map(f => f.name)]);
|
||||
setCourseVideoFileObjects(prev => [...prev, ...validFiles]);
|
||||
setCourseVideoFileKeys(prev => [...prev, ...fileKeys]);
|
||||
setCourseVideoCount(prev => prev + validFiles.length);
|
||||
} else {
|
||||
throw new Error('파일 업로드는 완료되었지만 fileKey를 받지 못했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('강좌 영상 업로드 실패:', error);
|
||||
alert('파일 업로드에 실패했습니다. 다시 시도해주세요.');
|
||||
@@ -703,6 +702,7 @@ export default function AdminLessonsPage() {
|
||||
onClick={() => {
|
||||
setCourseVideoFiles(prev => prev.filter((_, i) => i !== index));
|
||||
setCourseVideoFileObjects(prev => prev.filter((_, i) => i !== index));
|
||||
setCourseVideoFileKeys(prev => prev.filter((_, i) => i !== index));
|
||||
setCourseVideoCount(prev => prev - 1);
|
||||
}}
|
||||
className="size-[16px] flex items-center justify-center cursor-pointer hover:opacity-70 transition-opacity shrink-0"
|
||||
@@ -740,30 +740,47 @@ export default function AdminLessonsPage() {
|
||||
if (!files) return;
|
||||
|
||||
const MAX_SIZE = 30 * 1024 * 1024; // 30MB
|
||||
const MAX_COUNT = 10; // 최대 10개
|
||||
const validFiles: File[] = [];
|
||||
const oversizedFiles: string[] = [];
|
||||
const invalidTypeFiles: string[] = [];
|
||||
|
||||
Array.from(files).forEach((file) => {
|
||||
// zip 파일인지 확인
|
||||
if (!file.name.toLowerCase().endsWith('.zip')) {
|
||||
// ZIP 파일인지 확인
|
||||
const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
|
||||
if (fileExtension !== '.zip') {
|
||||
invalidTypeFiles.push(file.name);
|
||||
return;
|
||||
}
|
||||
// 각 파일이 30MB 미만인지 검사
|
||||
if (file.size < MAX_SIZE) {
|
||||
// 각 파일이 30MB 이하인지 검사
|
||||
if (file.size <= MAX_SIZE) {
|
||||
validFiles.push(file);
|
||||
} else {
|
||||
oversizedFiles.push(file.name);
|
||||
}
|
||||
});
|
||||
|
||||
// 30MB 이상인 파일이 있으면 알림
|
||||
// 파일 타입 오류
|
||||
if (invalidTypeFiles.length > 0) {
|
||||
alert(`다음 파일은 ZIP 형식만 가능합니다:\n${invalidTypeFiles.join('\n')}`);
|
||||
}
|
||||
|
||||
// 30MB 초과 파일이 있으면 알림
|
||||
if (oversizedFiles.length > 0) {
|
||||
alert(`다음 파일은 30MB 미만이어야 합니다:\n${oversizedFiles.join('\n')}`);
|
||||
alert(`다음 파일은 30MB 이하여야 합니다:\n${oversizedFiles.join('\n')}`);
|
||||
}
|
||||
|
||||
// 파일 개수 제한 확인
|
||||
if (vrContentCount + validFiles.length > 10) {
|
||||
alert('VR 콘텐츠는 최대 10개까지 첨부할 수 있습니다.');
|
||||
const totalCount = vrContentCount + validFiles.length;
|
||||
if (totalCount > MAX_COUNT) {
|
||||
const availableCount = MAX_COUNT - vrContentCount;
|
||||
alert(`VR 콘텐츠는 최대 ${MAX_COUNT}개까지 첨부할 수 있습니다. (현재 ${vrContentCount}개, 추가 가능 ${availableCount > 0 ? availableCount : 0}개)`);
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 30MB 초과 파일이나 잘못된 타입 파일만 있는 경우 중단
|
||||
if (validFiles.length === 0 && (oversizedFiles.length > 0 || invalidTypeFiles.length > 0)) {
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
@@ -771,10 +788,26 @@ export default function AdminLessonsPage() {
|
||||
if (validFiles.length > 0) {
|
||||
try {
|
||||
// 다중 파일 업로드
|
||||
await apiService.uploadFiles(validFiles);
|
||||
setVrContentFiles(prev => [...prev, ...validFiles.map(f => f.name)]);
|
||||
setVrContentFileObjects(prev => [...prev, ...validFiles]);
|
||||
setVrContentCount(prev => prev + validFiles.length);
|
||||
const uploadResponse = await apiService.uploadFiles(validFiles);
|
||||
|
||||
// 응답에서 fileKey 배열 추출
|
||||
const fileKeys: string[] = [];
|
||||
if (uploadResponse.data?.results && Array.isArray(uploadResponse.data.results)) {
|
||||
uploadResponse.data.results.forEach((result: any) => {
|
||||
if (result.ok && result.fileKey) {
|
||||
fileKeys.push(result.fileKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (fileKeys.length > 0) {
|
||||
setVrContentFiles(prev => [...prev, ...validFiles.map(f => f.name)]);
|
||||
setVrContentFileObjects(prev => [...prev, ...validFiles]);
|
||||
setVrContentFileKeys(prev => [...prev, ...fileKeys]);
|
||||
setVrContentCount(prev => prev + validFiles.length);
|
||||
} else {
|
||||
throw new Error('파일 업로드는 완료되었지만 fileKey를 받지 못했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('VR 콘텐츠 업로드 실패:', error);
|
||||
alert('파일 업로드에 실패했습니다. 다시 시도해주세요.');
|
||||
@@ -808,6 +841,7 @@ export default function AdminLessonsPage() {
|
||||
onClick={() => {
|
||||
setVrContentFiles(prev => prev.filter((_, i) => i !== index));
|
||||
setVrContentFileObjects(prev => prev.filter((_, i) => i !== index));
|
||||
setVrContentFileKeys(prev => prev.filter((_, i) => i !== index));
|
||||
setVrContentCount(prev => prev - 1);
|
||||
}}
|
||||
className="size-[16px] flex items-center justify-center cursor-pointer hover:opacity-70 transition-opacity shrink-0"
|
||||
@@ -844,25 +878,59 @@ export default function AdminLessonsPage() {
|
||||
<span>첨부</span>
|
||||
<input
|
||||
type="file"
|
||||
accept=".csv"
|
||||
accept=".csv,text/csv,application/vnd.ms-excel"
|
||||
className="hidden"
|
||||
onChange={async (e) => {
|
||||
const files = e.target.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
|
||||
// CSV 파일만 허용
|
||||
if (file.name.toLowerCase().endsWith('.csv')) {
|
||||
try {
|
||||
// 단일 파일 업로드
|
||||
await apiService.uploadFile(file);
|
||||
setQuestionFileObject(file);
|
||||
setQuestionFileCount(1);
|
||||
} catch (error) {
|
||||
console.error('학습 평가 문제 업로드 실패:', error);
|
||||
alert('파일 업로드에 실패했습니다. 다시 시도해주세요.');
|
||||
}
|
||||
if (!file.name.toLowerCase().endsWith('.csv')) {
|
||||
alert('CSV 파일 형식만 첨부할 수 있습니다.');
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 단일 파일 업로드
|
||||
const uploadResponse = await apiService.uploadFile(file);
|
||||
|
||||
// 응답에서 fileKey 추출
|
||||
let fileKey: string | null = null;
|
||||
if (uploadResponse.data?.fileKey) {
|
||||
fileKey = uploadResponse.data.fileKey;
|
||||
} else if (uploadResponse.data?.key) {
|
||||
fileKey = uploadResponse.data.key;
|
||||
} else if (uploadResponse.data?.id) {
|
||||
fileKey = uploadResponse.data.id;
|
||||
} else if (uploadResponse.data?.imageKey) {
|
||||
fileKey = uploadResponse.data.imageKey;
|
||||
} else if (uploadResponse.data?.fileId) {
|
||||
fileKey = uploadResponse.data.fileId;
|
||||
} else if (uploadResponse.data?.data && (uploadResponse.data.data.key || uploadResponse.data.data.fileKey)) {
|
||||
fileKey = uploadResponse.data.data.key || uploadResponse.data.data.fileKey;
|
||||
} else if (uploadResponse.data?.results && Array.isArray(uploadResponse.data.results) && uploadResponse.data.results.length > 0) {
|
||||
const result = uploadResponse.data.results[0];
|
||||
if (result.ok && result.fileKey) {
|
||||
fileKey = result.fileKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileKey) {
|
||||
setQuestionFileObject(file);
|
||||
setQuestionFileKey(fileKey);
|
||||
setQuestionFileCount(1);
|
||||
} else {
|
||||
throw new Error('파일 업로드는 완료되었지만 fileKey를 받지 못했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('학습 평가 문제 업로드 실패:', error);
|
||||
alert('파일 업로드에 실패했습니다. 다시 시도해주세요.');
|
||||
}
|
||||
// input 초기화 (같은 파일 다시 선택 가능하도록)
|
||||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user