From d8b3b974d4fee59644b82e56a474a1f25c3d0113 Mon Sep 17 00:00:00 2001 From: Fovway Date: Sat, 18 Oct 2025 00:32:56 +0700 Subject: [PATCH] feat: Add sidebar calendar to notes page with month navigation --- public/calendar.js | 164 +++++++++++++++++++++++++++++++++++++++++ public/notes.html | 90 +++++++++++++++-------- public/style.css | 177 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 398 insertions(+), 33 deletions(-) create mode 100644 public/calendar.js diff --git a/public/calendar.js b/public/calendar.js new file mode 100644 index 0000000..dbeb366 --- /dev/null +++ b/public/calendar.js @@ -0,0 +1,164 @@ +// Календарь +class Calendar { + constructor() { + this.currentDate = new Date(); + this.selectedDate = new Date(); + this.today = new Date(); + this.init(); + } + + init() { + this.calendarTitle = document.getElementById("calendarTitle"); + this.calendarDays = document.getElementById("calendarDays"); + this.prevMonthBtn = document.getElementById("prevMonth"); + this.nextMonthBtn = document.getElementById("nextMonth"); + + this.prevMonthBtn.addEventListener("click", () => this.previousMonth()); + this.nextMonthBtn.addEventListener("click", () => this.nextMonth()); + + this.render(); + } + + previousMonth() { + this.currentDate.setMonth(this.currentDate.getMonth() - 1); + this.render(); + } + + nextMonth() { + this.currentDate.setMonth(this.currentDate.getMonth() + 1); + this.render(); + } + + render() { + this.renderTitle(); + this.renderDays(); + } + + renderTitle() { + const monthNames = [ + "Январь", + "Февраль", + "Март", + "Апрель", + "Май", + "Июнь", + "Июль", + "Август", + "Сентябрь", + "Октябрь", + "Ноябрь", + "Декабрь", + ]; + const month = monthNames[this.currentDate.getMonth()]; + const year = this.currentDate.getFullYear(); + this.calendarTitle.textContent = `${month} ${year}`; + } + + renderDays() { + this.calendarDays.innerHTML = ""; + + // Первый день месяца + const firstDay = new Date( + this.currentDate.getFullYear(), + this.currentDate.getMonth(), + 1 + ); + // Последний день месяца + const lastDay = new Date( + this.currentDate.getFullYear(), + this.currentDate.getMonth() + 1, + 0 + ); + // Число дней в месяце + const daysInMonth = lastDay.getDate(); + // День недели, на который выпадает 1-е число месяца (0 - понедельник в нашей системе) + let startingDayOfWeek = firstDay.getDay() - 1; // Преобразуем так, чтобы понедельник был 0 + if (startingDayOfWeek === -1) startingDayOfWeek = 6; // Воскресенье = 6 + + // Добавляем дни из предыдущего месяца + const prevLastDay = new Date( + this.currentDate.getFullYear(), + this.currentDate.getMonth(), + 0 + ).getDate(); + for (let i = startingDayOfWeek - 1; i >= 0; i--) { + const dayDiv = document.createElement("div"); + dayDiv.classList.add("calendar-day", "other-month"); + dayDiv.textContent = prevLastDay - i; + this.calendarDays.appendChild(dayDiv); + } + + // Добавляем дни текущего месяца + for (let day = 1; day <= daysInMonth; day++) { + const dayDiv = document.createElement("div"); + dayDiv.classList.add("calendar-day"); + dayDiv.textContent = day; + + const currentCellDate = new Date( + this.currentDate.getFullYear(), + this.currentDate.getMonth(), + day + ); + + // Проверяем, сегодняшний ли это день + if (this.isToday(currentCellDate)) { + dayDiv.classList.add("today"); + } + + // Проверяем, выбранный ли это день + if (this.isSelected(currentCellDate)) { + dayDiv.classList.add("selected"); + } + + // Добавляем обработчик клика + dayDiv.addEventListener("click", () => this.selectDate(currentCellDate)); + + this.calendarDays.appendChild(dayDiv); + } + + // Добавляем дни из следующего месяца + const totalCells = this.calendarDays.children.length; + const remainingCells = 42 - totalCells; // 6 строк * 7 дней = 42 + for (let day = 1; day <= remainingCells; day++) { + const dayDiv = document.createElement("div"); + dayDiv.classList.add("calendar-day", "other-month"); + dayDiv.textContent = day; + this.calendarDays.appendChild(dayDiv); + } + } + + isToday(date) { + return ( + date.getDate() === this.today.getDate() && + date.getMonth() === this.today.getMonth() && + date.getFullYear() === this.today.getFullYear() + ); + } + + isSelected(date) { + return ( + date.getDate() === this.selectedDate.getDate() && + date.getMonth() === this.selectedDate.getMonth() && + date.getFullYear() === this.selectedDate.getFullYear() + ); + } + + selectDate(date) { + this.selectedDate = new Date(date); + this.render(); + // Вы можете добавить дополнительную логику здесь, например, фильтрацию заметок по дате + console.log("Выбрана дата:", this.formatDate(date)); + } + + formatDate(date) { + const day = String(date.getDate()).padStart(2, "0"); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const year = date.getFullYear(); + return `${day}.${month}.${year}`; + } +} + +// Инициализируем календарь при загрузке страницы +document.addEventListener("DOMContentLoaded", function () { + new Calendar(); +}); diff --git a/public/notes.html b/public/notes.html index b97f309..d16e240 100644 --- a/public/notes.html +++ b/public/notes.html @@ -39,39 +39,64 @@ -
-
- - - - - - - -
- -
- - или нажмите Alt + Enter +
+ + + + +
+
+ + + + + + + +
+ + +
+ + или нажмите Alt + Enter +
@@ -85,5 +110,6 @@
+ diff --git a/public/style.css b/public/style.css index 23a58a0..afebf1c 100644 --- a/public/style.css +++ b/public/style.css @@ -295,6 +295,7 @@ textarea:focus { /* Стили для личного кабинета */ .profile-container { margin-top: 20px; + padding-bottom: 80px; /* Отступ снизу, чтобы контент не обрезался футером */ } .avatar-section { @@ -306,6 +307,9 @@ textarea:focus { .avatar-wrapper { margin-bottom: 15px; + display: flex; + justify-content: center; + align-items: center; } .avatar-preview { @@ -321,7 +325,7 @@ textarea:focus { height: 150px; border-radius: 50%; background-color: #e0e0e0; - display: inline-flex; + display: flex; align-items: center; justify-content: center; font-size: 64px; @@ -435,3 +439,174 @@ textarea:focus { color: #007bff; text-decoration: underline; } + +/* Стили для календаря */ +.main-wrapper { + display: flex; + gap: 20px; + align-items: flex-start; + width: 100%; +} + +.calendar-sidebar { + flex-shrink: 0; + width: 240px; +} + +.calendar-container { + background: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 15px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.calendar-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + gap: 10px; +} + +.calendar-header h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: #333; + min-width: 120px; + text-align: center; + flex: 1; +} + +.calendar-nav-btn { + background: #f0f0f0; + border: 1px solid #ddd; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + font-size: 14px; + color: #333; + transition: all 0.2s ease; +} + +.calendar-nav-btn:hover { + background: #007bff; + color: white; + border-color: #007bff; +} + +.calendar-weekdays { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 2px; + margin-bottom: 10px; +} + +.weekday { + text-align: center; + font-size: 11px; + font-weight: 600; + color: #666; + padding: 5px 0; + text-transform: uppercase; +} + +.calendar-days { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 2px; +} + +.calendar-day { + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + border-radius: 4px; + cursor: pointer; + background: #f8f9fa; + color: #666; + border: 1px solid transparent; + transition: all 0.2s ease; + position: relative; +} + +.calendar-day:hover { + background: #e8f4f8; + border-color: #007bff; +} + +.calendar-day.other-month { + color: #ccc; + background: #fafafa; +} + +.calendar-day.today { + background: #007bff; + color: white; + font-weight: 600; + border-color: #0056cc; +} + +.calendar-day.today:hover { + background: #0056cc; +} + +.calendar-day.selected { + background: #28a745; + color: white; + font-weight: 600; + border-color: #1e7e34; +} + +.calendar-day.selected:hover { + background: #1e7e34; +} + +.calendar-day.has-notes { + font-weight: 600; + color: #007bff; +} + +.calendar-day.has-notes::before { + content: ''; + display: block; + width: 4px; + height: 4px; + background: #007bff; + border-radius: 50%; + position: absolute; + bottom: 2px; + left: 50%; + transform: translateX(-50%); +} + +.main { + flex: 1; + min-width: 300px; +} + +/* Адаптация для мобильных устройств */ +@media (max-width: 768px) { + .main-wrapper { + flex-direction: column; + } + + .calendar-sidebar { + width: 100%; + } + + .calendar-container { + max-width: 100%; + } + + .main { + min-width: auto; + } + + .container { + max-width: 900px; + } +}