From 837a3eeab520c19212c5bf147d5e39f1884eb1cb Mon Sep 17 00:00:00 2001 From: efim Date: Thu, 7 May 2026 14:19:49 +0300 Subject: [PATCH 1/3] feat: add calendar link to navigation and add nav bar styles --- frontend/src/App.tsx | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4a6d772..2f94b21 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -27,6 +27,14 @@ function Navigation() { navigate('/'); }; + const getNavLinkStyle = (path: string) => ({ + textDecoration: 'none', + color: location.pathname.startsWith(path) + ? 'var(--color-primary)' + : 'var(--color-text-main)', + fontWeight: location.pathname.startsWith(path) ? 700 : 500, + }); + useEffect(() => { if (location.pathname === '/') { setFullName(''); @@ -76,12 +84,25 @@ function Navigation() {

UniFlow

+ const calendarDays = useMemo(() => { + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + + const firstWeekDay = firstDay.getDay() === 0 ? 6 : firstDay.getDay() - 1; + const daysInMonth = lastDay.getDate(); + + return [ + ...Array.from({ length: firstWeekDay }, () => null), + ...Array.from({ length: daysInMonth }, (_, index) => index + 1), + ]; + }, [year, month]); + + const goToPreviousMonth = () => { + setCurrentDate(new Date(year, month - 1, 1)); + }; + + const goToNextMonth = () => { + setCurrentDate(new Date(year, month + 1, 1)); + }; + + const formatMonth = currentDate.toLocaleDateString('ru-RU', { + month: 'long', + year: 'numeric', + }); -
- {/* Шапка с днями недели */} - {weekDays.map(day => ( -
{day}
- ))} - - {/* Пустые ячейки (понедельник, вторник) */} - {emptyDays.map((_, i) => ( -
- ))} - - {/* Дни месяца */} - {daysInMonth.map(day => { - // Фейковая логика: помечаем 4 число как "сегодня", а 15-е как день с двумя дедлайнами - const isToday = day === 4; - const hasDeadlines = day === 15; - const hasCompletedTask = day === 10; - - return ( -
-
{day}
- - {/* Если сегодня, рисуем горящий дедлайн */} - {isToday && ( - - Презентация UniFlow - - )} - - {/* Выполненная задача в прошлом */} - {hasCompletedTask && ( - - Лабораторная №1 - - )} - - {/* Несколько дедлайнов в один день */} - {hasDeadlines && ( - <> - - Эссе по истории - - - Тест по матану - - - )} -
- ); - })} + const todayKey = new Date().toISOString().slice(0, 10); + + return ( +
+
+

Календарь дедлайнов

-
-
-

Предстоящие события списком

-
- -
Презентация UniFlow
-
Сегодня 23:59
- - -
Эссе по истории
-
15 апреля 12:00
- +
+
+ + +

+ {formatMonth} +

+ + +
+ +
+ {weekDays.map((day) => ( +
+ {day} +
+ ))} + + {calendarDays.map((day, index) => { + if (!day) { + return
; + } + + const dateKey = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const isToday = dateKey === todayKey; + + return ( +
+
{day}
+
+ ); + })} +
-
); -} \ No newline at end of file +} From 0c13bc1060d74104eaf9544a4c191faecdc5c580 Mon Sep 17 00:00:00 2001 From: efim Date: Thu, 7 May 2026 18:58:24 +0300 Subject: [PATCH 3/3] feat: show task deadlines --- frontend/src/pages/CalendarPage.tsx | 187 ++++++++++++++++++++++++---- 1 file changed, 166 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/CalendarPage.tsx b/frontend/src/pages/CalendarPage.tsx index 9437e4c..99a5426 100644 --- a/frontend/src/pages/CalendarPage.tsx +++ b/frontend/src/pages/CalendarPage.tsx @@ -1,13 +1,87 @@ -import { useMemo, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { useEffect, useMemo, useState } from 'react'; +import { supabase } from '../lib/supabase'; const weekDays = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']; +type CalendarTask = { + id: string; + title: string; + deadline: string; + is_completed: boolean; + course_id: string; +}; + export default function CalendarPage() { const [currentDate, setCurrentDate] = useState(new Date()); + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const year = currentDate.getFullYear(); const month = currentDate.getMonth(); + useEffect(() => { + const loadTasks = async () => { + setLoading(true); + setError(''); + + try { + const { data: sessionData, error: sessionError } = await supabase.auth.getSession(); + + if (sessionError) { + throw sessionError; + } + + const userId = sessionData.session?.user.id; + + if (!userId) { + setTasks([]); + return; + } + + const { data: courses, error: coursesError } = await supabase + .from('courses') + .select('id') + .eq('user_id', userId); + + if (coursesError) { + throw coursesError; + } + + const courseIds = (courses ?? []).map((course) => course.id); + + if (courseIds.length === 0) { + setTasks([]); + return; + } + + const { data, error } = await supabase + .from('tasks') + .select('id, title, deadline, is_completed, course_id') + .in('course_id', courseIds) + .not('deadline', 'is', null) + .order('deadline', { ascending: true }); + + if (error) { + throw error; + } + + setTasks((data ?? []) as CalendarTask[]); + } catch (error) { + console.error('Ошибка загрузки задач для календаря:', error); + setError('Не удалось загрузить задачи'); + setTasks([]); + } finally { + setLoading(false); + } + }; + + loadTasks(); + }, []); + + const calendarDays = useMemo(() => { const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); @@ -21,6 +95,43 @@ export default function CalendarPage() { ]; }, [year, month]); + const getDateKey = (dateValue: string | Date) => { + const date = new Date(dateValue); + + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; + }; + + + const tasksByDate = useMemo(() => { + return tasks.reduce>((acc, task) => { + const dateKey = getDateKey(task.deadline); + + if (!acc[dateKey]) { + acc[dateKey] = []; + } + + acc[dateKey].push(task); + return acc; + }, {}); + }, [tasks]); + + const upcomingTasks = useMemo(() => { + const now = new Date(); + + return tasks + .filter((task) => new Date(task.deadline) >= now) + .slice(0, 5); + }, [tasks]); + + const formatDeadline = (deadline: string) => { + return new Date(deadline).toLocaleDateString('ru-RU', { + day: 'numeric', + month: 'long', + hour: '2-digit', + minute: '2-digit', + }); + }; + const goToPreviousMonth = () => { setCurrentDate(new Date(year, month - 1, 1)); }; @@ -34,7 +145,7 @@ export default function CalendarPage() { year: 'numeric', }); - const todayKey = new Date().toISOString().slice(0, 10); + const todayKey = getDateKey(new Date()); return (
@@ -48,7 +159,7 @@ export default function CalendarPage() { ← -

+

{formatMonth}

@@ -57,29 +168,63 @@ export default function CalendarPage() {
-
- {weekDays.map((day) => ( -
- {day} -
- ))} - - {calendarDays.map((day, index) => { - if (!day) { - return
; - } + {loading &&

Загрузка календаря...

} + {error &&

{error}

} + {!loading && !error && ( +
+ {weekDays.map((day) => ( +
+ {day} +
+ ))} + + {calendarDays.map((day, index) => { + if (!day) { + return
; + } + + const dateKey = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + + const isToday = dateKey === todayKey; + + const dayTasks = tasksByDate[dateKey] ?? []; + + return ( +
+
{day}
+ {dayTasks.map((task) => ( + + {task.title} + + ))} +
+ ); + })} +
+ )} +
+
+

Ближайшие дедлайны

- const dateKey = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; - const isToday = dateKey === todayKey; +
+ {upcomingTasks.length === 0 &&

Ближайших дедлайнов нет

} - return ( -
-
{day}
+ {upcomingTasks.map((task) => ( + +
{task.title}
+
+ {task.is_completed ? 'Выполнено' : formatDeadline(task.deadline)}
- ); - })} + + ))}
+
); }