From 3345eb2e3f67e566325944538cdd995ea4b149ac Mon Sep 17 00:00:00 2001 From: kilyabin <65072190+kilyabin@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:15:07 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D1=8B?= =?UTF-8?q?=20=D1=80=D0=B0=D1=81=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B8=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлены таймауты (8 сек) для запросов расписания - Реализована навигация по неделям с поддержкой параметра wk - Улучшено кэширование с автоматической очисткой старых записей - Добавлена валидация параметров в getSchedule - Улучшен UI загрузки с анимированными сообщениями и предупреждениями - Оптимизирована обработка ошибок и очистка памяти JSDOM - Обновлены зависимости проекта - Добавлена документация для старых файлов --- old/README.md | 1 + old/old-schedule.txt | 1 + package-lock.json | 7 ++-- package.json | 1 + src/app/agregator/schedule.ts | 13 +++++-- src/pages/[group].tsx | 7 +++- src/pages/api/admin/check-auth.ts | 1 + src/pages/api/admin/logout.ts | 1 + src/pages/index.tsx | 2 +- src/shared/context/loading-context.tsx | 49 ++++++++++++++++++++------ 10 files changed, 65 insertions(+), 18 deletions(-) diff --git a/old/README.md b/old/README.md index f328044..2527393 100644 --- a/old/README.md +++ b/old/README.md @@ -27,3 +27,4 @@ - При необходимости можно удалить эту папку без влияния на работу приложения + diff --git a/old/old-schedule.txt b/old/old-schedule.txt index d8cf0bb..a2c6f96 100644 --- a/old/old-schedule.txt +++ b/old/old-schedule.txt @@ -62,3 +62,4 @@ export async function parseSchedule(groupID: number, groupName: string) { throw new Error('Error while fetching lk.ks.psuti.ru') } } + diff --git a/package-lock.json b/package-lock.json index 580611d..caef81d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "@types/react-dom": "19.2.0", "@typescript-eslint/eslint-plugin": "^6.7.3", "autoprefixer": "10.4.20", + "baseline-browser-mapping": "^2.8.32", "eslint": "8.57.0", "eslint-config-next": "16.0.3", "postcss": "8.4.47", @@ -4335,9 +4336,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.29.tgz", - "integrity": "sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==", + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", + "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 68904d9..b7e5848 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@types/react-dom": "19.2.0", "@typescript-eslint/eslint-plugin": "^6.7.3", "autoprefixer": "10.4.20", + "baseline-browser-mapping": "^2.8.32", "eslint": "8.57.0", "eslint-config-next": "16.0.3", "postcss": "8.4.47", diff --git a/src/app/agregator/schedule.ts b/src/app/agregator/schedule.ts index 5224013..a647076 100644 --- a/src/app/agregator/schedule.ts +++ b/src/app/agregator/schedule.ts @@ -11,6 +11,13 @@ export type ScheduleResult = { availableWeeks?: WeekInfo[] } +export class ScheduleTimeoutError extends Error { + constructor(message: string) { + super(message) + this.name = 'ScheduleTimeoutError' + } +} + export async function getSchedule(groupID: number, groupName: string, wk?: number, parseWeekNavigation: boolean = true): Promise { // Валидация параметров if (!Number.isInteger(groupID) || groupID <= 0) { @@ -23,9 +30,9 @@ export async function getSchedule(groupID: number, groupName: string, wk?: numbe const url = `${PROXY_URL}/?mn=2&obj=${groupID}${wk ? `&wk=${wk}` : ''}` - // Добавляем таймаут 30 секунд для fetch запроса + // Добавляем таймаут 8 секунд для fetch запроса const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), 30000) // 30 секунд + const timeoutId = setTimeout(() => controller.abort(), 8000) // 8 секунд try { const page = await fetch(url, { signal: controller.signal }) @@ -65,7 +72,7 @@ export async function getSchedule(groupID: number, groupName: string, wk?: numbe } catch (error) { clearTimeout(timeoutId) if (error instanceof Error && error.name === 'AbortError') { - throw new Error(`Request timeout while fetching ${PROXY_URL}`) + throw new ScheduleTimeoutError(`Request timeout while fetching ${PROXY_URL}`) } throw error } diff --git a/src/pages/[group].tsx b/src/pages/[group].tsx index bdb7e9d..6813ed5 100644 --- a/src/pages/[group].tsx +++ b/src/pages/[group].tsx @@ -1,7 +1,7 @@ import { Schedule } from '@/widgets/schedule' import { Day } from '@/shared/model/day' import { GetServerSidePropsContext, GetServerSidePropsResult } from 'next' -import { getSchedule, ScheduleResult } from '@/app/agregator/schedule' +import { getSchedule, ScheduleResult, ScheduleTimeoutError } from '@/app/agregator/schedule' import { NextSerialized, nextDeserialized, nextSerialized } from '@/app/utils/date-serializer' import { NavBar } from '@/widgets/navbar' import { LastUpdateAt } from '@/entities/last-update-at' @@ -149,9 +149,14 @@ export async function getServerSideProps(context: GetServerSidePropsContext<{ gr cleanupCache() } } catch(e) { + // При таймауте или любой другой ошибке используем кэш, если он доступен if (cachedSchedule?.lastFetched) { scheduleResult = cachedSchedule.results parsedAt = cachedSchedule.lastFetched + // Логируем использование кэша при таймауте + if (e instanceof ScheduleTimeoutError) { + console.warn(`Schedule fetch timeout for group ${group}, using cached data from ${cachedSchedule.lastFetched.toISOString()}`) + } } else { throw e } diff --git a/src/pages/api/admin/check-auth.ts b/src/pages/api/admin/check-auth.ts index 53f7091..4efd6be 100644 --- a/src/pages/api/admin/check-auth.ts +++ b/src/pages/api/admin/check-auth.ts @@ -23,3 +23,4 @@ export default function handler( + diff --git a/src/pages/api/admin/logout.ts b/src/pages/api/admin/logout.ts index c193189..2d814dd 100644 --- a/src/pages/api/admin/logout.ts +++ b/src/pages/api/admin/logout.ts @@ -23,3 +23,4 @@ export default function handler( + diff --git a/src/pages/index.tsx b/src/pages/index.tsx index f4929fc..ee499ea 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -57,7 +57,7 @@ export default function HomePage({ groups, groupsByCourse }: HomePageProps) { <> Расписание занятий — Колледж Связи ПГУТИ - +
diff --git a/src/shared/context/loading-context.tsx b/src/shared/context/loading-context.tsx index 400415a..8c3eb09 100644 --- a/src/shared/context/loading-context.tsx +++ b/src/shared/context/loading-context.tsx @@ -61,11 +61,15 @@ interface LoadingOverlayProps { export function LoadingOverlay({ isLoading }: LoadingOverlayProps) { const [currentMessage, setCurrentMessage] = React.useState('') const [messageOpacity, setMessageOpacity] = React.useState(0) + const [showError, setShowError] = React.useState(false) + const [errorOpacity, setErrorOpacity] = React.useState(0) React.useEffect(() => { if (!isLoading) { setCurrentMessage('') setMessageOpacity(0) + setShowError(false) + setErrorOpacity(0) return } @@ -79,6 +83,15 @@ export function LoadingOverlay({ isLoading }: LoadingOverlayProps) { setCurrentMessage(getRandomMessage()) setMessageOpacity(1) + // Таймер для показа сообщения об ошибке после 5 секунд + const errorTimeout = setTimeout(() => { + setShowError(true) + // Плавное появление с небольшой задержкой для анимации + setTimeout(() => { + setErrorOpacity(1) + }, 50) + }, 5000) + // Меняем сообщение каждые 2 секунды const interval = setInterval(() => { // Fade out @@ -93,6 +106,7 @@ export function LoadingOverlay({ isLoading }: LoadingOverlayProps) { return () => { clearInterval(interval) + clearTimeout(errorTimeout) } }, [isLoading]) @@ -109,17 +123,32 @@ export function LoadingOverlay({ isLoading }: LoadingOverlayProps) { aria-hidden={!isLoading} > {isLoading && ( -
-
- + <> +
+
+ +
+
+ {currentMessage} +
-
- {currentMessage} -
-
+ {showError && ( +
+

+ ⚠️ Не удается получить актуальное расписание с официального сайта. Возможно, сервер временно недоступен. Будут показаны данные из кэша. Попробуйте обновить страницу позже. +

+
+ )} + )}
)