From 87386c6a73e7ce93d76c793f8cbd58e47797e47a Mon Sep 17 00:00:00 2001 From: itoshi Date: Sun, 1 Jun 2025 00:34:59 +0300 Subject: [PATCH] feat: Add translation on 3 languages for sight page --- src/features/navigation/ui/index.tsx | 14 +- src/pages/CreateSightPage/index.tsx | 4 +- src/pages/EditSightPage/index.tsx | 78 ++- src/shared/api/index.tsx | 16 +- src/shared/config/constants.tsx | 8 +- .../modals/PreviewMediaDialog/index.tsx | 14 +- .../modals/SelectArticleDialog/index.tsx | 12 +- src/shared/modals/SelectMediaDialog/index.tsx | 7 +- src/shared/store/ArticlesStore/index.tsx | 12 +- src/shared/store/AuthStore/index.tsx | 2 +- src/shared/store/EditSightStore/index.tsx | 232 ++++--- src/shared/store/SightsStore/index.tsx | 105 +-- src/shared/ui/CoordinatesInput/index.tsx | 2 +- src/widgets/DevicesTable/index.tsx | 8 +- src/widgets/Layout/index.tsx | 1 - .../SightTabs/InformationTab/index.tsx | 156 +++-- src/widgets/SightTabs/LeftWidgetTab/index.tsx | 168 ++--- .../SightTabs/RightWidgetTab/index.tsx | 643 +++++++++--------- src/widgets/SightsTable/index.tsx | 2 +- .../modals/SelectArticleDialog/index.tsx | 12 +- tsconfig.json | 3 +- tsconfig.tsbuildinfo | 1 + 22 files changed, 768 insertions(+), 732 deletions(-) create mode 100644 tsconfig.tsbuildinfo diff --git a/src/features/navigation/ui/index.tsx b/src/features/navigation/ui/index.tsx index 2cc1983..28c6bc3 100644 --- a/src/features/navigation/ui/index.tsx +++ b/src/features/navigation/ui/index.tsx @@ -1,7 +1,7 @@ import List from "@mui/material/List"; import Divider from "@mui/material/Divider"; import { NAVIGATION_ITEMS } from "@shared"; -import { NavigationItemComponent } from "@entities"; +import { NavigationItem, NavigationItemComponent } from "@entities"; export const NavigationList = ({ open }: { open: boolean }) => { const primaryItems = NAVIGATION_ITEMS.primary; @@ -11,13 +11,21 @@ export const NavigationList = ({ open }: { open: boolean }) => { <> {primaryItems.map((item) => ( - + ))} {secondaryItems.map((item) => ( - + ))} diff --git a/src/pages/CreateSightPage/index.tsx b/src/pages/CreateSightPage/index.tsx index 8134015..38ed3a0 100644 --- a/src/pages/CreateSightPage/index.tsx +++ b/src/pages/CreateSightPage/index.tsx @@ -1,5 +1,5 @@ import { Box, Tab, Tabs } from "@mui/material"; -import { articlesStore, cityStore } from "@shared"; +import { articlesStore, cityStore, languageStore } from "@shared"; import { InformationTab, RightWidgetTab } from "@widgets"; import { LeftWidgetTab } from "@widgets"; import { useEffect, useState } from "react"; @@ -22,7 +22,7 @@ export const CreateSightPage = observer(() => { useEffect(() => { getCities(); - getArticles(); + getArticles(languageStore.language); }, []); return ( diff --git a/src/pages/EditSightPage/index.tsx b/src/pages/EditSightPage/index.tsx index b74e10b..73c104c 100644 --- a/src/pages/EditSightPage/index.tsx +++ b/src/pages/EditSightPage/index.tsx @@ -3,7 +3,12 @@ import { InformationTab, RightWidgetTab } from "@widgets"; import { LeftWidgetTab } from "@widgets"; import { useEffect, useState } from "react"; import { observer } from "mobx-react-lite"; -import { articlesStore, languageStore, sightsStore } from "@shared"; +import { + articlesStore, + cityStore, + editSightStore, + languageStore, +} from "@shared"; import { useParams } from "react-router-dom"; function a11yProps(index: number) { @@ -15,10 +20,11 @@ function a11yProps(index: number) { export const EditSightPage = observer(() => { const [value, setValue] = useState(0); - const { sight, getSight } = sightsStore; - const { articles, getArticles } = articlesStore; + const { getSightInfo } = editSightStore; + const { getArticles } = articlesStore; const { language } = languageStore; const { id } = useParams(); + const { getCities } = cityStore; const handleChange = (_: React.SyntheticEvent, newValue: number) => { setValue(newValue); @@ -27,55 +33,53 @@ export const EditSightPage = observer(() => { useEffect(() => { const fetchData = async () => { if (id) { - await getSight(Number(id)); + await getSightInfo(+id, language); await getArticles(language); + await getCities(); } }; fetchData(); }, [id, language]); return ( - articles && - sight && ( + - - - - - - - - -
- - - -
+ + + +
- ) + +
+ + + +
+
); }); diff --git a/src/shared/api/index.tsx b/src/shared/api/index.tsx index dd3973d..1a803db 100644 --- a/src/shared/api/index.tsx +++ b/src/shared/api/index.tsx @@ -1,16 +1,28 @@ import { languageStore, Language } from "@shared"; -import axios from "axios"; +import axios, { AxiosError, InternalAxiosRequestConfig } from "axios"; const authInstance = axios.create({ baseURL: "https://wn.krbl.ru", }); -authInstance.interceptors.request.use((config) => { +authInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => { + console.log(config); config.headers.Authorization = `Bearer ${localStorage.getItem("token")}`; config.headers["X-Language"] = languageStore.language ?? "ru"; return config; }); +authInstance.interceptors.response.use( + (response) => response, + (error: AxiosError) => { + if (error.response?.status === 401) { + localStorage.removeItem("token"); + window.location.href = "/login"; + } + return Promise.reject(error); + } +); + const languageInstance = (language: Language) => { const instance = axios.create({ baseURL: "https://wn.krbl.ru", diff --git a/src/shared/config/constants.tsx b/src/shared/config/constants.tsx index 125514b..db715fe 100644 --- a/src/shared/config/constants.tsx +++ b/src/shared/config/constants.tsx @@ -1,3 +1,4 @@ +import { authStore } from "@shared"; import { Power, LucideIcon, Building2, MonitorSmartphone } from "lucide-react"; export const DRAWER_WIDTH = 300; @@ -5,7 +6,8 @@ interface NavigationItem { id: string; label: string; icon: LucideIcon; - path: string; + path?: string; + onClick?: () => void; } export const NAVIGATION_ITEMS: { @@ -31,7 +33,9 @@ export const NAVIGATION_ITEMS: { id: "logout", label: "Выйти", icon: Power, - path: "/logout", + onClick: () => { + authStore.logout(); + }, }, ], }; diff --git a/src/shared/modals/PreviewMediaDialog/index.tsx b/src/shared/modals/PreviewMediaDialog/index.tsx index 43d6a15..a7f96b7 100644 --- a/src/shared/modals/PreviewMediaDialog/index.tsx +++ b/src/shared/modals/PreviewMediaDialog/index.tsx @@ -1,13 +1,6 @@ -import { - articlesStore, - authStore, - Language, - mediaStore, - MEDIA_TYPE_LABELS, - API_URL, -} from "@shared"; +import { mediaStore, MEDIA_TYPE_LABELS } from "@shared"; import { observer } from "mobx-react-lite"; -import { useEffect, useRef, useState, useCallback } from "react"; +import { useEffect, useState } from "react"; import { Dialog, DialogTitle, @@ -17,13 +10,12 @@ import { TextField, Paper, Box, - Typography, CircularProgress, Alert, Snackbar, } from "@mui/material"; import { Download, Save } from "lucide-react"; -import { ReactMarkdownComponent, MediaViewer } from "@widgets"; +import { MediaViewer } from "@widgets"; import { authInstance } from "@shared"; interface PreviewMediaDialogProps { diff --git a/src/shared/modals/SelectArticleDialog/index.tsx b/src/shared/modals/SelectArticleDialog/index.tsx index cf27742..8c0e831 100644 --- a/src/shared/modals/SelectArticleDialog/index.tsx +++ b/src/shared/modals/SelectArticleDialog/index.tsx @@ -1,4 +1,4 @@ -import { articlesStore, authStore, Language } from "@shared"; +import { articlesStore } from "@shared"; import { observer } from "mobx-react-lite"; import { useEffect, useState } from "react"; import { @@ -73,7 +73,10 @@ export const SelectArticleModal = observer( setIsLoading(true); try { - await Promise.all([getArticle(articleId), getArticleMedia(articleId)]); + await Promise.all([ + getArticle(Number(articleId)), + getArticleMedia(Number(articleId)), + ]); } catch (error) { console.error("Failed to fetch article data:", error); // Reset article data on error @@ -83,9 +86,11 @@ export const SelectArticleModal = observer( setIsLoading(false); } }; - + // @ts-ignore const filteredArticles = articles + // @ts-ignore .filter((article) => !linkedArticleIds.includes(article.id)) + // @ts-ignore .filter((article) => article.service_name.toLowerCase().includes(searchQuery.toLowerCase()) ); @@ -140,6 +145,7 @@ export const SelectArticleModal = observer( {searchQuery ? "Статьи не найдены" : "Нет доступных статей"} ) : ( + // @ts-ignore filteredArticles.map((article) => ( { - editSightStore.sightInfo[languageStore.language].right = response.data; + editSightStore.sight[languageStore.language].right = response.data; }); }; @@ -66,10 +66,14 @@ class ArticlesStore { }; getArticleByArticleId = computed(() => { - if (editSightStore.sightInfo.left_article) { - return this.articles[languageStore.language].find( - (a) => a.id == editSightStore.sightInfo.left_article + if (editSightStore.sight.common.left_article) { + const language = languageStore.language; + const foundArticle = this.articles[language].find( + (a) => a.id == editSightStore.sight.common.left_article ); + editSightStore.sight[language].left.heading = foundArticle?.heading ?? ""; + editSightStore.sight[language].left.body = foundArticle?.body ?? ""; + return foundArticle; } return null; }); diff --git a/src/shared/store/AuthStore/index.tsx b/src/shared/store/AuthStore/index.tsx index 0b30d65..e317155 100644 --- a/src/shared/store/AuthStore/index.tsx +++ b/src/shared/store/AuthStore/index.tsx @@ -79,7 +79,7 @@ class AuthStore { }; get isAuthenticated() { - return this.payload?.token !== null; + return this.payload !== null; } get user() { diff --git a/src/shared/store/EditSightStore/index.tsx b/src/shared/store/EditSightStore/index.tsx index 8faf2d9..19b8c8b 100644 --- a/src/shared/store/EditSightStore/index.tsx +++ b/src/shared/store/EditSightStore/index.tsx @@ -1,15 +1,16 @@ // @shared/stores/editSightStore.ts -import { Language } from "@shared"; -import { makeAutoObservable } from "mobx"; +import { authInstance, Language } from "@shared"; +import { makeAutoObservable, runInAction } from "mobx"; -export interface MediaObject { - id: string; - filename: string; - media_type: number; -} - -type SightBaseInfo = { +export type SightLanguageInfo = { id: number; + name: string; + address: string; + left: { heading: string; body: string }; + right: { heading: string; body: string }[]; +}; + +export type SightCommonInfo = { city_id: number; city: string; latitude: number; @@ -22,103 +23,14 @@ type SightBaseInfo = { video_preview: string; }; -export interface RightArticleBlock { - id: string; - type: "article" | "preview_media"; - name: string; - linkedArticleId?: string; - heading: string; - body: string; - media: MediaObject | null; -} - -type SightInfo = SightBaseInfo & { - [key in Language]: { - info: { - name: string; - address: string; - }; - left: { - loaded: boolean; // Означает, что данные для этого языка были инициализированы/загружены - heading: string; - body: string; - media: MediaObject | null; - }; - right: RightArticleBlock[]; - }; +export type SightBaseInfo = { + common: SightCommonInfo; +} & { + [key in Language]: SightLanguageInfo; }; - class EditSightStore { - sightInfo: SightInfo = { - id: 0, - city_id: 0, - city: "", - latitude: 0, - longitude: 0, - thumbnail: "", - watermark_lu: "", - watermark_rd: "", - left_article: 0, - preview_media: "", - video_preview: "", - ru: { - info: { name: "", address: "" }, - left: { loaded: false, heading: "", body: "", media: null }, - right: [], - }, - en: { - info: { name: "", address: "" }, - left: { loaded: false, heading: "", body: "", media: null }, - right: [], - }, - zh: { - info: { name: "", address: "" }, - left: { loaded: false, heading: "", body: "", media: null }, - right: [], - }, - }; - - constructor() { - makeAutoObservable(this); - } - - // loadSightInfo: Используется для первоначальной загрузки данных для ЯЗЫКА. - // Она устанавливает loaded: true, чтобы в будущем не перезатирать данные. - loadSightInfo = ( - language: Language, - heading: string, - body: string, - media: MediaObject | null - ) => { - // Важно: если данные уже были загружены или изменены, не перезаписывайте их. - // Это предотвращает потерю пользовательского ввода при переключении языков. - // Если хотите принудительную загрузку, добавьте другой метод или параметр. - if (!this.sightInfo[language].left.loaded) { - // <--- Только если еще не загружено - this.sightInfo[language].left.heading = heading; - this.sightInfo[language].left.body = body; - this.sightInfo[language].left.media = media; - this.sightInfo[language].left.loaded = true; // <--- Устанавливаем loaded только при загрузке - } - }; - - // updateSightInfo: Используется для сохранения ЛЮБЫХ пользовательских изменений. - // Она НЕ должна влиять на флаг 'loaded', который управляется 'loadSightInfo'. - updateSightInfo = ( - language: Language, - heading: string, - body: string, - media: MediaObject | null - ) => { - this.sightInfo[language].left.heading = heading; - this.sightInfo[language].left.body = body; - this.sightInfo[language].left.media = media; - // this.sightInfo[language].left.loaded = true; // <-- УДАЛИТЕ эту строку - }; - - clearSightInfo = () => { - this.sightInfo = { - id: 0, + sight: SightBaseInfo = { + common: { city_id: 0, city: "", latitude: 0, @@ -129,23 +41,123 @@ class EditSightStore { left_article: 0, preview_media: "", video_preview: "", + }, + ru: { + id: 0, + name: "", + address: "", + left: { heading: "", body: "" }, + right: [], + }, + en: { + id: 0, + name: "", + address: "", + left: { heading: "", body: "" }, + right: [], + }, + zh: { + id: 0, + name: "", + address: "", + left: { heading: "", body: "" }, + right: [], + }, + }; + + constructor() { + makeAutoObservable(this); + } + + hasLoadedCommon = false; + getSightInfo = async (id: number, language: Language) => { + if (this.sight[language].id === id) { + return; + } + + const response = await authInstance.get(`/sight/${id}`); + const data = response.data; + + runInAction(() => { + // Обновляем языковую часть + this.sight[language] = { + ...this.sight[language], + ...data, + }; + + // Только при первом запросе обновляем общую часть + if (!this.hasLoadedCommon) { + this.sight.common = { + ...this.sight.common, + ...data, + }; + this.hasLoadedCommon = true; + } + }); + }; + + updateLeftInfo = (language: Language, heading: string, body: string) => { + this.sight[language].left.heading = heading; + this.sight[language].left.body = body; + }; + + clearSightInfo = () => { + this.sight = { + common: { + city_id: 0, + city: "", + latitude: 0, + longitude: 0, + thumbnail: "", + watermark_lu: "", + watermark_rd: "", + left_article: 0, + preview_media: "", + video_preview: "", + }, ru: { - info: { name: "", address: "" }, - left: { loaded: false, heading: "", body: "", media: null }, + id: 0, + name: "", + address: "", + left: { heading: "", body: "" }, right: [], }, + en: { - info: { name: "", address: "" }, - left: { loaded: false, heading: "", body: "", media: null }, + id: 0, + name: "", + address: "", + left: { heading: "", body: "" }, right: [], }, + zh: { - info: { name: "", address: "" }, - left: { loaded: false, heading: "", body: "", media: null }, + id: 0, + name: "", + address: "", + left: { heading: "", body: "" }, right: [], }, }; }; + + updateSightInfo = ( + language: Language, + content: Partial, + common: boolean = false + ) => { + if (common) { + this.sight.common = { + ...this.sight.common, + ...content, + }; + } else { + this.sight[language] = { + ...this.sight[language], + ...content, + }; + } + }; } export const editSightStore = new EditSightStore(); diff --git a/src/shared/store/SightsStore/index.tsx b/src/shared/store/SightsStore/index.tsx index 5df9823..62897aa 100644 --- a/src/shared/store/SightsStore/index.tsx +++ b/src/shared/store/SightsStore/index.tsx @@ -1,9 +1,8 @@ import { - articlesStore, authInstance, languageInstance, languageStore, - editSightStore, + SightBaseInfo, } from "@shared"; import { computed, makeAutoObservable, runInAction } from "mobx"; @@ -59,48 +58,40 @@ class SightsStore { }); }; - getSight = async (id: number) => { - const response = await authInstance.get(`/sight/${id}`); + // getSight = async (id: number) => { + // const response = await authInstance.get(`/sight/${id}`); - runInAction(() => { - this.sight = response.data; - editSightStore.sightInfo = { - ...editSightStore.sightInfo, - id: response.data.id, - city_id: response.data.city_id, - city: response.data.city, - latitude: response.data.latitude, - longitude: response.data.longitude, - thumbnail: response.data.thumbnail, - watermark_lu: response.data.watermark_lu, - watermark_rd: response.data.watermark_rd, - left_article: response.data.left_article, - preview_media: response.data.preview_media, - video_preview: response.data.video_preview, - [languageStore.language]: { - info: { - name: response.data.name, - address: response.data.address, - description: response.data.description, - }, - left: { - heading: editSightStore.sightInfo[languageStore.language].left - .loaded - ? editSightStore.sightInfo[languageStore.language].left.heading - : articlesStore.articles[languageStore.language].find( - (article) => article.id === response.data.left_article - )?.heading, - body: editSightStore.sightInfo[languageStore.language].left.loaded - ? editSightStore.sightInfo[languageStore.language].left.body - : articlesStore.articles[languageStore.language].find( - (article) => article.id === response.data.left_article - )?.body, - }, - }, - }; - console.log(editSightStore.sightInfo); - }); - }; + // runInAction(() => { + // this.sight = response.data; + // editSightStore.sightInfo = { + // ...editSightStore.sightInfo, + // id: response.data.id, + // city_id: response.data.city_id, + // city: response.data.city, + // latitude: response.data.latitude, + // longitude: response.data.longitude, + // thumbnail: response.data.thumbnail, + // watermark_lu: response.data.watermark_lu, + // watermark_rd: response.data.watermark_rd, + // left_article: response.data.left_article, + // preview_media: response.data.preview_media, + // video_preview: response.data.video_preview, + + // [languageStore.language]: { + // info: { + // name: response.data.name, + // address: response.data.address, + // }, + // left: { + // heading: articlesStore.articles[languageStore.language].find( + // (article) => article.id === response.data.left_article + // )?.heading, + // body: articlesStore.articles[languageStore.language].find( + // }, + // }, + // }; + // }); + // }; createSightAction = async ( city: number, @@ -160,12 +151,32 @@ class SightsStore { language: Language, content: Partial ) => { - runInAction(() => { - this.createSight[language] = { - ...this.createSight[language], + this.createSight[language] = { + ...this.createSight[language], + ...content, + }; + }; + + updateSight = ( + language: Language, + content: Partial, + common: boolean + ) => { + if (common) { + // @ts-ignore + this.sight!.common = { + // @ts-ignore + ...this.sight!.common, ...content, }; - }); + } else { + // @ts-ignore + this.sight![language] = { + // @ts-ignore + ...this.sight![language], + ...content, + }; + } }; clearCreateSight = () => { diff --git a/src/shared/ui/CoordinatesInput/index.tsx b/src/shared/ui/CoordinatesInput/index.tsx index b34361b..9a0b603 100644 --- a/src/shared/ui/CoordinatesInput/index.tsx +++ b/src/shared/ui/CoordinatesInput/index.tsx @@ -23,7 +23,7 @@ export const CoordinatesInput = ({ { setInputValue(e.target.value); }} diff --git a/src/widgets/DevicesTable/index.tsx b/src/widgets/DevicesTable/index.tsx index bbc40c9..58cceaf 100644 --- a/src/widgets/DevicesTable/index.tsx +++ b/src/widgets/DevicesTable/index.tsx @@ -5,7 +5,7 @@ import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; -import { Check, RotateCcw, Send, X } from "lucide-react"; +import { Check, RotateCcw, X } from "lucide-react"; import { authInstance, devicesStore, @@ -49,7 +49,7 @@ function createData( } // Keep the rows function as you provided it, without additional filters -const rows = (devices: any[], vehicles: any[]) => { +const rows = (vehicles: any[]) => { return vehicles.map((vehicle) => { return createData( vehicle?.vehicle?.tail_number ?? "1243000", // Using tail_number as UUID, as in your original code @@ -72,11 +72,11 @@ export const DevicesTable = observer(() => { toggleSendSnapshotModal, } = devicesStore; const { snapshots, getSnapshots } = snapshotStore; - const { vehicles, getVehicles } = vehicleStore; + const { getVehicles } = vehicleStore; const [selectedDevices, setSelectedDevices] = useState([]); // Get the current list of rows displayed in the table - const currentRows = rows(devices, vehicles); + const currentRows = rows(devices); useEffect(() => { const fetchData = async () => { diff --git a/src/widgets/Layout/index.tsx b/src/widgets/Layout/index.tsx index 35c765e..7238714 100644 --- a/src/widgets/Layout/index.tsx +++ b/src/widgets/Layout/index.tsx @@ -2,7 +2,6 @@ import * as React from "react"; import Box from "@mui/material/Box"; import Toolbar from "@mui/material/Toolbar"; import IconButton from "@mui/material/IconButton"; -import Typography from "@mui/material/Typography"; import { Menu, ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; import { useTheme } from "@mui/material/styles"; import { AppBar } from "./ui/AppBar"; diff --git a/src/widgets/SightTabs/InformationTab/index.tsx b/src/widgets/SightTabs/InformationTab/index.tsx index 2d044ee..3b771a5 100644 --- a/src/widgets/SightTabs/InformationTab/index.tsx +++ b/src/widgets/SightTabs/InformationTab/index.tsx @@ -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(""); const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false); - const { createSight, updateCreateSight, createSightAction } = sightsStore; - const { sightInfo } = editSightStore; - const [city, setCity] = useState(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(common.city_id ?? 0); + const [coordinates, setCoordinates] = useState(`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 + content: Partial, + 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( > { handleChange(language as Language, { name: e.target.value, @@ -147,7 +147,7 @@ export const InformationTab = observer( { handleChange(language as Language, { address: e.target.value, @@ -158,20 +158,57 @@ export const InformationTab = observer( /> 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) => ( )} /> - { + 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="Введите координаты в формате: широта долгота" /> @@ -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 ? ( Логотип { 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 ? ( Знак л.в { 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 ? ( Знак п.в { 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 }} > - @@ -463,7 +501,7 @@ export const InformationTab = observer( setIsAddMediaOpen(false); setActiveMenuType(null); }} - onSelectArticle={handleMediaSelect} + onSelectMedia={handleMediaSelect} /> { - const { sightInfo, updateSightInfo, loadSightInfo } = editSightStore; - const { articleLoading, getArticleByArticleId } = articlesStore; - const { language } = languageStore; + const { sight, updateSightInfo } = editSightStore; + const { getArticleByArticleId } = articlesStore; + const linkedArticle = getArticleByArticleId.get(); // Получаем связанную статью - const data = sightInfo[languageStore.language]; // Получаем данные для текущего языка - - useEffect(() => { - // Этот useEffect должен загружать данные ИЗ СВЯЗАННОЙ СТАТЬИ - // ТОЛЬКО ЕСЛИ данные для ТЕКУЩЕГО ЯЗЫКА еще не были загружены - // или если sightInfo.left_article изменился (т.е. привязали новую статью). - - // Мы также должны учитывать, что linkedArticle может измениться (т.е. новую статью привязали) - // или language изменился. - // Если для текущего языка данные еще не "загружены" (`loaded: false`), - // тогда мы берем их из `linkedArticle` и инициализируем. - console.log("data.left.loaded", data.left.loaded); - if (!data.left.loaded) { - // <--- КЛЮЧЕВОЕ УСЛОВИЕ - if (linkedArticle && !articleLoading) { - console.log("loadSightInfo", linkedArticle, language); - loadSightInfo( - languageStore.language, - linkedArticle.heading, - linkedArticle.body || "", - null - ); - } - } - // Зависимости: linkedArticle (для реакции на изменение привязанной статьи), - // languageStore.language (для реакции на изменение языка), - // loadSightInfo (чтобы useEffect знал об изменениях в функции), - // data.left.loaded (чтобы useEffect перепроверил условие, когда этот флаг изменится). - // Важно: если data.left.loaded становится true, то этот эффект не будет - // перезапускаться для того же языка. - }, [ - linkedArticle?.heading, - language, - loadSightInfo, - data.left.loaded, - articleLoading, - ]); + const data = sight[languageStore.language]; // Получаем данные для текущего языка const [isSelectMediaDialogOpen, setIsSelectMediaDialogOpen] = useState(false); - const handleOpenMediaDialog = useCallback(() => { - setIsSelectMediaDialogOpen(true); - }, []); - - const handleMediaSelected = useCallback( - (selectedMedia: any) => { - // При выборе медиа, обновляем данные для ТЕКУЩЕГО ЯЗЫКА - // сохраняя текущие heading и body. - updateSightInfo( - languageStore.language, - data.left.heading, - data.left.body, - selectedMedia - ); - setIsSelectMediaDialogOpen(false); - }, - [ + const handleMediaSelected = useCallback(() => { + // При выборе медиа, обновляем данные для ТЕКУЩЕГО ЯЗЫКА + // сохраняя текущие heading и body. + updateSightInfo( languageStore.language, - data.left.heading, - data.left.body, - updateSightInfo, - ] - ); + { + left: { + heading: data.left.heading, + body: data.left.body, + }, + }, + false + ); + setIsSelectMediaDialogOpen(false); + }, [ + languageStore.language, + { + left: { + heading: data.left.heading, + body: data.left.body, + }, + }, + false, + ]); const handleCloseMediaDialog = useCallback(() => { setIsSelectMediaDialogOpen(false); @@ -150,13 +115,17 @@ export const LeftWidgetTab = observer( > updateSightInfo( languageStore.language, - e.target.value, - data.left.body, - data.left.media + { + left: { + heading: e.target.value, + body: data.left.body, + }, + }, + false ) } variant="outlined" @@ -164,19 +133,23 @@ export const LeftWidgetTab = observer( /> updateSightInfo( languageStore.language, - data.left.heading, - value, - data.left.media + { + left: { + heading: data.left.heading, + body: value, + }, + }, + false ) } /> {/* Блок МЕДИА для статьи */} - + {/* МЕДИА @@ -226,16 +199,21 @@ export const LeftWidgetTab = observer( onClick={() => updateSightInfo( languageStore.language, - data.left.heading, - data.left.body, - null + { + left: { + heading: data.left.heading, + body: data.left.body, + media: null, + }, + }, + false ) } > Удалить медиа )} - + */} {/* Правая колонка: Предпросмотр */} @@ -247,7 +225,6 @@ export const LeftWidgetTab = observer( gap: 1.5, }} > - Предпросмотр - {/* Медиа в превью (если есть) */} - {data.left.media ? ( + {/* {data.left.media?.filename ? ( Превью медиа ) : ( - - - - )} + + )} */} + + + + {/* Заголовок в превью */} - {data.left.heading || "Название информации"} + {data?.left?.heading || "Название информации"} @@ -324,7 +302,7 @@ export const LeftWidgetTab = observer( flexGrow: 1, }} > - + diff --git a/src/widgets/SightTabs/RightWidgetTab/index.tsx b/src/widgets/SightTabs/RightWidgetTab/index.tsx index 53c80af..c04442c 100644 --- a/src/widgets/SightTabs/RightWidgetTab/index.tsx +++ b/src/widgets/SightTabs/RightWidgetTab/index.tsx @@ -1,404 +1,371 @@ -// RightWidgetTab.tsx -import { Box, Button, Paper, TextField, Typography } from "@mui/material"; import { - TabPanel, + Box, + Button, + List, + ListItemButton, + ListItemText, + Paper, + Typography, + Menu, + MenuItem, +} from "@mui/material"; +import { + articlesStore, BackButton, - languageStore, // Предполагаем, что он есть в @shared - Language, // Предполагаем, что он есть в @shared - // SelectArticleModal, // Добавим позже - // articlesStore, // Добавим позже + SelectArticleModal, + TabPanel, } from "@shared"; -import { LanguageSwitcher } from "@widgets"; // Предполагаем, что LanguageSwitcher у вас есть +import { SightEdit } from "@widgets"; +import { Plus } from "lucide-react"; import { observer } from "mobx-react-lite"; -import { useState, useMemo, useEffect } from "react"; -import { editSightStore, BlockItem } from "@shared"; // Путь к вашему стору +import { useState } from "react"; -// Импортируем сюда же определения BlockItem, если не выносим в types.ts -// export interface BlockItem { id: string; type: 'media' | 'article'; nameForSidebar: string; linkedArticleStoreId?: string; } - -// --- Начальные данные для структуры блоков (позже это может загружаться) --- -// ID здесь должны быть уникальными для списка. -const initialBlockStructures: Omit[] = [ - { id: "preview_media_main", type: "media" }, - { id: "article_1_local", type: "article" }, // Эти статьи будут редактироваться локально - { id: "article_2_local", type: "article" }, +// --- Mock Data (can be moved to a separate file or fetched from an API) --- +const mockRightWidgetBlocks = [ + { id: "preview_media", name: "Превью-медиа", type: "special" }, + { id: "article_1", name: "1. История", type: "article" }, + { id: "article_2", name: "2. Факты", type: "article" }, + { + id: "article_3", + name: "3. Блокада (Пример длинного названия)", + type: "article", + }, ]; -interface RightWidgetTabProps { - value: number; - index: number; +const mockSelectedBlockData = { + id: "article_1", + heading: "История основания Санкт-Петербурга", + body: "## Начало\nГород был основан 27 мая 1703 года Петром I...", + media: [], +}; + +// --- ArticleListSidebar Component --- +interface ArticleBlock { + id: string; + name: string; + type: string; + linkedArticleId?: string; // Added for linked articles } -export const RightWidgetTab = observer( - ({ value, index }: RightWidgetTabProps) => { - const { language } = languageStore; // Текущий язык - const { sightInfo } = editSightStore; // Данные достопримечательности +interface ArticleListSidebarProps { + blocks: ArticleBlock[]; + selectedBlockId: string | null; + onSelectBlock: (blockId: string) => void; + onCreateNew: () => void; + onSelectExisting: () => void; +} - // 1. Структура блоков: порядок, тип, связи (не сам контент) - // Имена nameForSidebar будут динамически браться из sightInfo или articlesStore - const [blockItemsStructure, setBlockItemsStructure] = useState< - Omit[] - >(initialBlockStructures); +const ArticleListSidebar = ({ + blocks, + selectedBlockId, + onSelectBlock, + onCreateNew, + onSelectExisting, +}: ArticleListSidebarProps) => { + const [menuAnchorEl, setMenuAnchorEl] = useState(null); - // 2. ID выбранного блока для редактирования - const [selectedBlockId, setSelectedBlockId] = useState( - () => { - // По умолчанию выбираем первый блок, если он есть - return initialBlockStructures.length > 0 - ? initialBlockStructures[0].id - : null; - } + const handleMenuOpen = (event: React.MouseEvent) => { + setMenuAnchorEl(event.currentTarget); + }; + + const handleMenuClose = () => { + setMenuAnchorEl(null); + }; + + return ( + + + {blocks.map((block) => ( + onSelectBlock(block.id)} + sx={{ + borderRadius: 1, + mb: 0.5, + backgroundColor: + selectedBlockId === block.id ? "primary.light" : "transparent", + "&.Mui-selected": { + backgroundColor: "primary.main", + color: "primary.contrastText", + "&:hover": { + backgroundColor: "primary.dark", + }, + }, + "&:hover": { + backgroundColor: + selectedBlockId !== block.id ? "action.hover" : undefined, + }, + }} + > + + + ))} + + + + + Создать новую + Выбрать существующую + + + ); +}; + +// --- ArticleEditorPane Component --- +interface ArticleData { + id: string; + heading: string; + body: string; + media: any[]; // Define a proper type for media if available +} + +interface ArticleEditorPaneProps { + articleData: ArticleData | null; +} + +const ArticleEditorPane = ({ articleData }: ArticleEditorPaneProps) => { + if (!articleData) { + return ( + + + Выберите блок для редактирования + + ); + } - // 3. Состояние для модального окна выбора существующей статьи (добавим позже) - // const [isSelectModalOpen, setIsSelectModalOpen] = useState(false); + return ( + + + + + МЕДИА + + + Нет медиа + + + + + ); +}; - // --- Производные данные (Derived State) --- +// --- RightWidgetTab (Parent) Component --- +export const RightWidgetTab = observer( + ({ value, index }: { value: number; index: number }) => { + const [rightWidgetBlocks, setRightWidgetBlocks] = useState( + mockRightWidgetBlocks + ); + const [selectedBlockId, setSelectedBlockId] = useState( + mockRightWidgetBlocks[1]?.id || null + ); + const [isSelectModalOpen, setIsSelectModalOpen] = useState(false); - // Блоки для отображения в сайдбаре (с локализованными именами) - const blocksForSidebar: BlockItem[] = useMemo(() => { - return blockItemsStructure.map((struct) => { - let name = `Блок ${struct.id}`; // Имя по умолчанию - - if (struct.type === "media" && struct.id === "preview_media_main") { - name = "Превью-медиа"; // Фиксированное имя для этого блока - } else if (struct.type === "article") { - if (struct.linkedArticleStoreId) { - // TODO: Найти имя в articlesStore по struct.linkedArticleStoreId - name = `Связанная: ${struct.linkedArticleStoreId}`; - } else { - // Это локальная статья, берем заголовок из editSightStore - const articleContent = sightInfo[language]?.right?.find( - (a) => a.id === struct.id - ); - name = - articleContent?.heading || - `Статья ${struct.id.slice(-4)} (${language.toUpperCase()})`; - } - } - return { ...struct, nameForSidebar: name }; - }); - }, [blockItemsStructure, language, sightInfo]); - - // Данные выбранного блока (структура + контент) - const selectedBlockData = useMemo(() => { - if (!selectedBlockId) return null; - const structure = blockItemsStructure.find( - (b) => b.id === selectedBlockId - ); - if (!structure) return null; - - if (structure.type === "article" && !structure.linkedArticleStoreId) { - const content = sightInfo[language]?.right?.find( - (a) => a.id === selectedBlockId - ); - return { - structure, - content: content || { id: selectedBlockId, heading: "", body: "" }, // Заглушка, если нет контента - }; - } - // Для media или связанных статей пока просто структура - return { structure, content: null }; - }, [selectedBlockId, blockItemsStructure, language, sightInfo]); - - // --- Обработчики событий --- const handleSelectBlock = (blockId: string) => { setSelectedBlockId(blockId); + console.log("Selected block:", blockId); }; - const handleCreateNewArticle = () => { - const newBlockId = `article_local_${Date.now()}`; - const newBlockStructure: Omit = { - id: newBlockId, - type: "article", - }; - setBlockItemsStructure((prev) => [...prev, newBlockStructure]); - - // Добавляем пустой контент для этой статьи во все языки в editSightStore - const baseName = `Новая статья ${ - blockItemsStructure.filter((b) => b.type === "article").length + 1 - }`; - ["ru", "en", "zh"].forEach((lang) => { - const currentLang = lang as Language; - if ( - editSightStore.sightInfo[currentLang] && - !editSightStore.sightInfo[currentLang].right?.find( - (r) => r.id === newBlockId - ) - ) { - editSightStore.sightInfo[currentLang].right.push({ - id: newBlockId, - heading: `${baseName} (${currentLang.toUpperCase()})`, - body: `Содержимое для ${baseName} (${currentLang.toUpperCase()})...`, - }); - } - }); + const handleCreateNew = () => { + const newBlockId = `article_${Date.now()}`; + setRightWidgetBlocks((prevBlocks) => [ + ...prevBlocks, + { + id: newBlockId, + name: `${ + prevBlocks.filter((b) => b.type === "article").length + 1 + }. Новый блок`, + type: "article", + }, + ]); setSelectedBlockId(newBlockId); }; - const handleHeadingChange = (newHeading: string) => { - if ( - selectedBlockData && - selectedBlockData.structure.type === "article" && - !selectedBlockData.structure.linkedArticleStoreId - ) { - const blockId = selectedBlockData.structure.id; - const langData = editSightStore.sightInfo[language]; - const article = langData?.right?.find((a) => a.id === blockId); - if (article) { - article.heading = newHeading; - } else if (langData) { - // Если статьи еще нет, добавляем - langData.right.push({ id: blockId, heading: newHeading, body: "" }); - } - // Обновить имя в сайдбаре (т.к. blocksForSidebar пересчитается) - // Для этого достаточно, чтобы sightInfo был observable и blocksForSidebar от него зависел - } + const handleSelectExisting = () => { + setIsSelectModalOpen(true); }; - const handleBodyChange = (newBody: string) => { - if ( - selectedBlockData && - selectedBlockData.structure.type === "article" && - !selectedBlockData.structure.linkedArticleStoreId - ) { - const blockId = selectedBlockData.structure.id; - const langData = editSightStore.sightInfo[language]; - const article = langData?.right?.find((a) => a.id === blockId); - if (article) { - article.body = newBody; - } else if (langData) { - // Если статьи еще нет, добавляем - langData.right.push({ id: blockId, heading: "", body: newBody }); - } - } + const handleCloseSelectModal = () => { + setIsSelectModalOpen(false); }; - const handleDeleteBlock = (blockIdToDelete: string) => { - setBlockItemsStructure((prev) => - prev.filter((b) => b.id !== blockIdToDelete) - ); - // Удаляем контент из editSightStore для всех языков - ["ru", "en", "zh"].forEach((lang) => { - const currentLang = lang as Language; - if (editSightStore.sightInfo[currentLang]) { - editSightStore.sightInfo[currentLang].right = - editSightStore.sightInfo[currentLang].right?.filter( - (r) => r.id !== blockIdToDelete - ); - } - }); - - if (selectedBlockId === blockIdToDelete) { - setSelectedBlockId( - blockItemsStructure.length > 1 - ? blockItemsStructure.filter((b) => b.id !== blockIdToDelete)[0]?.id - : null - ); + const handleSelectArticle = (articleId: string) => { + // @ts-ignore + const article = articlesStore.articles.find((a) => a.id === articleId); + if (article) { + const newBlockId = `article_linked_${article.id}_${Date.now()}`; + setRightWidgetBlocks((prevBlocks) => [ + ...prevBlocks, + { + id: newBlockId, + name: `${ + prevBlocks.filter((b) => b.type === "article").length + 1 + }. ${article.service_name}`, + type: "article", + linkedArticleId: article.id, + }, + ]); + setSelectedBlockId(newBlockId); } + handleCloseSelectModal(); }; const handleSave = () => { - console.log( - "Сохранение Right Widget:", - JSON.stringify(editSightStore.sightInfo, null, 2) - ); - // Здесь будет логика отправки editSightStore.sightInfo на сервер - alert("Данные для сохранения (см. консоль)"); + console.log("Saving right widget..."); + // Implement save logic here, e.g., send data to an API }; - // --- Инициализация контента в сторе для initialBlockStructures (если его там нет) --- - useEffect(() => { - initialBlockStructures.forEach((struct) => { - if (struct.type === "article" && !struct.linkedArticleStoreId) { - const baseName = `Статья ${struct.id.split("_")[1]}`; // Пример "История" или "Факты" - ["ru", "en", "zh"].forEach((lang) => { - const currentLang = lang as Language; - if ( - editSightStore.sightInfo[currentLang] && - !editSightStore.sightInfo[currentLang].right?.find( - (r) => r.id === struct.id - ) - ) { - editSightStore.sightInfo[currentLang].right?.push({ - id: struct.id, - heading: `${baseName} (${currentLang.toUpperCase()})`, // Например: "История (RU)" - body: `Начальное содержимое для ${baseName} на ${currentLang.toUpperCase()}.`, - }); - } - }); - } - }); - }, []); // Запускается один раз при монтировании + // Determine the current block data to pass to the editor pane + const currentBlockToEdit = selectedBlockId + ? selectedBlockId === mockSelectedBlockData.id + ? mockSelectedBlockData + : { + id: selectedBlockId, + heading: + rightWidgetBlocks.find((b) => b.id === selectedBlockId)?.name || + "Заголовок...", + body: "Содержимое...", + media: [], + } + : null; + + // Get list of already linked article IDs + const linkedArticleIds = rightWidgetBlocks + .filter((block) => block.linkedArticleId) + .map((block) => block.linkedArticleId as string); return ( - - - {/* Компонент сайдбара списка блоков */} - - - Блоки - - - {blocksForSidebar.map((block) => ( - - ))} - - - {/* TODO: Кнопка "Выбрать существующую" */} - + + - {/* Компонент редактора выбранного блока */} - - - Редактор блока ({language.toUpperCase()}) - - {selectedBlockData ? ( - - - ID: {selectedBlockData.structure.id} - - - Тип: {selectedBlockData.structure.type} - - {selectedBlockData.structure.type === "media" && ( - - - Загрузчик медиа для "{selectedBlockData.structure.id}" - - - )} - {selectedBlockData.structure.type === "article" && - !selectedBlockData.structure.linkedArticleStoreId && - selectedBlockData.content && ( - - handleHeadingChange(e.target.value)} - sx={{ mb: 2 }} - /> - handleBodyChange(e.target.value)} - sx={{ mb: 2 }} - // Здесь позже можно будет вставить SightEdit - /> - {/* TODO: Секция медиа для статьи */} - - - )} - {selectedBlockData.structure.type === "article" && - selectedBlockData.structure.linkedArticleStoreId && ( - - - Это связанная статья:{" "} - {selectedBlockData.structure.linkedArticleStoreId} - - {/* TODO: Кнопки "Открепить", "Удалить из списка" */} - - )} - - ) : ( - - Выберите блок для редактирования - - )} - + - - {/* */} + + ); } diff --git a/src/widgets/SightsTable/index.tsx b/src/widgets/SightsTable/index.tsx index cc5f262..8bd0bee 100644 --- a/src/widgets/SightsTable/index.tsx +++ b/src/widgets/SightsTable/index.tsx @@ -8,7 +8,7 @@ import Paper from "@mui/material/Paper"; import { authInstance, cityStore, languageStore, sightsStore } from "@shared"; import { useEffect } from "react"; import { observer } from "mobx-react-lite"; -import { Button, Checkbox } from "@mui/material"; +import { Button } from "@mui/material"; import { LanguageSwitcher } from "@widgets"; import { Pencil, Trash2 } from "lucide-react"; import { useNavigate } from "react-router-dom"; diff --git a/src/widgets/modals/SelectArticleDialog/index.tsx b/src/widgets/modals/SelectArticleDialog/index.tsx index e59b718..4c7a776 100644 --- a/src/widgets/modals/SelectArticleDialog/index.tsx +++ b/src/widgets/modals/SelectArticleDialog/index.tsx @@ -1,4 +1,4 @@ -import { articlesStore } from "@shared"; +import { articlesStore, languageStore } from "@shared"; import { observer } from "mobx-react-lite"; import { useEffect, useRef, useState } from "react"; import { @@ -44,7 +44,7 @@ export const SelectArticleModal = observer( useEffect(() => { if (hoveredArticleId) { hoverTimerRef.current = setTimeout(() => { - getArticle(hoveredArticleId); + getArticle(Number(hoveredArticleId)); }, 200); } @@ -66,7 +66,8 @@ export const SelectArticleModal = observer( } }; - const filteredArticles = articles + const filteredArticles = articles[languageStore.language] + // @ts-ignore .filter((article) => !linkedArticleIds.includes(article.id)) .filter((article) => article.service_name.toLowerCase().includes(searchQuery.toLowerCase()) @@ -96,11 +97,12 @@ export const SelectArticleModal = observer( }} /> + {/* @ts-ignore */} {filteredArticles.map((article) => ( onSelectArticle(article.id)} - onMouseEnter={() => handleArticleHover(article.id)} + onClick={() => onSelectArticle(article.id.toString())} + onMouseEnter={() => handleArticleHover(article.id.toString())} onMouseLeave={handleArticleLeave} sx={{ borderRadius: 1, diff --git a/tsconfig.json b/tsconfig.json index 5436761..3f20fd8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,6 +25,5 @@ "@app": ["src/app"] } }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] + "include": ["src"] } diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo new file mode 100644 index 0000000..1735a1a --- /dev/null +++ b/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/app/index.tsx","./src/app/router/index.tsx","./src/entities/index.ts","./src/entities/navigation/index.ts","./src/entities/navigation/model/index.ts","./src/entities/navigation/ui/index.tsx","./src/features/index.ts","./src/features/navigation/index.ts","./src/features/navigation/ui/index.tsx","./src/pages/index.ts","./src/pages/createsightpage/index.tsx","./src/pages/devicespage/index.tsx","./src/pages/editsightpage/index.tsx","./src/pages/loginpage/index.tsx","./src/pages/mainpage/index.tsx","./src/pages/sightpage/index.tsx","./src/shared/index.tsx","./src/shared/api/index.tsx","./src/shared/config/constants.tsx","./src/shared/config/index.ts","./src/shared/const/index.ts","./src/shared/lib/index.ts","./src/shared/lib/decodejwt/index.ts","./src/shared/lib/mui/theme.ts","./src/shared/modals/index.ts","./src/shared/modals/previewmediadialog/index.tsx","./src/shared/modals/selectarticledialog/index.tsx","./src/shared/modals/selectmediadialog/index.tsx","./src/shared/store/index.ts","./src/shared/store/articlesstore/index.tsx","./src/shared/store/authstore/index.tsx","./src/shared/store/citystore/index.tsx","./src/shared/store/devicesstore/index.tsx","./src/shared/store/editsightstore/index.tsx","./src/shared/store/languagestore/index.tsx","./src/shared/store/mediastore/index.tsx","./src/shared/store/sightsstore/index.tsx","./src/shared/store/snapshotstore/index.ts","./src/shared/store/vehiclestore/index.ts","./src/shared/ui/index.ts","./src/shared/ui/backbutton/index.tsx","./src/shared/ui/coordinatesinput/index.tsx","./src/shared/ui/input/index.tsx","./src/shared/ui/modal/index.tsx","./src/shared/ui/tabpanel/index.tsx","./src/widgets/index.ts","./src/widgets/devicestable/index.tsx","./src/widgets/languageswitcher/index.tsx","./src/widgets/layout/index.tsx","./src/widgets/layout/ui/appbar.tsx","./src/widgets/layout/ui/drawer.tsx","./src/widgets/layout/ui/drawerheader.tsx","./src/widgets/mediaviewer/threeview.tsx","./src/widgets/mediaviewer/index.tsx","./src/widgets/reactmarkdown/index.tsx","./src/widgets/reactmarkdowneditor/index.tsx","./src/widgets/sightedit/index.tsx","./src/widgets/sightheader/index.ts","./src/widgets/sightheader/ui/index.tsx","./src/widgets/sighttabs/index.ts","./src/widgets/sighttabs/informationtab/index.tsx","./src/widgets/sighttabs/leftwidgettab/index.tsx","./src/widgets/sighttabs/rightwidgettab/index.tsx","./src/widgets/sightstable/index.tsx","./src/widgets/modals/index.ts","./src/widgets/modals/selectarticledialog/index.tsx"],"errors":true,"version":"5.8.3"} \ No newline at end of file