From 561bf35f13fc3040278329ee7f21cf81d41ecd19 Mon Sep 17 00:00:00 2001 From: Fovway Date: Sun, 2 Nov 2025 14:21:11 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BE=D1=87=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BE=D0=B2=D0=B5=D1=80=D0=BB=D0=B5=D1=8F=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8C=D1=81=D0=BA=D0=BE=D0=B3=D0=BE=20=D0=BE?= =?UTF-8?q?=D0=BF=D1=8B=D1=82=D0=B0=20=D0=BF=D1=80=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B5=20=D0=B0=D1=83=D1=82=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B2=20ProtectedRoute.=20=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B2=20NotesList,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=B1=D0=B5=D0=B6=D0=B0=D1=82=D1=8C=20=D0=B4=D1=83=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=BE=D0=B2=20=D0=B8=20=D1=83?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D1=88=D0=B8=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D0=B7=D0=B2=D0=BE=D0=B4=D0=B8=D1=82=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C.=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BB=D0=B5=D1=8F=20=D0=B8=20=D1=83=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=8B=20=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=BE=D1=82=D1=81=D1=82=D1=83=D0=BF=D1=8B=20=D0=B2=20CSS.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProtectedRoute.tsx | 3 +- src/components/common/LoadingOverlay.tsx | 11 ++++ src/components/notes/NotesList.tsx | 66 ++++++++---------------- src/styles/style.css | 52 ++++++++++++++++--- 4 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 src/components/common/LoadingOverlay.tsx diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx index 0c1bf5c..e9f4bb0 100644 --- a/src/components/ProtectedRoute.tsx +++ b/src/components/ProtectedRoute.tsx @@ -3,6 +3,7 @@ import { Navigate } from "react-router-dom"; import { useAppSelector, useAppDispatch } from "../store/hooks"; import { setAuth, clearAuth } from "../store/slices/authSlice"; import { authApi } from "../api/authApi"; +import { LoadingOverlay } from "./common/LoadingOverlay"; export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children, @@ -40,7 +41,7 @@ export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ }, [dispatch, isAuthenticated]); if (isChecking) { - return
Загрузка...
; + return ; } return isAuthenticated ? <>{children} : ; diff --git a/src/components/common/LoadingOverlay.tsx b/src/components/common/LoadingOverlay.tsx new file mode 100644 index 0000000..4eec3be --- /dev/null +++ b/src/components/common/LoadingOverlay.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +export const LoadingOverlay: React.FC = () => { + return ( +
+
+
Загрузка...
+
+
+ ); +}; diff --git a/src/components/notes/NotesList.tsx b/src/components/notes/NotesList.tsx index 30b3311..6e92156 100644 --- a/src/components/notes/NotesList.tsx +++ b/src/components/notes/NotesList.tsx @@ -18,70 +18,48 @@ export const NotesList = forwardRef((props, ref) => { const dispatch = useAppDispatch(); const { showNotification } = useNotification(); - useEffect(() => { - loadNotes(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchQuery, selectedDate, selectedTag]); - + // Функция для загрузки данных (используется для reloadNotes) const loadNotes = async () => { try { - let data; + // Всегда загружаем все заметки для тегов и календаря + const allData = await notesApi.getAll(); + let filteredAllData = allData; + if (userId) { + filteredAllData = allData.filter((note) => note.user_id === userId); + } + dispatch(setAllNotes(filteredAllData)); + + // Для списка заметок: если есть фильтры - делаем поисковый запрос, иначе используем все заметки + let notesData; if (searchQuery || selectedDate || selectedTag) { - data = await notesApi.search({ + notesData = await notesApi.search({ q: searchQuery || undefined, date: selectedDate || undefined, tag: selectedTag || undefined, }); - // Дополнительная проверка на клиенте - фильтруем по user_id на случай проблем на сервере - if (userId) { - data = data.filter((note) => note.user_id === userId); - } - dispatch(setNotes(data)); - // Обновляем также все заметки для тегов и календаря - const allData = await notesApi.getAll(); - if (userId) { - const filteredAllData = allData.filter( - (note) => note.user_id === userId - ); - dispatch(setAllNotes(filteredAllData)); - } else { - dispatch(setAllNotes(allData)); - } - } else { - data = await notesApi.getAll(); // Дополнительная проверка на клиенте if (userId) { - data = data.filter((note) => note.user_id === userId); + notesData = notesData.filter((note) => note.user_id === userId); } - dispatch(setNotes(data)); - dispatch(setAllNotes(data)); // Сохраняем все заметки для тегов и календаря + } else { + // Если нет фильтров, используем все заметки + notesData = filteredAllData; } + + dispatch(setNotes(notesData)); } catch (error) { console.error("Ошибка загрузки заметок:", error); showNotification("Ошибка загрузки заметок", "error"); } }; - // Загружаем все заметки при монтировании компонента для тегов и календаря + // Объединенная загрузка данных при изменении зависимостей useEffect(() => { - const loadAllNotes = async () => { - try { - const data = await notesApi.getAll(); - // Дополнительная проверка на клиенте - if (userId) { - const filteredData = data.filter((note) => note.user_id === userId); - dispatch(setAllNotes(filteredData)); - } else { - dispatch(setAllNotes(data)); - } - } catch (error) { - console.error("Ошибка загрузки всех заметок:", error); - } - }; if (userId) { - loadAllNotes(); + loadNotes(); } - }, [dispatch, userId]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userId, searchQuery, selectedDate, selectedTag]); useImperativeHandle(ref, () => ({ reloadNotes: loadNotes, diff --git a/src/styles/style.css b/src/styles/style.css index fd46132..7bee4a8 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -560,7 +560,6 @@ header { transition: background-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease; } - /* Убираем margin-top у заметок, так как gap уже обеспечивает отступы */ .notes-container .container { margin-top: 0; @@ -2506,17 +2505,17 @@ textarea:focus { padding: 10px; box-sizing: border-box; } - + /* Уменьшаем отступ сверху для первого блока "Мои заметки" на мобильных */ .center > .container:first-child { margin-top: 3px; } - + /* Убираем margin-top у заметок на мобильных, так как gap уже обеспечивает отступы */ .notes-container .container { margin-top: 0; } - + /* Добавляем отступ сверху для контейнера заметок на мобильных */ .notes-container { margin-top: 5px; @@ -4551,17 +4550,17 @@ textarea:focus { max-width: 100%; margin-top: 60px; /* Отступ для кнопки меню */ } - + /* Уменьшаем отступ сверху для первого блока "Мои заметки" на мобильных */ .center > .container:first-child { margin-top: 3px; } - + /* Убираем margin-top у заметок на мобильных, так как gap уже обеспечивает отступы */ .notes-container .container { margin-top: 0; } - + /* Заметки */ .notes-container { padding-bottom: 80px; @@ -4657,5 +4656,42 @@ textarea:focus { .user-info > * { font-size: 12px; } - +} + +/* Стили для загрузочного оверлея */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(2px); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.loading-content { + background-color: var(--bg-color); + padding: 20px 30px; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + display: flex; + align-items: center; + justify-content: center; + min-width: 150px; +} + +.loading-text { + color: var(--text-color); + font-size: 16px; + font-weight: 500; + text-align: center; +} + +/* Темная тема для загрузочного оверлея */ +[data-theme="dark"] .loading-content { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.6); }