feat: добавлены отметки на календаре и фильтрация заметок по дате

- Добавлены зеленые индикаторы на днях с заметками в календаре
- Реализована фильтрация заметок по выбранной дате при клике на день
- Добавлен индикатор активного фильтра с кнопкой сброса
- Исправлено выравнивание элементов заголовка при появлении фильтра
- Добавлена навигация по месяцам с сохранением фильтра
- Повторный клик на день снимает фильтр

Изменения:
- public/app.js: логика фильтрации и отображения индикаторов
- public/notes.html: структура заголовка с индикатором фильтра
- public/style.css: стили для индикаторов и исправление выравнивания
This commit is contained in:
Fovway 2025-10-18 15:42:03 +07:00
parent 8c5b01ef75
commit 493e1a57be
3 changed files with 506 additions and 70 deletions

View File

@ -1,7 +1,7 @@
// DOM элементы
const noteInput = document.getElementById("noteInput");
const saveBtn = document.getElementById("saveBtn");
const notesList = document.getElementById("notesList");
const notesList = document.getElementById("notes-container");
// Получаем кнопки markdown
const boldBtn = document.getElementById("boldBtn");
@ -12,6 +12,10 @@ const quoteBtn = document.getElementById("quoteBtn");
const codeBtn = document.getElementById("codeBtn");
const linkBtn = document.getElementById("linkBtn");
// Глобальные переменные для заметок и фильтрации
let allNotes = [];
let selectedDateFilter = null;
// Функция для получения текущей даты и времени
function getFormattedDateTime() {
let now = new Date();
@ -194,7 +198,9 @@ async function loadNotes() {
throw new Error("Ошибка загрузки заметок");
}
const notes = await response.json();
allNotes = notes; // Сохраняем все заметки в глобальную переменную
renderNotes(notes);
renderCalendar(); // Обновляем календарь после загрузки заметок
} catch (error) {
console.error("Ошибка:", error);
notesList.innerHTML = "<p>Ошибка загрузки заметок</p>";
@ -205,8 +211,25 @@ async function loadNotes() {
function renderNotes(notes) {
notesList.innerHTML = "";
// Фильтруем заметки, если выбрана дата
let notesToDisplay = notes;
if (selectedDateFilter) {
notesToDisplay = notes.filter((note) => note.date === selectedDateFilter);
}
// Если нет заметок для отображения
if (notesToDisplay.length === 0) {
if (selectedDateFilter) {
notesList.innerHTML = `<div class="container"><p style="text-align: center; color: #999;">Нет заметок за выбранную дату (${selectedDateFilter})</p></div>`;
} else {
notesList.innerHTML =
'<div class="container"><p style="text-align: center; color: #999;">Заметок пока нет</p></div>';
}
return;
}
// Итерируемся по заметкам в обычном порядке, чтобы новые были сверху
notes.forEach(function (note) {
notesToDisplay.forEach(function (note) {
const noteHtml = `
<div id="note" class="container">
<div class="date">
@ -400,6 +423,7 @@ noteInput.addEventListener("keydown", function (event) {
document.addEventListener("DOMContentLoaded", function () {
loadUserInfo();
loadNotes();
updateFilterIndicator();
});
// Функция для загрузки информации о пользователе
@ -440,3 +464,224 @@ async function loadUserInfo() {
console.error("Ошибка загрузки информации о пользователе:", error);
}
}
// Календарь
let currentDate = new Date();
// Функция для отображения календаря
function renderCalendar() {
const calendarDays = document.getElementById("calendarDays");
const monthYear = document.getElementById("monthYear");
// Проверяем, существуют ли элементы календаря
if (!calendarDays || !monthYear) return;
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
// Массив названий месяцев
const monthNames = [
"Январь",
"Февраль",
"Март",
"Апрель",
"Май",
"Июнь",
"Июль",
"Август",
"Сентябрь",
"Октябрь",
"Ноябрь",
"Декабрь",
];
// Устанавливаем заголовок месяца и года
monthYear.textContent = `${monthNames[month]} ${year}`;
// Получаем первый день месяца
const firstDay = new Date(year, month, 1);
// Получаем последний день месяца
const lastDay = new Date(year, month + 1, 0);
// Получаем день недели первого дня (0 - воскресенье, 1 - понедельник и т.д.)
let firstDayOfWeek = firstDay.getDay();
// Преобразуем так, чтобы понедельник был первым днем (0)
firstDayOfWeek = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1;
// Очищаем календарь
calendarDays.innerHTML = "";
// Создаём Set дат, когда были созданы заметки
const noteDates = new Set();
allNotes.forEach((note) => {
noteDates.add(note.date);
});
// Получаем последний день предыдущего месяца
const prevMonthLastDay = new Date(year, month, 0).getDate();
const prevMonth = month === 0 ? 11 : month - 1;
const prevYear = month === 0 ? year - 1 : year;
// Добавляем дни предыдущего месяца
for (let i = firstDayOfWeek - 1; i >= 0; i--) {
const day = prevMonthLastDay - i;
const dateStr = `${String(day).padStart(2, "0")}.${String(
prevMonth + 1
).padStart(2, "0")}.${prevYear}`;
const dayDiv = document.createElement("div");
dayDiv.classList.add("calendar-day", "other-month");
dayDiv.textContent = day;
dayDiv.dataset.date = dateStr;
// Проверяем, есть ли заметки на этот день
if (noteDates.has(dateStr)) {
dayDiv.classList.add("has-notes");
}
// Проверяем, выбран ли этот день
if (selectedDateFilter === dateStr) {
dayDiv.classList.add("selected");
}
// Добавляем обработчик клика
dayDiv.addEventListener("click", handleDayClick);
calendarDays.appendChild(dayDiv);
}
// Добавляем дни текущего месяца
const today = new Date();
for (let day = 1; day <= lastDay.getDate(); day++) {
const dateStr = `${String(day).padStart(2, "0")}.${String(
month + 1
).padStart(2, "0")}.${year}`;
const dayDiv = document.createElement("div");
dayDiv.classList.add("calendar-day");
dayDiv.textContent = day;
dayDiv.dataset.date = dateStr;
// Проверяем, является ли день сегодняшним
if (
day === today.getDate() &&
month === today.getMonth() &&
year === today.getFullYear()
) {
dayDiv.classList.add("today");
}
// Проверяем, есть ли заметки на этот день
if (noteDates.has(dateStr)) {
dayDiv.classList.add("has-notes");
}
// Проверяем, выбран ли этот день
if (selectedDateFilter === dateStr) {
dayDiv.classList.add("selected");
}
// Добавляем обработчик клика
dayDiv.addEventListener("click", handleDayClick);
calendarDays.appendChild(dayDiv);
}
// Добавляем дни следующего месяца
const totalCells = calendarDays.children.length;
const remainingCells = 42 - totalCells; // 6 недель по 7 дней
const nextMonth = month === 11 ? 0 : month + 1;
const nextYear = month === 11 ? year + 1 : year;
for (let day = 1; day <= remainingCells; day++) {
const dateStr = `${String(day).padStart(2, "0")}.${String(
nextMonth + 1
).padStart(2, "0")}.${nextYear}`;
const dayDiv = document.createElement("div");
dayDiv.classList.add("calendar-day", "other-month");
dayDiv.textContent = day;
dayDiv.dataset.date = dateStr;
// Проверяем, есть ли заметки на этот день
if (noteDates.has(dateStr)) {
dayDiv.classList.add("has-notes");
}
// Проверяем, выбран ли этот день
if (selectedDateFilter === dateStr) {
dayDiv.classList.add("selected");
}
// Добавляем обработчик клика
dayDiv.addEventListener("click", handleDayClick);
calendarDays.appendChild(dayDiv);
}
}
// Обработчик клика на день в календаре
function handleDayClick(event) {
const clickedDate = event.target.dataset.date;
// Если кликнули на тот же день, снимаем фильтр
if (selectedDateFilter === clickedDate) {
selectedDateFilter = null;
} else {
selectedDateFilter = clickedDate;
}
// Перерисовываем заметки и календарь
renderNotes(allNotes);
renderCalendar();
updateFilterIndicator();
}
// Функция для обновления индикатора фильтра
function updateFilterIndicator() {
const filterIndicator = document.getElementById("filter-indicator");
if (!filterIndicator) return;
if (selectedDateFilter) {
filterIndicator.style.display = "inline-block";
filterIndicator.innerHTML = `Фильтр: ${selectedDateFilter} <button id="clear-filter-btn">✕</button>`;
// Добавляем обработчик клика для кнопки сброса
const clearBtn = document.getElementById("clear-filter-btn");
if (clearBtn) {
clearBtn.addEventListener("click", clearFilter);
}
} else {
filterIndicator.style.display = "none";
}
}
// Функция для сброса фильтра (глобальная)
window.clearFilter = function () {
selectedDateFilter = null;
renderNotes(allNotes);
renderCalendar();
updateFilterIndicator();
};
// Обработчики для кнопок навигации календаря
const prevMonthBtn = document.getElementById("prevMonth");
const nextMonthBtn = document.getElementById("nextMonth");
if (prevMonthBtn) {
prevMonthBtn.addEventListener("click", function () {
currentDate.setMonth(currentDate.getMonth() - 1);
renderCalendar();
});
}
if (nextMonthBtn) {
nextMonthBtn.addEventListener("click", function () {
currentDate.setMonth(currentDate.getMonth() + 1);
renderCalendar();
});
}
// Инициализируем календарь при загрузке страницы
document.addEventListener("DOMContentLoaded", function () {
renderCalendar();
});

View File

@ -11,75 +11,100 @@
/>
</head>
<body>
<div class="container">
<header class="notes-header">
<span>📝 Мои заметки</span>
<div class="user-info">
<div
id="user-avatar-container"
style="display: none; margin-right: 10px"
>
<img
id="user-avatar"
src=""
alt="Аватар"
style="
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
vertical-align: middle;
border: 2px solid #007bff;
"
/>
<div class="container-leftside">
<div class="mini-calendar">
<div class="calendar-header">
<button class="calendar-nav" id="prevMonth"></button>
<span class="calendar-month-year" id="monthYear"></span>
<button class="calendar-nav" id="nextMonth"></button>
</div>
<div class="calendar-weekdays">
<div class="calendar-weekday">Пн</div>
<div class="calendar-weekday">Вт</div>
<div class="calendar-weekday">Ср</div>
<div class="calendar-weekday">Чт</div>
<div class="calendar-weekday">Пт</div>
<div class="calendar-weekday">Сб</div>
<div class="calendar-weekday">Вс</div>
</div>
<div class="calendar-days" id="calendarDays"></div>
</div>
</div>
<div class="center">
<div class="container">
<header class="notes-header">
<div class="notes-header-left">
<span>📝 Мои заметки</span>
<div
id="filter-indicator"
class="filter-indicator"
style="display: none"
></div>
</div>
<div class="user-info">
<div
id="user-avatar-container"
style="display: none; margin-right: 10px"
>
<img
id="user-avatar"
src=""
alt="Аватар"
style="
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
vertical-align: middle;
border: 2px solid #007bff;
"
/>
</div>
<span id="username-display" class="username-clickable"></span>
<form action="/logout" method="POST" style="display: inline">
<button type="submit" class="logout-btn">🚪 Выйти</button>
</form>
</div>
</header>
<div class="main">
<div class="markdown-buttons">
<button class="btnMarkdown" id="boldBtn">
<i class="fas fa-bold"></i>
</button>
<button class="btnMarkdown" id="italicBtn">
<i class="fas fa-italic"></i>
</button>
<button class="btnMarkdown" id="headerBtn">
<i class="fas fa-heading"></i>
</button>
<button class="btnMarkdown" id="listBtn">
<i class="fas fa-list-ul"></i>
</button>
<button class="btnMarkdown" id="quoteBtn">
<i class="fas fa-quote-right"></i>
</button>
<button class="btnMarkdown" id="codeBtn">
<i class="fas fa-code"></i>
</button>
<button class="btnMarkdown" id="linkBtn">
<i class="fas fa-link"></i>
</button>
</div>
<span id="username-display" class="username-clickable"></span>
<form action="/logout" method="POST" style="display: inline">
<button type="submit" class="logout-btn">🚪 Выйти</button>
</form>
</div>
</header>
<div class="main">
<div class="markdown-buttons">
<button class="btnMarkdown" id="boldBtn">
<i class="fas fa-bold"></i>
</button>
<button class="btnMarkdown" id="italicBtn">
<i class="fas fa-italic"></i>
</button>
<button class="btnMarkdown" id="headerBtn">
<i class="fas fa-heading"></i>
</button>
<button class="btnMarkdown" id="listBtn">
<i class="fas fa-list-ul"></i>
</button>
<button class="btnMarkdown" id="quoteBtn">
<i class="fas fa-quote-right"></i>
</button>
<button class="btnMarkdown" id="codeBtn">
<i class="fas fa-code"></i>
</button>
<button class="btnMarkdown" id="linkBtn">
<i class="fas fa-link"></i>
</button>
</div>
<textarea
class="textInput"
id="noteInput"
placeholder="Ваша заметка..."
></textarea>
<div class="save-button-container">
<button class="btnSave" id="saveBtn">Сохранить</button>
<span class="save-hint">или нажмите Alt + Enter</span>
<textarea
class="textInput"
id="noteInput"
placeholder="Ваша заметка..."
></textarea>
<div class="save-button-container">
<button class="btnSave" id="saveBtn">Сохранить</button>
<span class="save-hint">или нажмите Alt + Enter</span>
</div>
</div>
</div>
<div id="notes-container" class="notes-container"></div>
</div>
<div class="notes-container">
<div id="notesList">
<!-- Заметки будут загружаться здесь -->
</div>
</div>
<div class="footer">
<p>Создатель: <span>Fovway</span></p>
</div>

View File

@ -2,10 +2,12 @@ body {
font-family: "Open Sans", sans-serif;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
background: #f5f5f5;
display: flex;
justify-content: center;
align-items: flex-start;
gap: 30px;
padding: 0 15px;
}
header {
@ -17,7 +19,40 @@ header {
.notes-header {
display: flex;
justify-content: space-between;
align-items: center;
align-items: flex-start;
}
.notes-header-left {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.filter-indicator {
display: inline-block;
margin-top: 5px;
padding: 4px 10px;
background-color: #e7f3ff;
border: 1px solid #007bff;
border-radius: 15px;
font-size: 12px;
color: #007bff;
font-weight: 500;
}
.filter-indicator button {
background: none;
border: none;
color: #dc3545;
cursor: pointer;
margin-left: 8px;
font-weight: bold;
padding: 0;
font-size: 14px;
}
.filter-indicator button:hover {
color: #a71d2a;
}
.user-info {
@ -435,3 +470,134 @@ textarea:focus {
color: #007bff;
text-decoration: underline;
}
.center {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 600px;
height: 100%;
}
.container-leftside {
width: 200px;
height: auto;
max-width: 200px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 12px;
margin-top: 20px;
background: white;
}
/* Мини-календарь */
.mini-calendar {
width: 100%;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.calendar-month-year {
font-size: 13px;
font-weight: bold;
color: #333;
}
.calendar-nav {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #007bff;
padding: 0 3px;
transition: color 0.3s ease;
}
.calendar-nav:hover {
color: #0056b3;
}
.calendar-weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
margin-bottom: 3px;
}
.calendar-weekday {
text-align: center;
font-size: 9px;
font-weight: bold;
color: #666;
padding: 3px 0;
}
.calendar-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
}
.calendar-day {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
cursor: pointer;
border-radius: 3px;
transition: all 0.2s ease;
color: #333;
padding: 1px;
font-weight: 500;
position: relative;
}
.calendar-day:hover {
background-color: #e7f3ff;
}
.calendar-day.today {
background-color: #007bff;
color: white;
font-weight: bold;
}
.calendar-day.other-month {
color: #ccc;
}
.calendar-day.selected {
background-color: #0056b3;
color: white;
font-weight: bold;
}
/* Индикатор для дней с заметками */
.calendar-day.has-notes::after {
content: "";
position: absolute;
bottom: 2px;
left: 50%;
transform: translateX(-50%);
width: 4px;
height: 4px;
background-color: #28a745;
border-radius: 50%;
}
/* Индикатор для выбранного дня с заметками */
.calendar-day.selected.has-notes::after {
background-color: #fff;
}
/* Индикатор для сегодняшнего дня с заметками */
.calendar-day.today.has-notes::after {
background-color: #fff;
}