import { useState, useEffect, useCallback, useRef } from 'react' import CurrentWeek from './components/CurrentWeek' import FullStatistics from './components/FullStatistics' import ProjectPriorityManager from './components/ProjectPriorityManager' import WordList from './components/WordList' import AddWords from './components/AddWords' import TestConfigSelection from './components/TestConfigSelection' import AddConfig from './components/AddConfig' import TestWords from './components/TestWords' import Integrations from './components/Integrations' // API endpoints (используем относительные пути, проксирование настроено в nginx/vite) const CURRENT_WEEK_API_URL = '/playlife-feed' const FULL_STATISTICS_API_URL = '/d2dc349a-0d13-49b2-a8f0-1ab094bfba9b' function App() { const [activeTab, setActiveTab] = useState('current') const [selectedProject, setSelectedProject] = useState(null) const [loadedTabs, setLoadedTabs] = useState({ current: false, priorities: false, full: false, words: false, 'add-words': false, 'test-config': false, 'add-config': false, test: false, integrations: false, }) // Отслеживаем, какие табы уже были загружены (для предотвращения повторных загрузок) const [tabsInitialized, setTabsInitialized] = useState({ current: false, priorities: false, full: false, words: false, 'add-words': false, 'test-config': false, 'add-config': false, test: false, integrations: false, }) // Параметры для навигации между вкладками const [tabParams, setTabParams] = useState({}) // Кеширование данных const [currentWeekData, setCurrentWeekData] = useState(null) const [fullStatisticsData, setFullStatisticsData] = useState(null) // Состояния загрузки для каждого таба (показываются только при первой загрузке) const [currentWeekLoading, setCurrentWeekLoading] = useState(false) const [fullStatisticsLoading, setFullStatisticsLoading] = useState(false) const [prioritiesLoading, setPrioritiesLoading] = useState(false) // Состояния фоновой загрузки (не показываются визуально) const [currentWeekBackgroundLoading, setCurrentWeekBackgroundLoading] = useState(false) const [fullStatisticsBackgroundLoading, setFullStatisticsBackgroundLoading] = useState(false) const [prioritiesBackgroundLoading, setPrioritiesBackgroundLoading] = useState(false) // Ошибки const [currentWeekError, setCurrentWeekError] = useState(null) const [fullStatisticsError, setFullStatisticsError] = useState(null) const [prioritiesError, setPrioritiesError] = useState(null) // Состояние для кнопки Refresh (если она есть) const [isRefreshing, setIsRefreshing] = useState(false) const [prioritiesRefreshTrigger, setPrioritiesRefreshTrigger] = useState(0) const [testConfigRefreshTrigger, setTestConfigRefreshTrigger] = useState(0) const [wordsRefreshTrigger, setWordsRefreshTrigger] = useState(0) // Восстанавливаем последний выбранный таб после перезагрузки const [isInitialized, setIsInitialized] = useState(false) useEffect(() => { if (isInitialized) return try { const savedTab = window.localStorage?.getItem('activeTab') const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'test-config', 'add-config', 'test', 'integrations'] if (savedTab && validTabs.includes(savedTab)) { setActiveTab(savedTab) setLoadedTabs(prev => ({ ...prev, [savedTab]: true })) setIsInitialized(true) } else { setIsInitialized(true) } } catch (err) { console.warn('Не удалось прочитать активный таб из localStorage', err) setIsInitialized(true) } }, [isInitialized]) const markTabAsLoaded = useCallback((tab) => { setLoadedTabs(prev => (prev[tab] ? prev : { ...prev, [tab]: true })) }, []) const fetchCurrentWeekData = useCallback(async (isBackground = false) => { try { if (isBackground) { setCurrentWeekBackgroundLoading(true) } else { setCurrentWeekLoading(true) } setCurrentWeekError(null) console.log('Fetching current week data from:', CURRENT_WEEK_API_URL) const response = await fetch(CURRENT_WEEK_API_URL) if (!response.ok) { throw new Error('Ошибка загрузки данных') } const jsonData = await response.json() // Обрабатываем ответ: приходит массив с одним объектом [{total: ..., projects: [...]}] let projects = [] let total = null if (Array.isArray(jsonData) && jsonData.length > 0) { // Если ответ - массив, проверяем первый элемент const firstItem = jsonData[0] if (firstItem && typeof firstItem === 'object') { // Если первый элемент - объект с полями total и projects if (firstItem.projects && Array.isArray(firstItem.projects)) { projects = firstItem.projects total = firstItem.total !== undefined ? firstItem.total : null } else { // Если это просто массив проектов projects = jsonData } } else { // Если это массив проектов напрямую projects = jsonData } } else if (jsonData && typeof jsonData === 'object' && !Array.isArray(jsonData)) { // Если ответ - объект напрямую projects = jsonData.projects || jsonData.data || [] total = jsonData.total !== undefined ? jsonData.total : null } setCurrentWeekData({ projects: Array.isArray(projects) ? projects : [], total: total }) } catch (err) { setCurrentWeekError(err.message) console.error('Ошибка загрузки данных текущей недели:', err) } finally { if (isBackground) { setCurrentWeekBackgroundLoading(false) } else { setCurrentWeekLoading(false) } } }, []) const fetchFullStatisticsData = useCallback(async (isBackground = false) => { try { if (isBackground) { setFullStatisticsBackgroundLoading(true) } else { setFullStatisticsLoading(true) } setFullStatisticsError(null) const response = await fetch(FULL_STATISTICS_API_URL) if (!response.ok) { throw new Error('Ошибка загрузки данных') } const jsonData = await response.json() setFullStatisticsData(jsonData) } catch (err) { setFullStatisticsError(err.message) console.error('Ошибка загрузки данных полной статистики:', err) } finally { if (isBackground) { setFullStatisticsBackgroundLoading(false) } else { setFullStatisticsLoading(false) } } }, []) // Используем ref для отслеживания инициализации табов (чтобы избежать лишних пересозданий функции) const tabsInitializedRef = useRef({ current: false, priorities: false, full: false, words: false, 'add-words': false, 'test-config': false, 'add-config': false, test: false, integrations: false, }) // Используем ref для отслеживания кеша (чтобы не зависеть от состояния в useCallback) const cacheRef = useRef({ current: null, full: null, }) // Обновляем ref при изменении данных useEffect(() => { cacheRef.current.current = currentWeekData }, [currentWeekData]) useEffect(() => { cacheRef.current.full = fullStatisticsData }, [fullStatisticsData]) // Функция для загрузки данных таба const loadTabData = useCallback((tab, isBackground = false) => { if (tab === 'current') { const hasCache = cacheRef.current.current !== null const isInitialized = tabsInitializedRef.current.current if (!isInitialized) { // Первая загрузка таба - загружаем с индикатором fetchCurrentWeekData(false) tabsInitializedRef.current.current = true setTabsInitialized(prev => ({ ...prev, current: true })) } else if (hasCache && isBackground) { // Возврат на таб с кешем - фоновая загрузка fetchCurrentWeekData(true) } // Если нет кеша и это не первая загрузка - ничего не делаем (данные уже загружаются) } else if (tab === 'full') { const hasCache = cacheRef.current.full !== null const isInitialized = tabsInitializedRef.current.full if (!isInitialized) { // Первая загрузка таба - загружаем с индикатором fetchFullStatisticsData(false) tabsInitializedRef.current.full = true setTabsInitialized(prev => ({ ...prev, full: true })) } else if (hasCache && isBackground) { // Возврат на таб с кешем - фоновая загрузка fetchFullStatisticsData(true) } } else if (tab === 'priorities') { const isInitialized = tabsInitializedRef.current.priorities if (!isInitialized) { // Первая загрузка таба setPrioritiesRefreshTrigger(prev => prev + 1) tabsInitializedRef.current.priorities = true setTabsInitialized(prev => ({ ...prev, priorities: true })) } else if (isBackground) { // Возврат на таб - фоновая загрузка setPrioritiesRefreshTrigger(prev => prev + 1) } } else if (tab === 'test-config') { const isInitialized = tabsInitializedRef.current['test-config'] if (!isInitialized) { // Первая загрузка таба setTestConfigRefreshTrigger(prev => prev + 1) tabsInitializedRef.current['test-config'] = true setTabsInitialized(prev => ({ ...prev, 'test-config': true })) } else if (isBackground) { // Возврат на таб - фоновая загрузка setTestConfigRefreshTrigger(prev => prev + 1) } } }, [fetchCurrentWeekData, fetchFullStatisticsData]) // Функция для обновления всех данных (для кнопки Refresh, если она есть) const refreshAllData = useCallback(async () => { setIsRefreshing(true) setPrioritiesError(null) setCurrentWeekError(null) setFullStatisticsError(null) // Триггерим обновление приоритетов setPrioritiesRefreshTrigger(prev => prev + 1) // Загружаем все данные параллельно (не фоново) await Promise.all([ fetchCurrentWeekData(false), fetchFullStatisticsData(false), ]) setIsRefreshing(false) }, [fetchCurrentWeekData, fetchFullStatisticsData]) // Обновляем данные при возвращении экрана в фокус (фоново) useEffect(() => { const handleFocus = () => { if (document.visibilityState === 'visible') { // Загружаем данные активного таба фоново loadTabData(activeTab, true) } } window.addEventListener('focus', handleFocus) document.addEventListener('visibilitychange', handleFocus) return () => { window.removeEventListener('focus', handleFocus) document.removeEventListener('visibilitychange', handleFocus) } }, [activeTab, loadTabData]) const handleProjectClick = (projectName) => { setSelectedProject(projectName) markTabAsLoaded('full') setActiveTab('full') } const handleTabChange = (tab, params = {}) => { if (tab === 'full' && activeTab === 'full') { // При повторном клике на "Полная статистика" сбрасываем выбранный проект setSelectedProject(null) } else if (tab !== activeTab) { markTabAsLoaded(tab) // Сбрасываем tabParams при переходе с add-config на другой таб if (activeTab === 'add-config' && tab !== 'add-config') { setTabParams({}) } else { setTabParams(params) } setActiveTab(tab) if (tab === 'current') { setSelectedProject(null) } // Обновляем список слов при возврате из экрана добавления слов if (activeTab === 'add-words' && tab === 'words') { setWordsRefreshTrigger(prev => prev + 1) } // Загрузка данных произойдет в useEffect при изменении activeTab } } // Обработчик навигации для компонентов const handleNavigate = (tab, params = {}) => { handleTabChange(tab, params) } // Загружаем данные при открытии таба (когда таб становится активным) const prevActiveTabRef = useRef(null) const lastLoadedTabRef = useRef(null) // Отслеживаем последний загруженный таб, чтобы избежать двойной загрузки useEffect(() => { if (!activeTab || !loadedTabs[activeTab]) return const isFirstLoad = !tabsInitializedRef.current[activeTab] const isReturningToTab = prevActiveTabRef.current !== null && prevActiveTabRef.current !== activeTab // Проверяем, не загружали ли мы уже этот таб в этом рендере const tabKey = `${activeTab}-${isFirstLoad ? 'first' : 'return'}` if (lastLoadedTabRef.current === tabKey) { return // Уже загружали } if (isFirstLoad) { // Первая загрузка таба lastLoadedTabRef.current = tabKey loadTabData(activeTab, false) } else if (isReturningToTab) { // Возврат на таб - фоновая загрузка lastLoadedTabRef.current = tabKey loadTabData(activeTab, true) } prevActiveTabRef.current = activeTab }, [activeTab, loadedTabs, loadTabData]) // Определяем общее состояние загрузки и ошибок для кнопки Refresh const isAnyLoading = currentWeekLoading || fullStatisticsLoading || prioritiesLoading || isRefreshing const hasAnyError = currentWeekError || fullStatisticsError || prioritiesError // Сохраняем выбранный таб, чтобы восстановить его после перезагрузки useEffect(() => { try { window.localStorage?.setItem('activeTab', activeTab) } catch (err) { console.warn('Не удалось сохранить активный таб в localStorage', err) } }, [activeTab]) // Определяем, нужно ли скрывать нижнюю панель (для fullscreen экранов) const isFullscreenTab = activeTab === 'test' || activeTab === 'add-words' || activeTab === 'add-config' return (
{loadedTabs.current && (
)} {loadedTabs.priorities && (
)} {loadedTabs.full && (
setSelectedProject(null)} data={fullStatisticsData} loading={fullStatisticsLoading} error={fullStatisticsError} onRetry={fetchFullStatisticsData} currentWeekData={currentWeekData} onNavigate={handleNavigate} />
)} {loadedTabs.words && (
)} {loadedTabs['add-words'] && (
)} {loadedTabs['test-config'] && (
)} {loadedTabs['add-config'] && (
)} {loadedTabs.test && (
)} {loadedTabs.integrations && (
)}
{!isFullscreenTab && (
)}
) } export default App