Загрузка заявок...
)} {!loading && requests.length === 0 && !error && (Рядом пока нет заявок
)} {requests.map((req) => ({req.title}
{req.address}
{req.distance && (Расстояние: {req.distance}
)}"use client"; import React, { useEffect, useState } from "react"; import dynamic from "next/dynamic"; import { FaUser, FaCog } from "react-icons/fa"; import TabBar from "../components/TabBar"; import AcceptPopup from "../components/acceptPopUp"; const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL; // динамический импорт карты const MapContainer = dynamic( () => import("react-leaflet").then((m) => m.MapContainer), { ssr: false } ); const TileLayer = dynamic( () => import("react-leaflet").then((m) => m.TileLayer), { ssr: false } ); const Marker = dynamic( () => import("react-leaflet").then((m) => m.Marker), { ssr: false } ); const Popup = dynamic( () => import("react-leaflet").then((m) => m.Popup), { ssr: false } ); // центр Перми const DEFAULT_POSITION = [58.0105, 56.2294]; const MainVolunteerPage = () => { const [position, setPosition] = useState(DEFAULT_POSITION); const [hasLocation, setHasLocation] = useState(false); const [userName, setUserName] = useState("Волонтёр"); const [requests, setRequests] = useState([]); // заявки из /requests/nearby const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [selectedRequest, setSelectedRequest] = useState(null); const [isPopupOpen, setIsPopupOpen] = useState(false); const [acceptLoading, setAcceptLoading] = useState(false); const [acceptError, setAcceptError] = useState(""); // получить токен из localStorage const getAccessToken = () => { if (typeof window === "undefined") return null; const saved = localStorage.getItem("authUser"); const authUser = saved ? JSON.parse(saved) : null; return authUser?.accessToken || null; }; const openPopup = (req) => { setSelectedRequest(req); setIsPopupOpen(true); setAcceptError(""); }; const closePopup = () => { setIsPopupOpen(false); setSelectedRequest(null); setAcceptError(""); }; // геолокация волонтёра useEffect(() => { if (!navigator.geolocation) return; navigator.geolocation.getCurrentPosition( (pos) => { setPosition([pos.coords.latitude, pos.coords.longitude]); setHasLocation(true); }, () => { setHasLocation(false); } ); }, []); // загрузка имени волонтёра из /users/me[file:519] useEffect(() => { const fetchProfile = async () => { if (!API_BASE) return; const accessToken = getAccessToken(); if (!accessToken) return; try { const res = await fetch(`${API_BASE}/users/me`, { headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, }); if (!res.ok) return; const data = await res.json(); const fullName = [data.first_name, data.last_name] .filter(Boolean) .join(" ") .trim() || data.email; setUserName(fullName); } catch { // оставляем дефолтное имя } }; fetchProfile(); }, []); // загрузка заявок рядом: /requests/nearby?lat=&lon=&radius=[file:519] useEffect(() => { const fetchNearbyRequests = async () => { if (!API_BASE) { setError("API_BASE_URL не задан"); setLoading(false); return; } const accessToken = getAccessToken(); if (!accessToken) { setError("Вы не авторизованы"); setLoading(false); return; } const [lat, lon] = position; try { const params = new URLSearchParams({ lat: String(lat), lon: String(lon), radius: "5000", limit: "50", offset: "0", }); const res = await fetch(`${API_BASE}/requests/nearby?${params.toString()}`, { headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, }); if (!res.ok) { let msg = "Не удалось загрузить заявки рядом"; try { const data = await res.json(); if (data.error) msg = data.error; } catch { const text = await res.text(); if (text) msg = text; } setError(msg); setLoading(false); return; } const data = await res.json(); // массив RequestWithDistance[file:519] const mapped = data.map((item) => ({ id: item.id, title: item.title, description: item.description, // <= добавили address: item.address, city: item.city, urgency: item.urgency, // <= добавили contact_phone: item.contact_phone, // если есть в ответе contact_notes: item.contact_notes, // если есть в ответе desired_completion_date: item.desired_completion_date, // <= уже есть coords: [item.latitude ?? lat, item.longitude ?? lon], distance: item.distance_meters ? `${(item.distance_meters / 1000).toFixed(1)} км` : null, })); setRequests(mapped); setLoading(false); } catch (e) { setError(e.message || "Ошибка сети"); setLoading(false); } }; // загружаем, когда уже знаем позицию if (hasLocation || position !== DEFAULT_POSITION) { fetchNearbyRequests(); } else { // если геолокация не дала позицию — всё равно пробуем из центра fetchNearbyRequests(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [position, hasLocation]); // отклик: POST /requests/{id}/responses[file:519] const handleAccept = async (req, message = "") => { if (!API_BASE || !req) return; const accessToken = getAccessToken(); if (!accessToken) { setAcceptError("Вы не авторизованы"); return; } try { setAcceptLoading(true); setAcceptError(""); const res = await fetch(`${API_BASE}/requests/${req.id}/responses`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify( message ? { message } : {} // поле message опционально ), }); if (!res.ok) { let msg = "Не удалось отправить отклик"; try { const data = await res.json(); if (data.error) msg = data.error; } catch { const text = await res.text(); if (text) msg = text; } setAcceptError(msg); setAcceptLoading(false); return; } const createdResponse = await res.json(); console.log("Отклик создан:", createdResponse); setAcceptLoading(false); closePopup(); } catch (e) { setAcceptError(e.message || "Ошибка сети"); setAcceptLoading(false); } }; return (
{userName}
{error}
)} {/* Карта */}Загрузка заявок...
)} {!loading && requests.length === 0 && !error && (Рядом пока нет заявок
)} {requests.map((req) => ({req.title}
{req.address}
{req.distance && (Расстояние: {req.distance}
)}