Import Vector lab project
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
.case-form {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
background: #0a0c0f;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #21262d;
|
||||
}
|
||||
|
||||
.form-header h1 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
color: #e6edf3;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.step-indicator {
|
||||
font-size: 14px;
|
||||
color: #8b949e;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.form-step {
|
||||
background: #161b22;
|
||||
border: 1px solid #21262d;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.form-step h2 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 20px;
|
||||
color: #e6edf3;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #e6edf3;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="number"],
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: #0d1117;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
color: #e6edf3;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #ff4d00;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
padding: 10px 16px;
|
||||
background: #21262d;
|
||||
color: #e6edf3;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.button-group button:hover {
|
||||
background: #30363d;
|
||||
border-color: #ff4d00;
|
||||
}
|
||||
|
||||
.button-group button.selected {
|
||||
background: #ff4d00;
|
||||
color: #ffffff;
|
||||
border-color: #ff4d00;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
background: #0d1117;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.checkbox-label:hover {
|
||||
background: #161b22;
|
||||
border-color: #ff4d00;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #ff4d00;
|
||||
}
|
||||
|
||||
.form-navigation {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.form-navigation button {
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.form-navigation button:first-child {
|
||||
background: #21262d;
|
||||
color: #e6edf3;
|
||||
border: 1px solid #30363d;
|
||||
}
|
||||
|
||||
.form-navigation button:first-child:hover {
|
||||
background: #30363d;
|
||||
}
|
||||
|
||||
.form-navigation button:last-child {
|
||||
background: #ff4d00;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.form-navigation button:last-child:hover {
|
||||
background: #ff6a2e;
|
||||
}
|
||||
|
||||
.form-navigation button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.error-banner {
|
||||
background: #da3633;
|
||||
color: white;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error-banner div {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.error-banner div:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Психотип - улучшенная читаемость */
|
||||
.psychotype-section {
|
||||
background: #161b22;
|
||||
border-left: 4px solid #ff4d00;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 14px;
|
||||
color: #8b949e;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.psychotype-question {
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid #21262d;
|
||||
}
|
||||
|
||||
.psychotype-question:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.question-label {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
color: #e6edf3;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.button-group-vertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.psychotype-btn {
|
||||
padding: 16px 20px;
|
||||
background: #0d1117;
|
||||
color: #e6edf3;
|
||||
border: 2px solid #30363d;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.psychotype-btn:hover {
|
||||
background: #161b22;
|
||||
border-color: #ff4d00;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.psychotype-btn.selected {
|
||||
background: #ff4d00;
|
||||
color: #ffffff;
|
||||
border-color: #ff4d00;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.case-form {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-step {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.form-navigation {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-navigation button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* GPS Button */
|
||||
.gps-button {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
background: #238636;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.gps-button:hover {
|
||||
background: #2ea043;
|
||||
}
|
||||
|
||||
.gps-button:disabled {
|
||||
background: #21262d;
|
||||
color: #8b949e;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Datetime input */
|
||||
input[type="datetime-local"] {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: #0d1117;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
color: #e6edf3;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
input[type="datetime-local"]:focus {
|
||||
outline: none;
|
||||
border-color: #ff4d00;
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import React, { useState } from "react";
|
||||
import Step1Child from "./Step1Child";
|
||||
import Step2Health from "./Step2Health";
|
||||
import Step2bPsychotype from "./Step2bPsychotype";
|
||||
import Step3Circumstances from "./Step3Circumstances";
|
||||
import Step4Environment from "./Step4Environment";
|
||||
import "./CaseForm.css";
|
||||
|
||||
const INITIAL_DATA = {
|
||||
has_diagnosis: false,
|
||||
diagnosis_type: [],
|
||||
cant_swim: false,
|
||||
terrain: [],
|
||||
has_dog: false,
|
||||
extra_resources: []
|
||||
};
|
||||
|
||||
const CaseForm = ({ isMobile = false }) => {
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [formData, setFormData] = useState(INITIAL_DATA);
|
||||
const [errors, setErrors] = useState([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
// Мобильная: 3 шага, Десктоп: 5 шагов
|
||||
const totalSteps = isMobile ? 3 : 5;
|
||||
|
||||
const updateFormData = (updates) => {
|
||||
setFormData(prev => ({ ...prev, ...updates }));
|
||||
};
|
||||
|
||||
const validateStep = (step) => {
|
||||
const newErrors = [];
|
||||
|
||||
if (step === 1) {
|
||||
if (!formData.age_years || formData.age_years < 0 || formData.age_years > 120) {
|
||||
newErrors.push("Укажите корректный возраст");
|
||||
}
|
||||
if (!formData.gender) {
|
||||
newErrors.push("Выберите пол");
|
||||
}
|
||||
}
|
||||
|
||||
// Для десктопа: Шаг 4 = Обстоятельства
|
||||
if (step === 4 && !isMobile) {
|
||||
if (!formData.reason) {
|
||||
newErrors.push("Укажите причину потери");
|
||||
}
|
||||
if (!formData.lost_time) {
|
||||
newErrors.push("Укажите дату и время потери");
|
||||
}
|
||||
if (!formData.last_known_place) {
|
||||
newErrors.push("Укажите последнее известное место");
|
||||
}
|
||||
}
|
||||
|
||||
// Для десктопа: Шаг 5 = Среда и местность
|
||||
if (step === 5 && !isMobile) {
|
||||
if (!formData.terrain || formData.terrain.length === 0) {
|
||||
newErrors.push("Выберите хотя бы один тип местности");
|
||||
}
|
||||
if (!formData.tnp_lat || !formData.tnp_lon) {
|
||||
newErrors.push("Укажите GPS координаты точки потери");
|
||||
}
|
||||
}
|
||||
|
||||
// Для мобильной: Шаг 3 = Обстоятельства (с GPS)
|
||||
if (step === 3 && isMobile) {
|
||||
if (!formData.reason) {
|
||||
newErrors.push("Укажите причину потери");
|
||||
}
|
||||
if (!formData.tnp_lat || !formData.tnp_lon) {
|
||||
newErrors.push("Укажите GPS координаты ТНП");
|
||||
}
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return newErrors.length === 0;
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (validateStep(currentStep)) {
|
||||
setCurrentStep(prev => Math.min(prev + 1, totalSteps));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
setCurrentStep(prev => Math.max(prev - 1, 1));
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!validateStep(currentStep)) return;
|
||||
|
||||
setSubmitting(true);
|
||||
setErrors([]);
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
...formData,
|
||||
clothes_description: [
|
||||
formData.clothes_upper,
|
||||
formData.clothes_lower,
|
||||
formData.clothes_shoes
|
||||
].filter(Boolean).join(", ")
|
||||
};
|
||||
|
||||
// Создаем карточку
|
||||
const response = await fetch("/api/v1/cases", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.detail || "Ошибка при создании карточки");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log("Case created:", result);
|
||||
|
||||
// Перенаправляем на страницу анализа с ID карточки
|
||||
window.location.href = `/analysis/${result.id}`;
|
||||
|
||||
} catch (err) {
|
||||
setErrors([err.message || "Ошибка при отправке данных"]);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderStep = () => {
|
||||
// Мобильная версия: 3 шага
|
||||
// 1 = Ребёнок, 2 = Здоровье, 3 = Обстоятельства
|
||||
if (isMobile) {
|
||||
switch (currentStep) {
|
||||
case 1: return <Step1Child data={formData} updateData={updateFormData} />;
|
||||
case 2: return <Step2Health data={formData} updateData={updateFormData} />;
|
||||
case 3: return <Step3Circumstances data={formData} updateData={updateFormData} />;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Десктопная версия: 5 шагов
|
||||
// 1 = Ребёнок, 2 = Здоровье, 3 = Психотип, 4 = Обстоятельства, 5 = Среда
|
||||
switch (currentStep) {
|
||||
case 1: return <Step1Child data={formData} updateData={updateFormData} />;
|
||||
case 2: return <Step2Health data={formData} updateData={updateFormData} />;
|
||||
case 3: return <Step2bPsychotype data={formData} updateData={updateFormData} />;
|
||||
case 4: return <Step3Circumstances data={formData} updateData={updateFormData} />;
|
||||
case 5: return <Step4Environment data={formData} updateData={updateFormData} />;
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="case-form">
|
||||
<div className="form-header">
|
||||
<h1>Карточка поиска</h1>
|
||||
<div className="step-indicator">
|
||||
Шаг {currentStep} из {totalSteps}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{errors.length > 0 && (
|
||||
<div className="error-banner">
|
||||
{errors.map((err, idx) => (
|
||||
<div key={idx}>{err}</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="form-content">
|
||||
{renderStep()}
|
||||
</div>
|
||||
|
||||
<div className="form-navigation">
|
||||
{currentStep > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn-secondary"
|
||||
onClick={handleBack}
|
||||
disabled={submitting}
|
||||
>
|
||||
Назад
|
||||
</button>
|
||||
)}
|
||||
|
||||
{currentStep < totalSteps ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn-primary"
|
||||
onClick={handleNext}
|
||||
disabled={submitting}
|
||||
>
|
||||
Далее
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn-primary submit-btn"
|
||||
onClick={handleSubmit}
|
||||
disabled={submitting}
|
||||
>
|
||||
{submitting ? "Создание карточки..." : "Создать карточку"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaseForm;
|
||||
@@ -0,0 +1,92 @@
|
||||
import React from "react";
|
||||
|
||||
const Step1Child = ({ data, updateData }) => {
|
||||
return (
|
||||
<div className="form-step">
|
||||
<h2>Шаг 1: Ребёнок</h2>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Имя *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.child_name || ""}
|
||||
onChange={(e) => updateData({ child_name: e.target.value })}
|
||||
placeholder="Имя ребёнка"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Возраст (лет) *</label>
|
||||
<input
|
||||
type="number"
|
||||
value={data.age_years || ""}
|
||||
onChange={(e) => updateData({ age_years: parseInt(e.target.value) })}
|
||||
min="0"
|
||||
max="120"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Пол *</label>
|
||||
<div className="button-group">
|
||||
<button
|
||||
type="button"
|
||||
className={data.gender === "М" ? "selected" : ""}
|
||||
onClick={() => updateData({ gender: "М" })}
|
||||
>
|
||||
Мужской
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.gender === "Ж" ? "selected" : ""}
|
||||
onClick={() => updateData({ gender: "Ж" })}
|
||||
>
|
||||
Женский
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Одежда: верх (цвет, тип) *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.clothes_upper || ""}
|
||||
onChange={(e) => updateData({ clothes_upper: e.target.value })}
|
||||
placeholder="Красная куртка"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Одежда: низ (цвет, тип)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.clothes_lower || ""}
|
||||
onChange={(e) => updateData({ clothes_lower: e.target.value })}
|
||||
placeholder="Синие джинсы"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Обувь (тип, цвет)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.clothes_shoes || ""}
|
||||
onChange={(e) => updateData({ clothes_shoes: e.target.value })}
|
||||
placeholder="Чёрные кроссовки"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Особые приметы</label>
|
||||
<textarea
|
||||
value={data.special_marks || ""}
|
||||
onChange={(e) => updateData({ special_marks: e.target.value })}
|
||||
placeholder="Родинка на щеке, носит очки..."
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step1Child;
|
||||
@@ -0,0 +1,188 @@
|
||||
import React from "react";
|
||||
|
||||
const Step2Health = ({ data, updateData }) => {
|
||||
const toggleDiagnosis = (diagnosis) => {
|
||||
const current = data.diagnosis_type || [];
|
||||
const updated = current.includes(diagnosis)
|
||||
? current.filter((d) => d !== diagnosis)
|
||||
: [...current, diagnosis];
|
||||
updateData({
|
||||
diagnosis_type: updated,
|
||||
has_diagnosis: updated.length > 0
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-step">
|
||||
<h2>Шаг 2: Здоровье и особенности</h2>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Заболевания / диагнозы *</label>
|
||||
<div className="checkbox-group">
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.diagnosis_type?.includes("здоров")}
|
||||
onChange={() => toggleDiagnosis("здоров")}
|
||||
/>
|
||||
Здоров
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.diagnosis_type?.includes("РАС")}
|
||||
onChange={() => toggleDiagnosis("РАС")}
|
||||
/>
|
||||
РАС / Аутизм
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.diagnosis_type?.includes("эпилепсия")}
|
||||
onChange={() => toggleDiagnosis("эпилепсия")}
|
||||
/>
|
||||
Эпилепсия
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.diagnosis_type?.includes("СДВГ")}
|
||||
onChange={() => toggleDiagnosis("СДВГ")}
|
||||
/>
|
||||
СДВГ
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.diagnosis_type?.includes("ЗПР")}
|
||||
onChange={() => toggleDiagnosis("ЗПР")}
|
||||
/>
|
||||
Задержка ПР / ЗПР
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.diagnosis_type?.includes("ДЦП")}
|
||||
onChange={() => toggleDiagnosis("ДЦП")}
|
||||
/>
|
||||
ДЦП
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.diagnosis_type?.includes("слабое_зрение")}
|
||||
onChange={() => toggleDiagnosis("слабое_зрение")}
|
||||
/>
|
||||
Слабое зрение
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.diagnosis_type?.includes("слабый_слух")}
|
||||
onChange={() => toggleDiagnosis("слабый_слух")}
|
||||
/>
|
||||
Слабый слух
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.cant_swim}
|
||||
onChange={(e) => updateData({ cant_swim: e.target.checked })}
|
||||
/>
|
||||
Не умеет плавать
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Уровень физподготовки</label>
|
||||
<div className="button-group">
|
||||
<button
|
||||
type="button"
|
||||
className={data.fitness_level === "low" ? "selected" : ""}
|
||||
onClick={() => updateData({ fitness_level: "low" })}
|
||||
>
|
||||
Низкий
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.fitness_level === "medium" ? "selected" : ""}
|
||||
onClick={() => updateData({ fitness_level: "medium" })}
|
||||
>
|
||||
Средний
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.fitness_level === "high" ? "selected" : ""}
|
||||
onClick={() => updateData({ fitness_level: "high" })}
|
||||
>
|
||||
Высокий
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Наличие транспорта *</label>
|
||||
<div className="button-group">
|
||||
<button
|
||||
type="button"
|
||||
className={data.has_transport === "none" ? "selected" : ""}
|
||||
onClick={() => updateData({ has_transport: "none" })}
|
||||
>
|
||||
Нет
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.has_transport === "bike" ? "selected" : ""}
|
||||
onClick={() => updateData({ has_transport: "bike" })}
|
||||
>
|
||||
Велосипед
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.has_transport === "scooter" ? "selected" : ""}
|
||||
onClick={() => updateData({ has_transport: "scooter" })}
|
||||
>
|
||||
Самокат
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.has_transport === "other" ? "selected" : ""}
|
||||
onClick={() => updateData({ has_transport: "other" })}
|
||||
>
|
||||
Другое
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Телефон *</label>
|
||||
<div className="button-group">
|
||||
<button
|
||||
type="button"
|
||||
className={data.phone_status === "answers" ? "selected" : ""}
|
||||
onClick={() => updateData({ phone_status: "answers" })}
|
||||
>
|
||||
Есть, отвечает
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.phone_status === "silent" ? "selected" : ""}
|
||||
onClick={() => updateData({ phone_status: "silent" })}
|
||||
>
|
||||
Есть, не отвечает
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.phone_status === "none" ? "selected" : ""}
|
||||
onClick={() => updateData({ phone_status: "none" })}
|
||||
>
|
||||
Нет
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step2Health;
|
||||
@@ -0,0 +1,152 @@
|
||||
import React from "react";
|
||||
|
||||
const Step2bPsychotype = ({ data, updateData }) => {
|
||||
const updateAnswer = (question, value) => {
|
||||
updateData({
|
||||
psychotype_answers: {
|
||||
...data.psychotype_answers,
|
||||
[question]: value
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const isSelected = (question, value) => {
|
||||
return data.psychotype_answers?.[question] === value;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-step psychotype-section">
|
||||
<div className="section-header">
|
||||
<h2>Шаг 2б: Поведение ребёнка</h2>
|
||||
<p className="section-subtitle">
|
||||
Спросить у родителей. Помогает уточнить зону поиска. Можно пропустить.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="psychotype-question">
|
||||
<label className="question-label">
|
||||
Как ведёт себя в незнакомой обстановке?
|
||||
</label>
|
||||
<div className="button-group-vertical">
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("unfamiliar_behavior", "explore") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("unfamiliar_behavior", "explore")}
|
||||
>
|
||||
Активно исследует
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("unfamiliar_behavior", "wait") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("unfamiliar_behavior", "wait")}
|
||||
>
|
||||
Ждёт и наблюдает
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("unfamiliar_behavior", "freeze") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("unfamiliar_behavior", "freeze")}
|
||||
>
|
||||
Замирает
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("unfamiliar_behavior", "panic") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("unfamiliar_behavior", "panic")}
|
||||
>
|
||||
Паникует
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="psychotype-question">
|
||||
<label className="question-label">
|
||||
Реакция на стресс и неудачи?
|
||||
</label>
|
||||
<div className="button-group-vertical">
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("stress_reaction", "angry") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("stress_reaction", "angry")}
|
||||
>
|
||||
Злится, кричит
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("stress_reaction", "cry") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("stress_reaction", "cry")}
|
||||
>
|
||||
Плачет, замыкается
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("stress_reaction", "calm") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("stress_reaction", "calm")}
|
||||
>
|
||||
Спокойно ищет выход
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="psychotype-question">
|
||||
<label className="question-label">
|
||||
Лидер или ведомый?
|
||||
</label>
|
||||
<div className="button-group-vertical">
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("leadership", "always_leader") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("leadership", "always_leader")}
|
||||
>
|
||||
Всегда лидер
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("leadership", "sometimes") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("leadership", "sometimes")}
|
||||
>
|
||||
Иногда
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("leadership", "always_follower") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("leadership", "always_follower")}
|
||||
>
|
||||
Всегда ведомый
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="psychotype-question">
|
||||
<label className="question-label">
|
||||
Любит рисковать?
|
||||
</label>
|
||||
<div className="button-group-vertical">
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("risk_taking", "very") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("risk_taking", "very")}
|
||||
>
|
||||
Очень
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("risk_taking", "sometimes") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("risk_taking", "sometimes")}
|
||||
>
|
||||
Иногда
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={isSelected("risk_taking", "no_cautious") ? "psychotype-btn selected" : "psychotype-btn"}
|
||||
onClick={() => updateAnswer("risk_taking", "no_cautious")}
|
||||
>
|
||||
Нет, осторожный
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step2bPsychotype;
|
||||
@@ -0,0 +1,162 @@
|
||||
import React from "react";
|
||||
|
||||
const Step3Circumstances = ({ data, updateData }) => {
|
||||
return (
|
||||
<div className="form-step">
|
||||
<h2>Шаг 3: Обстоятельства потери</h2>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Причина потери *</label>
|
||||
<div className="button-group">
|
||||
<button
|
||||
type="button"
|
||||
className={data.reason === "ушёл_сам" ? "selected" : ""}
|
||||
onClick={() => updateData({ reason: "ушёл_сам" })}
|
||||
>
|
||||
Ушёл сам
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.reason === "заблудился" ? "selected" : ""}
|
||||
onClick={() => updateData({ reason: "заблудился" })}
|
||||
>
|
||||
Заблудился
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.reason === "отстал_от_группы" ? "selected" : ""}
|
||||
onClick={() => updateData({ reason: "отстал_от_группы" })}
|
||||
>
|
||||
Отстал от группы
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.reason === "конфликт" ? "selected" : ""}
|
||||
onClick={() => updateData({ reason: "конфликт" })}
|
||||
>
|
||||
После конфликта
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.reason === "неизвестно" ? "selected" : ""}
|
||||
onClick={() => updateData({ reason: "неизвестно" })}
|
||||
>
|
||||
Неизвестно
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Дата и время потери *</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={data.lost_time || ""}
|
||||
onChange={(e) => updateData({ lost_time: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Прошло времени с момента потери (часов)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={data.elapsed_hours || ""}
|
||||
onChange={(e) => updateData({ elapsed_hours: parseFloat(e.target.value) })}
|
||||
min="0"
|
||||
step="0.5"
|
||||
placeholder="Например: 2.5"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Последнее известное место *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.last_known_place || ""}
|
||||
onChange={(e) => updateData({ last_known_place: e.target.value })}
|
||||
placeholder="Деревня Заречье, у магазина"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Предполагаемое направление движения</label>
|
||||
<div className="button-group">
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "N" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "N" })}
|
||||
>
|
||||
Север
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "NE" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "NE" })}
|
||||
>
|
||||
Северо-Восток
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "E" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "E" })}
|
||||
>
|
||||
Восток
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "SE" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "SE" })}
|
||||
>
|
||||
Юго-Восток
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "S" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "S" })}
|
||||
>
|
||||
Юг
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "SW" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "SW" })}
|
||||
>
|
||||
Юго-Запад
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "W" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "W" })}
|
||||
>
|
||||
Запад
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "NW" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "NW" })}
|
||||
>
|
||||
Северо-Запад
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.direction === "unknown" ? "selected" : ""}
|
||||
onClick={() => updateData({ direction: "unknown" })}
|
||||
>
|
||||
Неизвестно
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Описание обстоятельств</label>
|
||||
<textarea
|
||||
value={data.circumstances_description || ""}
|
||||
onChange={(e) => updateData({ circumstances_description: e.target.value })}
|
||||
placeholder="Подробное описание: что произошло, кто видел последним, что говорил ребёнок..."
|
||||
rows={5}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step3Circumstances;
|
||||
@@ -0,0 +1,274 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
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 handleGetLocation = () => {
|
||||
if (!navigator.geolocation) {
|
||||
alert("Геолокация не поддерживается вашим браузером");
|
||||
return;
|
||||
}
|
||||
|
||||
updateData({ gps_loading: true });
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
updateData({
|
||||
tnp_lat: position.coords.latitude.toFixed(6),
|
||||
tnp_lon: position.coords.longitude.toFixed(6),
|
||||
gps_loading: false
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
alert("Ошибка получения координат: " + error.message);
|
||||
updateData({ gps_loading: false });
|
||||
},
|
||||
{
|
||||
enableHighAccuracy: true,
|
||||
timeout: 10000,
|
||||
maximumAge: 0
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-step">
|
||||
<h2>Шаг 5: Среда и местность</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>
|
||||
<input
|
||||
type="text"
|
||||
value={data.tnp_address || ""}
|
||||
onChange={(e) => updateData({ tnp_address: e.target.value })}
|
||||
placeholder="Минск, ул. Ленина 10"
|
||||
/>
|
||||
<p style={{ fontSize: "12px", color: "#8b949e", marginTop: "4px" }}>
|
||||
Адрес будет преобразован в координаты автоматически
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step4Environment;
|
||||
@@ -0,0 +1,159 @@
|
||||
import React from "react";
|
||||
|
||||
const Step5Resources = ({ data, updateData }) => {
|
||||
const toggleEquipment = (equipment) => {
|
||||
const current = data.equipment || [];
|
||||
const updated = current.includes(equipment)
|
||||
? current.filter((e) => e !== equipment)
|
||||
: [...current, equipment];
|
||||
updateData({ equipment: updated });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-step">
|
||||
<h2>Шаг 5: Ресурсы для поиска</h2>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Количество поисковых групп</label>
|
||||
<input
|
||||
type="number"
|
||||
value={data.search_groups || ""}
|
||||
onChange={(e) => updateData({ search_groups: parseInt(e.target.value) })}
|
||||
min="0"
|
||||
max="100"
|
||||
placeholder="Например: 3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Количество человек в поиске</label>
|
||||
<input
|
||||
type="number"
|
||||
value={data.search_people || ""}
|
||||
onChange={(e) => updateData({ search_people: parseInt(e.target.value) })}
|
||||
min="0"
|
||||
max="1000"
|
||||
placeholder="Например: 25"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Поисковая собака</label>
|
||||
<div className="button-group">
|
||||
<button
|
||||
type="button"
|
||||
className={data.has_dog === true ? "selected" : ""}
|
||||
onClick={() => updateData({ has_dog: true })}
|
||||
>
|
||||
Да
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={data.has_dog === false ? "selected" : ""}
|
||||
onClick={() => updateData({ has_dog: false })}
|
||||
>
|
||||
Нет
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Дополнительное оборудование (можно выбрать несколько)</label>
|
||||
<div className="checkbox-group">
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("дрон")}
|
||||
onChange={() => toggleEquipment("дрон")}
|
||||
/>
|
||||
Дрон / БПЛА
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("тепловизор")}
|
||||
onChange={() => toggleEquipment("тепловизор")}
|
||||
/>
|
||||
Тепловизор
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("квадроцикл")}
|
||||
onChange={() => toggleEquipment("квадроцикл")}
|
||||
/>
|
||||
Квадроцикл / мотовездеход
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("лодка")}
|
||||
onChange={() => toggleEquipment("лодка")}
|
||||
/>
|
||||
Лодка
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("вертолёт")}
|
||||
onChange={() => toggleEquipment("вертолёт")}
|
||||
/>
|
||||
Вертолёт
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("рация")}
|
||||
onChange={() => toggleEquipment("рация")}
|
||||
/>
|
||||
Рации
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("gps")}
|
||||
onChange={() => toggleEquipment("gps")}
|
||||
/>
|
||||
GPS навигаторы
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("медик")}
|
||||
onChange={() => toggleEquipment("медик")}
|
||||
/>
|
||||
Медицинская бригада
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("прожектор")}
|
||||
onChange={() => toggleEquipment("прожектор")}
|
||||
/>
|
||||
Прожекторы / фонари
|
||||
</label>
|
||||
<label className="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.equipment?.includes("мегафон")}
|
||||
onChange={() => toggleEquipment("мегафон")}
|
||||
/>
|
||||
Мегафон / громкоговоритель
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Дополнительная информация о ресурсах</label>
|
||||
<textarea
|
||||
value={data.resources_notes || ""}
|
||||
onChange={(e) => updateData({ resources_notes: e.target.value })}
|
||||
placeholder="Особенности организации поиска, доступные ресурсы, ограничения..."
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step5Resources;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./CaseForm";
|
||||
Reference in New Issue
Block a user