diff --git a/frontend/src/pages/CourseDetails.tsx b/frontend/src/pages/CourseDetails.tsx index 065d869..86cd711 100644 --- a/frontend/src/pages/CourseDetails.tsx +++ b/frontend/src/pages/CourseDetails.tsx @@ -183,19 +183,41 @@ export default function CourseDetails() { setErrorMessage(''); setIsAddingTask(true); - const { data, error } = await supabase - .from('tasks') - .insert([ - { - title: newTaskTitle.trim(), - course_id: id, - is_completed: false, - deadline: newTaskDeadline || null, + const normalizedTitle = newTaskTitle.trim(); + const normalizedDeadline = newTaskDeadline || null; - }, - ]) - .select() - .single(); + const { data: existingTask, error: duplicateCheckError } = await supabase + .from('tasks') + .select('id') + .eq('course_id', id) + .eq('title', normalizedTitle) + .eq('deadline', normalizedDeadline) + .maybeSingle(); + + if (duplicateCheckError) { + setErrorMessage('Ошибка проверки дубликата задачи: ' + duplicateCheckError.message); + setIsAddingTask(false); + return; + } + + if (existingTask) { + setErrorMessage('Задача с таким названием и дедлайном уже существует.'); + setIsAddingTask(false); + return; + } + + const { data, error } = await supabase + .from('tasks') + .insert([ + { + title: normalizedTitle, + course_id: id, + is_completed: false, + deadline: normalizedDeadline, + }, + ]) + .select() + .single(); if (error) { setErrorMessage('Ошибка создания задачи: ' + error.message); diff --git a/frontend/src/pages/CoursesPage.tsx b/frontend/src/pages/CoursesPage.tsx index ef3368e..31323f4 100644 --- a/frontend/src/pages/CoursesPage.tsx +++ b/frontend/src/pages/CoursesPage.tsx @@ -106,17 +106,38 @@ export default function CoursesPage() { return; } + const normalizedCourseName = newCourseName.trim(); + + const { data: existingCourse, error: duplicateCheckError } = await supabase + .from('courses') + .select('id') + .eq('user_id', user.id) + .eq('name', normalizedCourseName) + .maybeSingle(); + + if (duplicateCheckError) { + setErrorMessage('Ошибка проверки дубликата курса: ' + duplicateCheckError.message); + setIsCreatingCourse(false); + return; + } + + if (existingCourse) { + setErrorMessage('Курс с таким названием уже существует.'); + setIsCreatingCourse(false); + return; + } + const { data: createdCourse, error: courseError } = await supabase - .from('courses') - .insert([ - { - name: newCourseName.trim(), - description: newCourseDescription.trim() || null, - user_id: user.id, - }, - ]) - .select() - .single(); + .from('courses') + .insert([ + { + name: normalizedCourseName, + description: newCourseDescription.trim() || null, + user_id: user.id, + }, + ]) + .select() + .single(); if (courseError) { setErrorMessage('Ошибка создания курса: ' + courseError.message); @@ -133,14 +154,14 @@ export default function CoursesPage() { if (selectedFile) { const originalFileName = selectedFile.name; const safeFileName = originalFileName - .replace(/\s+/g, '_') - .replace(/[^a-zA-Z0-9._-]/g, '_'); + .replace(/\s+/g, '_') + .replace(/[^a-zA-Z0-9._-]/g, '_'); const filePath = `courses/${createdCourse.id}/${Date.now()}_${safeFileName}`; const { error: uploadError } = await supabase.storage - .from('file_attachments') - .upload(filePath, selectedFile); + .from('file_attachments') + .upload(filePath, selectedFile); if (uploadError) { setCourses((prevCourses) => [createdCourse, ...prevCourses]); @@ -160,21 +181,21 @@ export default function CoursesPage() { } const { error: attachmentError } = await supabase - .from('course_attachments') - .insert([ - { - id: crypto.randomUUID(), - course_id: createdCourse.id, - name: selectedFile.name, - url: filePath, - is_external: false, - }, - ]); + .from('course_attachments') + .insert([ + { + id: crypto.randomUUID(), + course_id: createdCourse.id, + name: selectedFile.name, + url: filePath, + is_external: false, + }, + ]); if (attachmentError) { setCourses((prevCourses) => [createdCourse, ...prevCourses]); setErrorMessage( - 'Курс создан, файл загружен, но запись о вложении не сохранилась: ' + + 'Курс создан, файл загружен, но запись о вложении не сохранилась: ' + attachmentError.message ); setNewCourseName(''); @@ -196,22 +217,20 @@ export default function CoursesPage() { const cleanLink = newCourseLink.trim(); const { error: linkError } = await supabase - .from('course_attachments') - .insert([ - { - id: crypto.randomUUID(), - course_id: createdCourse.id, - name: cleanLink, - url: cleanLink, - is_external: true, - }, - ]); + .from('course_attachments') + .insert([ + { + id: crypto.randomUUID(), + course_id: createdCourse.id, + name: cleanLink, + url: cleanLink, + is_external: true, + }, + ]); if (linkError) { setCourses((prevCourses) => [createdCourse, ...prevCourses]); - setErrorMessage( - 'Курс создан, но ссылка не сохранилась: ' + linkError.message - ); + setErrorMessage('Курс создан, но ссылка не сохранилась: ' + linkError.message); setNewCourseName(''); setNewCourseDescription(''); setNewCourseLink(''); diff --git a/frontend/src/pages/TaskDetails.tsx b/frontend/src/pages/TaskDetails.tsx index 9d46008..7e66ca6 100644 --- a/frontend/src/pages/TaskDetails.tsx +++ b/frontend/src/pages/TaskDetails.tsx @@ -224,15 +224,39 @@ export default function TaskDetails() { return; } + const normalizedTitle = editTitle.trim(); + const normalizedDeadline = editDeadline || null; + setErrorMessage(''); setIsSavingTask(true); + const { data: existingTask, error: duplicateCheckError } = await supabase + .from('tasks') + .select('id') + .eq('course_id', task.course_id) + .eq('title', normalizedTitle) + .eq('deadline', normalizedDeadline) + .neq('id', task.id) + .maybeSingle(); + + if (duplicateCheckError) { + setErrorMessage('Ошибка проверки дубликата задачи: ' + duplicateCheckError.message); + setIsSavingTask(false); + return; + } + + if (existingTask) { + setErrorMessage('Задача с таким названием и дедлайном уже существует.'); + setIsSavingTask(false); + return; + } + const { data, error } = await supabase .from('tasks') .update({ - title: editTitle.trim(), + title: normalizedTitle, description: editDescription.trim() || null, - deadline: editDeadline || null, + deadline: normalizedDeadline, }) .eq('id', task.id) .select()