feat: добавлена админ-панель и главная страница с навигацией по курсам

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

Админ-панель:
- Создана защищенная паролем админ-панель по пути /admin
- Реализована система авторизации с сессионными куками
- Добавлен CRUD для управления группами (создание, редактирование, удаление)
- Добавлено поле "курс" (1-5) для каждой группы с возможностью редактирования

Структура данных:
- Миграция групп из TypeScript файла в JSON формат (groups.json)
- Обновлена структура данных: добавлено поле course
- Реализована автоматическая миграция старых данных в новый формат
- Создан groups-loader для работы с JSON файлом

Главная страница:
- Создана главная страница с аккордеоном по курсам (1-5)
- Группы сгруппированы по курсам для удобной навигации
- Добавлены кнопки: "Добавить группу", переключение темы и GitHub
- Убрана верхняя навигация с главной страницы

Навигация:
- Добавлена кнопка "К группам" в начало навигации на страницах расписания
- На мобильных устройствах скрыты кнопки групп, оставлена только кнопка возврата
- Улучшена адаптивность навигации

Технические улучшения:
- Исправлена проблема с tailwind-scrollbar-hide (заменен плагин на CSS класс)
- Обновлены все компоненты для работы с новой структурой данных групп
- Добавлена поддержка переменных окружения ADMIN_PASSWORD и ADMIN_SESSION_SECRET
This commit is contained in:
kilyabin
2025-11-23 00:58:58 +04:00
parent 808d577964
commit e5262f8203
20 changed files with 1320 additions and 34 deletions

View File

@@ -5,7 +5,7 @@ import { getSchedule } 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'
import { groups } from '@/shared/data/groups'
import { loadGroups, GroupsData } from '@/shared/data/groups-loader'
import { SITE_URL } from '@/shared/constants/urls'
import crypto from 'crypto'
import React from 'react'
@@ -20,10 +20,11 @@ type PageProps = {
}
parsedAt: Date
cacheAvailableFor: string[]
groups: GroupsData
}
export default function HomePage(props: NextSerialized<PageProps>) {
const { schedule, group, cacheAvailableFor, parsedAt } = nextDeserialized<PageProps>(props)
const { schedule, group, cacheAvailableFor, parsedAt, groups } = nextDeserialized<PageProps>(props)
React.useEffect(() => {
if (typeof window === 'undefined') return
@@ -47,7 +48,7 @@ export default function HomePage(props: NextSerialized<PageProps>) {
<meta property="og:title" content={`Группа ${group.name} — Расписание занятий в Колледже Связи`} />
<meta property="og:description" content={`Расписание занятий группы ${group.name} на неделю в Колледже Связи ПГУТИ. Расписание пар, материалы для подготовки и изменения в расписании.`} />
</Head>
<NavBar cacheAvailableFor={cacheAvailableFor} />
<NavBar cacheAvailableFor={cacheAvailableFor} groups={groups} />
<LastUpdateAt date={parsedAt} />
<Schedule days={schedule} />
</>
@@ -57,6 +58,7 @@ export default function HomePage(props: NextSerialized<PageProps>) {
const cachedSchedules = new Map<string, { lastFetched: Date, results: Day[] }>()
const maxCacheDurationInMS = 1000 * 60 * 60
export async function getServerSideProps(context: GetServerSidePropsContext<{ group: string }>): Promise<GetServerSidePropsResult<NextSerialized<PageProps>>> {
const groups = loadGroups()
const group = context.params?.group
if (group && Object.hasOwn(groups, group) && group in groups) {
let schedule
@@ -68,7 +70,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext<{ gr
parsedAt = cachedSchedule.lastFetched
} else {
try {
schedule = await getSchedule(...groups[group])
const groupInfo = groups[group]
schedule = await getSchedule(groupInfo.parseId, groupInfo.name)
parsedAt = new Date()
cachedSchedules.set(group, { lastFetched: new Date(), results: schedule })
} catch(e) {
@@ -109,9 +112,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext<{ gr
parsedAt: parsedAt,
group: {
id: group,
name: groups[group][1]
name: groups[group].name
},
cacheAvailableFor
cacheAvailableFor,
groups
})
}
} else {