+
+ {/* Статус + дата/время */}
+
+
+ {isDone ? "Выполнена" : request.status}
+
+
+
+ {request.date}
+
+
+ {request.time}
+
+
+
+
+ {/* Название */}
+
+ {request.title}
+
+
+ {/* Инфо по заявке */}
+
+ {loading && (
+
+ Загрузка информации о заявке...
+
+ )}
+
+ {loadError && !loading && (
+
+ {loadError}
+
+ )}
+
+ {!loading && !loadError && (
+ <>
+ {requestTypeName && (
+
+ Тип: {requestTypeName}
+
+ )}
+
+ {addressLine && (
+
+ Адрес: {addressLine}
+
+ )}
+
+ {details?.urgency && (
+
+ Срочность:{" "}
+ {details.urgency}
+
+ )}
+
+ {requesterName && (
+
+ Заявитель:{" "}
+ {requesterName}
+
+ )}
+
+ {details?.contact_phone && (
+
+ Телефон:{" "}
+ {details.contact_phone}
+
+ )}
+
+ {details?.contact_notes && (
+
+ Комментарий к контакту:{" "}
+ {details.contact_notes}
+
+ )}
+
+
+ Описание:{" "}
+ {fullDescription}
-
-
+ >
+ )}
+
- {/* Карточка */}
-
-
- {/* Статус + дата/время */}
-
-
- {isDone ? "Выполнена" : request.status}
-
-
-
- {request.date}
-
-
- {request.time}
-
-
-
-
- {/* Название */}
-
- {request.title}
+ {/* Отклики волонтёров (как было) */}
+ {!responsesLoading && !responsesError && responses.length > 0 && (
+
+
+ Отклики волонтёров
+
+ {responses.map((resp) => (
+
+
+ Волонтёр:{" "}
+ {resp.volunteer_name ||
+ resp.volunteername ||
+ resp.volunteer?.name}
+
+ {resp.message && (
+
+ Сообщение:{" "}
+ {resp.message}
-
- {/* Инфо по заявке */}
-
- {loading && (
-
- Загрузка информации о заявке...
-
- )}
-
- {loadError && !loading && (
-
- {loadError}
-
- )}
-
- {!loading && !loadError && (
- <>
- {requestTypeName && (
-
- Тип: {requestTypeName}
-
- )}
-
- {addressLine && (
-
- Адрес: {addressLine}
-
- )}
-
- {details?.urgency && (
-
- Срочность:{" "}
- {details.urgency}
-
- )}
-
- {requesterName && (
-
- Заявитель:{" "}
- {requesterName}
-
- )}
-
- {details?.contact_phone && (
-
- Телефон:{" "}
- {details.contact_phone}
-
- )}
-
- {details?.contact_notes && (
-
- Комментарий к контакту:{" "}
- {details.contact_notes}
-
- )}
-
-
- Описание:{" "}
- {fullDescription}
-
- >
- )}
-
-
- {/* Отклики волонтёров */}
- {!responsesLoading && !responsesError && responses.length > 0 && (
-
-
- Отклики волонтёров
-
- {responses.map((resp) => (
-
-
- Волонтёр:{" "}
- {resp.volunteer_name ||
- resp.volunteername ||
- resp.volunteer?.name}
-
- {resp.message && (
-
- Сообщение:{" "}
- {resp.message}
-
- )}
-
-
-
-
- ))}
-
- {acceptError && (
-
- {acceptError}
-
- )}
- {acceptSuccess && (
-
- {acceptSuccess}
-
- )}
-
- )}
-
- {responsesLoading && !responsesError && (
-
- Загрузка откликов волонтёров...
-
- )}
-
- {responsesError && (
-
- {responsesError}
-
- )}
-
- {/* Отклонена */}
- {isRejected && (
- <>
- {request.rejectReason && (
-
-
- Причина отказа
-
-
- {request.rejectReason}
-
-
- )}
-
-
- >
- )}
-
- {/* Отзыв и рейтинг ПОСЛЕ завершения */}
- {isDone && !isRejected && (
- <>
-
-
- Отзыв о волонтёре
-
-
-
-
-
- Оценить волонтера
-
-
- {[1, 2, 3, 4, 5].map((star) => (
-
- ))}
-
-
-
- {reviewError && (
-
- {reviewError}
-
- )}
- {reviewSuccess && (
-
- {reviewSuccess}
-
- )}
- >
- )}
-
- {completeError && (
-
- {completeError}
-
- )}
- {completeSuccess && (
-
- {completeSuccess}
-
- )}
+ )}
+
+
+
+ ))}
+
+ {acceptError && (
+
+ {acceptError}
+
+ )}
+ {acceptSuccess && (
+
+ {acceptSuccess}
+
+ )}
+ )}
- {/* Низ: две разные кнопки */}
- {!isRejected && !isDone && (
-
- )}
+ {responsesLoading && !responsesError && (
+
+ Загрузка откликов волонтёров...
+
+ )}
- {isDone && !isRejected && (
-
-
-
+ {responsesError && (
+
+ {responsesError}
+
+ )}
+
+ {/* Отклонена */}
+ {isRejected && (
+ <>
+ {request.rejectReason && (
+
+
+ Причина отказа
+
+
+ {request.rejectReason}
+
- )}
+ )}
- {isRejected && (
-
- )}
+
+ >
+ )}
+
+ {/* Отзыв и рейтинг — до завершения */}
+ {!isRejected && !isDone && (
+ <>
+
+
+ Отзыв о волонтёре
+
+
+
+
+
+ Оценить волонтера
+
+
+ {[1, 2, 3, 4, 5].map((star) => (
+
+ ))}
+
+
+ >
+ )}
+
+ {completeError && (
+
+ {completeError}
+
+ )}
+ {completeSuccess && (
+
+ {completeSuccess}
+
+ )}
- );
+
+
+ {/* Низ */}
+ {!isRejected && !isDone && (
+
+ )}
+
+ {(isDone || isRejected) && (
+
+ )}
+
+ );
};
export default RequestDetailsModal;
diff --git a/app/components/TabBar.jsx b/app/components/TabBar.jsx
index 5cba616..c4da1b1 100644
--- a/app/components/TabBar.jsx
+++ b/app/components/TabBar.jsx
@@ -17,19 +17,19 @@ const TabBar = () => {
requester: [
{ key: "home", icon: FaHome, href: "/createRequest" },
{ key: "history", icon: FaClock, href: "/historyRequest" },
- { key: "news", icon: FaNewspaper, href: "/news" },
+ { key: "news", icon: FaNewspaper, href: "/notification" },
{ key: "profile", icon: FaCog, href: "/ProfilePage" },
],
volunteer: [
{ key: "home", icon: FaHome, href: "/mainValounter" },
{ key: "history", icon: FaClock, href: "/valounterHistoryRequest" },
- { key: "news", icon: FaNewspaper, href: "/volunteer/news" },
+ { key: "news", icon: FaNewspaper, href: "/notification" },
{ key: "profile", icon: FaCog, href: "/valounterProfilePage" },
],
moderator: [
{ key: "queue", icon: FaHome, href: "/moderatorMain" },
{ key: "history", icon: FaClock, href: "/moderatorHistoryRequest" },
- { key: "news", icon: FaNewspaper, href: "/moderator/news" },
+ { key: "news", icon: FaNewspaper, href: "/notification" },
{ key: "profile", icon: FaCog, href: "/moderatorProfilePage" },
],
};
diff --git a/app/components/ValounterRequestDetailsModal.jsx b/app/components/ValounterRequestDetailsModal.jsx
index 4af9260..f335dcf 100644
--- a/app/components/ValounterRequestDetailsModal.jsx
+++ b/app/components/ValounterRequestDetailsModal.jsx
@@ -4,16 +4,30 @@ import React, { useEffect, useState } from "react";
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 VolunteerRequestDetailsModal = ({ request, onClose }) => {
const [details, setDetails] = useState(null);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState("");
- const normalizedStatus = String(request.rawStatus || request.status || "").toLowerCase();
- const isAccepted = normalizedStatus === "accepted";
- const isInProgress =
- normalizedStatus === "in_progress" || normalizedStatus === "inprogress" || isAccepted;
- const isDone = normalizedStatus === "completed";
+ // статус отклика (из списка /responses/my)
+ const responseRawStatus = String(
+ request.rawStatus || request.status || ""
+ ).toLowerCase();
+ const responseStatus =
+ responseStatusMap[responseRawStatus] ||
+ { label: "Неизвестен", color: "#E2E2E2" };
+
+ const isAccepted = responseRawStatus === "accepted";
+ const isDone = false; // для волонтёра нет completed в ResponseStatus
+ const isInProgress = isAccepted; // принятый отклик ~ «в процессе»
const getAccessToken = () => {
if (typeof window === "undefined") return null;
@@ -38,7 +52,7 @@ const VolunteerRequestDetailsModal = ({ request, onClose }) => {
try {
const res = await fetch(
- `${API_BASE}/requests/${request.request_id || request.id}`,
+ `${API_BASE}/requests/${request.requestId || request.request_id || request.id}`,
{
headers: {
Accept: "application/json",
@@ -78,7 +92,7 @@ const VolunteerRequestDetailsModal = ({ request, onClose }) => {
};
fetchDetails();
- }, [request.request_id, request.id]);
+ }, [request.requestId, request.request_id, request.id]);
if (loading) {
return (
@@ -105,7 +119,9 @@ const VolunteerRequestDetailsModal = ({ request, onClose }) => {
);
}
+ // Тип заявки из RequestDetail
const requestTypeName = details.request_type?.name || "Не указан";
+
const urgencyText = (() => {
switch (details.urgency) {
case "low":
@@ -122,6 +138,7 @@ const VolunteerRequestDetailsModal = ({ request, onClose }) => {
})();
const place = [details.address, details.city].filter(Boolean).join(", ");
+
const requesterName =
(details.requester &&
[details.requester.first_name, details.requester.last_name]
@@ -152,26 +169,6 @@ const VolunteerRequestDetailsModal = ({ request, onClose }) => {
}
}
- const statusColorMap = {
- pending_moderation: "#E9D171",
- approved: "#94E067",
- in_progress: "#E971E1",
- completed: "#71A5E9",
- cancelled: "#FF8282",
- rejected: "#FF8282",
- };
- const statusLabelMap = {
- pending_moderation: "На модерации",
- approved: "Принята",
- in_progress: "В процессе",
- completed: "Выполнена",
- cancelled: "Отменена",
- rejected: "Отклонена",
- };
- const rawReqStatus = String(details.status || "").toLowerCase();
- const badgeColor = statusColorMap[rawReqStatus] || "#E2E2E2";
- const statusLabel = statusLabelMap[rawReqStatus] || "Неизвестен";
-
return (
{/* Хедер */}
@@ -192,13 +189,13 @@ const VolunteerRequestDetailsModal = ({ request, onClose }) => {
{/* Карточка */}
- {/* Статус + дата/время */}
+ {/* Статус ОТКЛИКА + дата/время */}
- {statusLabel}
+ {responseStatus.label}
@@ -237,10 +234,7 @@ const VolunteerRequestDetailsModal = ({ request, onClose }) => {
)}
- {/* Доп. блоки для волонтёра по желанию:
- - данные назначенного волонтёра details.assigned_volunteer
- - статус отклика request.rawStatus / request.status
- */}
+ {/* Блок для принятых откликов */}
{(isAccepted || isInProgress || isDone) && details.assigned_volunteer && (
diff --git a/app/createRequest/page.jsx b/app/createRequest/page.jsx
index cb18b8c..97a546a 100644
--- a/app/createRequest/page.jsx
+++ b/app/createRequest/page.jsx
@@ -41,17 +41,19 @@ const CreateRequestPage = () => {
latitude &&
longitude;
- // профиль
+ 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 saved =
- typeof window !== "undefined"
- ? localStorage.getItem("authUser")
- : null;
- const authUser = saved ? JSON.parse(saved) : null;
- const accessToken = authUser?.accessToken;
+ const accessToken = getAccessToken();
if (!accessToken) return;
try {
@@ -67,32 +69,43 @@ const CreateRequestPage = () => {
return;
}
- const data = await res.json();
+ const data = await res.json(); // UserProfile
const fullName =
- [data.first_name, data.last_name]
- .filter(Boolean)
- .join(" ")
- .trim() || data.email;
+ [data.first_name, data.last_name].filter(Boolean).join(" ").trim() ||
+ data.email;
setUserName(fullName);
+
+ // подставляем только если поля ещё пустые, чтобы не перетирать ручной ввод
+ if (!address && data.address) {
+ setAddress(data.address);
+ }
+ if (!city && data.city) {
+ setCity(data.city);
+ }
+ if (!phone && data.phone) {
+ setPhone(data.phone);
+ }
} catch (e) {
setProfileError("Ошибка загрузки профиля");
}
};
fetchProfile();
- }, []);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []); // однократно при монтировании
+ // геолокация
useEffect(() => {
- if (!("geolocation" in navigator)) {
+ if (typeof navigator === "undefined" || !("geolocation" in navigator)) {
setGeoError("Геолокация не поддерживается браузером");
return;
}
navigator.geolocation.getCurrentPosition(
(pos) => {
- const { latitude, longitude } = pos.coords;
- setLatitude(latitude.toFixed(6));
- setLongitude(longitude.toFixed(6));
+ const { latitude: lat, longitude: lon } = pos.coords;
+ setLatitude(lat.toFixed(6));
+ setLongitude(lon.toFixed(6));
setGeoError("");
},
(err) => {
@@ -115,13 +128,7 @@ const CreateRequestPage = () => {
setError("");
setIsSubmitting(true);
- const saved =
- typeof window !== "undefined"
- ? localStorage.getItem("authUser")
- : null;
- const authUser = saved ? JSON.parse(saved) : null;
- const accessToken = authUser?.accessToken;
-
+ const accessToken = getAccessToken();
if (!accessToken) {
setError("Вы не авторизованы");
setIsSubmitting(false);
@@ -132,7 +139,7 @@ const CreateRequestPage = () => {
const desired_completion_date = desiredDateTime.toISOString();
const body = {
- request_type_id: 1, // можно потом вынести в селект
+ request_type_id: 1, // TODO: вынести в селект типов
title,
description,
latitude: Number(latitude),
@@ -236,7 +243,7 @@ const CreateRequestPage = () => {
{/* Адрес */}
-
+
@@ -258,7 +265,7 @@ const CreateRequestPage = () => {
type="text"
value={city}
onChange={(e) => setCity(e.target.value)}
- className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm font-mонтserrat text-white placeholder:text-white/70 outline-none focus:ring-2 focus:ring-blue-200"
+ className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm font-montserrat text-white placeholder:text-white/70 outline-none focus:ring-2 focus:ring-blue-200"
placeholder="Например: Пермь"
/>
@@ -287,7 +294,7 @@ const CreateRequestPage = () => {
step="0.000001"
value={longitude}
onChange={(e) => setLongitude(e.target.value)}
- className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm font-mонтserrat text-white placeholder:text.white/70 outline-none focus:ring-2 focus:ring-blue-200"
+ className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm font-montserrat text-white placeholder:text-white/70 outline-none focus:ring-2 focus:ring-blue-200"
placeholder="37.618423"
/>
@@ -299,9 +306,8 @@ const CreateRequestPage = () => {
)}
-
{/* Дата и Время */}
-
+
{/* Срочность */}
-
+
{/* Телефон для связи */}
-
+
@@ -352,65 +358,48 @@ const CreateRequestPage = () => {
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
- className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm.font-montserrat text-white placeholder:text.white/70 outline-none focus:ring-2 focus:ring-blue-200"
+ className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm font-montserrat text-white placeholder:text-white/70 outline-none focus:ring-2 focus:ring-blue-200"
placeholder="+7 900 000 00 00"
/>
{/* Описание */}
-
-