import {useCallback, useEffect, useState} from "react"; import PinIcon from "./icons/PinIcon"; import CloseIcon from "./icons/CloseIcon"; import { EMPTY_FORM, CATEGORIES } from "../config/feedConstants"; import { usePostMutation } from "../hooks/usePostMutation"; import { useImageUpload, ACCEPT } from "../hooks/useImageUpload"; import { validatePostForm } from "../utils/validatePostForm"; import { formatPhone } from "../utils/formatPhone"; import { useYandexMap } from "../hooks/useYandexMap"; import { API_URL } from "../config/authConstants"; const MAX_SIZE_MB = 5; /** * CreateUpdatePost — модальная форма создания и редактирования объявления. * * @param {function} onClose — закрыть модалку без сохранения * @param {function} onSuccess — колбэк после успешного сохранения (перезагружает ленту) * @param {object} [initialData] — если передан, форма работает в режиме редактирования */ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) { // Режим: редактирование (true) или создание нового (false) const isEdit = !!initialData; // Поля формы; при редактировании заполняем из initialData const [form, setForm] = useState( isEdit ? { type: (initialData.type || "loss").toLowerCase(), title: initialData.title || "", description: initialData.description || "", category: initialData.category || "", date: initialData.date || "", phone: initialData.phone || "", address: initialData.city || "" } : EMPTY_FORM ); // Ошибки валидации — ключ совпадает с именем поля const [errors, setErrors] = useState({}); // URL существующей картинки с сервера (только при редактировании) const existingImageUrl = isEdit && initialData.images?.[0] ? `${API_URL}${initialData.images[0]}` : null; // Вся логика работы с фото вынесена в хук const { imageFile, displayImage, imgError, compressing, fileInputRef, handleFileChange, removeImage, } = useImageUpload(existingImageUrl); // Единый хук для создания и обновления const { save, loading: saving, error: apiError } = usePostMutation(); // Внутри компонента, рядом с другими хуками const initialCoords = isEdit && initialData.latitude && initialData.longitude ? { latitude: initialData.latitude, longitude: initialData.longitude } : null; // CreateUpdatePost.jsx const { mapRef, coords, address, addressRef } = useYandexMap( true, initialCoords, isEdit ? (initialData.location || "") : "" ); // После объявления хука useYandexMap: useEffect(() => { if (address) { setForm(prev => ({ ...prev, address })); setErrors(prev => ({ ...prev, address: "" })); } }, [address]); // Единый флаг загрузки — блокирует кнопку отправки const loading = saving || compressing; /** Обновляет одно поле формы и сбрасывает его ошибку валидации. */ const set = useCallback((key, value) => { setForm(prev => ({ ...prev, [key]: value })); setErrors(prev => ({ ...prev, [key]: "" })); }, []); /** Собирает FormData и отправляет запрос на создание или обновление. */ const handleSubmit = async () => { const validationErrors = validatePostForm(form); if (Object.keys(validationErrors).length) { setErrors(validationErrors); return; } if (coords && !addressRef.current) { setErrors(prev => ({ ...prev, map: "Подождите, адрес загружается..." })); return; } const data = new FormData(); data.append("type", form.type.toUpperCase()); // "loss" → "LOSS", "found" → "FOUND" для enum на бэкенде data.append("title", form.title.trim()); data.append("description", form.description.trim()); data.append("category", form.category); data.append("phone", form.phone.trim()); if (form.date) data.append("date", form.date); if (imageFile) data.append("image", imageFile); if (coords) { data.append("latitude", coords.latitude); data.append("longitude", coords.longitude); data.append("address", form.address.trim()); // ← из поля формы } // Адрес без координат тоже отправляем: if (!coords && form.address.trim()) { data.append("address", form.address.trim()); } // save(id, data): id = null → POST (создание), id = string → PUT (обновление) const result = await save(isEdit ? initialData.id : null, data); if (result) { onSuccess(result); onClose(); } }; return ( // Клик по фону (не по самой модалке) закрывает её
{errors.title}
}{imgError}
}JPEG / PNG / WebP · до {MAX_SIZE_MB} МБ
{errors.category}
}Нажмите на карту, чтобы отметить место
} {errors.map &&{errors.map}
}{errors.address}
}{errors.phone}
}{apiError}
}