import React, { useState, useEffect, useRef } from 'react' import { useAuth } from './auth/AuthContext' import LoadingError from './LoadingError' import './Wishlist.css' const API_URL = '/api/wishlist' const CACHE_KEY = 'wishlist_cache' const CACHE_COMPLETED_KEY = 'wishlist_completed_cache' function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) { const { authFetch } = useAuth() const [items, setItems] = useState([]) const [completed, setCompleted] = useState([]) const [completedCount, setCompletedCount] = useState(0) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [completedExpanded, setCompletedExpanded] = useState(false) const [completedLoading, setCompletedLoading] = useState(false) const [selectedItem, setSelectedItem] = useState(null) const fetchingRef = useRef(false) const fetchingCompletedRef = useRef(false) const initialFetchDoneRef = useRef(false) const prevIsActiveRef = useRef(isActive) // Проверка наличия кэша const hasCache = () => { try { return localStorage.getItem(CACHE_KEY) !== null } catch (err) { return false } } // Загрузка основных данных из кэша const loadFromCache = () => { try { const cached = localStorage.getItem(CACHE_KEY) if (cached) { const data = JSON.parse(cached) setItems(data.items || []) setCompletedCount(data.completedCount || 0) return true } } catch (err) { console.error('Error loading from cache:', err) } return false } // Загрузка завершённых из кэша const loadCompletedFromCache = () => { try { const cached = localStorage.getItem(CACHE_COMPLETED_KEY) if (cached) { const data = JSON.parse(cached) setCompleted(data || []) return true } } catch (err) { console.error('Error loading completed from cache:', err) } return false } // Сохранение основных данных в кэш const saveToCache = (itemsData, count) => { try { localStorage.setItem(CACHE_KEY, JSON.stringify({ items: itemsData, completedCount: count, timestamp: Date.now() })) } catch (err) { console.error('Error saving to cache:', err) } } // Сохранение завершённых в кэш const saveCompletedToCache = (completedData) => { try { localStorage.setItem(CACHE_COMPLETED_KEY, JSON.stringify(completedData)) } catch (err) { console.error('Error saving completed to cache:', err) } } // Загрузка основного списка const fetchWishlist = async () => { if (fetchingRef.current) return fetchingRef.current = true try { const hasDataInState = items.length > 0 || completedCount > 0 const cacheExists = hasCache() if (!hasDataInState && !cacheExists) { setLoading(true) } const response = await authFetch(API_URL) if (!response.ok) { throw new Error('Ошибка при загрузке желаний') } const data = await response.json() const allItems = [...(data.unlocked || []), ...(data.locked || [])] const count = data.completed_count || 0 setItems(allItems) setCompletedCount(count) saveToCache(allItems, count) setError('') } catch (err) { setError(err.message) if (!hasCache()) { setItems([]) setCompletedCount(0) } } finally { setLoading(false) fetchingRef.current = false } } // Загрузка завершённых const fetchCompleted = async () => { if (fetchingCompletedRef.current) return fetchingCompletedRef.current = true try { setCompletedLoading(true) const response = await authFetch(`${API_URL}/completed`) if (!response.ok) { throw new Error('Ошибка при загрузке завершённых желаний') } const data = await response.json() const completedData = Array.isArray(data) ? data : [] setCompleted(completedData) saveCompletedToCache(completedData) } catch (err) { console.error('Error fetching completed items:', err) setCompleted([]) } finally { setCompletedLoading(false) fetchingCompletedRef.current = false } } // Первая инициализация useEffect(() => { if (!initialFetchDoneRef.current) { initialFetchDoneRef.current = true // Загружаем из кэша const cacheLoaded = loadFromCache() if (cacheLoaded) { setLoading(false) } // Загружаем свежие данные fetchWishlist() // Если список завершённых раскрыт - загружаем их тоже if (completedExpanded) { loadCompletedFromCache() fetchCompleted() } } }, []) // Обработка активации/деактивации таба useEffect(() => { const wasActive = prevIsActiveRef.current prevIsActiveRef.current = isActive // Пропускаем первую инициализацию (она обрабатывается отдельно) if (!initialFetchDoneRef.current) return // Когда таб становится видимым if (isActive && !wasActive) { // Показываем кэш, если есть данные const hasDataInState = items.length > 0 || completedCount > 0 if (!hasDataInState) { const cacheLoaded = loadFromCache() if (cacheLoaded) { setLoading(false) } else { setLoading(true) } } // Всегда загружаем свежие данные основного списка fetchWishlist() // Если список завершённых раскрыт - загружаем их тоже if (completedExpanded && completedCount > 0) { fetchCompleted() } } }, [isActive]) // Обновляем данные при изменении refreshTrigger useEffect(() => { if (refreshTrigger > 0) { fetchWishlist() if (completedExpanded && completedCount > 0) { fetchCompleted() } } }, [refreshTrigger]) const handleToggleCompleted = () => { const newExpanded = !completedExpanded setCompletedExpanded(newExpanded) // При раскрытии загружаем завершённые if (newExpanded && completedCount > 0) { // Показываем из кэша если есть if (completed.length === 0) { loadCompletedFromCache() } // Загружаем свежие данные fetchCompleted() } } const handleAddClick = () => { onNavigate?.('wishlist-form', { wishlistId: undefined }) } const handleItemClick = (item) => { onNavigate?.('wishlist-detail', { wishlistId: item.id }) } const handleMenuClick = (item, e) => { e.stopPropagation() setSelectedItem(item) } const handleEdit = () => { if (selectedItem) { onNavigate?.('wishlist-form', { wishlistId: selectedItem.id }) setSelectedItem(null) } } const handleDelete = async () => { if (!selectedItem) return try { const response = await authFetch(`${API_URL}/${selectedItem.id}`, { method: 'DELETE', }) if (!response.ok) { throw new Error('Ошибка при удалении') } setSelectedItem(null) await fetchWishlist() if (completedExpanded) { await fetchCompleted() } } catch (err) { setError(err.message) setSelectedItem(null) } } const handleComplete = async () => { if (!selectedItem) return try { const response = await authFetch(`${API_URL}/${selectedItem.id}/complete`, { method: 'POST', }) if (!response.ok) { throw new Error('Ошибка при завершении') } setSelectedItem(null) await fetchWishlist() if (completedExpanded) { await fetchCompleted() } } catch (err) { setError(err.message) setSelectedItem(null) } } const handleCopy = async () => { if (!selectedItem) return try { const response = await authFetch(`${API_URL}/${selectedItem.id}/copy`, { method: 'POST', }) if (!response.ok) { throw new Error('Ошибка при копировании') } const newItem = await response.json() setSelectedItem(null) onNavigate?.('wishlist-form', { wishlistId: newItem.id }) } catch (err) { setError(err.message) setSelectedItem(null) } } const formatPrice = (price) => { return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(price) } const findFirstUnmetCondition = (item) => { if (!item.unlock_conditions || item.unlock_conditions.length === 0) { return null } for (const condition of item.unlock_conditions) { let isMet = false if (condition.type === 'task_completion') { isMet = condition.task_completed === true } else if (condition.type === 'project_points') { const currentPoints = condition.current_points || 0 const requiredPoints = condition.required_points || 0 isMet = currentPoints >= requiredPoints } if (!isMet) { return condition } } return null } const renderUnlockCondition = (item) => { if (item.completed) return null const condition = findFirstUnmetCondition(item) if (!condition) return null let conditionText = '' if (condition.type === 'task_completion') { conditionText = condition.task_name || 'Задача' } else { const points = condition.required_points || 0 const project = condition.project_name || 'Проект' let dateText = '' if (condition.start_date) { const date = new Date(condition.start_date + 'T00:00:00') dateText = ` с ${date.toLocaleDateString('ru-RU')}` } else { dateText = ' за всё время' } conditionText = `${points} в ${project}${dateText}` } return (