// State let tasks = []; let schedule = []; let currentPage = 'dashboard'; // Day names in Russian const dayNames = { mon: 'Понедельник', tue: 'Вторник', wed: 'Среда', thu: 'Четверг', fri: 'Пятница', sat: 'Суббота', sun: 'Воскресенье' }; const dayNamesShort = { mon: 'Пн', tue: 'Вт', wed: 'Ср', thu: 'Чт', fri: 'Пт', sat: 'Сб', sun: 'Вс' }; // Initialize document.addEventListener('DOMContentLoaded', () => { loadDate(); loadTasks(); loadSchedule(); setupNavigation(); checkReminders(); }); // Load current date function loadDate() { const now = new Date(); const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; document.getElementById('current-date').textContent = now.toLocaleDateString('ru-RU', options); } // Navigation function setupNavigation() { document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const page = link.dataset.page; navigateTo(page); }); }); } function navigateTo(page) { currentPage = page; document.querySelectorAll('.page').forEach(p => p.style.display = 'none'); document.getElementById('page-' + page).style.display = 'block'; document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active')); document.querySelector(`[data-page="${page}"]`).classList.add('active'); const titles = { dashboard: 'Главная', tasks: 'Задания', schedule: 'Расписание', calendar: 'Календарь', progress: 'Прогресс' }; document.getElementById('page-title').textContent = titles[page]; if (page === 'tasks') renderTasks(); if (page === 'schedule') renderScheduleFull(); if (page === 'calendar') renderCalendar(); if (page === 'progress') renderProgress(); } // Load tasks from API async function loadTasks() { try { const res = await fetch('/api/tasks'); tasks = await res.json(); updateStats(); renderDashboardTasks(); } catch (e) { console.error('Failed to load tasks:', e); } } // Load schedule from API async function loadSchedule() { try { const res = await fetch('/api/schedule'); schedule = await res.json(); renderDashboardSchedule(); } catch (e) { console.error('Failed to load schedule:', e); } } // Update statistics function updateStats() { const today = new Date().toISOString().slice(0, 10); const stats = { total: tasks.length, completed: tasks.filter(t => t.status === 'completed').length, pending: tasks.filter(t => t.status === 'pending').length, overdue: tasks.filter(t => t.status === 'overdue' || (t.status === 'pending' && t.dueDate < today)).length }; document.getElementById('stat-total').textContent = stats.total; document.getElementById('stat-completed').textContent = stats.completed; document.getElementById('stat-pending').textContent = stats.pending; document.getElementById('stat-overdue').textContent = stats.overdue; } // Render dashboard tasks function renderDashboardTasks() { const today = new Date().toISOString().slice(0, 10); const upcoming = tasks .filter(t => t.status !== 'completed' && t.dueDate >= today) .sort((a, b) => a.dueDate.localeCompare(b.dueDate)) .slice(0, 5); const container = document.getElementById('dashboard-tasks'); if (upcoming.length === 0) { container.innerHTML = '
🎉
Нет предстоящих заданий
'; return; } container.innerHTML = upcoming.map(t => renderTaskItem(t)).join(''); } // Render task item function renderTaskItem(t) { const today = new Date().toISOString().slice(0, 10); const isOverdue = t.status === 'pending' && t.dueDate < today; const isCompleted = t.status === 'completed'; return `
  • ${escapeHtml(t.title)}
    ${escapeHtml(t.subject)} ${getPriorityLabel(t.priority)} 📅 ${formatDate(t.dueDate)}
  • `; } // Render dashboard schedule function renderDashboardSchedule() { const today = new Date().toLocaleDateString('en-US', { weekday: 'lowercase' }); const dayMap = { monday: 'mon', tuesday: 'tue', wednesday: 'wed', thursday: 'thu', friday: 'fri', saturday: 'sat', sunday: 'sun' }; const todayKey = dayMap[today]; const todaySchedule = schedule.filter(s => s.day === todayKey).sort((a, b) => a.time.localeCompare(b.time)); const container = document.getElementById('dashboard-schedule'); if (todaySchedule.length === 0) { container.innerHTML = '
    🌟
    Сегодня занятий нет
    '; return; } container.innerHTML = todaySchedule.map(s => `
    ${escapeHtml(s.subject)}
    🕐 ${s.time}
    ${s.location ? `
    📍 ${escapeHtml(s.location)}
    ` : ''}
    `).join(''); } // Render all tasks function renderTasks() { const filter = document.getElementById('task-filter').value; const today = new Date().toISOString().slice(0, 10); let filtered = [...tasks]; if (filter === 'completed') { filtered = filtered.filter(t => t.status === 'completed'); } else if (filter === 'pending') { filtered = filtered.filter(t => t.status === 'pending'); } else if (filter === 'overdue') { filtered = filtered.filter(t => t.status === 'overdue' || (t.status === 'pending' && t.dueDate < today)); } filtered.sort((a, b) => { if (a.status === 'completed' && b.status !== 'completed') return 1; if (a.status !== 'completed' && b.status === 'completed') return -1; return a.dueDate.localeCompare(b.dueDate); }); const container = document.getElementById('tasks-list'); if (filtered.length === 0) { container.innerHTML = '
    📝
    Нет заданий
    '; return; } container.innerHTML = filtered.map(t => renderTaskItem(t)).join(''); } // Render full schedule function renderScheduleFull() { const container = document.getElementById('schedule-full'); const days = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']; container.innerHTML = days.map(day => { const daySchedule = schedule.filter(s => s.day === day).sort((a, b) => a.time.localeCompare(b.time)); return `
    ${dayNames[day]}
    ${daySchedule.length === 0 ? '
    Нет занятий
    ' : ''} ${daySchedule.map(s => `
    ${escapeHtml(s.subject)}
    🕐 ${s.time}
    ${s.location ? `
    📍 ${escapeHtml(s.location)}
    ` : ''}
    `).join('')}
    `; }).join(''); } // Render calendar function renderCalendar() { const now = new Date(); const year = now.getFullYear(); const month = now.getMonth(); const today = new Date().toISOString().slice(0, 10); const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const startDay = firstDay.getDay() || 7; const totalDays = lastDay.getDate(); const monthNames = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь']; let html = `

    ${monthNames[month]} ${year}

    `; html += '
    '; // Day headers ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'].forEach(d => { html += `
    ${d}
    `; }); // Empty cells before first day for (let i = 1; i < startDay; i++) { html += '
    '; } // Days for (let day = 1; day <= totalDays; day++) { const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const dayTasks = tasks.filter(t => t.dueDate === dateStr); const isToday = dateStr === today; const hasTasks = dayTasks.length > 0; html += `
    ${day}
    ${dayTasks.slice(0, 2).map(t => `
    ${escapeHtml(t.title)}
    `).join('')} ${dayTasks.length > 2 ? `
    +${dayTasks.length - 2} ещё
    ` : ''}
    `; } html += '
    '; document.getElementById('calendar-view').innerHTML = html; } // Render progress function renderProgress() { const total = tasks.length; const completed = tasks.filter(t => t.status === 'completed').length; const pending = tasks.filter(t => t.status === 'pending').length; const today = new Date().toISOString().slice(0, 10); const overdue = tasks.filter(t => t.status === 'overdue' || (t.status === 'pending' && t.dueDate < today)).length; const percent = total > 0 ? Math.round((completed / total) * 100) : 0; const circumference = 2 * Math.PI * 45; const offset = circumference - (percent / 100) * circumference; document.getElementById('progress-ring').style.strokeDashoffset = offset; document.getElementById('progress-percent').textContent = percent + '%'; document.getElementById('progress-total').textContent = total; document.getElementById('progress-completed').textContent = completed; document.getElementById('progress-pending').textContent = pending; document.getElementById('progress-overdue').textContent = overdue; // By subject const bySubject = {}; tasks.forEach(t => { bySubject[t.subject] = (bySubject[t.subject] || 0) + 1; }); const container = document.getElementById('progress-by-subject'); const subjects = Object.entries(bySubject).sort((a, b) => b[1] - a[1]); if (subjects.length === 0) { container.innerHTML = '
    Нет данных
    '; return; } container.innerHTML = subjects.map(([subject, count]) => `
    ${escapeHtml(subject)} ${count} зад.
    `).join(''); } // Modal functions function openTaskModal() { document.getElementById('task-modal').classList.add('open'); document.getElementById('task-form').reset(); document.querySelector('[name="dueDate"]').value = new Date().toISOString().slice(0, 10); } function closeTaskModal() { document.getElementById('task-modal').classList.remove('open'); } function openScheduleModal() { document.getElementById('schedule-modal').classList.add('open'); document.getElementById('schedule-form').reset(); } function closeScheduleModal() { document.getElementById('schedule-modal').classList.remove('open'); } // Save task async function saveTask(e) { e.preventDefault(); const form = e.target; const task = { title: form.title.value, subject: form.subject.value, dueDate: form.dueDate.value, priority: form.priority.value, description: form.description.value, status: 'pending' }; try { const res = await fetch('/api/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(task) }); if (res.ok) { closeTaskModal(); await loadTasks(); renderDashboardTasks(); } } catch (err) { console.error('Failed to save task:', err); } } // Toggle task completion async function toggleTask(id) { const task = tasks.find(t => t.id === id); if (!task) return; const newStatus = task.status === 'completed' ? 'pending' : 'completed'; try { const res = await fetch(`/api/tasks/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus }) }); if (res.ok) { await loadTasks(); renderDashboardTasks(); if (currentPage === 'tasks') renderTasks(); if (currentPage === 'progress') renderProgress(); } } catch (err) { console.error('Failed to update task:', err); } } // Delete task async function deleteTask(id) { if (!confirm('Удалить это задание?')) return; try { const res = await fetch(`/api/tasks/${id}`, { method: 'DELETE' }); if (res.ok) { await loadTasks(); renderDashboardTasks(); if (currentPage === 'tasks') renderTasks(); if (currentPage === 'progress') renderProgress(); } } catch (err) { console.error('Failed to delete task:', err); } } // Save schedule async function saveSchedule(e) { e.preventDefault(); const form = e.target; const item = { subject: form.subject.value, day: form.day.value, time: form.time.value, location: form.location.value }; try { const res = await fetch('/api/schedule', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item) }); if (res.ok) { closeScheduleModal(); await loadSchedule(); renderDashboardSchedule(); if (currentPage === 'schedule') renderScheduleFull(); } } catch (err) { console.error('Failed to save schedule:', err); } } // Check reminders function checkReminders() { const today = new Date().toISOString().slice(0, 10); const upcoming = tasks.filter(t => { if (t.status === 'completed') return false; const diff = (new Date(t.dueDate) - new Date(today)) / (1000 * 60 * 60 * 24); return diff >= 0 && diff <= 1; }); if (upcoming.length > 0) { // Could show notification here console.log('Upcoming deadlines:', upcoming.length); } } // Helpers function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function getPriorityLabel(p) { return { high: 'Высокий', medium: 'Средний', low: 'Низкий' }[p]; } function formatDate(dateStr) { const d = new Date(dateStr); return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }); } // Close modals on overlay click document.querySelectorAll('.modal-overlay').forEach(overlay => { overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.classList.remove('open'); } }); });