WIP API
This commit is contained in:
@@ -1,94 +1,163 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { FaBell, FaUser, FaStar } from "react-icons/fa";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { FaBell, FaUser } from "react-icons/fa";
|
||||
import TabBar from "../components/TabBar";
|
||||
import RequestDetailsModal from "../components/RequestDetailsModal";
|
||||
|
||||
const requests = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Приобрести продукты пенсионерке",
|
||||
status: "Отклонена",
|
||||
statusColor: "#FF8282",
|
||||
date: "До 28.11.2025",
|
||||
time: "13:00",
|
||||
createdAt: "28.11.2025",
|
||||
rejectReason: "Адрес вне зоны обслуживания",
|
||||
description: "Купить продукты и принести по адресу.",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Приобрести продукты пенсионерке",
|
||||
status: "Принята",
|
||||
statusColor: "#94E067",
|
||||
date: "До 28.11.2025",
|
||||
time: "13:00",
|
||||
createdAt: "28.11.2025",
|
||||
description: "Купить продукты и принести по адресу.",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Приобрести продукты пенсионерке",
|
||||
status: "На модерации",
|
||||
statusColor: "#E9D171",
|
||||
date: "До 28.11.2025",
|
||||
time: "13:00",
|
||||
createdAt: "28.11.2025",
|
||||
description: "Купить продукты и принести по адресу.",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Приобрести продукты пенсионерке",
|
||||
status: "Выполнена",
|
||||
statusColor: "#71A5E9",
|
||||
date: "До 28.11.2025",
|
||||
time: "13:00",
|
||||
createdAt: "28.11.2025",
|
||||
description: "Купить продукты и принести по адресу.",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "Приобрести продукты пенсионерке",
|
||||
status: "В процессе",
|
||||
statusColor: "#E971E1",
|
||||
date: "До 28.11.2025",
|
||||
time: "13:00",
|
||||
createdAt: "28.11.2025",
|
||||
description: "Купить продукты и принести по адресу.",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "Приобрести продукты пенсионерке",
|
||||
status: "В процессе",
|
||||
statusColor: "#E971E1",
|
||||
date: "До 28.11.2025",
|
||||
time: "13:00",
|
||||
createdAt: "28.11.2025",
|
||||
description: "Купить продукты и принести по адресу.",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: "Приобрести продукты пенсионерке",
|
||||
status: "В процессе",
|
||||
statusColor: "#E971E1",
|
||||
date: "До 28.11.2025",
|
||||
time: "13:00",
|
||||
createdAt: "28.11.2025",
|
||||
description: "Купить продукты и принести по адресу.",
|
||||
},
|
||||
];
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
// маппинг статусов API -> текст/цвет для UI
|
||||
const statusMap = {
|
||||
pending_moderation: { label: "На модерации", color: "#E9D171" },
|
||||
approved: { label: "Принята", color: "#94E067" },
|
||||
in_progress: { label: "В процессе", color: "#E971E1" },
|
||||
completed: { label: "Выполнена", color: "#71A5E9" },
|
||||
cancelled: { label: "Отменена", color: "#FF8282" },
|
||||
rejected: { label: "Отклонена", color: "#FF8282" },
|
||||
};
|
||||
|
||||
const HistoryRequestPage = () => {
|
||||
const [requests, setRequests] = useState([]);
|
||||
const [selectedRequest, setSelectedRequest] = useState(null);
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const handleOpen = (req) => {
|
||||
setSelectedRequest(req);
|
||||
};
|
||||
const [userName, setUserName] = useState("Пользователь");
|
||||
const [profileError, setProfileError] = useState("");
|
||||
|
||||
const handleClose = () => {
|
||||
setSelectedRequest(null);
|
||||
};
|
||||
// профиль: /users/me
|
||||
useEffect(() => {
|
||||
const fetchProfile = async () => {
|
||||
if (!API_BASE) return;
|
||||
|
||||
const saved =
|
||||
typeof window !== "undefined"
|
||||
? localStorage.getItem("authUser")
|
||||
: null;
|
||||
const authUser = saved ? JSON.parse(saved) : null;
|
||||
const accessToken = authUser?.accessToken;
|
||||
if (!accessToken) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/users/me`, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
setProfileError("Не удалось загрузить профиль");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const fullName =
|
||||
[data.first_name, data.last_name]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.trim() || data.email;
|
||||
setUserName(fullName);
|
||||
} catch {
|
||||
setProfileError("Ошибка загрузки профиля");
|
||||
}
|
||||
};
|
||||
|
||||
fetchProfile();
|
||||
}, []);
|
||||
|
||||
// заявки: /requests/my
|
||||
useEffect(() => {
|
||||
const fetchRequests = async () => {
|
||||
if (!API_BASE) {
|
||||
setError("API_BASE_URL не задан");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const saved =
|
||||
typeof window !== "undefined"
|
||||
? localStorage.getItem("authUser")
|
||||
: null;
|
||||
const authUser = saved ? JSON.parse(saved) : null;
|
||||
const accessToken = authUser?.accessToken;
|
||||
|
||||
if (!accessToken) {
|
||||
setError("Вы не авторизованы");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/requests/my`, {
|
||||
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();
|
||||
console.log("requests/my data:", data);
|
||||
|
||||
const mapped = data.map((item) => {
|
||||
const rawStatus =
|
||||
typeof item.status === "string"
|
||||
? item.status
|
||||
: item.status?.request_status;
|
||||
|
||||
const m = statusMap[rawStatus] || {
|
||||
label: rawStatus || "unknown",
|
||||
color: "#E2E2E2",
|
||||
};
|
||||
|
||||
const created = new Date(item.created_at);
|
||||
const createdAt = created.toLocaleDateString("ru-RU");
|
||||
const time = created.toLocaleTimeString("ru-RU", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
status: m.label,
|
||||
statusColor: m.color,
|
||||
createdAt,
|
||||
date: createdAt,
|
||||
time,
|
||||
description: item.description,
|
||||
// если позже появятся причина/оценка — можно добавить сюда
|
||||
};
|
||||
});
|
||||
|
||||
setRequests(mapped);
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
setError(e.message || "Ошибка сети");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchRequests();
|
||||
}, []);
|
||||
|
||||
const handleOpen = (req) => setSelectedRequest(req);
|
||||
const handleClose = () => setSelectedRequest(null);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen w-full bg-[#90D2F9] flex justify-center px-4">
|
||||
@@ -99,9 +168,16 @@ const HistoryRequestPage = () => {
|
||||
<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-[11px] text-white">
|
||||
Александр
|
||||
</p>
|
||||
<div>
|
||||
<p className="font-montserrat font-extrabold text-[20px] leading-[11px] text-white">
|
||||
{userName}
|
||||
</p>
|
||||
{profileError && (
|
||||
<p className="text-[10px] text-red-200 font-montserrat mt-1">
|
||||
{profileError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -115,8 +191,24 @@ const HistoryRequestPage = () => {
|
||||
История заявок
|
||||
</h1>
|
||||
|
||||
{error && (
|
||||
<div className="mb-2 bg-red-500 text-white text-xs font-montserrat px-3 py-2 rounded-lg">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Список заявок */}
|
||||
<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}
|
||||
@@ -132,9 +224,9 @@ const HistoryRequestPage = () => {
|
||||
>
|
||||
{req.status}
|
||||
</span>
|
||||
<div className="text-right leading-tight">
|
||||
<div className="text-right.leading-tight">
|
||||
<p className="font-montserrat text-[10px] text-black">
|
||||
{req.date}
|
||||
{`До ${req.date}`}
|
||||
</p>
|
||||
<p className="font-montserrat text-[10px] text-black">
|
||||
{req.time}
|
||||
@@ -157,7 +249,7 @@ const HistoryRequestPage = () => {
|
||||
))}
|
||||
</main>
|
||||
|
||||
{/* Попап */}
|
||||
{/* Попап деталей */}
|
||||
{selectedRequest && (
|
||||
<RequestDetailsModal request={selectedRequest} onClose={handleClose} />
|
||||
)}
|
||||
@@ -169,91 +261,3 @@ const HistoryRequestPage = () => {
|
||||
};
|
||||
|
||||
export default HistoryRequestPage;
|
||||
|
||||
// const RequestDetailsModal = ({ request, onClose }) => {
|
||||
// const isDone = request.status === "Выполнена";
|
||||
|
||||
// return (
|
||||
// <div className="fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4">
|
||||
// <div className="w-full max-w-sm bg-[#90D2F9] rounded-2xl p-3 relative">
|
||||
// {/* Белая карточка */}
|
||||
// <div className="bg-white rounded-xl p-3 flex flex-col gap-3">
|
||||
// {/* Шапка попапа */}
|
||||
// <div className="flex items-center justify-between mb-1">
|
||||
// <button
|
||||
// type="button"
|
||||
// onClick={onClose}
|
||||
// className="text-white bg-[#90D2F9] w-7 h-7 rounded-full flex items-center justify-center text-sm"
|
||||
// >
|
||||
// ←
|
||||
// </button>
|
||||
// <p className="flex-1 text-center font-montserrat font-extrabold text-[15px] text-white">
|
||||
// Заявка от {request.createdAt}
|
||||
// </p>
|
||||
// <span className="w-7" />
|
||||
// </div>
|
||||
|
||||
// {/* Статус + срок */}
|
||||
// <div className="flex items-center justify-between">
|
||||
// <span
|
||||
// className="inline-flex items-center justify-center px-2 py-0.5 rounded-full font-montserrat text-[8px] font-light text-black"
|
||||
// style={{ backgroundColor: "#71A5E9" }}
|
||||
// >
|
||||
// Выполнена
|
||||
// </span>
|
||||
// <div className="text-right leading-tight">
|
||||
// <p className="font-montserrat text-[8px] text-black">
|
||||
// До {request.date.replace("До ", "")}
|
||||
// </p>
|
||||
// <p className="font-montserrat text-[8px] text-black">
|
||||
// {request.time}
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Название задачи */}
|
||||
// <p className="font-montserrat font-semibold text-[12px] leading-[15px] text-black">
|
||||
// {request.title}
|
||||
// </p>
|
||||
|
||||
// {/* Блок отзыва */}
|
||||
// {isDone && (
|
||||
// <div className="bg-[#72B8E2] rounded-lg p-2 flex flex-col gap-2">
|
||||
// <p className="font-montserrat font-bold text-[10px] text-white">
|
||||
// Отзыв
|
||||
// </p>
|
||||
// <p className="font-montserrat text-[10px] text-white">
|
||||
// Здесь будет текст отзыва с бэка.
|
||||
// </p>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
// {/* Оценка волонтера */}
|
||||
// <div className="mt-1">
|
||||
// <p className="font-montserrat font-semibold text-[12px] text-black mb-1">
|
||||
// Оценить волонтера
|
||||
// </p>
|
||||
// <div className="flex gap-1">
|
||||
// {[1, 2, 3, 4, 5].map((star) => (
|
||||
// <FaStar key={star} className="text-[#F6E168]" size={20} />
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Кнопка оставить отзыв */}
|
||||
// {isDone && (
|
||||
// <button
|
||||
// type="button"
|
||||
// className="mt-3 w-full bg-[#94E067] rounded-lg py-2 flex items-center justify-center"
|
||||
// >
|
||||
// <span className="font-montserrat font-bold text-[14px] text-white">
|
||||
// Оставить отзыв
|
||||
// </span>
|
||||
// </button>
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user