import React, { useState, useEffect } from 'react' import { useAuth } from './auth/AuthContext' import LoadingError from './LoadingError' import './Wishlist.css' const API_URL = '/api/wishlist' function Wishlist({ onNavigate, refreshTrigger = 0 }) { 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 [tasks, setTasks] = useState([]) const [projects, setProjects] = useState([]) useEffect(() => { fetchWishlist() loadTasksAndProjects() }, []) const loadTasksAndProjects = async () => { try { // Загружаем задачи const tasksResponse = await authFetch('/api/tasks') if (tasksResponse.ok) { const tasksData = await tasksResponse.json() setTasks(Array.isArray(tasksData) ? tasksData : []) } // Загружаем проекты const projectsResponse = await authFetch('/projects') if (projectsResponse.ok) { const projectsData = await projectsResponse.json() setProjects(Array.isArray(projectsData) ? projectsData : []) } } catch (err) { console.error('Error loading tasks and projects:', err) } } // Обновляем данные при изменении refreshTrigger useEffect(() => { if (refreshTrigger > 0) { fetchWishlist() } }, [refreshTrigger]) const fetchWishlist = async () => { try { 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 || [])] setItems(allItems) const count = data.completed_count || 0 setCompletedCount(count) // Загружаем завершённые сразу, если они есть if (count > 0) { fetchCompleted() } else { setCompleted([]) } setError('') } catch (err) { setError(err.message) setItems([]) setCompleted([]) setCompletedCount(0) } finally { setLoading(false) } } const fetchCompleted = async () => { try { setCompletedLoading(true) const response = await authFetch(`${API_URL}?include_completed=true`) if (!response.ok) { throw new Error('Ошибка при загрузке завершённых желаний') } const data = await response.json() setCompleted(data.completed || []) } catch (err) { console.error('Error fetching completed items:', err) setCompleted([]) } finally { setCompletedLoading(false) } } const handleToggleCompleted = () => { const newExpanded = !completedExpanded setCompletedExpanded(newExpanded) if (newExpanded && completed.length === 0 && completedCount > 0) { 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(completedExpanded) } 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(completedExpanded) } catch (err) { setError(err.message) setSelectedItem(null) } } const handleCopy = async () => { if (!selectedItem) return try { // Загружаем полные данные желания const response = await authFetch(`${API_URL}/${selectedItem.id}`) if (!response.ok) { throw new Error('Ошибка при загрузке желания') } const itemData = await response.json() // Преобразуем условия из формата Display в формат Request const unlockConditions = (itemData.unlock_conditions || []).map((cond) => { const condition = { type: cond.type, display_order: cond.display_order, } if (cond.type === 'task_completion' && cond.task_name) { // Находим task_id по имени задачи const task = tasks.find(t => t.name === cond.task_name) if (task) { condition.task_id = task.id } } else if (cond.type === 'project_points' && cond.project_name) { // Находим project_id по имени проекта const project = projects.find(p => p.project_name === cond.project_name) if (project) { condition.project_id = project.project_id } if (cond.required_points !== undefined && cond.required_points !== null) { condition.required_points = cond.required_points } if (cond.start_date) { condition.start_date = cond.start_date } } return condition }) // Создаем копию желания const copyData = { name: `${itemData.name} (копия)`, price: itemData.price || null, link: itemData.link || null, unlock_conditions: unlockConditions, } const createResponse = await authFetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(copyData), }) if (!createResponse.ok) { throw new Error('Ошибка при создании копии') } const newItem = await createResponse.json() // Копируем изображение, если оно есть if (itemData.image_url) { try { // Загружаем изображение по URL (используем authFetch для авторизованных запросов) const imageResponse = await authFetch(itemData.image_url) if (imageResponse.ok) { const blob = await imageResponse.blob() // Проверяем, что это изображение и размер не превышает 5MB if (blob.type.startsWith('image/') && blob.size <= 5 * 1024 * 1024) { // Загружаем изображение для нового желания const formData = new FormData() formData.append('image', blob, 'image.jpg') const uploadResponse = await authFetch(`${API_URL}/${newItem.id}/image`, { method: 'POST', body: formData, }) if (!uploadResponse.ok) { console.error('Ошибка при копировании изображения') } } } } catch (imgErr) { console.error('Ошибка при копировании изображения:', imgErr) // Не прерываем процесс, просто логируем ошибку } } 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') { // Условие выполнено, если task_completed === true isMet = condition.task_completed === true } else if (condition.type === 'project_points') { // Условие выполнено, если current_points >= required_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 (
{conditionText}
) } const renderItem = (item) => { const isFaded = (!item.unlocked && !item.completed) || item.completed return (
handleItemClick(item)} >
{item.image_url ? ( {item.name} ) : (
)}
{item.name}
{(() => { // Показываем первое невыполненное условие, если есть const unmetCondition = findFirstUnmetCondition(item) if (unmetCondition && !item.completed) { return renderUnlockCondition(item) } // Если все условия выполнены или условий нет - показываем цену if (item.price) { return
{formatPrice(item.price)}
} return null })()}
) } if (loading) { return (
Загрузка...
) } if (error) { return (
fetchWishlist()} />
) } return (
{/* Кнопка добавления */} {/* Основной список (разблокированные и заблокированные вместе) */} {items.length > 0 && (
{items.map(renderItem)}
)} {/* Завершённые - показываем только если есть завершённые желания */} {completedCount > 0 && ( <>
{completedExpanded && ( <> {completedLoading ? (
) : (
{completed.map(renderItem)}
)} )} )} {/* Модальное окно для действий */} {selectedItem && (
setSelectedItem(null)}>
e.stopPropagation()}>

{selectedItem.name}

)}
) } export default Wishlist