import { useCallback, useEffect, useState } from "react"; import CloseIcon from "../components/icons/CloseIcon"; import RulesModal from "./RulesModal"; import { EMPTY_FORM, CATEGORY_KEYS } from "../config/feedConstants"; import { useTranslation } from 'react-i18next'; import { usePostMutation } from "../hooks/usePostMutation"; import { useImageUpload, ACCEPT, MAX_SIZE_MB } from "../hooks/useImageUpload"; import { validatePostForm } from "../utils/validatePostForm"; import { formatPhone } from "../utils/formatPhone"; import { useYandexMap } from "../hooks/useYandexMap"; import { useGeolocation, GEO_STATUS } from "../hooks/useGeolocation"; import MapModal from "./MapModal"; import ClearableField from "../components/ClearableField"; /** * CreateUpdatePost — модальная форма создания и редактирования объявления. * * @param {function} onClose — закрыть модалку без сохранения * @param {function} onSuccess — колбэк после успешного сохранения (перезагружает ленту) * @param {object} [initialData] — если передан, форма работает в режиме редактирования */ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) { const isEdit = !!initialData; const { t } = useTranslation(); 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 || "", rulesAccepted: true, reward: initialData.reward ?? false, rewardText: initialData.rewardText || (initialData.reward ? t('post.rewardDefault') : "") } : EMPTY_FORM ); const [rulesModalOpen, setRulesModalOpen] = useState(false); const [mapModalOpen, setMapModalOpen] = useState(false); const [errors, setErrors] = useState({}); const [step, setStep] = useState(1); // 1 — первая часть, 2 — вторая часть const { images: uploadedImages, imgError, compressing, fileInputRef, handleFileChange, removeImage, getFilesForUpload, getExistingUrlsToKeep, } = useImageUpload(isEdit ? initialData.images : []); const { save, loading: saving, error: apiError } = usePostMutation(); const [resultModal, setResultModal] = useState(null); // null | "success" | "blocked" const initialCoords = isEdit && initialData.latitude && initialData.longitude ? { latitude: initialData.latitude, longitude: initialData.longitude } : null; const { mapRef, coords, address, addressRef, loading: mapLoading, moveTo } = useYandexMap( mapModalOpen, initialCoords, isEdit ? (initialData.location || "") : "" ); const { coords: geoCoords, status: geoStatus, requestLocation: requestGeoLocation } = useGeolocation(); // При открытии окна карты запрашиваем геолокацию; если пользователь дал // разрешение — автоматически центрируем карту и ставим маркер на его место. // При отказе/недоступности API ничего не делаем — остаётся дефолтный центр. useEffect(() => { if (mapModalOpen) { requestGeoLocation(); } }, [mapModalOpen, requestGeoLocation]); useEffect(() => { if (mapModalOpen && geoStatus === GEO_STATUS.SUCCESS && geoCoords) { moveTo(geoCoords.latitude, geoCoords.longitude); } }, [mapModalOpen, geoStatus, geoCoords, moveTo]); useEffect(() => { if (address) { setForm(prev => ({ ...prev, address })); // Адрес пришёл — очищаем и ошибку поля, и "карта грузится", // иначе последняя может остаться висеть на экране навсегда. setErrors(prev => ({ ...prev, address: "", map: "" })); } }, [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: t('post.mapLoading') })); return; } const data = new FormData(); data.append("type", form.type.toUpperCase()); data.append("title", form.title.trim()); data.append("description", form.description.trim()); data.append("category", form.category); data.append("phone", form.phone.trim()); data.append("reward", form.reward ? "true" : "false"); if (form.reward) data.append("rewardText", (form.rewardText || "").trim()); if (form.date) data.append("date", form.date); const files = getFilesForUpload(); files.forEach(f => data.append("images", f)); if (isEdit) { const existing = getExistingUrlsToKeep(); if (existing.length > 0) { data.append("existingImages", JSON.stringify(existing)); } } 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()); } data.append("rulesAccepted", form.rulesAccepted ? "true" : "false"); const result = await save(isEdit ? initialData.id : null, data); if (!result) return; if (result.blocked) { setResultModal("blocked"); return; } if (result.data) { setResultModal("success"); } }; return ( <>
e.target === e.currentTarget && !loading && onClose()} >
{isEdit ? t('post.editTitle') : t('post.newTitle')}
{step === 1 && ( <> {/* Тип: Потеря / Находка */}
{/* Название */}
set("title", "")} visible={!!form.title}> set("title", e.target.value)} /> {errors.title &&

{errors.title}

}
{/* Описание */}
set("description", "")} visible={!!form.description}>