Files
frontend/app/createRequest/page.jsx
fullofempt e4bfbd30cc end
2025-12-15 13:18:18 +05:00

416 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { FaBell, FaUser } from "react-icons/fa";
import TabBar from "../components/TabBar";
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
const CreateRequestPage = () => {
const router = useRouter();
const [title, setTitle] = useState("");
const [date, setDate] = useState(""); // desired_completion_date
const [time, setTime] = useState("");
const [description, setDescription] = useState("");
const [note, setNote] = useState(""); // contact_notes
const [address, setAddress] = useState("");
const [city, setCity] = useState("");
const [phone, setPhone] = useState("");
const [urgency, setUrgency] = useState("medium");
const [latitude, setLatitude] = useState("");
const [longitude, setLongitude] = useState("");
const [geoError, setGeoError] = useState("");
const [error, setError] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [userName, setUserName] = useState("Пользователь");
const [profileError, setProfileError] = useState("");
const isFormValid =
title &&
date &&
time &&
description &&
address &&
city &&
urgency &&
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 accessToken = getAccessToken();
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(); // UserProfile
const fullName =
[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 (typeof navigator === "undefined" || !("geolocation" in navigator)) {
setGeoError("Геолокация не поддерживается браузером");
return;
}
navigator.geolocation.getCurrentPosition(
(pos) => {
const { latitude: lat, longitude: lon } = pos.coords;
setLatitude(lat.toFixed(6));
setLongitude(lon.toFixed(6));
setGeoError("");
},
(err) => {
console.error("Geolocation error:", err);
setGeoError("Не удалось получить геолокацию, введите координаты вручную");
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 60000,
}
);
}, []);
const handleSubmit = async (e) => {
e.preventDefault();
if (!isFormValid || !API_BASE) return;
try {
setError("");
setIsSubmitting(true);
const accessToken = getAccessToken();
if (!accessToken) {
setError("Вы не авторизованы");
setIsSubmitting(false);
return;
}
const desiredDateTime = new Date(`${date}T${time}:00`);
const desired_completion_date = desiredDateTime.toISOString();
const body = {
request_type_id: 1, // TODO: вынести в селект типов
title,
description,
latitude: Number(latitude),
longitude: Number(longitude),
address,
city,
desired_completion_date,
urgency, // low | medium | high | urgent
contact_phone: phone || null,
contact_notes: note || null,
};
const res = await fetch(`${API_BASE}/requests`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify(body),
});
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);
setIsSubmitting(false);
return;
}
const created = await res.json();
console.log("Заявка создана:", created);
router.push("/home");
} catch (err) {
setError(err.message || "Ошибка сети");
setIsSubmitting(false);
}
};
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>
<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"
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 && (
<div className="mb-2 bg-red-500 text-white text-xs font-montserrat px-3 py-2 rounded-lg">
{error}
</div>
)}
{/* Карточка с формой */}
<main className="bg-white rounded-xl p-4 flex flex-col gap-3">
<form onSubmit={handleSubmit} className="flex flex-col">
{/* Что сделать */}
<div className="flex flex-col gap-1">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Что сделать
</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(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"
placeholder="Опишите основную задачу"
/>
</div>
{/* Адрес */}
<div className="flex flex-col gap-1 mt-2">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Адрес
</label>
<input
type="text"
value={address}
onChange={(e) => setAddress(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"
placeholder="ул. Ленина, д. 10, кв. 5"
/>
</div>
{/* Город */}
<div className="flex flex-col gap-1">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Город
</label>
<input
type="text"
value={city}
onChange={(e) => setCity(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"
placeholder="Например: Пермь"
/>
</div>
{/* Координаты */}
<div className="flex gap-3">
<div className="flex-1 flex flex-col gap-1">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Широта (lat)
</label>
<input
type="number"
step="0.000001"
value={latitude}
onChange={(e) => setLatitude(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"
placeholder="55.751244"
/>
</div>
<div className="flex-1 flex flex-col gap-1">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Долгота (lon)
</label>
<input
type="number"
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-montserrat text-white placeholder:text-white/70 outline-none focus:ring-2 focus:ring-blue-200"
placeholder="37.618423"
/>
</div>
</div>
{geoError && (
<p className="mt-1 text-[10px] text-yellow-200 font-montserrat">
{geoError}
</p>
)}
{/* Дата и Время */}
<div className="flex gap-3 mt-2">
<div className="flex-1 flex flex-col gap-1">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Дата
</label>
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm font-montserrat text-white outline-none focus:ring-2 focus:ring-blue-200"
/>
</div>
<div className="flex-1 flex flex-col gap-1">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Время
</label>
<input
type="time"
value={time}
onChange={(e) => setTime(e.target.value)}
className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm font-montserrat text-white outline-none focus:ring-2 focus:ring-blue-200"
/>
</div>
</div>
{/* Срочность */}
<div className="flex flex-col gap-1 mt-2">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Срочность
</label>
<select
value={urgency}
onChange={(e) => setUrgency(e.target.value)}
className="w-full bg-[#72B8E2] rounded-lg px-3 py-3 text-sm font-montserrat text-white outline-none focus:ring-2 focus:ring-blue-200"
>
<option value="low">Низкая</option>
<option value="medium">Средняя</option>
<option value="high">Высокая</option>
<option value="urgent">Срочно</option>
</select>
</div>
{/* Телефон для связи */}
<div className="flex flex-col gap-1 mt-2">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Телефон для связи
</label>
<input
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"
placeholder="+7 900 000 00 00"
/>
</div>
{/* Описание */}
<div className="flex flex-col gap-1 mt-2">
<label className="font-montserrat font-bold text-[10px] text-white">
Описание
</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={3}
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 resize-none"
placeholder="Подробно опишите, что нужно сделать"
/>
</div>
{/* Дополнительно */}
<div className="flex flex-col gap-1 mt-2">
<label className="font-montserrat font-bold text-[10px] text-white/90">
Дополнительно
</label>
<textarea
value={note}
onChange={(e) => setNote(e.target.value)}
rows={2}
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 resize-none"
placeholder="Комментарий (необязательно)"
/>
</div>
{/* Кнопка Отправить */}
<button
type="submit"
disabled={!isFormValid || isSubmitting}
className={`mt-5 w-full rounded-lg py-3 text-center font-montserrat font-bold text-sm transition-colors ${
isFormValid && !isSubmitting
? "bg-[#94E067] text-white hover:bg-green-600"
: "bg-[#94E067]/60 text-white/70 cursor-not-allowed"
}`}
>
{isSubmitting ? "Отправка..." : "Отправить"}
</button>
</form>
</main>
<TabBar />
</div>
</div>
);
};
export default CreateRequestPage;