import { useEffect, useRef, useCallback, useState } from 'react' import { Link } from 'react-router-dom' import { Clock, Gamepad2, Plus, Trophy, Target, Loader2, ChevronDown, Timer, Play, Square } from 'lucide-react' import { useTrackingStore } from '../store/tracking' import { useAuthStore } from '../store/auth' import { useMarathonStore } from '../store/marathon' import { GlassCard } from '../components/ui/GlassCard' import { NeonButton } from '../components/ui/NeonButton' function formatTime(ms: number): string { const seconds = Math.floor(ms / 1000) const minutes = Math.floor(seconds / 60) const hours = Math.floor(minutes / 60) if (hours > 0) { const remainingMinutes = minutes % 60 return `${hours}ч ${remainingMinutes}м` } else if (minutes > 0) { return `${minutes}м` } else { return `${seconds}с` } } function formatMinutes(minutes: number): string { const hours = Math.floor(minutes / 60) const mins = minutes % 60 if (hours > 0) { return `${hours}ч ${mins}м` } return `${mins}м` } function getDifficultyColor(difficulty: string): string { switch (difficulty) { case 'easy': return 'text-green-400' case 'medium': return 'text-yellow-400' case 'hard': return 'text-red-400' default: return 'text-gray-400' } } function getDifficultyLabel(difficulty: string): string { switch (difficulty) { case 'easy': return 'Легкий' case 'medium': return 'Средний' case 'hard': return 'Сложный' default: return difficulty } } export function DashboardPage() { const { user } = useAuthStore() const { trackedGames, stats, currentGame, loadTrackedGames, updateStats } = useTrackingStore() const { marathons, selectedMarathonId, currentAssignment, isLoading, loadMarathons, selectMarathon, syncTime } = useMarathonStore() // Monitoring state const [isMonitoring, setIsMonitoring] = useState(false) const [localSessionSeconds, setLocalSessionSeconds] = useState(0) // Refs for time tracking sync const syncIntervalRef = useRef(null) const lastSyncedMinutesRef = useRef(0) const sessionStartRef = useRef(null) // Check if we should track time: any tracked game is running + active assignment exists const isTrackingAssignment = !!(currentGame && currentAssignment && currentAssignment.status === 'active') // Sync time to server const doSyncTime = useCallback(async () => { if (!currentAssignment || !isTrackingAssignment) { return } // Calculate total minutes: previous tracked + current session const sessionDuration = sessionStartRef.current ? Math.floor((Date.now() - sessionStartRef.current) / 60000) : 0 const totalMinutes = currentAssignment.tracked_time_minutes + sessionDuration if (totalMinutes !== lastSyncedMinutesRef.current && totalMinutes > 0) { console.log(`[Sync] Syncing ${totalMinutes} minutes for assignment ${currentAssignment.id}`) await syncTime(totalMinutes) lastSyncedMinutesRef.current = totalMinutes } }, [currentAssignment, isTrackingAssignment, syncTime]) useEffect(() => { loadTrackedGames() loadMarathons() // Load monitoring status window.electronAPI.getMonitoringStatus().then(setIsMonitoring) // Subscribe to tracking updates const unsubscribe = window.electronAPI.onTrackingUpdate((newStats) => { updateStats(newStats) }) // Subscribe to game started event const unsubGameStarted = window.electronAPI.onGameStarted((gameName, _gameId) => { console.log(`[Game] Started: ${gameName}`) sessionStartRef.current = Date.now() setLocalSessionSeconds(0) }) // Subscribe to game stopped event const unsubGameStopped = window.electronAPI.onGameStopped((gameName, _duration) => { console.log(`[Game] Stopped: ${gameName}`) sessionStartRef.current = null setLocalSessionSeconds(0) }) // Get initial stats window.electronAPI.getTrackingStats().then(updateStats) return () => { unsubscribe() unsubGameStarted() unsubGameStopped() } }, [loadTrackedGames, loadMarathons, updateStats]) // Setup sync interval and local timer when tracking useEffect(() => { let localTimerInterval: NodeJS.Timeout | null = null if (isTrackingAssignment) { // Start session if not already started if (!sessionStartRef.current) { sessionStartRef.current = Date.now() } // Sync immediately when game starts doSyncTime() // Setup periodic sync every 60 seconds syncIntervalRef.current = setInterval(() => { doSyncTime() }, 60000) // Update local timer every second for UI localTimerInterval = setInterval(() => { if (sessionStartRef.current) { setLocalSessionSeconds(Math.floor((Date.now() - sessionStartRef.current) / 1000)) } }, 1000) } else { // Do final sync when game stops if (syncIntervalRef.current) { doSyncTime() clearInterval(syncIntervalRef.current) syncIntervalRef.current = null sessionStartRef.current = null } setLocalSessionSeconds(0) } return () => { if (syncIntervalRef.current) { clearInterval(syncIntervalRef.current) syncIntervalRef.current = null } if (localTimerInterval) { clearInterval(localTimerInterval) } } }, [isTrackingAssignment, doSyncTime]) // Toggle monitoring const toggleMonitoring = async () => { if (isMonitoring) { await window.electronAPI.stopMonitoring() setIsMonitoring(false) } else { await window.electronAPI.startMonitoring() setIsMonitoring(true) } } const todayTime = stats?.totalTimeToday || 0 const weekTime = stats?.totalTimeWeek || 0 const selectedMarathon = marathons.find(m => m.id === selectedMarathonId) const renderCurrentChallenge = () => { if (isLoading) { return (
) } if (marathons.length === 0) { return (

Нет активных марафонов. Присоединитесь к марафону на сайте.

) } if (!currentAssignment) { return (

Нет активного задания. Крутите колесо на сайте!

) } const assignment = currentAssignment // Playthrough assignment if (assignment.is_playthrough && assignment.playthrough_info) { // Use localSessionSeconds for live display (updates every second) const sessionSeconds = isTrackingAssignment ? localSessionSeconds : 0 const totalSeconds = (assignment.tracked_time_minutes * 60) + sessionSeconds const totalMinutes = Math.floor(totalSeconds / 60) const trackedHours = totalMinutes / 60 const estimatedPoints = Math.floor(trackedHours * 30) // Format with seconds when actively tracking const formatLiveTime = () => { if (isTrackingAssignment && sessionSeconds > 0) { const hours = Math.floor(totalSeconds / 3600) const mins = Math.floor((totalSeconds % 3600) / 60) const secs = totalSeconds % 60 if (hours > 0) { return `${hours}ч ${mins}м ${secs}с` } return `${mins}м ${secs}с` } return formatMinutes(totalMinutes) } return (

