import { useState, useEffect, useCallback } from "react"; import "../styles/index.css"; import { API_URL } from "../config/authConstants"; import Sidebar from "./Sidebar"; import RightSidebar from "./RightSidebar"; import PostOnFeed from "./PostOnFeed"; import CreateUpdatePost from "./CreateUpdatePost"; import MenuIcon from "./icons/MenuIcon"; /** * MainFeed — корневой компонент приложения. * Управляет состоянием ленты, модалок и сайдбара; * остальные компоненты получают данные через пропсы. */ export default function MainFeed() { // Открыт ли левый сайдбар (мобильный слайд-аут) const [sidebarOpen, setSidebarOpen] = useState(false); // Активный пункт навигации в сайдбаре const [activeNav, setActiveNav] = useState("Главная"); // Фильтр по типу объявления: null (все) | "loss" | "found" const [typeFilter, setTypeFilter] = useState(null); // Открыта ли модалка создания нового объявления const [modalOpen, setModalOpen] = useState(false); // Объявление, которое сейчас редактируется (null — модалка закрыта) const [editAd, setEditAd] = useState(null); // Массив объявлений из API const [ads, setAds] = useState([]); // Set id объявлений, которые пользователь лайкнул (только локально) const [liked, setLiked] = useState(new Set()); // Состояния загрузки и ошибки для ленты const [loading, setLoading] = useState(true); const [error, setError] = useState(""); /** Загружает ленту объявлений с бэкенда. */ const loadFeed = useCallback(async () => { setLoading(true); setError(""); try { const res = await fetch(`${API_URL}/api/v1/posts`); if (!res.ok) throw new Error(`Ошибка ${res.status}`); const data = await res.json(); // API может вернуть массив напрямую или объект { items: [] } const ads = Array.isArray(data) ? data : (data.items ?? []); // Нормализуем type: enum LOSS/FOUND → "loss"/"found" const normalizedAds = ads.map(ad => ({ ...ad, type: ad.type ? ad.type.toLowerCase() : "loss" })); setAds(normalizedAds); } catch (err) { setError(err.message || "Не удалось загрузить объявления"); } finally { setLoading(false); } }, []); // Первичная загрузка при монтировании компонента useEffect(() => { loadFeed(); }, [loadFeed]); /** * Переключает лайк на объявлении. * Состояние хранится только на клиенте — без запроса к API. */ const toggleLike = useCallback((id) => { setLiked(prev => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }); }, []); // После создания или редактирования объявления перезагружаем ленту, // чтобы получить актуальные данные (включая изображения) из БД const handleNewAd = useCallback(() => loadFeed(), [loadFeed]); const handleEditSave = useCallback(() => loadFeed(), [loadFeed]); // Фильтруем объявления в зависимости от выбранной вкладки и фильтра типа const filteredAds = (() => { let result = activeNav === "Главная" ? ads : ads.filter(ad => activeNav === "Вещи" ? ["Вещь", "Другое"].includes(ad.category) : activeNav === "Питомцы" ? ad.category === "Питомец" : false ); // Применяем фильтр по типу объявления if (typeFilter) { result = result.filter(ad => ad.type === typeFilter); } return result; })(); /** * Рендерит содержимое ленты в зависимости от текущего состояния: * — спиннер во время загрузки * — сообщение об ошибке * — заглушка при пустой ленте * — список карточек объявлений */ const renderContent = () => { if (loading) { return (