feat: добавлена навигация по неделям с возможностью отключения через админ-панель

feat: добавлена навигация по неделям с возможностью отключения через админ-панель

Реализована навигация по неделям в расписании с парсингом ссылок из HTML страницы
оригинального сайта. Добавлена возможность управления навигацией через админ-панель
с сохранением настроек в файл.

Основные изменения:

- Парсинг навигации по неделям:
  * Добавлены типы WeekInfo и ParseResult в парсер
  * Реализована функция parseWeekNavigation для извлечения ссылок с параметром wk
  * Парсер ищет ссылки в href, onclick, формах и других атрибутах
  * Автоматическое определение номеров недель из текста ссылок и контекста
  * Вычисление соседних недель на основе найденных данных

- API и функции:
  * Обновлена функция getSchedule для поддержки параметра wk в URL
  * Обновлен getServerSideProps для чтения параметра wk из query string
  * Кэширование расписания с учетом недели (ключ включает group + wk)

- Компоненты:
  * Создан компонент WeekNavigation с кнопками навигации
  * Интегрирована навигация в компонент Schedule
  * Навигация работает через изменение URL параметра wk

- Система настроек:
  * Создан settings-loader для загрузки/сохранения настроек в JSON
  * Добавлен API endpoint /api/admin/settings для управления настройками
  * Добавлен переключатель в админ-панели для включения/выключения навигации
  * Настройки сохраняются в src/shared/data/settings.json и переживают перезапуски

- Файлы:
  * src/app/parser/schedule.ts - парсинг навигации по неделям
  * src/app/agregator/schedule.ts - поддержка параметра wk
  * src/pages/[group].tsx - чтение wk из query и передача настроек
  * src/widgets/schedule/week-navigation.tsx - компонент навигации
  * src/widgets/schedule/index.tsx - интеграция навигации
  * src/pages/admin.tsx - управление настройками
  * src/shared/data/settings-loader.ts - загрузка/сохранение настроек
  * src/pages/api/admin/settings.ts - API для настроек
  * src/shared/data/settings.json - файл с настройками
This commit is contained in:
kilyabin
2025-11-23 02:24:27 +04:00
parent cf0137a8d6
commit 2893a9fd18
10 changed files with 681 additions and 27 deletions

View File

@@ -1,14 +1,27 @@
import type { Day as DayType } from '@/shared/model/day'
import { Day } from '@/widgets/schedule/day'
import { WeekNavigation } from '@/widgets/schedule/week-navigation'
import { useRouter } from 'next/router'
import React from 'react'
import { getDayOfWeek } from '@/shared/utils'
import { WeekInfo } from '@/app/parser/schedule'
export function Schedule({ days }: {
export function Schedule({
days,
currentWk,
availableWeeks,
weekNavigationEnabled = true
}: {
days: DayType[]
currentWk: number | null | undefined
availableWeeks: WeekInfo[] | null | undefined
weekNavigationEnabled?: boolean
}) {
const group = useRouter().query['group']
const hasScrolledRef = React.useRef(false)
// Определяем текущий номер недели из дней
const currentWeekNumber = days.length > 0 ? days[0]?.weekNumber : undefined
React.useEffect(() => {
if (hasScrolledRef.current || typeof window === 'undefined') return
@@ -49,6 +62,13 @@ export function Schedule({ days }: {
return (
<div className="flex flex-col p-4 md:p-8 lg:p-16 gap-6 md:gap-12 lg:gap-14">
{weekNavigationEnabled && (
<WeekNavigation
currentWk={currentWk}
availableWeeks={availableWeeks}
currentWeekNumber={currentWeekNumber}
/>
)}
{days.map((day, i) => (
<div
key={`${group}_day${i}`}

View File

@@ -0,0 +1,73 @@
import React from 'react'
import { useRouter } from 'next/router'
import { Button } from '@/shadcn/ui/button'
import { ChevronLeft, ChevronRight } from 'lucide-react'
import { WeekInfo } from '@/app/parser/schedule'
export function WeekNavigation({
currentWk,
availableWeeks,
currentWeekNumber
}: {
currentWk: number | null | undefined
availableWeeks: WeekInfo[] | null | undefined
currentWeekNumber?: number
}) {
const router = useRouter()
const group = router.query.group as string
if (!availableWeeks || availableWeeks.length === 0) {
return null
}
// Находим текущую неделю в списке
const currentIndex = currentWk
? availableWeeks.findIndex(w => w.wk === currentWk)
: currentWeekNumber
? availableWeeks.findIndex(w => w.weekNumber === currentWeekNumber)
: -1
const currentWeek = currentIndex >= 0 ? availableWeeks[currentIndex] : availableWeeks[0]
const prevWeek = currentIndex > 0 ? availableWeeks[currentIndex - 1] : null
const nextWeek = currentIndex >= 0 && currentIndex < availableWeeks.length - 1
? availableWeeks[currentIndex + 1]
: null
const navigateToWeek = (wk: number) => {
router.push({
pathname: `/${group}`,
query: { wk }
}, undefined, { scroll: false })
}
return (
<div className="flex items-center justify-center gap-4 p-4 bg-background/50 rounded-lg border">
<Button
variant="outline"
size="icon"
onClick={() => prevWeek && navigateToWeek(prevWeek.wk)}
disabled={!prevWeek}
aria-label="Предыдущая неделя"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<div className="text-center min-w-[120px]">
<div className="text-sm font-medium">
{currentWeek ? `Неделя ${currentWeek.weekNumber}` : 'Неделя'}
</div>
</div>
<Button
variant="outline"
size="icon"
onClick={() => nextWeek && navigateToWeek(nextWeek.wk)}
disabled={!nextWeek}
aria-label="Следующая неделя"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
)
}