This commit is contained in:
fullofempt
2025-12-14 18:47:14 +05:00
parent bb833d956e
commit 433b9e896c
18 changed files with 2891 additions and 1260 deletions

View File

@@ -1,73 +1,163 @@
"use client";
import React, { useState } from "react";
import { FaBell, FaUser, FaStar } from "react-icons/fa";
import React, { useEffect, useState } from "react";
import { FaBell, FaUser } from "react-icons/fa";
import TabBar from "../components/TabBar";
import RequestDetailsModal from "../components/ModeratorRequestDetailsModal";
const requests = [
{
id: 1,
title: "Приобрести продукты пенсионерке",
status: "На модерации",
statusColor: "#E9D171",
date: "До 28.11.2025",
time: "13:00",
createdAt: "28.11.2025",
description: "Купить продукты и принести по адресу.",
},
{
id: 2,
title: "Приобрести продукты пенсионерке",
status: "На модерации",
statusColor: "#E9D171",
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: "#E9D171",
date: "До 28.11.2025",
time: "13:00",
createdAt: "28.11.2025",
description: "Купить продукты и принести по адресу.",
},
{
id: 5,
title: "Приобрести продукты пенсионерке",
status: "На модерации",
statusColor: "#E9D171",
date: "До 28.11.2025",
time: "13:00",
createdAt: "28.11.2025",
description: "Купить продукты и принести по адресу.",
}
];
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
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 handleOpen = (req) => {
setSelectedRequest(req);
const [moderatorName, setModeratorName] = useState("Модератор");
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
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 handleClose = () => {
setSelectedRequest(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;
setModeratorName(fullName);
} catch {
// дефолт остаётся
}
};
fetchProfile();
}, []);
// список заявок на модерации
useEffect(() => {
const fetchRequestsForModeration = 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}/moderation/requests/pending`, {
method: "GET",
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 : [];
// только pending_moderation
const pending = list.filter(
(item) =>
item.status &&
item.status.request_status === "pending_moderation"
);
const mapped = pending.map((item) => {
const rawStatus = item.status?.request_status || "pending_moderation";
const m = statusMap[rawStatus] || {
label: rawStatus,
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,
description: item.description,
status: m.label,
statusColor: m.color,
createdAt,
date: createdAt,
time,
address: item.address,
city: item.city,
urgency: item.urgency,
rawStatus,
};
});
setRequests(mapped);
setLoading(false);
} catch (e) {
setError(e.message || "Ошибка сети");
setLoading(false);
}
};
fetchRequestsForModeration();
}, []);
const handleOpen = (req) => setSelectedRequest(req);
const handleClose = () => setSelectedRequest(null);
return (
<div className="min-h-screen w-full bg-[#90D2F9] flex justify-center px-4">
@@ -79,7 +169,7 @@ const HistoryRequestPage = () => {
<FaUser className="text-white text-sm" />
</div>
<p className="font-montserrat font-extrabold text-[20px] leading-[11px] text-white">
Александр
{moderatorName}
</p>
</div>
<button
@@ -91,11 +181,29 @@ const HistoryRequestPage = () => {
</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}
@@ -103,10 +211,9 @@ const HistoryRequestPage = () => {
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-light text-black"
className="inline-flex.items-center justify-center px-2 py-0.5 rounded-full font-montserrat text-[12px] font-light text-black"
style={{ backgroundColor: req.statusColor }}
>
{req.status}
@@ -121,12 +228,10 @@ const HistoryRequestPage = () => {
</div>
</div>
{/* Заголовок заявки */}
<p className="font-montserrat font-semibold text-[15px] leading-[18px] text-black mt-1">
{req.title}
</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">
Развернуть
@@ -136,9 +241,11 @@ const HistoryRequestPage = () => {
))}
</main>
{/* Попап */}
{selectedRequest && (
<RequestDetailsModal request={selectedRequest} onClose={handleClose} />
<RequestDetailsModal
request={selectedRequest}
onClose={handleClose}
/>
)}
<TabBar />
@@ -148,91 +255,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>
// );
// };