Initial commit: Schedule service for son
This commit is contained in:
commit
af2ea7be06
19 changed files with 2270 additions and 0 deletions
39
frontend/admin/index.html
Normal file
39
frontend/admin/index.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Админка - Расписание</title>
|
||||
<link rel="stylesheet" href="/admin/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Админка расписания</h1>
|
||||
<div class="week-navigation">
|
||||
<button id="prev-week">← Предыдущая неделя</button>
|
||||
<span id="week-range"></span>
|
||||
<button id="next-week">Следующая неделя →</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="actions">
|
||||
<button id="add-task-btn" class="btn btn-primary">➕ Добавить задачу</button>
|
||||
<button id="add-event-btn" class="btn btn-primary">➕ Добавить занятие</button>
|
||||
</div>
|
||||
|
||||
<div id="schedule-grid" class="schedule-grid"></div>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно для добавления/редактирования -->
|
||||
<div id="modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<div id="modal-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/admin/static/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
434
frontend/admin/static/script.js
Normal file
434
frontend/admin/static/script.js
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
264
frontend/admin/static/style.css
Normal file
264
frontend/admin/static/style.css
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.week-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.week-navigation button {
|
||||
padding: 8px 16px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.week-navigation button:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
#week-range {
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0b7dda;
|
||||
}
|
||||
|
||||
.schedule-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.day-column {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.day-header {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.day-items {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: #f9f9f9;
|
||||
padding: 10px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #4CAF50;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.item.task {
|
||||
border-left-color: #FF9800;
|
||||
}
|
||||
|
||||
.item.event {
|
||||
border-left-color: #2196F3;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.item-time {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.item-actions button {
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background: #FFC107;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Модальное окно */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 5% auto;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 15px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.checkbox-group input[type="checkbox"] {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.weekdays-select {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.weekdays-select label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.schedule-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.schedule-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.schedule-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue