postroit-raspisanie-kruzhkov/index.html

294 lines
13 KiB
HTML
Raw Permalink 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>
<style>
* { margin: 0; box-sizing: border-box; }
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: #f5f5f7; color: #1d1d1f; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 28px; margin-bottom: 20px; color: #1d1d1f; }
.stats { display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; }
.stat { background: white; padding: 12px 20px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.stat strong { font-size: 24px; color: #0071e3; }
.filters { display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap; }
.filter-btn { padding: 8px 16px; border: none; border-radius: 20px; background: white; cursor: pointer; font-size: 14px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.filter-btn.active { background: #0071e3; color: white; }
.main { display: grid; grid-template-columns: 1fr 350px; gap: 24px; }
@media (max-width: 900px) { .main { grid-template-columns: 1fr; } }
.card { background: white; border-radius: 16px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; margin-bottom: 16px; color: #1d1d1f; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e5e5; }
th { font-weight: 600; color: #6e6e73; font-size: 13px; text-transform: uppercase; }
tr:hover { background: #f5f5f7; }
.day-badge { display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
.day-Пн { background: #e3f2fd; color: #1565c0; }
.day-Вт { background: #fff3e0; color: #e65100; }
.day-Ср { background: #e8f5e9; color: #2e7d32; }
.day-Чт { background: #fce4ec; color: #c2185b; }
.day-Пт { background: #f3e5f5; color: #7b1fa2; }
.day-Сб { background: #fff8e1; color: #f57f17; }
.day-Вс { background: #ffebee; color: #c62828; }
.actions { display: flex; gap: 8px; }
.btn { padding: 6px 12px; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; }
.btn-edit { background: #0071e3; color: white; }
.btn-delete { background: #ff3b30; color: white; }
.btn-primary { background: #0071e3; color: white; padding: 10px 20px; font-size: 14px; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 13px; color: #6e6e73; margin-bottom: 6px; }
.form-group input, .form-group select { width: 100%; padding: 10px; border: 1px solid #d2d2d7; border-radius: 8px; font-size: 14px; }
.form-group input:focus, .form-group select:focus { outline: none; border-color: #0071e3; }
.reminder-item { background: #fff9e6; border-left: 4px solid #ffcc00; padding: 12px; margin-bottom: 12px; border-radius: 8px; }
.reminder-item .time { font-size: 12px; color: #6e6e73; }
.reminder-item .text { font-size: 14px; margin: 4px 0; }
.reminder-item .delete { color: #ff3b30; cursor: pointer; font-size: 12px; }
.export-btn { margin-top: 16px; }
</style>
</head>
<body>
<div class="container">
<h1>📅 Расписание кружков</h1>
<div class="stats">
<div class="stat"><strong id="total">0</strong> всего кружков</div>
<div class="stat"><strong id="byWeek">0</strong> на этой неделе</div>
</div>
<div class="filters" id="filters"></div>
<div class="main">
<div class="card">
<h2>Расписание</h2>
<table>
<thead>
<tr>
<th>День</th>
<th>Время</th>
<th>Кружок</th>
<th>Место</th>
<th>Преподаватель</th>
<th>Такси</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="table"></tbody>
</table>
<button class="btn export-btn" onclick="exportCsv()">📥 Выгрузить в Excel</button>
</div>
<div class="card">
<h2 id="formTitle">➕ Добавить кружок</h2>
<form id="form">
<input type="hidden" id="editId">
<div class="form-group">
<label>День недели</label>
<select id="day" required>
<option value="Пн">Понедельник</option>
<option value="Вт">Вторник</option>
<option value="Ср">Среда</option>
<option value="Чт">Четверг</option>
<option value="Пт">Пятница</option>
<option value="Сб">Суббота</option>
<option value="Вс">Воскресенье</option>
</select>
</div>
<div class="form-group">
<label>Время</label>
<input type="time" id="time" required>
</div>
<div class="form-group">
<label>Название кружка</label>
<input type="text" id="name" placeholder="Например: Футбол" required>
</div>
<div class="form-group">
<label>Место</label>
<input type="text" id="location" placeholder="Адрес или название">
</div>
<div class="form-group">
<label>Преподаватель</label>
<input type="text" id="teacher" placeholder="Имя преподавателя">
</div>
<div class="form-group">
<label>Телефон тренера</label>
<input type="tel" id="phone" placeholder="+7 777 123 45 67">
</div>
<div class="form-group">
<label style="display:flex;align-items:center;gap:8px">
<input type="checkbox" id="taxi" style="width:auto">
Нужно такси
</label>
</div>
<button type="submit" class="btn btn-primary" style="width:100%">Сохранить</button>
<button type="button" class="btn" onclick="resetForm()" style="width:100%;margin-top:8px">Отмена</button>
</form>
<h2 style="margin-top:24px">⏰ Напоминания</h2>
<div id="reminders"></div>
<form id="reminderForm" style="margin-top:12px">
<div class="form-group">
<label>Текст напоминания</label>
<input type="text" id="reminderText" placeholder="Например: Не забыть форму!" required>
</div>
<button type="submit" class="btn btn-primary" style="width:100%">Добавить напоминание</button>
</form>
</div>
</div>
</div>
<script>
const DAYS = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"];
let classes = [];
let reminders = [];
let filterDay = null;
async function load() {
classes = await fetch("/api/classes").then(r => r.json());
reminders = await fetch("/api/reminders").then(r => r.json());
render();
}
function render() {
const filtered = filterDay ? classes.filter(c => c.day === filterDay) : classes;
document.getElementById("table").innerHTML = filtered
.sort((a, b) => DAYS.indexOf(a.day) - DAYS.indexOf(b.day) || a.time.localeCompare(b.time))
.map(c => `
<tr>
<td><span class="day-badge day-${c.day}">${c.day}</span></td>
<td>${c.time}</td>
<td><strong>${c.name}</strong></td>
<td>${c.location || "—"}</td>
<td>${c.teacher || "—"}${c.phone ? `<br><a href="tel:${c.phone}" style="color:#0071e3">${c.phone}</a>` : ""}</td>
<td>${c.taxi ? "🚕" : "—"}</td>
<td class="actions">
<button class="btn btn-edit" onclick="editClass(${c.id})">✏️</button>
<button class="btn btn-delete" onclick="deleteClass(${c.id})">🗑️</button>
<button class="btn" style="background:#ff9500;color:white" onclick="notUrgent(${c.id})">⏰ Не успеваем</button>
</td>
</tr>
`).join("");
document.getElementById("total").textContent = classes.length;
document.getElementById("byWeek").textContent = classes.length;
document.getElementById("filters").innerHTML =
`<button class="filter-btn ${!filterDay ? "active" : ""}" onclick="setFilter(null)">Все</button>` +
DAYS.map(d => `<button class="filter-btn ${filterDay === d ? "active" : ""}" onclick="setFilter('${d}')">${d}</button>`).join("");
document.getElementById("reminders").innerHTML = reminders
.map(r => `
<div class="reminder-item">
<div class="time">${new Date(r.createdAt).toLocaleString("ru-RU")}</div>
<div class="text">${r.text}</div>
<div class="delete" onclick="deleteReminder(${r.id})">Удалить</div>
</div>
`).join("") || "<p style='color:#6e6e73;font-size:14px'>Нет напоминаний</p>";
}
function setFilter(day) {
filterDay = day;
render();
}
function resetForm() {
document.getElementById("form").reset();
document.getElementById("editId").value = "";
document.getElementById("formTitle").textContent = "➕ Добавить кружок";
}
function editClass(id) {
const c = classes.find(x => x.id === id);
if (!c) return;
document.getElementById("editId").value = c.id;
document.getElementById("day").value = c.day;
document.getElementById("time").value = c.time;
document.getElementById("name").value = c.name;
document.getElementById("location").value = c.location || "";
document.getElementById("teacher").value = c.teacher || "";
document.getElementById("phone").value = c.phone || "";
document.getElementById("taxi").checked = c.taxi || false;
document.getElementById("formTitle").textContent = "✏️ Редактировать";
}
async function deleteClass(id) {
if (!confirm("Удалить этот кружок?")) return;
await fetch("/api/classes", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id }) });
load();
}
document.getElementById("form").addEventListener("submit", async (e) => {
e.preventDefault();
const id = document.getElementById("editId").value;
const data = {
day: document.getElementById("day").value,
time: document.getElementById("time").value,
name: document.getElementById("name").value,
location: document.getElementById("location").value,
teacher: document.getElementById("teacher").value,
phone: document.getElementById("phone").value,
taxi: document.getElementById("taxi").checked
};
if (id) {
await fetch("/api/classes", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: Number(id), ...data }) });
} else {
await fetch("/api/classes", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) });
}
resetForm();
load();
});
document.getElementById("reminderForm").addEventListener("submit", async (e) => {
e.preventDefault();
await fetch("/api/reminders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: document.getElementById("reminderText").value, createdAt: new Date().toISOString() }) });
document.getElementById("reminderText").value = "";
load();
});
async function deleteReminder(id) {
await fetch("/api/reminders", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id }) });
load();
}
async function notUrgent(id) {
const c = classes.find(x => x.id === id);
if (!c) return;
const msg = `⚠️ Не успеваем на ${c.name} (${c.day} ${c.time})!`;
if (c.phone && confirm(`Позвонить тренеру ${c.teacher || ""}?`)) {
window.location.href = "tel:" + c.phone;
} else {
await fetch("/api/reminders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: msg + " — позвоните тренеру: " + (c.phone || "нет телефона"), createdAt: new Date().toISOString() }) });
alert("Напоминание добавлено! Позвоните тренеру: " + (c.phone || "нет телефона"));
}
load();
}
function exportCsv() {
const head = ["День", "Время", "Кружок", "Место", "Преподаватель", "Телефон", "Такси"];
const body = classes.sort((a, b) => DAYS.indexOf(a.day) - DAYS.indexOf(b.day) || a.time.localeCompare(b.time))
.map(r => [r.day, r.time, r.name, r.location || "", r.teacher || "", r.phone || "", r.taxi ? "Да" : "Нет"]
.map(v => '"' + String(v ?? "").replace(/"/g, '""') + '"').join(";"));
const csv = "\uFEFF" + [head.join(";"), ...body].join("\n");
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
a.download = "raspisanie.csv";
a.click();
}
load();
</script>
</body>
</html>