chto-ty-umeesh/index.html

367 lines
12 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>
<style>
* { margin: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container { max-width: 1400px; margin: 0 auto; }
h1 { color: #fff; text-align: center; margin-bottom: 20px; font-size: clamp(24px, 4vw, 36px); }
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
background: rgba(255,255,255,0.95);
border-radius: 12px;
padding: 20px;
text-align: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.stat-card .num { font-size: 32px; font-weight: bold; color: #667eea; }
.stat-card .label { color: #666; font-size: 14px; margin-top: 5px; }
.add-btn {
display: block;
margin: 0 auto 25px;
padding: 14px 40px;
background: #fff;
color: #667eea;
border: none;
border-radius: 30px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: transform 0.2s;
}
.add-btn:hover { transform: scale(1.05); }
.modal {
display: none;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.6);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal.on { display: flex; }
.modal-content {
background: #fff;
border-radius: 16px;
padding: 30px;
width: 90%;
max-width: 450px;
max-height: 90vh;
overflow-y: auto;
}
.modal-content h2 { margin-bottom: 20px; color: #333; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; color: #555; font-weight: 500; }
.form-group input, .form-group select, .form-group textarea {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 15px;
}
.form-group textarea { resize: vertical; min-height: 80px; }
.form-actions { display: flex; gap: 10px; margin-top: 20px; }
.form-actions button {
flex: 1;
padding: 12px;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
}
.btn-save { background: #667eea; color: #fff; }
.btn-cancel { background: #e0e0e0; color: #333; }
.schedule-grid {
background: rgba(255,255,255,0.95);
border-radius: 16px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
overflow-x: auto;
}
table { width: 100%; border-collapse: collapse; min-width: 800px; }
th, td {
padding: 12px 8px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
font-weight: 600;
position: sticky;
top: 0;
}
tr:hover { background: #f5f5f5; }
.day-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.day-1 { background: #ffe0e0; color: #c62828; }
.day-2 { background: #fff3e0; color: #ef6c00; }
.day-3 { background: #e8f5e9; color: #2e7d32; }
.day-4 { background: #e3f2fd; color: #1565c0; }
.day-5 { background: #f3e5f5; color: #6a1b9a; }
.day-6 { background: #fff8e1; color: #f9a825; }
.day-0 { background: #fce4ec; color: #c2185b; }
.event-title { font-weight: 600; color: #333; }
.event-time { color: #666; font-size: 14px; }
.event-location { color: #888; font-size: 13px; }
.actions { display: flex; gap: 8px; }
.actions button {
padding: 6px 12px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
}
.btn-edit { background: #e3f2fd; color: #1565c0; }
.btn-delete { background: #ffebee; color: #c62828; }
.empty-state {
text-align: center;
padding: 60px 20px;
color: #888;
}
.empty-state p { margin-top: 15px; font-size: 16px; }
</style>
</head>
<body>
<div class="container">
<h1>📅 Моё расписание на неделю</h1>
<div class="stats">
<div class="stat-card">
<div class="num" id="stat-total">0</div>
<div class="label">Всего событий</div>
</div>
<div class="stat-card">
<div class="num" id="stat-today">0</div>
<div class="label">Сегодня</div>
</div>
<div class="stat-card">
<div class="num" id="stat-week">0</div>
<div class="label">На этой неделе</div>
</div>
<div class="stat-card">
<div class="num" id="stat-days">0</div>
<div class="label">Дней занято</div>
</div>
</div>
<button class="add-btn" onclick="openModal()">+ Добавить событие</button>
<div class="schedule-grid">
<table>
<thead>
<tr>
<th>День</th>
<th>Время</th>
<th>Событие</th>
<th>Место</th>
<th>Заметки</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="events-body"></tbody>
</table>
<div class="empty-state" id="empty-state">
<div style="font-size: 48px;">📝</div>
<p>Пока нет событий. Добавьте первое!</p>
</div>
</div>
</div>
<div class="modal" id="modal">
<div class="modal-content">
<h2 id="modal-title">Добавить событие</h2>
<form id="event-form">
<input type="hidden" id="event-id">
<div class="form-group">
<label>Название *</label>
<input type="text" id="event-title" required placeholder="Встреча, урок, тренировка...">
</div>
<div class="form-group">
<label>День недели *</label>
<select id="event-day" required>
<option value="1">Понедельник</option>
<option value="2">Вторник</option>
<option value="3">Среда</option>
<option value="4">Четверг</option>
<option value="5">Пятница</option>
<option value="6">Суббота</option>
<option value="0">Воскресенье</option>
</select>
</div>
<div class="form-group">
<label>Время начала *</label>
<input type="time" id="event-time" required>
</div>
<div class="form-group">
<label>Место</label>
<input type="text" id="event-location" placeholder="Офис, онлайн, зал...">
</div>
<div class="form-group">
<label>Заметки</label>
<textarea id="event-notes" placeholder="Детали, контакты, подготовка..."></textarea>
</div>
<div class="form-actions">
<button type="button" class="btn-cancel" onclick="closeModal()">Отмена</button>
<button type="submit" class="btn-save">Сохранить</button>
</div>
</form>
</div>
</div>
<script>
const days = { 1: 'Пн', 2: 'Вт', 3: 'Ср', 4: 'Чт', 5: 'Пт', 6: 'Сб', 0: 'Вс' };
let events = [];
let editingId = null;
async function load() {
const data = await fetch('/api/events').then(r => r.json());
events = data.events || [];
render();
updateStats();
}
function render() {
const tbody = document.getElementById('events-body');
const empty = document.getElementById('empty-state');
if (events.length === 0) {
tbody.innerHTML = '';
empty.style.display = 'block';
return;
}
empty.style.display = 'none';
events.sort((a, b) => {
if (a.day !== b.day) return a.day - b.day;
return a.time.localeCompare(b.time);
});
tbody.innerHTML = events.map(e => `
<tr>
<td><span class="day-badge day-${e.day}">${days[e.day]}</span></td>
<td class="event-time">${e.time}</td>
<td class="event-title">${escapeHtml(e.title)}</td>
<td class="event-location">${escapeHtml(e.location || '—')}</td>
<td class="event-location">${escapeHtml(e.notes || '—')}</td>
<td class="actions">
<button class="btn-edit" onclick="editEvent(${e.id})">✏️</button>
<button class="btn-delete" onclick="deleteEvent(${e.id})">🗑️</button>
</td>
</tr>
`).join('');
}
function updateStats() {
const today = new Date().getDay();
today = today === 0 ? 7 : today;
document.getElementById('stat-total').textContent = events.length;
document.getElementById('stat-today').textContent = events.filter(e => e.day === today).length;
document.getElementById('stat-week').textContent = events.length;
const uniqueDays = new Set(events.map(e => e.day)).size;
document.getElementById('stat-days').textContent = uniqueDays;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function openModal() {
document.getElementById('modal-title').textContent = 'Добавить событие';
document.getElementById('event-form').reset();
document.getElementById('event-id').value = '';
editingId = null;
document.getElementById('modal').classList.add('on');
}
function closeModal() {
document.getElementById('modal').classList.remove('on');
}
function editEvent(id) {
const e = events.find(ev => ev.id === id);
if (!e) return;
editingId = id;
document.getElementById('modal-title').textContent = 'Редактировать событие';
document.getElementById('event-id').value = e.id;
document.getElementById('event-title').value = e.title;
document.getElementById('event-day').value = e.day;
document.getElementById('event-time').value = e.time;
document.getElementById('event-location').value = e.location || '';
document.getElementById('event-notes').value = e.notes || '';
document.getElementById('modal').classList.add('on');
}
async function deleteEvent(id) {
if (!confirm('Удалить это событие?')) return;
await fetch('/api/events/' + id, { method: 'DELETE' });
load();
}
document.getElementById('event-form').addEventListener('submit', async (e) => {
e.preventDefault();
const data = {
title: document.getElementById('event-title').value,
day: parseInt(document.getElementById('event-day').value),
time: document.getElementById('event-time').value,
location: document.getElementById('event-location').value,
notes: document.getElementById('event-notes').value
};
if (editingId) {
await fetch('/api/events/' + editingId, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
} else {
await fetch('/api/events', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
closeModal();
load();
});
document.getElementById('modal').addEventListener('click', (e) => {
if (e.target.id === 'modal') closeModal();
});
load();
</script>
</body>
</html>