146 lines
5.5 KiB
HTML
146 lines
5.5 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Календарь</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: #1a1b1e; color: #e4e4e7; padding: 20px; }
|
||
h1 { font-size: 24px; margin-bottom: 16px; color: #fff; }
|
||
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||
.nav-btn { background: #27272a; border: 1px solid #3f3f46; color: #e4e4e7;
|
||
padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 14px; }
|
||
.nav-btn:hover { background: #3f3f46; }
|
||
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
|
||
.day-header { text-align: center; font-size: 12px; color: #a1a1aa; padding: 8px 0;
|
||
font-weight: 600; text-transform: uppercase; }
|
||
.day { aspect-ratio: 1; display: flex; flex-direction: column; align-items: center;
|
||
justify-content: center; border-radius: 8px; cursor: pointer; font-size: 14px;
|
||
background: #27272a; border: 1px solid #3f3f46; position: relative; }
|
||
.day:hover { background: #3f3f46; }
|
||
.day.other { opacity: 0.3; }
|
||
.day.today { border-color: #3b82f6; background: #1e3a5f; }
|
||
.day.has-event::after { content: ''; position: absolute; bottom: 6px; width: 6px; height: 6px;
|
||
border-radius: 50%; background: #3b82f6; }
|
||
.day.selected { border-color: #8b5cf6; box-shadow: 0 0 0 2px #8b5cf6; }
|
||
.events { margin-top: 20px; }
|
||
.event { padding: 12px; background: #27272a; border: 1px solid #3f3f46;
|
||
border-radius: 8px; margin-bottom: 8px; display: flex; justify-content: space-between; }
|
||
.event-title { font-weight: 500; }
|
||
.event-time { color: #a1a1aa; font-size: 13px; }
|
||
.event-actions { display: flex; gap: 8px; }
|
||
.event-actions button { background: none; border: none; color: #a1a1aa;
|
||
cursor: pointer; padding: 2px 6px; border-radius: 4px; }
|
||
.event-actions button:hover { background: #3f3f46; color: #ef4444; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<button class="nav-btn" onclick="prevMonth()">←</button>
|
||
<h1 id="month-title">Июнь 2025</h1>
|
||
<button class="nav-btn" onclick="nextMonth()">→</button>
|
||
</div>
|
||
<div class="calendar-grid" id="calendar"></div>
|
||
<div class="events" id="events"></div>
|
||
|
||
<script>
|
||
const MONTHS = ['Январь','Февраль','Март','Апрель','Май','Июнь',
|
||
'Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'];
|
||
const DAYS = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
|
||
|
||
let currentDate = new Date();
|
||
let selectedDate = null;
|
||
let events = [];
|
||
|
||
function render() {
|
||
const year = currentDate.getFullYear();
|
||
const month = currentDate.getMonth();
|
||
document.getElementById('month-title').textContent = MONTHS[month] + ' ' + year;
|
||
|
||
const firstDay = new Date(year, month, 1);
|
||
const lastDay = new Date(year, month + 1, 0);
|
||
const startDay = firstDay.getDay() || 7; // Mon=1, Sun=7
|
||
|
||
const grid = document.getElementById('calendar');
|
||
grid.innerHTML = DAYS.map(d => `<div class="day-header">${d}</div>`).join('');
|
||
|
||
// Previous month padding
|
||
const prevLastDay = new Date(year, month, 0).getDate();
|
||
for (let i = startDay - 2; i >= 0; i--) {
|
||
grid.innerHTML += `<div class="day other">${prevLastDay - i}</div>`;
|
||
}
|
||
|
||
// Current month
|
||
const today = new Date();
|
||
for (let d = 1; d <= lastDay.getDate(); d++) {
|
||
const dateStr = `${year}-${String(month+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
|
||
const isToday = today.getFullYear() === year && today.getMonth() === month && today.getDate() === d;
|
||
const hasEvent = events.some(e => e.date === dateStr);
|
||
const isSelected = selectedDate === dateStr;
|
||
let cls = 'day';
|
||
if (isToday) cls += ' today';
|
||
if (hasEvent) cls += ' has-event';
|
||
if (isSelected) cls += ' selected';
|
||
grid.innerHTML += `<div class="${cls}" onclick="selectDate('${dateStr}')">${d}</div>`;
|
||
}
|
||
|
||
renderEvents();
|
||
}
|
||
|
||
function renderEvents() {
|
||
const el = document.getElementById('events');
|
||
if (!selectedDate) { el.innerHTML = '<p style="color:#a1a1aa">Выберите дату</p>'; return; }
|
||
|
||
const dayEvents = events.filter(e => e.date === selectedDate);
|
||
if (dayEvents.length === 0) {
|
||
el.innerHTML = '<p style="color:#a1a1aa">Нет событий на этот день</p>';
|
||
return;
|
||
}
|
||
|
||
el.innerHTML = '<h2 style="font-size:18px;margin-bottom:12px">События</h2>';
|
||
dayEvents.forEach(ev => {
|
||
const time = ev.time || 'весь день';
|
||
el.innerHTML += `
|
||
<div class="event">
|
||
<div>
|
||
<div class="event-title">${ev.title}</div>
|
||
<div class="event-time">${time}${ev.location ? ' · ' + ev.location : ''}</div>
|
||
</div>
|
||
<div class="event-actions">
|
||
<button onclick="deleteEvent('${ev.id}')">✕</button>
|
||
</div>
|
||
</div>`;
|
||
});
|
||
}
|
||
|
||
function selectDate(dateStr) {
|
||
selectedDate = dateStr;
|
||
render();
|
||
}
|
||
|
||
function prevMonth() {
|
||
currentDate.setMonth(currentDate.getMonth() - 1);
|
||
selectedDate = null;
|
||
render();
|
||
}
|
||
|
||
function nextMonth() {
|
||
currentDate.setMonth(currentDate.getMonth() + 1);
|
||
selectedDate = null;
|
||
render();
|
||
}
|
||
|
||
async function loadEvents() {
|
||
// In production, this would call verstak HTTP endpoint or Wails binding
|
||
// For now, data is injected via __VERSTAK_DATA__
|
||
events = window.__VERSTAK_DATA__ || [];
|
||
render();
|
||
}
|
||
|
||
loadEvents();
|
||
</script>
|
||
</body>
|
||
</html>
|