Files
vector/frontend/src/components/CaseForm/Step4Environment.jsx
T

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;