chto-sozdat-veb-prilozhenie/index.html

396 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Умный планировщик учёбы</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📚</text></svg>">
<style>
:root {
--primary: #4F46E5;
--primary-light: #818CF8;
--success: #10B981;
--warning: #F59E0B;
--danger: #EF4444;
--background: #F9FAFB;
--surface: #FFFFFF;
--text: #111827;
--text-secondary: #6B7280;
--border: #E5E7EB;
--shadow: 0 1px 3px rgba(0,0,0,0.1);
--radius: 12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif; background: var(--background); color: var(--text); line-height: 1.5; }
/* Layout */
.app { display: flex; min-height: 100vh; }
.sidebar { width: 260px; background: var(--surface); border-right: 1px solid var(--border); padding: 24px 16px; position: fixed; height: 100vh; overflow-y: auto; }
.main { margin-left: 260px; flex: 1; padding: 32px; }
/* Logo */
.logo { display: flex; align-items: center; gap: 12px; margin-bottom: 32px; padding: 0 8px; }
.logo-icon { width: 42px; height: 42px; background: linear-gradient(135deg, var(--primary), var(--primary-light)); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 22px; }
.logo-text { font-size: 18px; font-weight: 700; color: var(--text); }
/* Navigation */
.nav { list-style: none; }
.nav-item { margin-bottom: 4px; }
.nav-link { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: var(--radius); color: var(--text-secondary); text-decoration: none; font-weight: 500; transition: all 0.2s; cursor: pointer; }
.nav-link:hover { background: var(--background); color: var(--primary); }
.nav-link.active { background: var(--primary); color: white; }
.nav-icon { font-size: 20px; width: 24px; text-align: center; }
/* Header */
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; }
.header h1 { font-size: 28px; font-weight: 700; color: var(--text); }
.header-date { color: var(--text-secondary); font-size: 14px; margin-top: 4px; }
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 12px 20px; border-radius: var(--radius); border: none; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; }
.btn-primary { background: var(--primary); color: white; }
.btn-primary:hover { background: #4338CA; }
.btn-secondary { background: var(--background); color: var(--text); border: 1px solid var(--border); }
.btn-secondary:hover { background: var(--border); }
/* Stats cards */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 32px; }
.stat-card { background: var(--surface); border-radius: var(--radius); padding: 24px; box-shadow: var(--shadow); }
.stat-value { font-size: 32px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
.stat-label { font-size: 14px; color: var(--text-secondary); }
.stat-card.highlight { background: linear-gradient(135deg, var(--primary), var(--primary-light)); }
.stat-card.highlight .stat-value, .stat-card.highlight .stat-label { color: white; }
/* Sections */
.section { background: var(--surface); border-radius: var(--radius); padding: 24px; box-shadow: var(--shadow); margin-bottom: 24px; }
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.section-title { font-size: 18px; font-weight: 600; color: var(--text); }
.section-link { color: var(--primary); text-decoration: none; font-size: 14px; font-weight: 500; cursor: pointer; }
.section-link:hover { text-decoration: underline; }
/* Task list */
.task-list { list-style: none; }
.task-item { display: flex; align-items: flex-start; gap: 12px; padding: 16px; border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 12px; transition: all 0.2s; }
.task-item:hover { border-color: var(--primary-light); }
.task-item.completed { opacity: 0.6; }
.task-item.completed .task-title { text-decoration: line-through; }
.task-checkbox { width: 20px; height: 20px; border: 2px solid var(--border); border-radius: 6px; cursor: pointer; flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
.task-checkbox.checked { background: var(--success); border-color: var(--success); }
.task-checkbox.checked::after { content: '✓'; color: white; font-size: 14px; }
.task-content { flex: 1; min-width: 0; }
.task-title { font-weight: 500; color: var(--text); margin-bottom: 4px; }
.task-meta { font-size: 13px; color: var(--text-secondary); display: flex; gap: 12px; }
.task-subject { background: var(--primary-light); color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
.task-priority { padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; }
.priority-high { background: #FEE2E2; color: var(--danger); }
.priority-medium { background: #FEF3C7; color: var(--warning); }
.priority-low { background: #D1FAE5; color: var(--success); }
.task-due { display: flex; align-items: center; gap: 4px; }
.task-due.overdue { color: var(--danger); font-weight: 500; }
.task-actions { display: flex; gap: 8px; opacity: 0; transition: opacity 0.2s; }
.task-item:hover .task-actions { opacity: 1; }
.task-action-btn { background: none; border: none; cursor: pointer; font-size: 16px; padding: 4px; border-radius: 4px; }
.task-action-btn:hover { background: var(--background); }
/* Schedule */
.schedule-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; }
.schedule-day { background: var(--background); border-radius: var(--radius); padding: 16px; }
.schedule-day-title { font-weight: 600; color: var(--text); margin-bottom: 12px; padding-bottom: 8px; border-bottom: 2px solid var(--border); }
.schedule-item { background: var(--surface); border-radius: 8px; padding: 12px; margin-bottom: 8px; border-left: 3px solid var(--primary); }
.schedule-subject { font-weight: 600; font-size: 14px; margin-bottom: 4px; }
.schedule-time { font-size: 12px; color: var(--text-secondary); }
.schedule-location { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
/* Modal */
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 1000; }
.modal-overlay.open { display: flex; }
.modal { background: var(--surface); border-radius: var(--radius); padding: 32px; width: 100%; max-width: 480px; max-height: 90vh; overflow-y: auto; }
.modal-title { font-size: 20px; font-weight: 600; margin-bottom: 24px; }
.form-group { margin-bottom: 16px; }
.form-label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 8px; color: var(--text); }
.form-input { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; font-family: inherit; }
.form-input:focus { outline: none; border-color: var(--primary); }
.form-select { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; font-family: inherit; background: white; }
.form-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 24px; }
/* Empty state */
.empty-state { text-align: center; padding: 48px 24px; color: var(--text-secondary); }
.empty-state-icon { font-size: 48px; margin-bottom: 16px; }
.empty-state-text { font-size: 16px; margin-bottom: 16px; }
/* Progress chart */
.progress-container { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; }
.progress-circle { width: 120px; height: 120px; position: relative; }
.progress-circle svg { transform: rotate(-90deg); }
.progress-circle-bg { fill: none; stroke: var(--border); stroke-width: 8; }
.progress-circle-fg { fill: none; stroke: var(--success); stroke-width: 8; stroke-dasharray: 283; stroke-dashoffset: 283; transition: stroke-dashoffset 0.5s; }
.progress-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: 700; color: var(--text); }
.progress-details { flex: 1; }
.progress-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border); }
.progress-item:last-child { border-bottom: none; }
/* Responsive */
@media (max-width: 768px) {
.sidebar { width: 100%; height: auto; position: relative; border-right: none; border-bottom: 1px solid var(--border); }
.main { margin-left: 0; padding: 20px; }
.nav { display: flex; overflow-x: auto; gap: 8px; padding-bottom: 8px; }
.nav-item { margin-bottom: 0; }
.nav-link { white-space: nowrap; padding: 10px 14px; }
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.header { flex-direction: column; gap: 16px; align-items: flex-start; }
.schedule-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="app">
<!-- Sidebar -->
<aside class="sidebar">
<div class="logo">
<div class="logo-icon">📚</div>
<div class="logo-text">StudyPlanner</div>
</div>
<ul class="nav">
<li class="nav-item">
<a class="nav-link active" data-page="dashboard">
<span class="nav-icon">🏠</span>
Главная
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-page="tasks">
<span class="nav-icon">✓</span>
Задания
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-page="schedule">
<span class="nav-icon">📅</span>
Расписание
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-page="calendar">
<span class="nav-icon">📆</span>
Календарь
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-page="progress">
<span class="nav-icon">📊</span>
Прогресс
</a>
</li>
</ul>
</aside>
<!-- Main content -->
<main class="main">
<div class="header">
<div>
<h1 id="page-title">Главная</h1>
<div class="header-date" id="current-date"></div>
</div>
<button class="btn btn-primary" onclick="openTaskModal()">
<span>+</span> Добавить задание
</button>
</div>
<!-- Dashboard -->
<div id="page-dashboard" class="page">
<div class="stats-grid">
<div class="stat-card highlight">
<div class="stat-value" id="stat-total">0</div>
<div class="stat-label">Всего заданий</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-completed">0</div>
<div class="stat-label">Выполнено</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-pending">0</div>
<div class="stat-label">В процессе</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-overdue" style="color: var(--danger)">0</div>
<div class="stat-label">Просрочено</div>
</div>
</div>
<div class="section">
<div class="section-header">
<h2 class="section-title">📋 Ближайшие дедлайны</h2>
<span class="section-link" onclick="navigateTo('tasks')">Все задания →</span>
</div>
<ul class="task-list" id="dashboard-tasks"></ul>
</div>
<div class="section">
<div class="section-header">
<h2 class="section-title">📚 Расписание на сегодня</h2>
<span class="section-link" onclick="navigateTo('schedule')">Всё расписание →</span>
</div>
<div class="schedule-grid" id="dashboard-schedule"></div>
</div>
</div>
<!-- Tasks page -->
<div id="page-tasks" class="page" style="display: none;">
<div class="section">
<div class="section-header">
<h2 class="section-title">Все задания</h2>
<div style="display: flex; gap: 8px;">
<select class="form-select" style="width: auto;" id="task-filter" onchange="renderTasks()">
<option value="all">Все</option>
<option value="pending">В процессе</option>
<option value="completed">Выполнено</option>
<option value="overdue">Просрочено</option>
</select>
</div>
</div>
<ul class="task-list" id="tasks-list"></ul>
</div>
</div>
<!-- Schedule page -->
<div id="page-schedule" class="page" style="display: none;">
<div class="section">
<div class="section-header">
<h2 class="section-title">Расписание на неделю</h2>
<button class="btn btn-primary" onclick="openScheduleModal()">+ Добавить</button>
</div>
<div class="schedule-grid" id="schedule-full"></div>
</div>
</div>
<!-- Calendar page -->
<div id="page-calendar" class="page" style="display: none;">
<div class="section">
<div class="section-header">
<h2 class="section-title">Календарь заданий</h2>
</div>
<div id="calendar-view"></div>
</div>
</div>
<!-- Progress page -->
<div id="page-progress" class="page" style="display: none;">
<div class="section">
<div class="section-header">
<h2 class="section-title">Ваш прогресс</h2>
</div>
<div class="progress-container">
<div class="progress-circle">
<svg width="120" height="120">
<circle class="progress-circle-bg" cx="60" cy="60" r="45"></circle>
<circle class="progress-circle-fg" id="progress-ring" cx="60" cy="60" r="45"></circle>
</svg>
<div class="progress-text" id="progress-percent">0%</div>
</div>
<div class="progress-details">
<div class="progress-item">
<span>Всего заданий</span>
<strong id="progress-total">0</strong>
</div>
<div class="progress-item">
<span>Выполнено</span>
<strong id="progress-completed" style="color: var(--success)">0</strong>
</div>
<div class="progress-item">
<span>В процессе</span>
<strong id="progress-pending">0</strong>
</div>
<div class="progress-item">
<span>Просрочено</span>
<strong id="progress-overdue" style="color: var(--danger)">0</strong>
</div>
</div>
</div>
</div>
<div class="section">
<div class="section-header">
<h2 class="section-title">По предметам</h2>
</div>
<div id="progress-by-subject"></div>
</div>
</div>
</main>
</div>
<!-- Task Modal -->
<div class="modal-overlay" id="task-modal">
<div class="modal">
<h3 class="modal-title">Новое задание</h3>
<form id="task-form" onsubmit="saveTask(event)">
<div class="form-group">
<label class="form-label">Название</label>
<input type="text" class="form-input" name="title" required placeholder="Например: Решить задачу №5">
</div>
<div class="form-group">
<label class="form-label">Предмет</label>
<input type="text" class="form-input" name="subject" required placeholder="Например: Математика">
</div>
<div class="form-group">
<label class="form-label">Срок выполнения</label>
<input type="date" class="form-input" name="dueDate" required>
</div>
<div class="form-group">
<label class="form-label">Приоритет</label>
<select class="form-select" name="priority">
<option value="low">Низкий</option>
<option value="medium" selected>Средний</option>
<option value="high">Высокий</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Описание (необязательно)</label>
<textarea class="form-input" name="description" rows="3" placeholder="Детали задания"></textarea>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="closeTaskModal()">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
<!-- Schedule Modal -->
<div class="modal-overlay" id="schedule-modal">
<div class="modal">
<h3 class="modal-title">Добавить занятие</h3>
<form id="schedule-form" onsubmit="saveSchedule(event)">
<div class="form-group">
<label class="form-label">Предмет</label>
<input type="text" class="form-input" name="subject" required placeholder="Например: Физика">
</div>
<div class="form-group">
<label class="form-label">День недели</label>
<select class="form-select" name="day">
<option value="mon">Понедельник</option>
<option value="tue">Вторник</option>
<option value="wed">Среда</option>
<option value="thu">Четверг</option>
<option value="fri">Пятница</option>
<option value="sat">Суббота</option>
<option value="sun">Воскресенье</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Время</label>
<input type="time" class="form-input" name="time" required>
</div>
<div class="form-group">
<label class="form-label">Место (кабинет или ссылка)</label>
<input type="text" class="form-input" name="location" placeholder="Например: каб. 305 или Zoom">
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="closeScheduleModal()">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
<script src="app.js"></script>
</body>
</html>