Доски желаний и политика награждения
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m0s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m0s
This commit is contained in:
@@ -1,18 +1,44 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import { useAuth } from './auth/AuthContext'
|
||||
import BoardSelector from './BoardSelector'
|
||||
import LoadingError from './LoadingError'
|
||||
import './Wishlist.css'
|
||||
|
||||
const API_URL = '/api/wishlist'
|
||||
const CACHE_KEY = 'wishlist_cache'
|
||||
const CACHE_COMPLETED_KEY = 'wishlist_completed_cache'
|
||||
const BOARDS_CACHE_KEY = 'wishlist_boards_cache'
|
||||
const ITEMS_CACHE_KEY = 'wishlist_items_cache'
|
||||
const SELECTED_BOARD_KEY = 'wishlist_selected_board_id'
|
||||
|
||||
function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false, initialBoardId = null, boardDeleted = false }) {
|
||||
const { authFetch } = useAuth()
|
||||
const [boards, setBoards] = useState([])
|
||||
|
||||
// Восстанавливаем выбранную доску из localStorage или используем initialBoardId
|
||||
const getInitialBoardId = () => {
|
||||
if (initialBoardId) return initialBoardId
|
||||
return getSavedBoardId()
|
||||
}
|
||||
|
||||
// Получает сохранённую доску из localStorage
|
||||
const getSavedBoardId = () => {
|
||||
try {
|
||||
const saved = localStorage.getItem(SELECTED_BOARD_KEY)
|
||||
if (saved) {
|
||||
const boardId = parseInt(saved, 10)
|
||||
if (!isNaN(boardId)) return boardId
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading selected board from cache:', err)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const [selectedBoardId, setSelectedBoardIdState] = useState(getInitialBoardId)
|
||||
const [items, setItems] = useState([])
|
||||
const [completed, setCompleted] = useState([])
|
||||
const [completedCount, setCompletedCount] = useState(0)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [boardsLoading, setBoardsLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [completedExpanded, setCompletedExpanded] = useState(false)
|
||||
const [completedLoading, setCompletedLoading] = useState(false)
|
||||
@@ -22,19 +48,66 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
const initialFetchDoneRef = useRef(false)
|
||||
const prevIsActiveRef = useRef(isActive)
|
||||
|
||||
// Проверка наличия кэша
|
||||
const hasCache = () => {
|
||||
// Обёртка для setSelectedBoardId с сохранением в localStorage
|
||||
const setSelectedBoardId = (boardId) => {
|
||||
setSelectedBoardIdState(boardId)
|
||||
try {
|
||||
return localStorage.getItem(CACHE_KEY) !== null
|
||||
if (boardId) {
|
||||
localStorage.setItem(SELECTED_BOARD_KEY, String(boardId))
|
||||
} else {
|
||||
localStorage.removeItem(SELECTED_BOARD_KEY)
|
||||
}
|
||||
} catch (err) {
|
||||
return false
|
||||
console.error('Error saving selected board to cache:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка основных данных из кэша
|
||||
const loadFromCache = () => {
|
||||
// Загрузка досок из кэша
|
||||
const loadBoardsFromCache = () => {
|
||||
try {
|
||||
const cached = localStorage.getItem(CACHE_KEY)
|
||||
const cached = localStorage.getItem(BOARDS_CACHE_KEY)
|
||||
if (cached) {
|
||||
const data = JSON.parse(cached)
|
||||
setBoards(data.boards || [])
|
||||
// Проверяем, что сохранённая доска существует в списке
|
||||
if (selectedBoardId) {
|
||||
const boardExists = data.boards?.some(b => b.id === selectedBoardId)
|
||||
if (!boardExists && data.boards?.length > 0) {
|
||||
setSelectedBoardId(data.boards[0].id)
|
||||
}
|
||||
} else if (data.boards?.length > 0) {
|
||||
// Пытаемся восстановить из localStorage
|
||||
const savedBoardId = getSavedBoardId()
|
||||
if (savedBoardId && data.boards.some(b => b.id === savedBoardId)) {
|
||||
setSelectedBoardId(savedBoardId)
|
||||
} else {
|
||||
setSelectedBoardId(data.boards[0].id)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading boards from cache:', err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Сохранение досок в кэш
|
||||
const saveBoardsToCache = (boardsData) => {
|
||||
try {
|
||||
localStorage.setItem(BOARDS_CACHE_KEY, JSON.stringify({
|
||||
boards: boardsData,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
} catch (err) {
|
||||
console.error('Error saving boards to cache:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка желаний из кэша (по board_id)
|
||||
const loadItemsFromCache = (boardId) => {
|
||||
try {
|
||||
const cached = localStorage.getItem(`${ITEMS_CACHE_KEY}_${boardId}`)
|
||||
if (cached) {
|
||||
const data = JSON.parse(cached)
|
||||
setItems(data.items || [])
|
||||
@@ -42,61 +115,72 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
return true
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading from cache:', err)
|
||||
console.error('Error loading items from cache:', err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Загрузка завершённых из кэша
|
||||
const loadCompletedFromCache = () => {
|
||||
// Сохранение желаний в кэш
|
||||
const saveItemsToCache = (boardId, itemsData, count) => {
|
||||
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({
|
||||
localStorage.setItem(`${ITEMS_CACHE_KEY}_${boardId}`, JSON.stringify({
|
||||
items: itemsData,
|
||||
completedCount: count,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
} catch (err) {
|
||||
console.error('Error saving to cache:', err)
|
||||
console.error('Error saving items to cache:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Сохранение завершённых в кэш
|
||||
const saveCompletedToCache = (completedData) => {
|
||||
// Загрузка списка досок
|
||||
const fetchBoards = async () => {
|
||||
try {
|
||||
localStorage.setItem(CACHE_COMPLETED_KEY, JSON.stringify(completedData))
|
||||
const response = await authFetch(`${API_URL}/boards`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setBoards(data || [])
|
||||
saveBoardsToCache(data || [])
|
||||
|
||||
// Проверяем, что выбранная доска существует в списке
|
||||
if (selectedBoardId) {
|
||||
const boardExists = data?.some(b => b.id === selectedBoardId)
|
||||
if (!boardExists && data?.length > 0) {
|
||||
// Сохранённая доска не существует, выбираем первую
|
||||
setSelectedBoardId(data[0].id)
|
||||
}
|
||||
} else if (data?.length > 0) {
|
||||
// Пытаемся восстановить из localStorage
|
||||
const savedBoardId = getSavedBoardId()
|
||||
if (savedBoardId && data.some(b => b.id === savedBoardId)) {
|
||||
setSelectedBoardId(savedBoardId)
|
||||
} else {
|
||||
setSelectedBoardId(data[0].id)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error saving completed to cache:', err)
|
||||
console.error('Error fetching boards:', err)
|
||||
} finally {
|
||||
setBoardsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка основного списка
|
||||
const fetchWishlist = async () => {
|
||||
if (fetchingRef.current) return
|
||||
// Загрузка желаний выбранной доски
|
||||
const fetchItems = async () => {
|
||||
if (!selectedBoardId || fetchingRef.current) return
|
||||
fetchingRef.current = true
|
||||
|
||||
try {
|
||||
const hasDataInState = items.length > 0 || completedCount > 0
|
||||
const cacheExists = hasCache()
|
||||
if (!hasDataInState && !cacheExists) {
|
||||
setLoading(true)
|
||||
if (!hasDataInState) {
|
||||
const cacheLoaded = loadItemsFromCache(selectedBoardId)
|
||||
if (!cacheLoaded) {
|
||||
setLoading(true)
|
||||
}
|
||||
}
|
||||
|
||||
const response = await authFetch(API_URL)
|
||||
const response = await authFetch(`${API_URL}/boards/${selectedBoardId}/items`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка при загрузке желаний')
|
||||
@@ -108,11 +192,11 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
|
||||
setItems(allItems)
|
||||
setCompletedCount(count)
|
||||
saveToCache(allItems, count)
|
||||
saveItemsToCache(selectedBoardId, allItems, count)
|
||||
setError('')
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
if (!hasCache()) {
|
||||
if (!loadItemsFromCache(selectedBoardId)) {
|
||||
setItems([])
|
||||
setCompletedCount(0)
|
||||
}
|
||||
@@ -122,14 +206,15 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка завершённых
|
||||
// Загрузка завершённых для текущей доски
|
||||
const fetchCompleted = async () => {
|
||||
if (fetchingCompletedRef.current) return
|
||||
if (fetchingCompletedRef.current || !selectedBoardId) return
|
||||
fetchingCompletedRef.current = true
|
||||
|
||||
try {
|
||||
setCompletedLoading(true)
|
||||
const response = await authFetch(`${API_URL}/completed`)
|
||||
// Используем новый API для получения завершённых на доске
|
||||
const response = await authFetch(`${API_URL}/boards/${selectedBoardId}/completed`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка при загрузке завершённых желаний')
|
||||
@@ -138,7 +223,6 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
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([])
|
||||
@@ -153,85 +237,173 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
if (!initialFetchDoneRef.current) {
|
||||
initialFetchDoneRef.current = true
|
||||
|
||||
// Загружаем из кэша
|
||||
const cacheLoaded = loadFromCache()
|
||||
// Загружаем доски из кэша
|
||||
const boardsCacheLoaded = loadBoardsFromCache()
|
||||
if (boardsCacheLoaded) {
|
||||
setBoardsLoading(false)
|
||||
}
|
||||
|
||||
// Загружаем доски с сервера
|
||||
fetchBoards()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Загружаем желания при смене доски
|
||||
useEffect(() => {
|
||||
if (selectedBoardId) {
|
||||
// Сбрасываем состояние
|
||||
setItems([])
|
||||
setCompletedCount(0)
|
||||
setCompleted([])
|
||||
setCompletedExpanded(false)
|
||||
setLoading(true)
|
||||
|
||||
// Пробуем загрузить из кэша
|
||||
const cacheLoaded = loadItemsFromCache(selectedBoardId)
|
||||
if (cacheLoaded) {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
// Загружаем свежие данные
|
||||
fetchWishlist()
|
||||
|
||||
// Если список завершённых раскрыт - загружаем их тоже
|
||||
if (completedExpanded) {
|
||||
loadCompletedFromCache()
|
||||
fetchCompleted()
|
||||
}
|
||||
fetchItems()
|
||||
}
|
||||
}, [])
|
||||
}, [selectedBoardId])
|
||||
|
||||
// Обработка активации/деактивации таба
|
||||
// Обновление при активации таба
|
||||
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()
|
||||
fetchBoards()
|
||||
if (selectedBoardId) {
|
||||
fetchItems()
|
||||
}
|
||||
}
|
||||
}, [isActive])
|
||||
|
||||
// Обновляем данные при изменении refreshTrigger
|
||||
// Обновление при refreshTrigger
|
||||
useEffect(() => {
|
||||
if (refreshTrigger > 0) {
|
||||
fetchWishlist()
|
||||
if (refreshTrigger > 0 && selectedBoardId) {
|
||||
// Очищаем кэш для текущей доски, чтобы загрузить свежие данные
|
||||
try {
|
||||
localStorage.removeItem(`${ITEMS_CACHE_KEY}_${selectedBoardId}`)
|
||||
} catch (err) {
|
||||
console.error('Error clearing cache:', err)
|
||||
}
|
||||
fetchBoards()
|
||||
fetchItems()
|
||||
if (completedExpanded && completedCount > 0) {
|
||||
fetchCompleted()
|
||||
}
|
||||
}
|
||||
}, [refreshTrigger])
|
||||
}, [refreshTrigger, selectedBoardId])
|
||||
|
||||
// Обновление при initialBoardId (когда создана новая доска или переход по ссылке)
|
||||
useEffect(() => {
|
||||
if (initialBoardId && initialBoardId !== selectedBoardId) {
|
||||
// Сбрасываем флаг загрузки, чтобы не блокировать новую загрузку
|
||||
fetchingRef.current = false
|
||||
|
||||
// Обновляем список досок (чтобы новая доска появилась)
|
||||
fetchBoards().then(() => {
|
||||
// Переключаемся на новую доску после обновления списка
|
||||
// Это вызовет useEffect для selectedBoardId, который загрузит данные
|
||||
setSelectedBoardId(initialBoardId)
|
||||
})
|
||||
}
|
||||
}, [initialBoardId])
|
||||
|
||||
// Обработка удаления доски - выбираем первую доступную
|
||||
useEffect(() => {
|
||||
if (boardDeleted && boards.length > 0) {
|
||||
// Очищаем текущие данные
|
||||
setItems([])
|
||||
setCompletedCount(0)
|
||||
setCompleted([])
|
||||
setCompletedExpanded(false)
|
||||
setLoading(true)
|
||||
|
||||
// Обновляем список досок и выбираем первую
|
||||
fetchBoards().then(() => {
|
||||
// fetchBoards обновит boards, но мы уже в этом useEffect
|
||||
// selectedBoardId обновится автоматически в useEffect ниже
|
||||
})
|
||||
}
|
||||
}, [boardDeleted])
|
||||
|
||||
// Если текущая доска больше не существует в списке - выбираем первую
|
||||
useEffect(() => {
|
||||
if (boards.length > 0 && selectedBoardId) {
|
||||
const boardExists = boards.some(b => b.id === selectedBoardId)
|
||||
if (!boardExists) {
|
||||
setSelectedBoardId(boards[0].id)
|
||||
}
|
||||
}
|
||||
}, [boards, selectedBoardId])
|
||||
|
||||
const handleBoardChange = (boardId) => {
|
||||
setSelectedBoardId(boardId)
|
||||
}
|
||||
|
||||
const handleBoardEdit = () => {
|
||||
const board = boards.find(b => b.id === selectedBoardId)
|
||||
if (board?.is_owner) {
|
||||
onNavigate?.('board-form', { boardId: selectedBoardId })
|
||||
} else {
|
||||
// Показать подтверждение выхода
|
||||
handleLeaveBoard()
|
||||
}
|
||||
}
|
||||
|
||||
const handleLeaveBoard = async () => {
|
||||
if (!window.confirm('Отвязаться от этой доски? Вы больше не будете видеть её желания.')) return
|
||||
|
||||
try {
|
||||
const response = await authFetch(`${API_URL}/boards/${selectedBoardId}/leave`, {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
// Убираем доску из списка
|
||||
const newBoards = boards.filter(b => b.id !== selectedBoardId)
|
||||
setBoards(newBoards)
|
||||
saveBoardsToCache(newBoards)
|
||||
|
||||
// Выбираем первую доску
|
||||
if (newBoards.length > 0) {
|
||||
setSelectedBoardId(newBoards[0].id)
|
||||
} else {
|
||||
setSelectedBoardId(null)
|
||||
setItems([])
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error leaving board:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddBoard = () => {
|
||||
onNavigate?.('board-form', { boardId: null })
|
||||
}
|
||||
|
||||
const handleToggleCompleted = () => {
|
||||
const newExpanded = !completedExpanded
|
||||
setCompletedExpanded(newExpanded)
|
||||
|
||||
// При раскрытии загружаем завершённые
|
||||
if (newExpanded && completedCount > 0) {
|
||||
// Показываем из кэша если есть
|
||||
if (completed.length === 0) {
|
||||
loadCompletedFromCache()
|
||||
}
|
||||
// Загружаем свежие данные
|
||||
fetchCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddClick = () => {
|
||||
onNavigate?.('wishlist-form', { wishlistId: undefined })
|
||||
onNavigate?.('wishlist-form', { wishlistId: undefined, boardId: selectedBoardId })
|
||||
}
|
||||
|
||||
const handleItemClick = (item) => {
|
||||
onNavigate?.('wishlist-detail', { wishlistId: item.id })
|
||||
onNavigate?.('wishlist-detail', { wishlistId: item.id, boardId: selectedBoardId })
|
||||
}
|
||||
|
||||
const handleMenuClick = (item, e) => {
|
||||
@@ -241,7 +413,7 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
|
||||
const handleEdit = () => {
|
||||
if (selectedItem) {
|
||||
onNavigate?.('wishlist-form', { wishlistId: selectedItem.id })
|
||||
onNavigate?.('wishlist-form', { wishlistId: selectedItem.id, boardId: selectedBoardId })
|
||||
setSelectedItem(null)
|
||||
}
|
||||
}
|
||||
@@ -259,30 +431,7 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
}
|
||||
|
||||
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()
|
||||
await fetchItems()
|
||||
if (completedExpanded) {
|
||||
await fetchCompleted()
|
||||
}
|
||||
@@ -307,7 +456,7 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
const newItem = await response.json()
|
||||
|
||||
setSelectedItem(null)
|
||||
onNavigate?.('wishlist-form', { wishlistId: newItem.id })
|
||||
onNavigate?.('wishlist-form', { wishlistId: newItem.id, boardId: selectedBoardId })
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
setSelectedItem(null)
|
||||
@@ -430,7 +579,8 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
// Показываем loading только если и доски и желания грузятся
|
||||
if (boardsLoading && loading) {
|
||||
return (
|
||||
<div className="wishlist">
|
||||
<div className="fixed inset-0 bottom-20 flex justify-center items-center">
|
||||
@@ -443,51 +593,77 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (error && items.length === 0) {
|
||||
return (
|
||||
<div className="wishlist">
|
||||
<LoadingError onRetry={() => fetchWishlist()} />
|
||||
<BoardSelector
|
||||
boards={boards}
|
||||
selectedBoardId={selectedBoardId}
|
||||
onBoardChange={handleBoardChange}
|
||||
onBoardEdit={handleBoardEdit}
|
||||
onAddBoard={handleAddBoard}
|
||||
loading={boardsLoading}
|
||||
/>
|
||||
<LoadingError onRetry={() => fetchItems()} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wishlist">
|
||||
{/* Основной список (разблокированные и заблокированные вместе) */}
|
||||
<div className="wishlist-grid">
|
||||
{items.map(renderItem)}
|
||||
<button
|
||||
onClick={handleAddClick}
|
||||
className="add-wishlist-button"
|
||||
>
|
||||
<div className="add-wishlist-icon">+</div>
|
||||
</button>
|
||||
</div>
|
||||
{/* Селектор доски */}
|
||||
<BoardSelector
|
||||
boards={boards}
|
||||
selectedBoardId={selectedBoardId}
|
||||
onBoardChange={handleBoardChange}
|
||||
onBoardEdit={handleBoardEdit}
|
||||
onAddBoard={handleAddBoard}
|
||||
loading={boardsLoading}
|
||||
/>
|
||||
|
||||
{/* Завершённые - показываем только если есть завершённые желания */}
|
||||
{completedCount > 0 && (
|
||||
{/* Основной список */}
|
||||
{loading ? (
|
||||
<div className="wishlist-loading">
|
||||
<div className="w-8 h-8 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="section-divider">
|
||||
<button
|
||||
className="completed-toggle"
|
||||
onClick={handleToggleCompleted}
|
||||
<div className="wishlist-grid">
|
||||
{items.map(renderItem)}
|
||||
<button
|
||||
onClick={handleAddClick}
|
||||
className="add-wishlist-button"
|
||||
>
|
||||
<span className="completed-toggle-icon">
|
||||
{completedExpanded ? '▼' : '▶'}
|
||||
</span>
|
||||
<span>Завершённые</span>
|
||||
<div className="add-wishlist-icon">+</div>
|
||||
</button>
|
||||
</div>
|
||||
{completedExpanded && (
|
||||
|
||||
{/* Завершённые */}
|
||||
{completedCount > 0 && (
|
||||
<>
|
||||
{completedLoading ? (
|
||||
<div className="loading-completed">
|
||||
<div className="w-8 h-8 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="wishlist-grid">
|
||||
{completed.map(renderItem)}
|
||||
</div>
|
||||
<div className="section-divider">
|
||||
<button
|
||||
className="completed-toggle"
|
||||
onClick={handleToggleCompleted}
|
||||
>
|
||||
<span className="completed-toggle-icon">
|
||||
{completedExpanded ? '▼' : '▶'}
|
||||
</span>
|
||||
<span>Завершённые</span>
|
||||
</button>
|
||||
</div>
|
||||
{completedExpanded && (
|
||||
<>
|
||||
{completedLoading ? (
|
||||
<div className="loading-completed">
|
||||
<div className="w-8 h-8 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="wishlist-grid">
|
||||
{completed.map(renderItem)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
@@ -520,4 +696,3 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false }) {
|
||||
}
|
||||
|
||||
export default Wishlist
|
||||
|
||||
|
||||
Reference in New Issue
Block a user