fixed lan and lon on post

This commit is contained in:
SlimusMinus
2026-10-06 01:24:28 +03:00
parent 3cfe389144
commit bd3f997677
6 changed files with 183 additions and 18 deletions

View File

@@ -1,7 +1,8 @@
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import "../styles/index.css";
import { getImageUrl } from "../config/authConstants";
import { getImageUrl, API_URL } from "../config/authConstants";
import { apiFetch } from "../api/apiClient";
import Sidebar from "../components/Sidebar";
import BottomNav from "../components/nav/BottomNav";
import RightSidebar from "../components/RightSidebar";
@@ -73,11 +74,14 @@ export default function MainFeed() {
};
const { fetchProfile } = useProfile();
const { cities } = useCities() as { cities: string[]; loading: boolean; error: string };
// t в этом файле отдаёт DefaultTFuncReturn (string | ReactElement), поэтому
// состояние ошибки типизируем как текст и приводим результат через String().
const { t } = useTranslation();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const modals = useModals() as any;
const [sidebarOpen, setSidebarOpen] = useState(false);
const [locationError, setLocationError] = useState("");
const [activeNav, setActiveNav] = useState("home");
const [typeFilter, setTypeFilter] = useState<"loss" | "found" | null>(null);
const [selectedCities, setSelectedCities] = useState<string[]>([]);
@@ -302,18 +306,65 @@ export default function MainFeed() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMapView, feed.ads, cityFeed.cityAds, feed.loading, cityFeed.cityLoading]);
// Клик по локации на карточке — карта с фокусом на этом объявлении
const handleLocationClick = useCallback((ad: Record<string, unknown>) => {
// Приводит значение к числу координат или null.
//
// Number(null) === 0, а не NaN: без явной проверки на null/пустую строку
// объявление без координат уехало бы в точку (0, 0) вместо догрузки из
// /detail/{id}. Ноль (экватор/Гринвич) сам по себе допустим, поэтому
// отбрасываем только отсутствие значения.
const toCoordinate = (value: unknown): number | null => {
if (value === null || value === undefined) return null;
if (typeof value === "string" && value.trim() === "") return null;
const num = Number(value);
return Number.isFinite(num) ? num : null;
};
// Клик по локации на карточке — карта с фокусом на этом объявлении.
//
// Координаты берём из карточки, а если их там нет (объявление создано без
// карты, либо лента отдаёт null) — догружаем их из /api/v1/posts/detail/{id}.
// Без этого карта открывалась на дефолтном центре Москвы: MapView получает
// lat/lng из URL, а без них инициализируется на MAP_CENTER, и маркера
// объявления в загруженных границах нет — фокус не к чему было привязать.
const handleLocationClick = useCallback(async (ad: Record<string, unknown>) => {
saveFeedState();
setLocationError("");
let focusLat = toCoordinate(ad.latitude);
let focusLng = toCoordinate(ad.longitude);
if (focusLat === null || focusLng === null) {
try {
// apiFetch, а не fetch: /detail/{id} владельцу отдаёт его собственные
// неопубликованные посты, а анонимному запросу бэкенд ответит 404.
const res = await apiFetch(`${API_URL}/api/v1/posts/detail/${ad.id}`);
if (res.ok) {
const data = await res.json();
const detailLat = toCoordinate(data?.latitude);
const detailLng = toCoordinate(data?.longitude);
if (detailLat !== null && detailLng !== null) {
focusLat = detailLat;
focusLng = detailLng;
}
}
} catch {
focusLat = null;
focusLng = null;
}
}
if (focusLat === null || focusLng === null) {
setLocationError(String(t('post.mapNoCoords')));
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const params = new URLSearchParams({ view: "map", post: ad.id as any });
if (ad.latitude != null && ad.longitude != null) {
params.set("lat", String(ad.latitude));
params.set("lng", String(ad.longitude));
}
params.set("lat", String(focusLat));
params.set("lng", String(focusLng));
setActiveNav("map");
navigate(`/feed?${params.toString()}`);
}, [navigate, saveFeedState]);
}, [navigate, saveFeedState, t]);
const handleProfileClick = useCallback(() => {
if (!isAuthenticated) modals.open("auth");
@@ -511,7 +562,7 @@ export default function MainFeed() {
}}
onLocationClick={(ad: Record<string, unknown>) => {
modals.close();
handleLocationClick(ad);
void handleLocationClick(ad);
}}
onAuthRequired={() => modals.open("auth")}
/>
@@ -522,9 +573,12 @@ export default function MainFeed() {
{/* Ошибки удаления/отметки «найдено»: без этого тоста неудачный запрос
выглядел бы как «ничего не произошло». */}
<Toast
message={actions.error}
message={actions.error || locationError}
type="error"
onDismiss={actions.clearError}
onDismiss={() => {
actions.clearError();
setLocationError("");
}}
/>
<BottomNav