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

@@ -13,19 +13,22 @@ import {
DialogTitle,
} from '@/shadcn/ui/dialog'
import { loadGroups, GroupsData } from '@/shared/data/groups-loader'
import { loadSettings, AppSettings } from '@/shared/data/settings-loader'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shadcn/ui/select'
import Head from 'next/head'
type AdminPageProps = {
groups: GroupsData
settings: AppSettings
}
export default function AdminPage({ groups: initialGroups }: AdminPageProps) {
export default function AdminPage({ groups: initialGroups, settings: initialSettings }: AdminPageProps) {
const [authenticated, setAuthenticated] = React.useState<boolean | null>(null)
const [password, setPassword] = React.useState('')
const [loading, setLoading] = React.useState(false)
const [error, setError] = React.useState<string | null>(null)
const [groups, setGroups] = React.useState<GroupsData>(initialGroups)
const [settings, setSettings] = React.useState<AppSettings>(initialSettings)
const [editingGroup, setEditingGroup] = React.useState<{ id: string; parseId: number; name: string; course: number } | null>(null)
const [showAddDialog, setShowAddDialog] = React.useState(false)
const [showEditDialog, setShowEditDialog] = React.useState(false)
@@ -72,8 +75,9 @@ export default function AdminPage({ groups: initialGroups }: AdminPageProps) {
if (res.ok && data.success) {
setAuthenticated(true)
setPassword('')
// Обновляем список групп после авторизации
// Обновляем список групп и настроек после авторизации
await loadGroupsList()
await loadSettingsList()
} else {
setError(data.error || 'Ошибка авторизации')
}
@@ -96,6 +100,43 @@ export default function AdminPage({ groups: initialGroups }: AdminPageProps) {
}
}
const loadSettingsList = async () => {
try {
const res = await fetch('/api/admin/settings')
const data = await res.json()
if (data.settings) {
setSettings(data.settings)
}
} catch (err) {
console.error('Error loading settings:', err)
}
}
const handleUpdateSettings = async (newSettings: AppSettings) => {
setLoading(true)
setError(null)
try {
const res = await fetch('/api/admin/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newSettings)
})
const data = await res.json()
if (res.ok && data.success) {
setSettings(data.settings)
} else {
setError(data.error || 'Ошибка при обновлении настроек')
}
} catch (err) {
setError('Ошибка соединения с сервером')
} finally {
setLoading(false)
}
}
const handleAddGroup = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
@@ -287,6 +328,35 @@ export default function AdminPage({ groups: initialGroups }: AdminPageProps) {
</div>
)}
<Card>
<CardHeader>
<CardTitle>Настройки</CardTitle>
<CardDescription>Управление настройками приложения</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between p-4 border rounded-lg">
<div>
<div className="font-semibold">Навигация по неделям</div>
<div className="text-sm text-muted-foreground">
Включить или выключить навигацию по неделям в расписании
</div>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={settings.weekNavigationEnabled}
onChange={(e) => handleUpdateSettings({ ...settings, weekNavigationEnabled: e.target.checked })}
disabled={loading}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 dark:bg-gray-700 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600 dark:peer-checked:bg-blue-500"></div>
</label>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<div className="flex justify-between items-center">
@@ -516,9 +586,11 @@ export default function AdminPage({ groups: initialGroups }: AdminPageProps) {
export const getServerSideProps: GetServerSideProps<AdminPageProps> = async () => {
const groups = loadGroups()
const settings = loadSettings()
return {
props: {
groups
groups,
settings
}
}
}