scheduleSon/frontend/admin/static/script.js
2025-12-30 12:23:42 +03:00

434 lines
17 KiB
JavaScript
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.

let currentWeekStart = null;
const weekdays = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'];
const weekdaysShort = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
function getTodayInLondon() {
// Получаем текущую дату в таймзоне London
const now = new Date();
const formatter = new Intl.DateTimeFormat('en-CA', {
timeZone: 'Europe/London',
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
return formatter.format(now);
}
function getWeekStart(dateStr = null) {
if (!dateStr) {
dateStr = getTodayInLondon();
}
const date = new Date(dateStr + 'T00:00:00');
const day = date.getDay();
const diff = date.getDate() - day + (day === 0 ? -6 : 1); // Понедельник = 1
const monday = new Date(date.setDate(diff));
return monday.toISOString().split('T')[0];
}
function formatDate(dateStr) {
const date = new Date(dateStr + 'T00:00:00');
return `${weekdaysShort[date.getDay()]}, ${date.getDate()}`;
}
function formatDateFull(dateStr) {
const date = new Date(dateStr + 'T00:00:00');
const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
return `${weekdays[date.getDay()]}, ${date.getDate()} ${months[date.getMonth()]}`;
}
function getWeekDates(startDate) {
const dates = [];
const start = new Date(startDate + 'T00:00:00');
for (let i = 0; i < 7; i++) {
const date = new Date(start);
date.setDate(start.getDate() + i);
dates.push(date.toISOString().split('T')[0]);
}
return dates;
}
async function loadSchedule() {
const weekDates = getWeekDates(currentWeekStart);
const fromDate = weekDates[0];
const toDate = weekDates[6];
console.log('Loading schedule from', fromDate, 'to', toDate);
try {
const response = await fetch(`/api/schedule?from_date=${fromDate}&to_date=${toDate}`);
const data = await response.json();
console.log('Received items:', data.items);
renderSchedule(weekDates, data.items);
// Обновляем заголовок недели
const startDate = new Date(weekDates[0] + 'T00:00:00');
const endDate = new Date(weekDates[6] + 'T00:00:00');
document.getElementById('week-range').textContent =
`${formatDateFull(weekDates[0])} - ${formatDateFull(weekDates[6])}`;
} catch (error) {
console.error('Error loading schedule:', error);
}
}
function renderSchedule(weekDates, items) {
const grid = document.getElementById('schedule-grid');
grid.innerHTML = '';
console.log('Rendering schedule for dates:', weekDates);
console.log('All items:', items);
weekDates.forEach(dateStr => {
const dayItems = items.filter(item => {
const match = item.date === dateStr;
if (!match && items.length > 0) {
console.log(`Item date ${item.date} !== ${dateStr}`);
}
return match;
});
console.log(`Date ${dateStr}: ${dayItems.length} items`);
const date = new Date(dateStr + 'T00:00:00');
const column = document.createElement('div');
column.className = 'day-column';
column.innerHTML = `
<div class="day-header">${formatDateFull(dateStr)}</div>
<div class="day-items" data-date="${dateStr}">
${renderDayItems(dayItems)}
</div>
`;
grid.appendChild(column);
});
}
function renderDayItems(items) {
console.log('Rendering items for day:', items);
if (items.length === 0) {
return '<div style="color: #999; font-style: italic; padding: 10px;">Нет записей</div>';
}
const tasks = items.filter(item => item.kind === 'task');
const events = items.filter(item => item.kind === 'event');
console.log('Tasks:', tasks, 'Events:', events);
let html = '';
// Tasks
tasks.forEach(task => {
html += `
<div class="item task" data-id="${task.id}" data-kind="task">
<div class="item-title">${task.title}</div>
${task.repeat_weekly ? '<div style="font-size: 11px; color: #FF9800;">Повторяется</div>' : ''}
<div class="item-actions">
<button class="btn-edit" onclick="editItem(${task.id}, 'task')">Изменить</button>
<button class="btn-delete" onclick="deleteItem(${task.id}, 'task')">Удалить</button>
</div>
</div>
`;
});
// Events
events.sort((a, b) => a.start_time.localeCompare(b.start_time)).forEach(event => {
const endTime = calculateEndTime(event.start_time, event.duration_min);
html += `
<div class="item event" data-id="${event.id}" data-kind="event">
<div class="item-time">${event.start_time}-${endTime}</div>
<div class="item-title">${event.title}</div>
<div class="item-actions">
<button class="btn-edit" onclick="editItem(${event.id}, 'event')">Изменить</button>
<button class="btn-delete" onclick="deleteItem(${event.id}, 'event')">Удалить</button>
</div>
</div>
`;
});
return html;
}
function calculateEndTime(startTime, durationMin) {
const [hour, minute] = startTime.split(':').map(Number);
const totalMinutes = hour * 60 + minute + durationMin;
const endHour = Math.floor(totalMinutes / 60);
const endMinute = totalMinutes % 60;
return `${String(endHour).padStart(2, '0')}:${String(endMinute).padStart(2, '0')}`;
}
function showModal(content) {
document.getElementById('modal-body').innerHTML = content;
document.getElementById('modal').style.display = 'block';
}
function closeModal() {
document.getElementById('modal').style.display = 'none';
}
function showAddTaskModal(selectedDate = null) {
const weekDates = getWeekDates(currentWeekStart);
const today = getTodayInLondon();
const todayDate = new Date(today + 'T00:00:00');
const selectedDateObj = selectedDate ? new Date(selectedDate + 'T00:00:00') : todayDate;
// getDay() возвращает 0=воскресенье, 1=понедельник, ..., 6=суббота
// Конвертируем в формат 0=понедельник, 6=воскресенье
const jsWeekday = selectedDateObj.getDay();
const selectedWeekday = jsWeekday === 0 ? 6 : jsWeekday - 1; // 0=пн, 6=вс
const remainingWeekdays = [];
// Оставшиеся дни недели после выбранного (среда=2, четверг=3, пятница=4, суббота=5, воскресенье=6)
for (let i = selectedWeekday + 1; i <= 6; i++) {
remainingWeekdays.push(i);
}
const content = `
<h2>Добавить задачу</h2>
<form id="add-task-form">
<div class="form-group">
<label>Дата:</label>
<input type="date" id="task-date" value="${selectedDate || today}" required>
</div>
<div class="form-group">
<label>Название:</label>
<textarea id="task-title" required></textarea>
</div>
<div class="form-group">
<div class="checkbox-group">
<input type="checkbox" id="task-repeat-weekly">
<label for="task-repeat-weekly">Повторять каждую неделю</label>
</div>
</div>
<div class="form-group" id="copy-weekdays-group" style="display: none;">
<label>Добавить на другие дни недели:</label>
<div class="weekdays-select">
${remainingWeekdays.map(day => {
// day в формате 0=пн, 6=вс, но weekdaysShort использует JS формат (0=вс)
const jsDay = day === 6 ? 0 : day + 1;
return `
<label>
<input type="checkbox" name="copy-weekday" value="${day}">
${weekdaysShort[jsDay]}
</label>
`;
}).join('')}
</div>
</div>
<div style="margin-top: 20px;">
<button type="submit" class="btn btn-primary">Добавить</button>
<button type="button" class="btn" onclick="closeModal()">Отмена</button>
</div>
</form>
`;
showModal(content);
document.getElementById('task-repeat-weekly').addEventListener('change', function() {
document.getElementById('copy-weekdays-group').style.display =
this.checked ? 'none' : 'block';
});
document.getElementById('add-task-form').addEventListener('submit', async (e) => {
e.preventDefault();
const date = document.getElementById('task-date').value;
const title = document.getElementById('task-title').value;
const repeatWeekly = document.getElementById('task-repeat-weekly').checked;
const copyWeekdays = Array.from(document.querySelectorAll('input[name="copy-weekday"]:checked'))
.map(cb => parseInt(cb.value));
try {
const response = await fetch('/api/events?kind=task', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
date,
title,
repeat_weekly: repeatWeekly,
copy_to_weekdays: copyWeekdays.length > 0 ? copyWeekdays : null
})
});
if (response.ok) {
const result = await response.json();
console.log('Task created:', result);
closeModal();
await loadSchedule();
} else {
const error = await response.json();
console.error('Error creating task:', error);
alert('Ошибка: ' + (error.detail || 'Неизвестная ошибка'));
}
} catch (error) {
console.error('Exception creating task:', error);
alert('Ошибка при добавлении задачи: ' + error.message);
}
});
}
function showAddEventModal(selectedDate = null) {
const today = getTodayInLondon();
const content = `
<h2>Добавить занятие</h2>
<form id="add-event-form">
<div class="form-group">
<label>Дата:</label>
<input type="date" id="event-date" value="${selectedDate || today}" required>
</div>
<div class="form-group">
<label>Время начала:</label>
<div style="display: flex; gap: 10px;">
<select id="event-hour" required>
${Array.from({length: 13}, (_, i) => i + 8).map(h =>
`<option value="${String(h).padStart(2, '0')}">${String(h).padStart(2, '0')}</option>`
).join('')}
</select>
<select id="event-minute" required>
<option value="00">00</option>
<option value="15">15</option>
<option value="30">30</option>
<option value="45">45</option>
</select>
</div>
</div>
<div class="form-group">
<label>Длительность:</label>
<div style="display: flex; gap: 10px;">
<select id="event-duration-hour">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select id="event-duration-minute">
<option value="00">00</option>
<option value="15">15</option>
<option value="30">30</option>
<option value="45">45</option>
</select>
</div>
</div>
<div class="form-group">
<label>Название:</label>
<textarea id="event-title" required></textarea>
</div>
<div style="margin-top: 20px;">
<button type="submit" class="btn btn-primary">Добавить</button>
<button type="button" class="btn" onclick="closeModal()">Отмена</button>
</div>
</form>
`;
showModal(content);
document.getElementById('add-event-form').addEventListener('submit', async (e) => {
e.preventDefault();
const date = document.getElementById('event-date').value;
const hour = document.getElementById('event-hour').value;
const minute = document.getElementById('event-minute').value;
const durHour = parseInt(document.getElementById('event-duration-hour').value);
const durMin = parseInt(document.getElementById('event-duration-minute').value);
const title = document.getElementById('event-title').value;
const durationMin = durHour * 60 + durMin;
if (durationMin === 0) {
alert('Длительность должна быть минимум 15 минут');
return;
}
try {
const response = await fetch('/api/events?kind=event', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
date,
start_time: `${hour}:${minute}`,
duration_min: durationMin,
title
})
});
if (response.ok) {
const result = await response.json();
console.log('Event created:', result);
closeModal();
await loadSchedule();
} else {
const error = await response.json();
console.error('Error creating event:', error);
alert('Ошибка: ' + (error.detail || 'Неизвестная ошибка'));
}
} catch (error) {
console.error('Exception creating event:', error);
alert('Ошибка при добавлении занятия: ' + error.message);
}
});
}
async function editItem(id, kind) {
// TODO: Реализовать редактирование
alert('Редактирование будет реализовано позже');
}
async function deleteItem(id, kind) {
if (!confirm('Удалить эту запись?')) return;
try {
const response = await fetch(`/api/events/${id}`, {
method: 'DELETE'
});
if (response.ok) {
loadSchedule();
} else {
alert('Ошибка при удалении');
}
} catch (error) {
alert('Ошибка при удалении');
console.error(error);
}
}
// Инициализация
document.addEventListener('DOMContentLoaded', () => {
currentWeekStart = getWeekStart();
loadSchedule();
document.getElementById('prev-week').addEventListener('click', () => {
const date = new Date(currentWeekStart + 'T00:00:00');
date.setDate(date.getDate() - 7);
currentWeekStart = date.toISOString().split('T')[0];
loadSchedule();
});
document.getElementById('next-week').addEventListener('click', () => {
const date = new Date(currentWeekStart + 'T00:00:00');
date.setDate(date.getDate() + 7);
currentWeekStart = date.toISOString().split('T')[0];
loadSchedule();
});
document.getElementById('add-task-btn').addEventListener('click', () => showAddTaskModal());
document.getElementById('add-event-btn').addEventListener('click', () => showAddEventModal());
document.querySelector('.close').addEventListener('click', closeModal);
window.addEventListener('click', (e) => {
const modal = document.getElementById('modal');
if (e.target === modal) {
closeModal();
}
});
// Клик по дню для добавления
document.addEventListener('click', (e) => {
if (e.target.closest('.day-items')) {
const date = e.target.closest('.day-items').dataset.date;
if (e.ctrlKey || e.metaKey) {
showAddEventModal(date);
} else if (e.shiftKey) {
showAddTaskModal(date);
}
}
});
});