334 lines
12 KiB
React
334 lines
12 KiB
React
import React, { useEffect } from "react";
|
|
import { apiFetch } from "../../api/client";
|
|
|
|
const Step4Environment = ({ data, updateData }) => {
|
|
const toggleTerrain = (terrain) => {
|
|
const current = data.terrain || [];
|
|
const updated = current.includes(terrain)
|
|
? current.filter((t) => t !== terrain)
|
|
: [...current, terrain];
|
|
updateData({ terrain: updated });
|
|
};
|
|
|
|
// Автоматически определяем сезон из даты потери
|
|
useEffect(() => {
|
|
if (data.lost_time && !data.season) {
|
|
const date = new Date(data.lost_time);
|
|
const month = date.getMonth() + 1; // 1-12
|
|
|
|
let season = 'лето';
|
|
if (month >= 3 && month <= 5) season = 'весна';
|
|
else if (month >= 6 && month <= 8) season = 'лето';
|
|
else if (month >= 9 && month <= 11) season = 'осень';
|
|
else season = 'зима'; // 12, 1, 2
|
|
|
|
updateData({ season });
|
|
}
|
|
}, [data.lost_time]);
|
|
|
|
const handleGeocode = async () => {
|
|
const query = (data.tnp_address || "").trim();
|
|
if (query.length < 3) {
|
|
updateData({ geocode_error: "Введите адрес (минимум 3 символа)" });
|
|
return;
|
|
}
|
|
updateData({ geocode_loading: true, geocode_error: null, geocode_found: null });
|
|
try {
|
|
const resp = await apiFetch(`/api/v1/geocode?q=${encodeURIComponent(query)}&limit=1`);
|
|
if (!resp.ok) {
|
|
const err = await resp.json().catch(() => ({}));
|
|
throw new Error(err.detail || `Ошибка ${resp.status}`);
|
|
}
|
|
const result = await resp.json();
|
|
const hit = result.results && result.results[0];
|
|
if (!hit) {
|
|
updateData({
|
|
geocode_loading: false,
|
|
geocode_error: "Адрес не найден. Уточните: «пункт, адрес» без района/области.",
|
|
});
|
|
return;
|
|
}
|
|
updateData({
|
|
tnp_lat: hit.lat.toFixed(6),
|
|
tnp_lon: hit.lng.toFixed(6),
|
|
geocode_loading: false,
|
|
geocode_error: null,
|
|
geocode_found: hit.display_name,
|
|
});
|
|
} catch (e) {
|
|
updateData({ geocode_loading: false, geocode_error: "Не удалось найти адрес: " + e.message });
|
|
}
|
|
};
|
|
|
|
const handleGetLocation = () => {
|
|
if (!navigator.geolocation) {
|
|
updateData({ gps_error: 'Геолокация не поддерживается вашим браузером' });
|
|
return;
|
|
}
|
|
|
|
updateData({ gps_loading: true, gps_error: null });
|
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
(position) => {
|
|
updateData({
|
|
tnp_lat: position.coords.latitude.toFixed(6),
|
|
tnp_lon: position.coords.longitude.toFixed(6),
|
|
gps_loading: false,
|
|
gps_error: null
|
|
});
|
|
},
|
|
(error) => {
|
|
updateData({ gps_error: 'Ошибка получения координат: ' + error.message, gps_loading: false });
|
|
},
|
|
{
|
|
enableHighAccuracy: true,
|
|
timeout: 10000,
|
|
maximumAge: 0
|
|
}
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="form-step">
|
|
<h2>Среда и местность</h2>
|
|
|
|
{/* Сезон определяется автоматически, показываем только для информации */}
|
|
{data.season && (
|
|
<div className="form-group">
|
|
<label>Сезон (определен автоматически)</label>
|
|
<div style={{ padding: "12px", background: "#161b22", borderRadius: "6px", color: "#8b949e" }}>
|
|
{data.season.charAt(0).toUpperCase() + data.season.slice(1)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="form-group">
|
|
<label>Температура (°C)</label>
|
|
<input
|
|
type="number"
|
|
value={data.temperature || ""}
|
|
onChange={(e) => updateData({ temperature: parseFloat(e.target.value) })}
|
|
min="-40"
|
|
max="40"
|
|
step="1"
|
|
placeholder="Например: 15"
|
|
/>
|
|
</div>
|
|
|
|
<div className="form-group">
|
|
<label>Осадки</label>
|
|
<div className="button-group">
|
|
<button
|
|
type="button"
|
|
className={data.precipitation === "нет" ? "selected" : ""}
|
|
onClick={() => updateData({ precipitation: "нет" })}
|
|
>
|
|
Нет
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={data.precipitation === "дождь" ? "selected" : ""}
|
|
onClick={() => updateData({ precipitation: "дождь" })}
|
|
>
|
|
Дождь
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={data.precipitation === "снег" ? "selected" : ""}
|
|
onClick={() => updateData({ precipitation: "снег" })}
|
|
>
|
|
Снег
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={data.precipitation === "туман" ? "selected" : ""}
|
|
onClick={() => updateData({ precipitation: "туман" })}
|
|
>
|
|
Туман
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="form-group">
|
|
<label>Видимость</label>
|
|
<div className="button-group">
|
|
<button
|
|
type="button"
|
|
className={data.visibility === "хорошая" ? "selected" : ""}
|
|
onClick={() => updateData({ visibility: "хорошая" })}
|
|
>
|
|
Хорошая
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={data.visibility === "средняя" ? "selected" : ""}
|
|
onClick={() => updateData({ visibility: "средняя" })}
|
|
>
|
|
Средняя
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={data.visibility === "плохая" ? "selected" : ""}
|
|
onClick={() => updateData({ visibility: "плохая" })}
|
|
>
|
|
Плохая
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="form-group">
|
|
<label>Типы местности (можно выбрать несколько) *</label>
|
|
<div className="checkbox-group">
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("лес")}
|
|
onChange={() => toggleTerrain("лес")}
|
|
/>
|
|
Лес
|
|
</label>
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("поле")}
|
|
onChange={() => toggleTerrain("поле")}
|
|
/>
|
|
Поле
|
|
</label>
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("болото")}
|
|
onChange={() => toggleTerrain("болото")}
|
|
/>
|
|
Болото
|
|
</label>
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("водоём")}
|
|
onChange={() => toggleTerrain("водоём")}
|
|
/>
|
|
Водоём (река, озеро)
|
|
</label>
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("населённый_пункт")}
|
|
onChange={() => toggleTerrain("населённый_пункт")}
|
|
/>
|
|
Населённый пункт
|
|
</label>
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("дороги")}
|
|
onChange={() => toggleTerrain("дороги")}
|
|
/>
|
|
Дороги / тропы
|
|
</label>
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("заброшенные_здания")}
|
|
onChange={() => toggleTerrain("заброшенные_здания")}
|
|
/>
|
|
Заброшенные здания
|
|
</label>
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("сельхоз")}
|
|
onChange={() => toggleTerrain("сельхоз")}
|
|
/>
|
|
Сельхоз угодья
|
|
</label>
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.terrain?.includes("овраги")}
|
|
onChange={() => toggleTerrain("овраги")}
|
|
/>
|
|
Овраги / ямы
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="form-group">
|
|
<label>Координаты точки потери (GPS) *</label>
|
|
<div style={{ marginBottom: "12px" }}>
|
|
<button
|
|
type="button"
|
|
className="gps-button"
|
|
onClick={handleGetLocation}
|
|
disabled={data.gps_loading}
|
|
>
|
|
{data.gps_loading ? "Получение координат..." : "📍 Получить текущие координаты"}
|
|
</button>
|
|
</div>
|
|
<div style={{ display: "flex", gap: "12px" }}>
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{ fontSize: "12px", color: "#8b949e" }}>Широта</label>
|
|
<input
|
|
type="number"
|
|
value={data.tnp_lat || ""}
|
|
onChange={(e) => updateData({ tnp_lat: e.target.value })}
|
|
placeholder="53.9000"
|
|
step="0.000001"
|
|
/>
|
|
</div>
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{ fontSize: "12px", color: "#8b949e" }}>Долгота</label>
|
|
<input
|
|
type="number"
|
|
value={data.tnp_lon || ""}
|
|
onChange={(e) => updateData({ tnp_lon: e.target.value })}
|
|
placeholder="27.5667"
|
|
step="0.000001"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="form-group">
|
|
<label>Или найдите координаты по адресу</label>
|
|
<div style={{ display: "flex", gap: "8px" }}>
|
|
<input
|
|
type="text"
|
|
style={{ flex: 1, height: "44px" }}
|
|
value={data.tnp_address || ""}
|
|
onChange={(e) => updateData({ tnp_address: e.target.value })}
|
|
placeholder="Минск, ул. Ленина 10"
|
|
/>
|
|
<button
|
|
type="button"
|
|
className="gps-button"
|
|
onClick={handleGeocode}
|
|
disabled={data.geocode_loading || !(data.tnp_address || "").trim()}
|
|
style={{ whiteSpace: "nowrap" }}
|
|
>
|
|
{data.geocode_loading ? "Поиск..." : "🔎 Найти координаты"}
|
|
</button>
|
|
</div>
|
|
{data.geocode_error && (
|
|
<p style={{ fontSize: "12px", color: "#f85149", marginTop: "4px" }}>
|
|
{data.geocode_error}
|
|
</p>
|
|
)}
|
|
{data.geocode_found && (
|
|
<p style={{ fontSize: "12px", color: "#3fb950", marginTop: "4px" }}>
|
|
Найдено: {data.geocode_found}
|
|
</p>
|
|
)}
|
|
{!data.geocode_error && !data.geocode_found && (
|
|
<p style={{ fontSize: "12px", color: "#8b949e", marginTop: "4px" }}>
|
|
Формат «пункт, адрес» (район и область не нужны). Координаты заполнятся в поля выше.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Step4Environment;
|