From dbabd5b878a47657643a2854dd11285ec66009d6 Mon Sep 17 00:00:00 2001 From: efim Date: Wed, 15 Apr 2026 01:57:17 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=82=D0=BE=20=D0=B5=D1=81=D1=82=D1=8C=20=D0=BB=D0=B8?= =?UTF-8?q?=20=D1=83=D0=B6=D0=B5=20=D1=82=D0=B0=D0=BA=D0=B0=D1=8F=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B4=D0=B0=D1=87=D0=B0=20=D0=B2=20=D0=B1=D0=B0=D0=B7?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/CourseDetails.tsx | 46 ++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 12 deletions(-) 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); From 1536466371896a11bcbaf4eb98f3dd2cc17534f2 Mon Sep 17 00:00:00 2001 From: efim Date: Wed, 15 Apr 2026 01:59:35 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=82=D0=BE=20=D0=B5=D1=81=D1=82=D1=8C=20=D0=BB=D0=B8?= =?UTF-8?q?=20=D1=83=D0=B6=D0=B5=20=D1=82=D0=B0=D0=BA=D0=B0=D1=8F=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B4=D0=B0=D1=87=D0=B0=20=D0=B2=20=D0=B1=D0=B0=D0=B7?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=B8=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B8=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B4=D0=B0=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/TaskDetails.tsx | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) 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() From 638d710de6705da6b9e2e63c37c37c040460c406 Mon Sep 17 00:00:00 2001 From: efim Date: Wed, 15 Apr 2026 02:04:44 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=82=D0=BE=20=D0=B5=D1=81=D1=82=D1=8C=20=D0=BB=D0=B8?= =?UTF-8?q?=20=D1=83=D0=B6=D0=B5=20=D1=82=D0=B0=D0=BA=D0=BE=D0=B9=20=D0=BA?= =?UTF-8?q?=D1=83=D1=80=D1=81=20=D0=B2=20=D0=B1=D0=B0=D0=B7=D0=B5=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=BA=D1=83=D1=80=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/CoursesPage.tsx | 95 ++++++++++++++++++------------ 1 file changed, 57 insertions(+), 38 deletions(-) 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('');