feat: Add translation on 3 languages for sight page

This commit is contained in:
2025-06-01 00:34:59 +03:00
parent 0d9bbb140f
commit 87386c6a73
22 changed files with 768 additions and 732 deletions

View File

@ -6,46 +6,45 @@ import {
Typography,
Paper,
Tooltip,
Dialog,
DialogTitle,
MenuItem,
Menu as MuiMenu,
} from "@mui/material";
import {
BackButton,
sightsStore,
TabPanel,
languageStore,
CreateSight,
Language,
cityStore,
CoordinatesInput,
editSightStore,
SelectMediaDialog,
PreviewMediaDialog,
SightLanguageInfo,
SightCommonInfo,
} from "@shared";
import { LanguageSwitcher } from "@widgets";
import { Info, ImagePlus } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useRef, useState } from "react";
import { useEffect, useState } from "react";
// Мокап для всплывающей подсказки
export const InformationTab = observer(
({ value, index }: { value: number; index: number }) => {
const { cities } = cityStore;
const [isMediaModalOpen, setIsMediaModalOpen] = useState(false);
const [, setIsMediaModalOpen] = useState(false);
const [mediaId, setMediaId] = useState<string>("");
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
const { createSight, updateCreateSight, createSightAction } = sightsStore;
const { sightInfo } = editSightStore;
const [city, setCity] = useState<number>(sightInfo.city_id ?? 0);
const [coordinates, setCoordinates] = useState({
latitude: sightInfo.latitude ?? 0,
longitude: sightInfo.longitude ?? 0,
});
const { language } = languageStore;
const { sight, updateSightInfo } = editSightStore;
const data = sight[language];
const common = sight.common;
const [, setCity] = useState<number>(common.city_id ?? 0);
const [coordinates, setCoordinates] = useState<string>(`0 0`);
const token = localStorage.getItem("token");
// Menu state for each media button
@ -63,6 +62,14 @@ export const InformationTab = observer(
setActiveMenuType(type);
};
useEffect(() => {
// Показывать только при инициализации (не менять при ошибках пользователя)
if (common.latitude !== 0 || common.longitude !== 0) {
setCoordinates(`${common.latitude} ${common.longitude}`);
}
// если координаты обнулились — оставить поле как есть
}, [common.latitude, common.longitude]);
const handleMenuClose = () => {
setMenuAnchorEl(null);
setActiveMenuType(null);
@ -77,7 +84,7 @@ export const InformationTab = observer(
handleMenuClose();
};
const handleMediaSelect = (selectedMediaId: string) => {
const handleMediaSelect = () => {
if (!activeMenuType) return;
// Close the dialog
@ -87,17 +94,10 @@ export const InformationTab = observer(
const handleChange = (
language: Language,
content: Partial<CreateSight[Language]>
content: Partial<SightLanguageInfo | SightCommonInfo>,
common: boolean = false
) => {
updateCreateSight(language, content);
};
const handleSave = async () => {
try {
await createSightAction(city, coordinates);
} catch (error) {
console.error(error);
}
updateSightInfo(language, content, common);
};
return (
@ -135,7 +135,7 @@ export const InformationTab = observer(
>
<TextField
label={`Название (${language.toUpperCase()})`}
value={sightInfo[language]?.info?.name ?? ""}
value={data.name}
onChange={(e) => {
handleChange(language as Language, {
name: e.target.value,
@ -147,7 +147,7 @@ export const InformationTab = observer(
<TextField
label="Адрес"
value={sightInfo[language]?.info?.address ?? ""}
value={data.address}
onChange={(e) => {
handleChange(language as Language, {
address: e.target.value,
@ -158,20 +158,57 @@ export const InformationTab = observer(
/>
<Autocomplete
options={cities}
value={cities.find((city) => city.id === sightInfo.city_id)}
options={cities ?? []}
value={
cities.find((city) => city.id === common.city_id) ?? null
}
getOptionLabel={(option) => option.name}
onChange={(_, value) => {
setCity(value?.id ?? 0);
handleChange(
language as Language,
{
city_id: value?.id ?? 0,
},
true
);
}}
renderInput={(params) => (
<TextField {...params} label="Город" />
)}
/>
<CoordinatesInput
initialValue={coordinates}
setValue={setCoordinates}
<TextField
label="Координаты"
value={coordinates}
onChange={(e) => {
const input = e.target.value;
setCoordinates(input); // показываем как есть
const [latStr, lonStr] = input.split(/\s+/); // учитываем любые пробелы
const lat = parseFloat(latStr);
const lon = parseFloat(lonStr);
// Проверка, что обе координаты валидные числа
const isValidLat = !isNaN(lat);
const isValidLon = !isNaN(lon);
if (isValidLat && isValidLon) {
handleChange(language as Language, {
latitude: lat,
longitude: lon,
});
} else {
handleChange(language as Language, {
latitude: 0,
longitude: 0,
});
}
}}
fullWidth
variant="outlined"
placeholder="Введите координаты в формате: широта долгота"
/>
</Box>
@ -222,11 +259,9 @@ export const InformationTab = observer(
justifyContent: "center",
borderRadius: 1,
mb: 1,
cursor: editSightStore.sightInfo?.thumbnail
? "pointer"
: "default", // Only clickable if there's an image
cursor: common.thumbnail ? "pointer" : "default",
"&:hover": {
backgroundColor: editSightStore.sightInfo?.thumbnail
backgroundColor: common.thumbnail
? "red.300"
: "grey.200",
},
@ -235,16 +270,16 @@ export const InformationTab = observer(
setIsMediaModalOpen(true);
}}
>
{editSightStore.sightInfo?.thumbnail ? (
{common.thumbnail ? (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
editSightStore.sightInfo?.thumbnail
common.thumbnail
}/download?token=${token}`}
alt="Логотип"
style={{ maxWidth: "100%", maxHeight: "100%" }}
onClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(editSightStore.sightInfo?.thumbnail);
setMediaId(common.thumbnail);
}}
/>
) : (
@ -297,31 +332,28 @@ export const InformationTab = observer(
justifyContent: "center",
borderRadius: 1,
mb: 1,
cursor: editSightStore.sightInfo?.watermark_lu
? "pointer"
: "default", // Only clickable if there's an image
cursor: common.watermark_lu ? "pointer" : "default",
"&:hover": {
backgroundColor: editSightStore.sightInfo
?.watermark_lu
backgroundColor: common.watermark_lu
? "grey.300"
: "grey.200",
},
}}
onClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(editSightStore.sightInfo?.watermark_lu);
setMediaId(common.watermark_lu);
}}
>
{editSightStore.sightInfo?.watermark_lu ? (
{common.watermark_lu ? (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
editSightStore.sightInfo?.watermark_lu
common.watermark_lu
}/download?token=${token}`}
alt="Знак л.в"
style={{ maxWidth: "100%", maxHeight: "100%" }}
onClick={() => {
setIsMediaModalOpen(true);
setMediaId(editSightStore.sightInfo?.watermark_lu);
setMediaId(common.watermark_lu);
}}
/>
) : (
@ -375,28 +407,28 @@ export const InformationTab = observer(
justifyContent: "center",
borderRadius: 1,
mb: 1,
cursor: editSightStore.sightInfo?.watermark_rd
? "pointer"
: "default", // Only clickable if there's an image
cursor: common.watermark_rd ? "pointer" : "default",
"&:hover": {
backgroundColor: editSightStore.sightInfo
?.watermark_rd
backgroundColor: common.watermark_rd
? "grey.300"
: "grey.200",
},
}}
onClick={() => editSightStore.sightInfo?.watermark_rd}
onClick={() => {
setIsMediaModalOpen(true);
setMediaId(common.watermark_rd);
}}
>
{editSightStore.sightInfo?.watermark_rd ? (
{common.watermark_rd ? (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
editSightStore.sightInfo?.watermark_rd
common.watermark_rd
}/download?token=${token}`}
alt="Знак п.в"
style={{ maxWidth: "100%", maxHeight: "100%" }}
onClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(editSightStore.sightInfo?.watermark_rd);
setMediaId(common.watermark_rd);
}}
/>
) : (
@ -432,7 +464,13 @@ export const InformationTab = observer(
justifyContent: "flex-end", // Align to the right
}}
>
<Button variant="contained" color="success" onClick={handleSave}>
<Button
variant="contained"
color="success"
onClick={() => {
console.log(sight);
}}
>
Сохранить
</Button>
</Box>
@ -463,7 +501,7 @@ export const InformationTab = observer(
setIsAddMediaOpen(false);
setActiveMenuType(null);
}}
onSelectArticle={handleMediaSelect}
onSelectMedia={handleMediaSelect}
/>
<PreviewMediaDialog