279 lines
8.8 KiB
JavaScript
279 lines
8.8 KiB
JavaScript
"use client";
|
||
|
||
import React, { useEffect, useState } from "react";
|
||
import { FaBell, FaUser } from "react-icons/fa";
|
||
import TabBar from "../components/TabBar";
|
||
import RequestDetailsModal from "../components/ValounterRequestDetailsModal";
|
||
|
||
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
|
||
|
||
// Статусы ОТКЛИКА волонтёра (ResponseStatus)
|
||
const responseStatusMap = {
|
||
pending: { label: "Ожидает ответа", color: "#E9D171" },
|
||
accepted: { label: "Принят", color: "#94E067" },
|
||
rejected: { label: "Отклонён", color: "#FF8282" },
|
||
cancelled: { label: "Отменён", color: "#FF8282" },
|
||
};
|
||
|
||
const HistoryRequestPage = () => {
|
||
const [userName, setUserName] = useState("Волонтёр");
|
||
const [requests, setRequests] = useState([]);
|
||
const [selectedRequest, setSelectedRequest] = useState(null);
|
||
|
||
const [error, setError] = useState("");
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
const getAccessToken = () => {
|
||
if (typeof window === "undefined") return null;
|
||
const saved = localStorage.getItem("authUser");
|
||
const authUser = saved ? JSON.parse(saved) : null;
|
||
return authUser?.accessToken || null;
|
||
};
|
||
|
||
// профиль волонтёра
|
||
useEffect(() => {
|
||
const fetchProfile = async () => {
|
||
if (!API_BASE) return;
|
||
const token = getAccessToken();
|
||
if (!token) return;
|
||
|
||
try {
|
||
const res = await fetch(`${API_BASE}/users/me`, {
|
||
headers: {
|
||
Accept: "application/json",
|
||
Authorization: `Bearer ${token}`,
|
||
},
|
||
});
|
||
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();
|
||
}, []);
|
||
|
||
// история откликов волонтёра: /responses/my
|
||
useEffect(() => {
|
||
const fetchVolunteerRequests = async () => {
|
||
if (!API_BASE) {
|
||
setError("API_BASE_URL не задан");
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
const token = getAccessToken();
|
||
if (!token) {
|
||
setError("Вы не авторизованы");
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await fetch(`${API_BASE}/responses/my`, {
|
||
headers: {
|
||
Accept: "application/json",
|
||
Authorization: `Bearer ${token}`,
|
||
},
|
||
});
|
||
|
||
const text = await res.text();
|
||
let data = null;
|
||
if (text) {
|
||
try {
|
||
data = JSON.parse(text);
|
||
} catch {
|
||
data = null;
|
||
}
|
||
}
|
||
|
||
if (!res.ok) {
|
||
let msg = "Не удалось загрузить историю заявок";
|
||
if (data && typeof data === "object" && data.error) {
|
||
msg = data.error;
|
||
} else if (text) {
|
||
msg = text;
|
||
}
|
||
setError(msg);
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
|
||
const list = Array.isArray(data) ? data : [];
|
||
|
||
const mapped = list.map((item) => {
|
||
// VolunteerResponse.status: pending | accepted | rejected | cancelled
|
||
const rawStatus = String(
|
||
item.status?.response_status || item.status || ""
|
||
).toLowerCase();
|
||
|
||
const m = responseStatusMap[rawStatus] || {
|
||
label: rawStatus || "Неизвестен",
|
||
color: "#E2E2E2",
|
||
};
|
||
|
||
const created = item.responded_at
|
||
? new Date(item.responded_at)
|
||
: item.created_at
|
||
? new Date(item.created_at)
|
||
: null;
|
||
|
||
const date = created
|
||
? created.toLocaleDateString("ru-RU")
|
||
: "";
|
||
const time = created
|
||
? created.toLocaleTimeString("ru-RU", {
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
})
|
||
: "";
|
||
|
||
return {
|
||
// отклик
|
||
id: item.id,
|
||
rawStatus, // сырой статус для модалки
|
||
status: m.label, // русский текст для списка
|
||
statusColor: m.color, // цвет плашки
|
||
|
||
// данные по заявке, если backend их уже кладёт в ответ
|
||
requestId: item.request_id,
|
||
title: item.request_title || item.title || "Заявка",
|
||
description: item.request_description || item.description || "",
|
||
address: item.request_address || item.address || "",
|
||
requesterName: item.requester_name || "",
|
||
requestTypeName: item.request_type_name || "",
|
||
|
||
date,
|
||
time,
|
||
createdAt: date,
|
||
};
|
||
});
|
||
|
||
setRequests(mapped);
|
||
setLoading(false);
|
||
} catch (e) {
|
||
setError(e.message || "Ошибка сети");
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
fetchVolunteerRequests();
|
||
}, []);
|
||
|
||
const handleOpen = (req) => {
|
||
setSelectedRequest(req);
|
||
};
|
||
|
||
const handleClose = () => {
|
||
setSelectedRequest(null);
|
||
};
|
||
|
||
return (
|
||
<div className="min-h-screen w-full bg-[#90D2F9] flex justify-center px-4">
|
||
<div className="relative w-full max-w-md flex flex-col pb-20 pt-4">
|
||
{/* Header */}
|
||
<header className="flex items-center justify-between mb-4">
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-full border border-white flex items-center justify-center">
|
||
<FaUser className="text-white text-sm" />
|
||
</div>
|
||
<p className="font-montserrat font-extrabold text-[20px] leading-[22px] text-white">
|
||
{userName}
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
className="w-8 h-8 rounded-full border border-white flex items-center justify-center"
|
||
>
|
||
<FaBell className="text-white text-sm" />
|
||
</button>
|
||
</header>
|
||
|
||
<h1 className="font-montserrat font-extrabold text-[20px] leading-[22px] text-white mb-3">
|
||
История заявок
|
||
</h1>
|
||
|
||
{error && (
|
||
<p className="mb-2 text-xs font-montserrat text-red-200">
|
||
{error}
|
||
</p>
|
||
)}
|
||
|
||
{/* Список откликов */}
|
||
<main className="space-y-3 overflow-y-auto pr-1 max-h-[80vh]">
|
||
{loading && (
|
||
<p className="text-white text-sm font-montserrat">
|
||
Загрузка заявок...
|
||
</p>
|
||
)}
|
||
|
||
{!loading && requests.length === 0 && !error && (
|
||
<p className="text-white text-sm font-montserrat">
|
||
У вас пока нет заявок
|
||
</p>
|
||
)}
|
||
|
||
{requests.map((req) => (
|
||
<button
|
||
key={req.id}
|
||
type="button"
|
||
onClick={() => handleOpen(req)}
|
||
className="w-full text-left bg-white rounded-xl px-3 py-2 flex flex-col gap-1"
|
||
>
|
||
<div className="flex items-center justify-between gap-2">
|
||
<span
|
||
className="inline-flex items-center justify-center px-2 py-0.5 rounded-full font-montserrat text-[12px] font-semibold text-white"
|
||
style={{ backgroundColor: req.statusColor }}
|
||
>
|
||
{req.status}
|
||
</span>
|
||
<div className="text-right leading-tight">
|
||
<p className="font-montserrat text-[10px] text-black">
|
||
{req.date}
|
||
</p>
|
||
<p className="font-montserrat text-[10px] text-black">
|
||
{req.time}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<p className="font-montserrat font-semibold text-[15px] leading-[18px] text-black mt-1">
|
||
{req.title}
|
||
</p>
|
||
|
||
{req.requesterName && (
|
||
<p className="font-montserrat text-[11px] text-black/80">
|
||
{req.requesterName}
|
||
</p>
|
||
)}
|
||
{req.address && (
|
||
<p className="font-montserrat text-[10px] text-black/70">
|
||
{req.address}
|
||
</p>
|
||
)}
|
||
|
||
<div className="mt-2.w-full bg-[#94E067] rounded-lg py-3 flex items-center justify-center">
|
||
<span className="font-montserrat font-bold text-[15px] leading-[18px] text-white">
|
||
Развернуть
|
||
</span>
|
||
</div>
|
||
</button>
|
||
))}
|
||
</main>
|
||
|
||
{selectedRequest && (
|
||
<RequestDetailsModal request={selectedRequest} onClose={handleClose} />
|
||
)}
|
||
|
||
<TabBar />
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default HistoryRequestPage;
|