Прохождение: {assignment.game.title}

{isTrackingAssignment && (
Идёт запись )}
{assignment.playthrough_info.description && (

{assignment.playthrough_info.description}

)}
{totalSeconds > 0 || isTrackingAssignment ? ( <> {formatLiveTime()} ~{estimatedPoints} очков ) : ( Базово: {assignment.playthrough_info.points} очков )}
) } // Challenge assignment if (assignment.challenge) { const challenge = assignment.challenge return (

{challenge.title}

{challenge.game.title}

{challenge.description}

[{getDifficultyLabel(challenge.difficulty)}] +{challenge.points} очков {challenge.estimated_time && ( ~{challenge.estimated_time} мин )}
) } return (

Задание загружается...

) } return (
{/* Header */}

Привет, {user?.nickname || 'Игрок'}!

{isMonitoring ? (currentGame ? `Играет: ${currentGame}` : 'Мониторинг активен') : 'Мониторинг выключен'}

{currentGame && isMonitoring && (
{currentGame}
)}
{/* Stats cards */}

Сегодня

{formatTime(todayTime)}

За неделю

{formatTime(weekTime)}

{/* Current challenge */}

Текущий челлендж

{/* Marathon selector */} {marathons.length > 1 && (
)}
{/* Marathon title for single marathon */} {marathons.length === 1 && selectedMarathon && (

{selectedMarathon.title}

)} {renderCurrentChallenge()}
{/* Tracked games */}

Отслеживаемые игры

}> Добавить
{trackedGames.length === 0 ? (

Нет отслеживаемых игр

Добавить игру
) : (
{trackedGames.slice(0, 4).map((game) => (
{currentGame === game.name &&
}

{game.name}

{formatTime(game.totalTime)}

))}
)} {trackedGames.length > 4 && ( Показать все ({trackedGames.length}) )}
) }