"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 = [57.997962, 56.147201]; const MainVolunteerPage = () => { const [position, setPosition] = useState(DEFAULT_POSITION); const [hasLocation, setHasLocation] = useState(false); const [userName, setUserName] = useState("Волонтёр"); const [requests, setRequests] = useState([]); 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(""); // фикс иконок leaflet: только на клиенте useEffect(() => { const setupLeafletIcons = async () => { if (typeof window === "undefined") return; const L = (await import("leaflet")).default; await import("leaflet/dist/leaflet.css"); // @ts-ignore delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: "/leaflet/marker-icon-2x.png", iconUrl: "/leaflet/marker-icon.png", shadowUrl: "/leaflet/marker-shadow.png", }); }; setupLeafletIcons(); }, []); 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); } ); }, []); // профиль 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(); }, []); // заявки рядом 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(); 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); } }; fetchNearbyRequests(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [position, hasLocation]); // отклик // волонтёр СОДАЁТ отклик const handleAccept = async (req, message = "") => { if (!API_BASE || !req || !req.id) { setAcceptError("Некорректная заявка (нет id)"); 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; } console.error("Ошибка отклика:", msg); setAcceptError(msg); setAcceptLoading(false); return; } await res.json(); // VolunteerResponse setAcceptLoading(false); closePopup(); } catch (e) { setAcceptError(e.message || "Ошибка сети"); setAcceptLoading(false); } }; return (
{/* Header */}

{userName}

Кому нужна помощь

{error && (

{error}

)} {/* Карта */}
{hasLocation && ( Вы здесь )} {requests.map((req) => ( {req.title} ))}
{/* Заявки ниже карты */}
{loading && (

Загрузка заявок...

)} {!loading && requests.length === 0 && !error && (

Рядом пока нет заявок

)} {requests.map((req) => (
openPopup(req)} >

{req.title}

{req.address}

{req.distance && (

Расстояние: {req.distance}

)}
))}
); }; export default MainVolunteerPage;