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:
@@ -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}`}
|
||||
|
||||
73
src/widgets/schedule/week-navigation.tsx
Normal file
73
src/widgets/schedule/week-navigation.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user