import { useState, useEffect, useCallback } from 'react' import { adminApi } from '@/api' import type { AdminMarathon } from '@/types' import { useToast } from '@/store/toast' import { useConfirm } from '@/store/confirm' import { NeonButton } from '@/components/ui' import { Search, Trash2, StopCircle, ChevronLeft, ChevronRight, Trophy, Clock, CheckCircle, Loader2 } from 'lucide-react' const STATUS_CONFIG: Record = { preparing: { label: 'Подготовка', icon: Loader2, className: 'bg-amber-500/20 text-amber-400 border border-amber-500/30' }, active: { label: 'Активный', icon: Clock, className: 'bg-green-500/20 text-green-400 border border-green-500/30' }, finished: { label: 'Завершён', icon: CheckCircle, className: 'bg-dark-600/50 text-gray-400 border border-dark-500' }, } function formatDate(dateStr: string | null) { if (!dateStr) return '—' return new Date(dateStr).toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', }) } export function AdminMarathonsPage() { const [marathons, setMarathons] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState('') const [page, setPage] = useState(0) const toast = useToast() const confirm = useConfirm() const LIMIT = 20 const loadMarathons = useCallback(async () => { setLoading(true) try { const data = await adminApi.listMarathons(page * LIMIT, LIMIT, search || undefined) setMarathons(data) } catch (err) { console.error('Failed to load marathons:', err) toast.error('Ошибка загрузки марафонов') } finally { setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [page, search]) useEffect(() => { loadMarathons() }, [loadMarathons]) const handleSearch = (e: React.FormEvent) => { e.preventDefault() setPage(0) loadMarathons() } const handleDelete = async (marathon: AdminMarathon) => { const confirmed = await confirm({ title: 'Удалить марафон', message: `Вы уверены, что хотите удалить марафон "${marathon.title}"? Это действие необратимо.`, confirmText: 'Удалить', variant: 'danger', }) if (!confirmed) return try { await adminApi.deleteMarathon(marathon.id) setMarathons(marathons.filter(m => m.id !== marathon.id)) toast.success('Марафон удалён') } catch (err) { console.error('Failed to delete marathon:', err) toast.error('Ошибка удаления') } } const handleForceFinish = async (marathon: AdminMarathon) => { const confirmed = await confirm({ title: 'Завершить марафон', message: `Принудительно завершить марафон "${marathon.title}"? Участники получат уведомление.`, confirmText: 'Завершить', variant: 'warning', }) if (!confirmed) return try { await adminApi.forceFinishMarathon(marathon.id) setMarathons(marathons.map(m => m.id === marathon.id ? { ...m, status: 'finished' } : m )) toast.success('Марафон завершён') } catch (err) { console.error('Failed to finish marathon:', err) toast.error('Ошибка завершения') } } return (
{/* Header */}

Марафоны

{/* Search */}
setSearch(e.target.value)} className="w-full bg-dark-700/50 border border-dark-600 rounded-xl pl-10 pr-4 py-2.5 text-white placeholder:text-gray-500 focus:outline-none focus:border-accent-500/50 focus:ring-1 focus:ring-accent-500/20 transition-all" />
Найти
{/* Marathons Table */}
{loading ? ( ) : marathons.length === 0 ? ( ) : ( marathons.map((marathon) => { const statusConfig = STATUS_CONFIG[marathon.status] || STATUS_CONFIG.finished const StatusIcon = statusConfig.icon return ( ) }) )}
ID Название Создатель Статус Участники Игры Даты Действия
Марафоны не найдены
{marathon.id} {marathon.title} {marathon.creator.nickname} {statusConfig.label} {marathon.participants_count} {marathon.games_count} {formatDate(marathon.start_date)} {formatDate(marathon.end_date)}
{marathon.status !== 'finished' && ( )}
{/* Pagination */}
Страница {page + 1}
) }