WIP API
This commit is contained in:
@@ -1,67 +1,170 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FaBell, FaUser } from "react-icons/fa";
|
||||
import TabBar from "../components/TabBar";
|
||||
import ModeratorRequestModal from "../components/ModeratorRequestDetailsModal";
|
||||
|
||||
// история для модератора: только Принята / Отклонена
|
||||
const requests = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Приобрести продукты пенсионерке",
|
||||
status: "Принята",
|
||||
statusColor: "#94E067",
|
||||
date: "28.11.2025",
|
||||
time: "13:00",
|
||||
createdAt: "28.11.2025",
|
||||
fullName: "Клавдия Березова",
|
||||
address: "г. Пермь, ул. Ленина 50",
|
||||
description: "Купить продукты и принести по указанному адресу.",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Приобрести медикаменты",
|
||||
status: "Отклонена",
|
||||
statusColor: "#E06767",
|
||||
date: "27.11.2025",
|
||||
time: "15:30",
|
||||
createdAt: "27.11.2025",
|
||||
fullName: "Иванова Анна Петровна",
|
||||
address: "г. Пермь, ул. Пушкина 24",
|
||||
description: "Приобрести необходимые лекарства в ближайшей аптеке.",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Сопроводить до поликлиники",
|
||||
status: "Принята",
|
||||
statusColor: "#94E067",
|
||||
date: "26.11.2025",
|
||||
time: "10:00",
|
||||
createdAt: "26.11.2025",
|
||||
fullName: "Сидоров Николай",
|
||||
address: "г. Пермь, ул. Куйбышева 95",
|
||||
description: "Помочь добраться до поликлиники и обратно.",
|
||||
},
|
||||
];
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
const statusMap = {
|
||||
approved: { label: "Принята", color: "#94E067" },
|
||||
rejected: { label: "Отклонена", color: "#E06767" },
|
||||
};
|
||||
|
||||
const HistoryRequestModeratorPage = () => {
|
||||
const [requests, setRequests] = useState([]);
|
||||
const [selectedRequest, setSelectedRequest] = useState(null);
|
||||
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;
|
||||
};
|
||||
|
||||
// профиль модератора
|
||||
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();
|
||||
}, []);
|
||||
|
||||
// история модерации: только approved / rejected
|
||||
useEffect(() => {
|
||||
const fetchHistory = 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/my`, {
|
||||
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 : [];
|
||||
|
||||
// RequestListItem: id, title, description, address, city, urgency, status, requester_name, request_type_name, created_at
|
||||
// оставляем только approved / rejected
|
||||
const filtered = list.filter(
|
||||
(item) => item.status === "approved" || item.status === "rejected"
|
||||
);
|
||||
|
||||
const mapped = filtered.map((item) => {
|
||||
const m = statusMap[item.status] || {
|
||||
label: item.status,
|
||||
color: "#E2E2E2",
|
||||
};
|
||||
|
||||
const created = new Date(item.created_at);
|
||||
const date = 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,
|
||||
date,
|
||||
time,
|
||||
createdAt: date,
|
||||
fullName: item.requester_name,
|
||||
address: item.city
|
||||
? `${item.city}, ${item.address}`
|
||||
: item.address,
|
||||
rawStatus: item.status, // "approved" | "rejected"
|
||||
};
|
||||
});
|
||||
|
||||
setRequests(mapped);
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
setError(e.message || "Ошибка сети");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchHistory();
|
||||
}, []);
|
||||
|
||||
const handleOpen = (req) => {
|
||||
setSelectedRequest(req);
|
||||
// пробрасываем rawStatus, чтобы модалка знала настоящий статус
|
||||
setSelectedRequest({
|
||||
...req,
|
||||
status: req.rawStatus,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setSelectedRequest(null);
|
||||
};
|
||||
|
||||
const handleApprove = (req) => {
|
||||
console.log("Подтверждение принятой заявки (история):", req.id);
|
||||
};
|
||||
|
||||
const handleReject = ({ request, reason }) => {
|
||||
console.log("Просмотр отклонённой заявки (история):", request.id, reason);
|
||||
const handleModeratedUpdate = (updated) => {
|
||||
setRequests((prev) =>
|
||||
prev.map((r) => (r.id === updated.id ? { ...r, rawStatus: updated.status } : r))
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -74,7 +177,7 @@ const HistoryRequestModeratorPage = () => {
|
||||
<FaUser className="text-white text-sm" />
|
||||
</div>
|
||||
<p className="font-montserrat font-extrabold text-[20px] leading-[22px] text-white">
|
||||
Модератор
|
||||
{moderatorName}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -89,16 +192,33 @@ const HistoryRequestModeratorPage = () => {
|
||||
История заявок
|
||||
</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"
|
||||
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"
|
||||
@@ -106,7 +226,7 @@ const HistoryRequestModeratorPage = () => {
|
||||
>
|
||||
{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}
|
||||
</p>
|
||||
@@ -116,12 +236,10 @@ const HistoryRequestModeratorPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Заголовок заявки */}
|
||||
<p className="font-montserrat font-semibold text-[15px] leading-[18px] text-black mt-1">
|
||||
{req.title}
|
||||
</p>
|
||||
|
||||
{/* Краткое ФИО/адрес */}
|
||||
<p className="font-montserrat text-[11px] text-black/80">
|
||||
{req.fullName}
|
||||
</p>
|
||||
@@ -129,8 +247,7 @@ const HistoryRequestModeratorPage = () => {
|
||||
{req.address}
|
||||
</p>
|
||||
|
||||
{/* Кнопка "Развернуть" */}
|
||||
<div className="mt-2 w-full bg-[#94E067] rounded-lg py-3 flex items-center justify-center">
|
||||
<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>
|
||||
@@ -139,13 +256,11 @@ const HistoryRequestModeratorPage = () => {
|
||||
))}
|
||||
</main>
|
||||
|
||||
{/* Попап модератора */}
|
||||
{selectedRequest && (
|
||||
<ModeratorRequestModal
|
||||
request={selectedRequest}
|
||||
onClose={handleClose}
|
||||
onApprove={handleApprove}
|
||||
onReject={handleReject}
|
||||
onModerated={handleModeratedUpdate}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user