feat: Add carriers translation on 3 languages
				
					
				
			This commit is contained in:
		| @@ -39,6 +39,7 @@ import { | ||||
|   RouteCreatePage, | ||||
|   RoutePreview, | ||||
|   RouteEditPage, | ||||
|   ArticlePreviewPage, | ||||
| } from "@pages"; | ||||
| import { authStore, createSightStore, editSightStore } from "@shared"; | ||||
| import { Layout } from "@widgets"; | ||||
| @@ -170,7 +171,7 @@ const router = createBrowserRouter([ | ||||
|       // { path: "vehicle/:id/edit", element: <VehicleEditPage /> }, | ||||
|       // Article | ||||
|       { path: "article", element: <ArticleListPage /> }, | ||||
|       // { path: "article/:id", element: <ArticlePreviewPage /> }, | ||||
|       { path: "article/:id", element: <ArticlePreviewPage /> }, | ||||
|       // { path: "media/create", element: <CreateMediaPage /> }, | ||||
|     ], | ||||
|   }, | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React, { useState } from "react"; | ||||
| import React from "react"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| import { ArrowLeft } from "lucide-react"; | ||||
| import { LanguageSwitcher } from "@widgets"; | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| import React, { useEffect, useState } from "react"; | ||||
| import React, { useEffect } from "react"; | ||||
| import { useNavigate, useParams } from "react-router-dom"; | ||||
| import { ArrowLeft } from "lucide-react"; | ||||
| import { LanguageSwitcher } from "@widgets"; | ||||
| import { articlesStore, languageStore } from "@shared"; | ||||
| import { articlesStore } from "@shared"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
|  | ||||
| const ArticleEditPage: React.FC = observer(() => { | ||||
|   const navigate = useNavigate(); | ||||
|   const { id } = useParams(); | ||||
|   const { language } = languageStore; | ||||
|  | ||||
|   const { articleData, getArticle } = articlesStore; | ||||
|  | ||||
|   useEffect(() => { | ||||
|   | ||||
							
								
								
									
										85
									
								
								src/pages/Article/ArticlePreviewPage/PreviewLeftWidget.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/pages/Article/ArticlePreviewPage/PreviewLeftWidget.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| import { Paper, Box, Typography } from "@mui/material"; | ||||
| import { MediaViewer, ReactMarkdownComponent } from "@widgets"; | ||||
| import { articlesStore, languageStore } from "@shared"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
|  | ||||
| export const PreviewLeftWidget = observer(() => { | ||||
|   const { articleMedia, articleData } = articlesStore; | ||||
|   const { language } = languageStore; | ||||
|  | ||||
|   return ( | ||||
|     <Paper | ||||
|       elevation={3} | ||||
|       sx={{ | ||||
|         width: "100%", | ||||
|         minWidth: 320, | ||||
|         background: | ||||
|           "#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)", | ||||
|         overflowY: "auto", | ||||
|         display: "flex", | ||||
|         flexDirection: "column", | ||||
|         borderRadius: "10px", | ||||
|       }} | ||||
|     > | ||||
|       <Box | ||||
|         sx={{ | ||||
|           overflow: "hidden", | ||||
|           width: "100%", | ||||
|           minHeight: 100, | ||||
|           padding: "3px", | ||||
|           display: "flex", | ||||
|           alignItems: "center", | ||||
|           justifyContent: "center", | ||||
|           "& img": { | ||||
|             borderTopLeftRadius: "10px", | ||||
|             borderTopRightRadius: "10px", | ||||
|             width: "100%", | ||||
|             height: "auto", | ||||
|             objectFit: "contain", | ||||
|           }, | ||||
|         }} | ||||
|       > | ||||
|         {articleMedia && <MediaViewer media={articleMedia} fullWidth />} | ||||
|       </Box> | ||||
|       <Box | ||||
|         sx={{ | ||||
|           background: | ||||
|             "#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)", | ||||
|           color: "white", | ||||
|           margin: "5px 0px 5px 0px", | ||||
|           display: "flex", | ||||
|           flexDirection: "column", | ||||
|           gap: 1, | ||||
|           padding: 1, | ||||
|         }} | ||||
|       > | ||||
|         <Typography | ||||
|           variant="h5" | ||||
|           component="h2" | ||||
|           sx={{ | ||||
|             wordBreak: "break-word", | ||||
|             fontSize: "24px", | ||||
|             fontWeight: 700, | ||||
|             lineHeight: "120%", | ||||
|           }} | ||||
|         > | ||||
|           {articleData?.[language]?.heading || "Название информации"} | ||||
|         </Typography> | ||||
|       </Box> | ||||
|       {articleData?.[language]?.body && ( | ||||
|         <Box | ||||
|           sx={{ | ||||
|             padding: 1, | ||||
|             maxHeight: "300px", | ||||
|             overflowY: "scroll", | ||||
|             background: | ||||
|               "#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)", | ||||
|             flexGrow: 1, | ||||
|           }} | ||||
|         > | ||||
|           <ReactMarkdownComponent value={articleData?.[language]?.body} /> | ||||
|         </Box> | ||||
|       )} | ||||
|     </Paper> | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										139
									
								
								src/pages/Article/ArticlePreviewPage/PreviewRightWidget.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/pages/Article/ArticlePreviewPage/PreviewRightWidget.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| import { Paper, Box, Typography } from "@mui/material"; | ||||
| import { MediaViewer, ReactMarkdownComponent } from "@widgets"; | ||||
| import { articlesStore, languageStore } from "@shared"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { ImagePlus } from "lucide-react"; | ||||
|  | ||||
| export const PreviewRightWidget = observer(() => { | ||||
|   const { articleData, articleMedia } = articlesStore; | ||||
|   const { language } = languageStore; | ||||
|   const article = articleData?.[language]; | ||||
|   if (!article) return null; | ||||
|  | ||||
|   return ( | ||||
|     <Paper | ||||
|       className="flex-1 flex flex-col max-w-[500px]" | ||||
|       sx={{ | ||||
|         borderRadius: "10px", | ||||
|         overflow: "hidden", | ||||
|       }} | ||||
|       elevation={2} | ||||
|     > | ||||
|       <Box | ||||
|         className="overflow-hidden" | ||||
|         sx={{ | ||||
|           width: "100%", | ||||
|           background: "#877361", | ||||
|           borderColor: "grey.300", | ||||
|           display: "flex", | ||||
|           flexDirection: "column", | ||||
|         }} | ||||
|       > | ||||
|         {articleMedia ? ( | ||||
|           <Box | ||||
|             sx={{ | ||||
|               overflow: "hidden", | ||||
|               width: "100%", | ||||
|               padding: "2px 2px 0px 2px", | ||||
|               "& img": { | ||||
|                 borderTopLeftRadius: "10px", | ||||
|                 borderTopRightRadius: "10px", | ||||
|                 width: "100%", | ||||
|                 height: "auto", | ||||
|                 objectFit: "contain", | ||||
|               }, | ||||
|             }} | ||||
|           > | ||||
|             <MediaViewer media={articleMedia} fullWidth /> | ||||
|           </Box> | ||||
|         ) : ( | ||||
|           <Box | ||||
|             sx={{ | ||||
|               width: "100%", | ||||
|               height: 200, | ||||
|               flexShrink: 0, | ||||
|               backgroundColor: "rgba(0,0,0,0.1)", | ||||
|               display: "flex", | ||||
|               alignItems: "center", | ||||
|               justifyContent: "center", | ||||
|             }} | ||||
|           > | ||||
|             <ImagePlus size={48} color="white" /> | ||||
|           </Box> | ||||
|         )} | ||||
|  | ||||
|         <Box | ||||
|           sx={{ | ||||
|             p: 1, | ||||
|             wordBreak: "break-word", | ||||
|             fontSize: "24px", | ||||
|             fontWeight: 700, | ||||
|             lineHeight: "120%", | ||||
|             backdropFilter: "blur(12px)", | ||||
|             borderBottom: "1px solid #A89F90", | ||||
|             boxShadow: "inset 4px 4px 12px 0 rgba(255,255,255,0.12)", | ||||
|             background: | ||||
|               "#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)", | ||||
|           }} | ||||
|         > | ||||
|           <Typography variant="h6" color="white"> | ||||
|             {article.heading || "Выберите статью"} | ||||
|           </Typography> | ||||
|         </Box> | ||||
|  | ||||
|         <Box | ||||
|           sx={{ | ||||
|             padding: 1, | ||||
|             minHeight: "200px", | ||||
|             maxHeight: "300px", | ||||
|             overflowY: "scroll", | ||||
|             background: | ||||
|               "rgba(179, 165, 152, 0.4), linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.2) 100%)", | ||||
|             flexGrow: 1, | ||||
|           }} | ||||
|         > | ||||
|           {article.body ? ( | ||||
|             <ReactMarkdownComponent value={article.body} /> | ||||
|           ) : ( | ||||
|             <Typography | ||||
|               color="rgba(255,255,255,0.7)" | ||||
|               sx={{ textAlign: "center", mt: 4 }} | ||||
|             > | ||||
|               Предпросмотр статьи появится здесь | ||||
|             </Typography> | ||||
|           )} | ||||
|         </Box> | ||||
|  | ||||
|         {/* @ts-ignore */} | ||||
|         {articleData?.right && articleData?.right.length > 1 && ( | ||||
|           <Box | ||||
|             sx={{ | ||||
|               p: 2, | ||||
|               display: "flex", | ||||
|               justifyContent: "space-between", | ||||
|               fontSize: "24px", | ||||
|               fontWeight: 700, | ||||
|               lineHeight: "120%", | ||||
|               flexWrap: "wrap", | ||||
|               gap: 1, | ||||
|               backdropFilter: "blur(12px)", | ||||
|               boxShadow: "inset 4px 4px 12px 0 rgba(255,255,255,0.12)", | ||||
|               background: | ||||
|                 "#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)", | ||||
|             }} | ||||
|           > | ||||
|             {/* @ts-ignore */} | ||||
|             {articleData.right.map((a, idx) => ( | ||||
|               <button | ||||
|                 key={idx} | ||||
|                 className="inline-block text-left text-xs text-white" | ||||
|               > | ||||
|                 {a.heading} | ||||
|               </button> | ||||
|             ))} | ||||
|           </Box> | ||||
|         )} | ||||
|       </Box> | ||||
|     </Paper> | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										57
									
								
								src/pages/Article/ArticlePreviewPage/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/pages/Article/ArticlePreviewPage/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import { useNavigate, useParams } from "react-router-dom"; | ||||
| import { useEffect } from "react"; | ||||
| import { Box } from "@mui/material"; | ||||
| import { PreviewLeftWidget } from "./PreviewLeftWidget"; | ||||
| import { PreviewRightWidget } from "./PreviewRightWidget"; | ||||
| import { articlesStore, languageStore } from "@shared"; | ||||
| import { ArrowLeft } from "lucide-react"; | ||||
|  | ||||
| export const ArticlePreviewPage = () => { | ||||
|   const navigate = useNavigate(); | ||||
|   const { id } = useParams(); | ||||
|   const { getArticle, getArticleMedia, getArticlePreview } = articlesStore; | ||||
|   const { language } = languageStore; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const fetchData = async () => { | ||||
|       if (id) { | ||||
|         await getArticle(Number(id), language); | ||||
|         await getArticleMedia(Number(id)); | ||||
|         await getArticlePreview(Number(id)); | ||||
|       } | ||||
|     }; | ||||
|     fetchData(); | ||||
|   }, [id, language]); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div className="flex items-center gap-4 mb-10"> | ||||
|         <button | ||||
|           className="flex items-center gap-2" | ||||
|           onClick={() => navigate(-1)} | ||||
|         > | ||||
|           <ArrowLeft size={20} /> | ||||
|           Назад | ||||
|         </button> | ||||
|       </div> | ||||
|  | ||||
|       <Box | ||||
|         sx={{ | ||||
|           display: "flex", | ||||
|           gap: 2, | ||||
|           p: 2, | ||||
|           justifyContent: "center", | ||||
|           margin: "0 auto", | ||||
|         }} | ||||
|       > | ||||
|         <Box sx={{ width: "320px" }}> | ||||
|           <PreviewLeftWidget /> | ||||
|         </Box> | ||||
|  | ||||
|         <Box sx={{ width: "500px" }}> | ||||
|           <PreviewRightWidget /> | ||||
|         </Box> | ||||
|       </Box> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -1 +1,2 @@ | ||||
| export * from "./ArticleListPage"; | ||||
| export * from "./ArticlePreviewPage"; | ||||
|   | ||||
| @@ -12,9 +12,9 @@ import { ArrowLeft, Save } from "lucide-react"; | ||||
| import { Loader2 } from "lucide-react"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| import { toast } from "react-toastify"; | ||||
| import { carrierStore, cityStore, mediaStore, languageStore } from "@shared"; | ||||
| import { carrierStore, cityStore, mediaStore } from "@shared"; | ||||
| import { useState, useEffect } from "react"; | ||||
| import { MediaViewer, ImageUploadCard, LanguageSwitcher } from "@widgets"; | ||||
| import { ImageUploadCard, LanguageSwitcher } from "@widgets"; | ||||
| import { | ||||
|   SelectMediaDialog, | ||||
|   UploadMediaDialog, | ||||
| @@ -23,7 +23,7 @@ import { | ||||
|  | ||||
| export const CarrierCreatePage = observer(() => { | ||||
|   const navigate = useNavigate(); | ||||
|   const { language } = languageStore; | ||||
|  | ||||
|   const [fullName, setFullName] = useState(""); | ||||
|   const [shortName, setShortName] = useState(""); | ||||
|   const [cityId, setCityId] = useState<number | null>(null); | ||||
|   | ||||
| @@ -12,9 +12,9 @@ import { ArrowLeft, Save } from "lucide-react"; | ||||
| import { Loader2 } from "lucide-react"; | ||||
| import { useNavigate, useParams } from "react-router-dom"; | ||||
| import { toast } from "react-toastify"; | ||||
| import { carrierStore, cityStore, mediaStore } from "@shared"; | ||||
| import { carrierStore, cityStore, mediaStore, languageStore } from "@shared"; | ||||
| import { useState, useEffect } from "react"; | ||||
| import { MediaViewer, ImageUploadCard } from "@widgets"; | ||||
| import { ImageUploadCard, LanguageSwitcher } from "@widgets"; | ||||
| import { | ||||
|   SelectMediaDialog, | ||||
|   UploadMediaDialog, | ||||
| @@ -24,8 +24,8 @@ import { | ||||
| export const CarrierEditPage = observer(() => { | ||||
|   const navigate = useNavigate(); | ||||
|   const { id } = useParams(); | ||||
|   const { carrier, getCarrier, setEditCarrierData, editCarrierData } = | ||||
|     carrierStore; | ||||
|   const { getCarrier, setEditCarrierData, editCarrierData } = carrierStore; | ||||
|   const { language } = languageStore; | ||||
|  | ||||
|   const [isLoading, setIsLoading] = useState(false); | ||||
|   const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false); | ||||
| @@ -41,16 +41,34 @@ export const CarrierEditPage = observer(() => { | ||||
|       await cityStore.getCities("ru"); | ||||
|       await cityStore.getCities("en"); | ||||
|       await cityStore.getCities("zh"); | ||||
|       await getCarrier(Number(id)); | ||||
|       const carrierData = await getCarrier(Number(id)); | ||||
|  | ||||
|       setEditCarrierData( | ||||
|         carrier?.[Number(id)]?.full_name as string, | ||||
|         carrier?.[Number(id)]?.short_name as string, | ||||
|  | ||||
|         carrier?.[Number(id)]?.city_id as number, | ||||
|         carrier?.[Number(id)]?.slogan as string, | ||||
|         carrier?.[Number(id)]?.logo as string | ||||
|       ); | ||||
|       if (carrierData) { | ||||
|         setEditCarrierData( | ||||
|           carrierData.ru?.full_name || "", | ||||
|           carrierData.ru?.short_name || "", | ||||
|           carrierData.ru?.city_id || 0, | ||||
|           carrierData.ru?.slogan || "", | ||||
|           carrierData.ru?.logo || "", | ||||
|           "ru" | ||||
|         ); | ||||
|         setEditCarrierData( | ||||
|           carrierData.en?.full_name || "", | ||||
|           carrierData.en?.short_name || "", | ||||
|           carrierData.en?.city_id || 0, | ||||
|           carrierData.en?.slogan || "", | ||||
|           carrierData.en?.logo || "", | ||||
|           "en" | ||||
|         ); | ||||
|         setEditCarrierData( | ||||
|           carrierData.zh?.full_name || "", | ||||
|           carrierData.zh?.short_name || "", | ||||
|           carrierData.zh?.city_id || 0, | ||||
|           carrierData.zh?.slogan || "", | ||||
|           carrierData.zh?.logo || "", | ||||
|           "zh" | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       mediaStore.getMedia(); | ||||
|     })(); | ||||
| @@ -76,12 +94,12 @@ export const CarrierEditPage = observer(() => { | ||||
|     media_type: number; | ||||
|   }) => { | ||||
|     setEditCarrierData( | ||||
|       editCarrierData.full_name, | ||||
|       editCarrierData.short_name, | ||||
|  | ||||
|       editCarrierData[language].full_name, | ||||
|       editCarrierData[language].short_name, | ||||
|       editCarrierData.city_id, | ||||
|       editCarrierData.slogan, | ||||
|       media.id | ||||
|       editCarrierData[language].slogan, | ||||
|       media.id, | ||||
|       language | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
| @@ -91,6 +109,7 @@ export const CarrierEditPage = observer(() => { | ||||
|  | ||||
|   return ( | ||||
|     <Paper className="w-full h-full p-3 flex flex-col gap-10"> | ||||
|       <LanguageSwitcher /> | ||||
|       <div className="flex items-center gap-4"> | ||||
|         <button | ||||
|           className="flex items-center gap-2" | ||||
| @@ -101,6 +120,9 @@ export const CarrierEditPage = observer(() => { | ||||
|         </button> | ||||
|       </div> | ||||
|  | ||||
|       <div className="flex gap-10 items-center mb-5 max-w-[80%] self-start"> | ||||
|         <h1 className="text-3xl break-words">{editCarrierData.ru.full_name}</h1> | ||||
|       </div> | ||||
|       <div className="flex flex-col gap-10 w-full items-end"> | ||||
|         <FormControl fullWidth> | ||||
|           <InputLabel>Город</InputLabel> | ||||
| @@ -110,15 +132,16 @@ export const CarrierEditPage = observer(() => { | ||||
|             required | ||||
|             onChange={(e) => | ||||
|               setEditCarrierData( | ||||
|                 editCarrierData.full_name, | ||||
|                 editCarrierData.short_name, | ||||
|                 editCarrierData[language].full_name, | ||||
|                 editCarrierData[language].short_name, | ||||
|                 Number(e.target.value), | ||||
|                 editCarrierData.slogan, | ||||
|                 editCarrierData.logo | ||||
|                 editCarrierData[language].slogan, | ||||
|                 editCarrierData.logo, | ||||
|                 language | ||||
|               ) | ||||
|             } | ||||
|           > | ||||
|             {cityStore.cities.ru.data?.map((city) => ( | ||||
|             {cityStore.cities[language].data?.map((city) => ( | ||||
|               <MenuItem key={city.id} value={city.id}> | ||||
|                 {city.name} | ||||
|               </MenuItem> | ||||
| @@ -129,16 +152,16 @@ export const CarrierEditPage = observer(() => { | ||||
|         <TextField | ||||
|           fullWidth | ||||
|           label="Полное название" | ||||
|           value={editCarrierData.full_name} | ||||
|           value={editCarrierData[language].full_name} | ||||
|           required | ||||
|           onChange={(e) => | ||||
|             setEditCarrierData( | ||||
|               e.target.value, | ||||
|               editCarrierData.short_name, | ||||
|  | ||||
|               editCarrierData[language].short_name, | ||||
|               editCarrierData.city_id, | ||||
|               editCarrierData.slogan, | ||||
|               editCarrierData.logo | ||||
|               editCarrierData[language].slogan, | ||||
|               editCarrierData.logo, | ||||
|               language | ||||
|             ) | ||||
|           } | ||||
|         /> | ||||
| @@ -146,16 +169,16 @@ export const CarrierEditPage = observer(() => { | ||||
|         <TextField | ||||
|           fullWidth | ||||
|           label="Короткое название" | ||||
|           value={editCarrierData.short_name} | ||||
|           value={editCarrierData[language].short_name} | ||||
|           required | ||||
|           onChange={(e) => | ||||
|             setEditCarrierData( | ||||
|               editCarrierData.full_name, | ||||
|               editCarrierData[language].full_name, | ||||
|               e.target.value, | ||||
|  | ||||
|               editCarrierData.city_id, | ||||
|               editCarrierData.slogan, | ||||
|               editCarrierData.logo | ||||
|               editCarrierData[language].slogan, | ||||
|               editCarrierData.logo, | ||||
|               language | ||||
|             ) | ||||
|           } | ||||
|         /> | ||||
| @@ -163,15 +186,15 @@ export const CarrierEditPage = observer(() => { | ||||
|         <TextField | ||||
|           fullWidth | ||||
|           label="Слоган" | ||||
|           value={editCarrierData.slogan} | ||||
|           value={editCarrierData[language].slogan} | ||||
|           onChange={(e) => | ||||
|             setEditCarrierData( | ||||
|               editCarrierData.full_name, | ||||
|               editCarrierData.short_name, | ||||
|  | ||||
|               editCarrierData[language].full_name, | ||||
|               editCarrierData[language].short_name, | ||||
|               editCarrierData.city_id, | ||||
|               e.target.value, | ||||
|               editCarrierData.logo | ||||
|               editCarrierData.logo, | ||||
|               language | ||||
|             ) | ||||
|           } | ||||
|         /> | ||||
| @@ -187,12 +210,12 @@ export const CarrierEditPage = observer(() => { | ||||
|             }} | ||||
|             onDeleteImageClick={() => { | ||||
|               setEditCarrierData( | ||||
|                 editCarrierData.full_name, | ||||
|                 editCarrierData.short_name, | ||||
|  | ||||
|                 editCarrierData[language].full_name, | ||||
|                 editCarrierData[language].short_name, | ||||
|                 editCarrierData.city_id, | ||||
|                 editCarrierData.slogan, | ||||
|                 "" | ||||
|                 editCarrierData[language].slogan, | ||||
|                 "", | ||||
|                 language | ||||
|               ); | ||||
|               setActiveMenuType(null); | ||||
|             }} | ||||
| @@ -214,8 +237,8 @@ export const CarrierEditPage = observer(() => { | ||||
|           onClick={handleEdit} | ||||
|           disabled={ | ||||
|             isLoading || | ||||
|             !editCarrierData.full_name || | ||||
|             !editCarrierData.short_name || | ||||
|             !editCarrierData[language].full_name || | ||||
|             !editCarrierData[language].short_name || | ||||
|             !editCarrierData.city_id || | ||||
|             !editCarrierData.logo | ||||
|           } | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; | ||||
| import { carrierStore } from "@shared"; | ||||
| import { carrierStore, languageStore } from "@shared"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { Eye, Pencil, Trash2, Minus } from "lucide-react"; | ||||
| import { Pencil, Trash2, Minus } from "lucide-react"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| import { CreateButton, DeleteModal } from "@widgets"; | ||||
| import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets"; | ||||
|  | ||||
| export const CarrierListPage = observer(() => { | ||||
|   const { carriers, getCarriers, deleteCarrier } = carrierStore; | ||||
| @@ -13,10 +13,13 @@ export const CarrierListPage = observer(() => { | ||||
|   const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false); | ||||
|   const [rowId, setRowId] = useState<number | null>(null); | ||||
|   const [ids, setIds] = useState<number[]>([]); | ||||
|   const { language } = languageStore; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     getCarriers(); | ||||
|   }, []); | ||||
|     (async () => { | ||||
|       await getCarriers(language); | ||||
|     })(); | ||||
|   }, [language]); | ||||
|  | ||||
|   const columns: GridColDef[] = [ | ||||
|     { | ||||
| @@ -96,7 +99,7 @@ export const CarrierListPage = observer(() => { | ||||
|     }, | ||||
|   ]; | ||||
|  | ||||
|   const rows = carriers.data?.map((carrier) => ({ | ||||
|   const rows = carriers[language].data?.map((carrier) => ({ | ||||
|     id: carrier.id, | ||||
|     full_name: carrier.full_name, | ||||
|     short_name: carrier.short_name, | ||||
| @@ -105,6 +108,7 @@ export const CarrierListPage = observer(() => { | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <LanguageSwitcher /> | ||||
|       <div className="w-full"> | ||||
|         <div className="flex justify-between items-center mb-10"> | ||||
|           <h1 className="text-2xl">Перевозчики</h1> | ||||
| @@ -155,7 +159,7 @@ export const CarrierListPage = observer(() => { | ||||
|         open={isBulkDeleteModalOpen} | ||||
|         onDelete={async () => { | ||||
|           await Promise.all(ids.map((id) => deleteCarrier(id))); | ||||
|           getCarriers(); | ||||
|           await getCarriers(language); | ||||
|           setIsBulkDeleteModalOpen(false); | ||||
|           setIds([]); | ||||
|         }} | ||||
|   | ||||
| @@ -1,116 +0,0 @@ | ||||
| import { Paper } from "@mui/material"; | ||||
| import { carrierStore, mediaStore } from "@shared"; | ||||
| import { MediaViewer } from "@widgets"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { ArrowLeft } from "lucide-react"; | ||||
| import { useEffect } from "react"; | ||||
| import { useNavigate, useParams } from "react-router-dom"; | ||||
|  | ||||
| export const CarrierPreviewPage = observer(() => { | ||||
|   const { id } = useParams(); | ||||
|   const { getCarrier, carrier, setEditCarrierData } = carrierStore; | ||||
|   const { oneMedia, getOneMedia } = mediaStore; | ||||
|   const navigate = useNavigate(); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     (async () => { | ||||
|       const carrierResponse = await getCarrier(Number(id)); | ||||
|       setEditCarrierData( | ||||
|         carrierResponse?.full_name as string, | ||||
|         carrierResponse?.short_name as string, | ||||
|         carrierResponse?.city as string, | ||||
|         carrierResponse?.city_id as number, | ||||
|         // carrierResponse?.main_color as string, | ||||
|         // carrierResponse?.left_color as string, | ||||
|         // carrierResponse?.right_color as string, | ||||
|         carrierResponse?.slogan as string, | ||||
|         carrierResponse?.logo as string | ||||
|       ); | ||||
|       console.log(carrierResponse); | ||||
|       await getOneMedia(carrierResponse?.logo as string); | ||||
|     })(); | ||||
|   }, [id]); | ||||
|  | ||||
|   return ( | ||||
|     <Paper className="w-full h-full p-3 flex flex-col gap-10"> | ||||
|       {carrier && ( | ||||
|         <> | ||||
|           <div className="flex justify-between items-center"> | ||||
|             <button | ||||
|               className="flex items-center gap-2" | ||||
|               onClick={() => navigate(-1)} | ||||
|             > | ||||
|               <ArrowLeft size={20} /> | ||||
|               Назад | ||||
|             </button> | ||||
|           </div> | ||||
|           <div className="flex flex-col gap-10 w-full"> | ||||
|             <div className="flex flex-col gap-2"> | ||||
|               <h1 className="text-lg font-bold">Полное имя</h1> | ||||
|               <p>{carrier[Number(id)]?.full_name}</p> | ||||
|             </div> | ||||
|  | ||||
|             <div className="flex flex-col gap-2"> | ||||
|               <h1 className="text-lg font-bold">Полное имя</h1> | ||||
|               <p>{carrier[Number(id)]?.full_name}</p> | ||||
|             </div> | ||||
|             <div className="flex flex-col gap-2"> | ||||
|               <h1 className="text-lg font-bold">Город</h1> | ||||
|               <p>{carrier[Number(id)]?.city}</p> | ||||
|             </div> | ||||
|             {/* <div className="flex flex-col gap-2 "> | ||||
|               <h1 className="text-lg font-bold">Основной цвет</h1> | ||||
|               <div | ||||
|                 className="w-min" | ||||
|                 style={{ | ||||
|                   backgroundColor: `${carrier[Number(id)]?.main_color}90`, | ||||
|                 }} | ||||
|               > | ||||
|                 {carrier[Number(id)]?.main_color} | ||||
|               </div> | ||||
|             </div> | ||||
|             <div className="flex flex-col gap-2"> | ||||
|               <h1 className="text-lg font-bold">Цвет левого виджета</h1> | ||||
|               <div | ||||
|                 className="w-min" | ||||
|                 style={{ | ||||
|                   backgroundColor: `${carrier[Number(id)]?.left_color}90`, | ||||
|                 }} | ||||
|               > | ||||
|                 {carrier[Number(id)]?.left_color} | ||||
|               </div> | ||||
|             </div> | ||||
|             <div className="flex flex-col gap-2"> | ||||
|               <h1 className="text-lg font-bold">Цвет правого виджета</h1> | ||||
|               <div | ||||
|                 className="w-min" | ||||
|                 style={{ | ||||
|                   backgroundColor: `${carrier[Number(id)]?.right_color}90`, | ||||
|                 }} | ||||
|               > | ||||
|                 {carrier[Number(id)]?.right_color} | ||||
|               </div> | ||||
|             </div> */} | ||||
|             <div className="flex flex-col gap-2"> | ||||
|               <h1 className="text-lg font-bold">Краткое имя</h1> | ||||
|               <p>{carrier[Number(id)]?.short_name}</p> | ||||
|             </div> | ||||
|             {oneMedia && ( | ||||
|               <div className="flex flex-col gap-2"> | ||||
|                 <h1 className="text-lg font-bold">Логотип</h1> | ||||
|  | ||||
|                 <MediaViewer | ||||
|                   media={{ | ||||
|                     id: oneMedia?.id as string, | ||||
|                     media_type: oneMedia?.media_type as number, | ||||
|                     filename: oneMedia?.filename, | ||||
|                   }} | ||||
|                 /> | ||||
|               </div> | ||||
|             )} | ||||
|           </div> | ||||
|         </> | ||||
|       )} | ||||
|     </Paper> | ||||
|   ); | ||||
| }); | ||||
| @@ -1,4 +1,4 @@ | ||||
| export * from "./CarrierListPage"; | ||||
| export * from "./CarrierPreviewPage"; | ||||
|  | ||||
| export * from "./CarrierCreatePage"; | ||||
| export * from "./CarrierEditPage"; | ||||
|   | ||||
| @@ -6,16 +6,15 @@ import { | ||||
|   MenuItem, | ||||
|   FormControl, | ||||
|   InputLabel, | ||||
|   Box, | ||||
| } from "@mui/material"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { ArrowLeft, Save, ImagePlus, Minus } from "lucide-react"; | ||||
| import { ArrowLeft, Save, Minus } from "lucide-react"; | ||||
| import { Loader2 } from "lucide-react"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| import { toast } from "react-toastify"; | ||||
| import { cityStore, countryStore, languageStore, mediaStore } from "@shared"; | ||||
| import { useState, useEffect } from "react"; | ||||
| import { LanguageSwitcher, MediaViewer, ImageUploadCard } from "@widgets"; | ||||
| import { LanguageSwitcher, ImageUploadCard } from "@widgets"; | ||||
| import { | ||||
|   SelectMediaDialog, | ||||
|   UploadMediaDialog, | ||||
| @@ -91,8 +90,8 @@ export const CityCreatePage = observer(() => { | ||||
|       </div> | ||||
|  | ||||
|       <div className="flex flex-col gap-10 w-full items-end"> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%]"> | ||||
|           <h1 className="text-3xl break-words">Создание города</h1> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%] self-start"> | ||||
|           <h1 className="text-3xl break-words">{createCityData.ru.name}</h1> | ||||
|         </div> | ||||
|         <TextField | ||||
|           fullWidth | ||||
|   | ||||
| @@ -6,10 +6,9 @@ import { | ||||
|   MenuItem, | ||||
|   FormControl, | ||||
|   InputLabel, | ||||
|   Box, | ||||
| } from "@mui/material"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { ArrowLeft, Save, ImagePlus } from "lucide-react"; | ||||
| import { ArrowLeft, Save } from "lucide-react"; | ||||
| import { Loader2 } from "lucide-react"; | ||||
| import { useNavigate, useParams } from "react-router-dom"; | ||||
| import { toast } from "react-toastify"; | ||||
| @@ -21,7 +20,7 @@ import { | ||||
|   CashedCities, | ||||
| } from "@shared"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import { LanguageSwitcher, MediaViewer, ImageUploadCard } from "@widgets"; | ||||
| import { LanguageSwitcher, ImageUploadCard } from "@widgets"; | ||||
| import { | ||||
|   SelectMediaDialog, | ||||
|   UploadMediaDialog, | ||||
| @@ -70,7 +69,7 @@ export const CityEditPage = observer(() => { | ||||
|         setEditCityData(zhData.name, zhData.country_code, zhData.arms, "zh"); | ||||
|  | ||||
|         await getOneMedia(ruData.arms as string); | ||||
|         await getCountries(language); | ||||
|         await getCountries("ru"); | ||||
|         await getMedia(); | ||||
|       } | ||||
|     })(); | ||||
| @@ -108,7 +107,7 @@ export const CityEditPage = observer(() => { | ||||
|       </div> | ||||
|  | ||||
|       <div className="flex flex-col gap-10 w-full items-end"> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%]"> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%] self-start    "> | ||||
|           <h1 className="text-3xl break-words">{editCityData.ru.name}</h1> | ||||
|         </div> | ||||
|         <TextField | ||||
| @@ -141,7 +140,7 @@ export const CityEditPage = observer(() => { | ||||
|               ); | ||||
|             }} | ||||
|           > | ||||
|             {countryStore.countries[language].data.map((country) => ( | ||||
|             {countryStore.countries.ru.data.map((country) => ( | ||||
|               <MenuItem key={country.code} value={country.code}> | ||||
|                 {country.name} | ||||
|               </MenuItem> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; | ||||
| import { languageStore, cityStore, CashedCities } from "@shared"; | ||||
| import { languageStore, cityStore } from "@shared"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { Eye, Pencil, Trash2, Minus } from "lucide-react"; | ||||
|   | ||||
| @@ -41,8 +41,8 @@ export const CountryCreatePage = observer(() => { | ||||
|       </div> | ||||
|  | ||||
|       <div className="flex flex-col gap-10 w-full items-end"> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%]"> | ||||
|           <h1 className="text-3xl break-words">Создание страны</h1> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%] self-start"> | ||||
|           <h1 className="text-3xl break-words">{createCountryData.ru.name}</h1> | ||||
|         </div> | ||||
|         <TextField | ||||
|           fullWidth | ||||
|   | ||||
| @@ -58,8 +58,8 @@ export const CountryEditPage = observer(() => { | ||||
|       </div> | ||||
|  | ||||
|       <div className="flex flex-col gap-10 w-full items-end"> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%]"> | ||||
|           <h1 className="text-3xl break-words">{editCountryData.ru.name}</h1> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%] self-start"> | ||||
|           <h1 className="text-3xl break-words t">{editCountryData.ru.name}</h1> | ||||
|         </div> | ||||
|         <TextField | ||||
|           fullWidth | ||||
|   | ||||
| @@ -3,12 +3,7 @@ import { InformationTab, LeaveAgree, RightWidgetTab } from "@widgets"; | ||||
| import { LeftWidgetTab } from "@widgets"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { | ||||
|   articlesStore, | ||||
|   cityStore, | ||||
|   editSightStore, | ||||
|   languageStore, | ||||
| } from "@shared"; | ||||
| import { articlesStore, cityStore, editSightStore } from "@shared"; | ||||
| import { useBlocker, useParams } from "react-router-dom"; | ||||
|  | ||||
| function a11yProps(index: number) { | ||||
| @@ -22,7 +17,7 @@ export const EditSightPage = observer(() => { | ||||
|   const [value, setValue] = useState(0); | ||||
|   const { sight, getSightInfo, needLeaveAgree } = editSightStore; | ||||
|   const { getArticles } = articlesStore; | ||||
|   const { language } = languageStore; | ||||
|  | ||||
|   const { id } = useParams(); | ||||
|   const { getRuCities } = cityStore; | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import React, { | ||||
|   useRef, | ||||
|   useState, | ||||
|   useCallback, | ||||
|   ReactNode, | ||||
|   useMemo, | ||||
| } from "react"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| @@ -33,7 +32,6 @@ import { | ||||
|   ArrowRightLeft, | ||||
|   Landmark, | ||||
|   Pencil, | ||||
|   Lasso, | ||||
|   InfoIcon, | ||||
|   X, | ||||
|   Loader2, | ||||
| @@ -86,9 +84,7 @@ class MapStore { | ||||
|     for (const id of routesIds) { | ||||
|       const route = await languageInstance("ru").get(`/route/${id}`); | ||||
|       this.routes.push({ | ||||
|         id: route.data.id, | ||||
|         route_number: route.data.route_number, | ||||
|         path: route.data.path, | ||||
|         ...route.data, | ||||
|       }); | ||||
|     } | ||||
|  | ||||
| @@ -100,21 +96,14 @@ class MapStore { | ||||
|   getStations = async () => { | ||||
|     const stations = await languageInstance("ru").get("/station"); | ||||
|     this.stations = stations.data.map((station: any) => ({ | ||||
|       id: station.id, | ||||
|       name: station.name, | ||||
|       latitude: station.latitude, | ||||
|       longitude: station.longitude, | ||||
|       ...station, | ||||
|     })); | ||||
|   }; | ||||
|  | ||||
|   getSights = async () => { | ||||
|     const sights = await languageInstance("ru").get("/sight"); | ||||
|     this.sights = sights.data.map((sight: any) => ({ | ||||
|       id: sight.id, | ||||
|       name: sight.name, | ||||
|       description: sight.description, | ||||
|       latitude: sight.latitude, | ||||
|       longitude: sight.longitude, | ||||
|       ...sight, | ||||
|     })); | ||||
|   }; | ||||
|  | ||||
| @@ -194,9 +183,25 @@ class MapStore { | ||||
|       throw new Error(`Unknown feature type for update: ${featureType}`); | ||||
|     } | ||||
|  | ||||
|     let oldData; | ||||
|     if (featureType === "route") { | ||||
|       oldData = this.routes.find((f) => f.id === numericId); | ||||
|     } else if (featureType === "station") { | ||||
|       oldData = this.stations.find((f) => f.id === numericId); | ||||
|     } else if (featureType === "sight") { | ||||
|       oldData = this.sights.find((f) => f.id === numericId); | ||||
|     } | ||||
|  | ||||
|     console.log(oldData); | ||||
|     console.log(data); | ||||
|  | ||||
|     const response = await languageInstance("ru").patch( | ||||
|       `/${featureType}/${numericId}`, | ||||
|       data | ||||
|       { | ||||
|         ...oldData, | ||||
|         latitude: data.latitude, | ||||
|         longitude: data.longitude, | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     if (featureType === "route") { | ||||
| @@ -277,6 +282,7 @@ class MapService { | ||||
|   private tooltipElement: HTMLElement; | ||||
|   private tooltipOverlay: Overlay | null; | ||||
|   private mode: string | null; | ||||
|   // @ts-ignore | ||||
|   private currentDrawingType: "Point" | "LineString" | null; | ||||
|   private currentDrawingFeatureType: FeatureType | null; | ||||
|   private currentInteraction: Draw | null; | ||||
| @@ -620,7 +626,7 @@ class MapService { | ||||
|       filter: (_: FeatureLike, l: Layer<Source, any> | null) => | ||||
|         l === this.vectorLayer, | ||||
|     }); | ||||
|  | ||||
|     // @ts-ignore | ||||
|     this.modifyInteraction.on("modifystart", (event) => { | ||||
|       const geoJSONFormat = new GeoJSON(); | ||||
|       if (!this.map) return; | ||||
| @@ -959,7 +965,8 @@ class MapService { | ||||
|       feature.set("name", `${baseName} ${maxNumber + 1}`); | ||||
|  | ||||
|       await this.saveNewFeature(feature); | ||||
|       this.stopDrawing(); | ||||
|       // Убираем вызов stopDrawing, чтобы режим рисования оставался активным | ||||
|       // this.stopDrawing(); | ||||
|     }); | ||||
|  | ||||
|     this.map.addInteraction(this.currentInteraction); | ||||
| @@ -988,7 +995,8 @@ class MapService { | ||||
|     this.currentInteraction = null; | ||||
|     this.currentDrawingType = null; | ||||
|     this.currentDrawingFeatureType = null; | ||||
|     this.activateEditMode(); | ||||
|     // Убираем автоматическое переключение в режим редактирования | ||||
|     // this.activateEditMode(); | ||||
|   } | ||||
|  | ||||
|   public finishDrawing(): void { | ||||
| @@ -1007,6 +1015,10 @@ class MapService { | ||||
|       this.currentInteraction instanceof Draw | ||||
|     ) { | ||||
|       this.finishDrawing(); | ||||
|       // После завершения рисования маршрута, останавливаем режим рисования | ||||
|       if (this.currentDrawingType === "LineString") { | ||||
|         this.stopDrawing(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -1098,6 +1110,7 @@ class MapService { | ||||
|     if (this.mode === "edit") { | ||||
|       this.selectInteraction.getFeatures().clear(); | ||||
|       this.selectInteraction.getFeatures().push(feature); | ||||
|       // @ts-ignore | ||||
|       const selectEvent = new SelectEvent("select", [feature], []); | ||||
|       this.selectInteraction.dispatchEvent(selectEvent); | ||||
|     } | ||||
| @@ -1332,7 +1345,8 @@ class MapService { | ||||
|       feature.set("name", newName); | ||||
|  | ||||
|       this.updateFeaturesInReact(); | ||||
|       this.selectFeature(newFeatureId); | ||||
|       // Убираем автоматический выбор созданного объекта | ||||
|       // this.selectFeature(newFeatureId); | ||||
|     } catch (error) { | ||||
|       console.error("Failed to save new feature:", error); | ||||
|       toast.error("Не удалось сохранить объект."); | ||||
| @@ -1366,6 +1380,7 @@ interface ControlItem { | ||||
| const MapControls: React.FC<MapControlsProps> = ({ | ||||
|   mapService, | ||||
|   activeMode, | ||||
|   // @ts-ignore | ||||
|   isLassoActive, | ||||
|   isUnselectDisabled, | ||||
| }) => { | ||||
| @@ -1473,11 +1488,13 @@ const MapSightbar: React.FC<MapSightbarProps> = ({ | ||||
|   }, [mapFeatures, searchQuery]); | ||||
|  | ||||
|   const handleFeatureClick = useCallback( | ||||
|     // @ts-ignore | ||||
|     (id) => mapService?.selectFeature(id), | ||||
|     [mapService] | ||||
|   ); | ||||
|  | ||||
|   const handleDeleteFeature = useCallback( | ||||
|     // @ts-ignore | ||||
|     (id, recourse) => { | ||||
|       if ( | ||||
|         mapService && | ||||
| @@ -1490,6 +1507,7 @@ const MapSightbar: React.FC<MapSightbarProps> = ({ | ||||
|   ); | ||||
|  | ||||
|   const handleCheckboxChange = useCallback( | ||||
|     // @ts-ignore | ||||
|     (id) => { | ||||
|       if (id === undefined) return; | ||||
|       const newSet = new Set(selectedIds); | ||||
| @@ -1512,7 +1530,10 @@ const MapSightbar: React.FC<MapSightbarProps> = ({ | ||||
|     } | ||||
|   }, [mapService, selectedIds, setSelectedIds]); | ||||
|  | ||||
|   // @ts-ignore | ||||
|  | ||||
|   const handleEditFeature = useCallback( | ||||
|     // @ts-ignore | ||||
|     (featureType, fullId) => { | ||||
|       if (!featureType || !fullId) return; | ||||
|       const numericId = String(fullId).split("-")[1]; | ||||
| @@ -1522,8 +1543,11 @@ const MapSightbar: React.FC<MapSightbarProps> = ({ | ||||
|   ); | ||||
|  | ||||
|   const sortFeatures = ( | ||||
|     // @ts-ignore | ||||
|     features, | ||||
|     // @ts-ignore | ||||
|     currentSelectedIds, | ||||
|     // @ts-ignore | ||||
|     currentSelectedFeature | ||||
|   ) => { | ||||
|     const selectedId = currentSelectedFeature?.getId(); | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import { | ||||
|   MenuItem, | ||||
|   FormControl, | ||||
|   InputLabel, | ||||
|   Typography, | ||||
|   // Typography, | ||||
|   Box, | ||||
| } from "@mui/material"; | ||||
| import { LanguageSwitcher } from "@widgets"; | ||||
| @@ -18,6 +18,7 @@ import { toast } from "react-toastify"; | ||||
| import { carrierStore } from "../../../shared/store/CarrierStore"; | ||||
| import { articlesStore } from "../../../shared/store/ArticlesStore"; | ||||
| import { Route, routeStore } from "../../../shared/store/RouteStore"; | ||||
| import { languageStore } from "@shared"; | ||||
|  | ||||
| export const RouteCreatePage = observer(() => { | ||||
|   const navigate = useNavigate(); | ||||
| @@ -33,11 +34,11 @@ export const RouteCreatePage = observer(() => { | ||||
|   const [centerLat, setCenterLat] = useState(""); | ||||
|   const [centerLng, setCenterLng] = useState(""); | ||||
|   const [isLoading, setIsLoading] = useState(false); | ||||
|  | ||||
|   const { language } = languageStore; | ||||
|   useEffect(() => { | ||||
|     carrierStore.getCarriers(); | ||||
|     carrierStore.getCarriers(language); | ||||
|     articlesStore.getArticleList(); | ||||
|   }, []); | ||||
|   }, [language]); | ||||
|  | ||||
|   const handleCreateRoute = async () => { | ||||
|     try { | ||||
| @@ -65,8 +66,9 @@ export const RouteCreatePage = observer(() => { | ||||
|       // Собираем объект маршрута | ||||
|       const newRoute: Partial<Route> = { | ||||
|         carrier: | ||||
|           carrierStore.carriers.data.find((c: any) => c.id === carrier_id) | ||||
|             ?.full_name || "", | ||||
|           carrierStore.carriers[ | ||||
|             language as keyof typeof carrierStore.carriers | ||||
|           ].data?.find((c: any) => c.id === carrier_id)?.full_name || "", | ||||
|         carrier_id, | ||||
|         route_number: routeNumber, | ||||
|         route_sys_number: govRouteNumber, | ||||
| @@ -112,16 +114,20 @@ export const RouteCreatePage = observer(() => { | ||||
|               value={carrier} | ||||
|               label="Выберите перевозчика" | ||||
|               onChange={(e) => setCarrier(e.target.value as string)} | ||||
|               disabled={carrierStore.carriers.data.length === 0} | ||||
|               disabled={ | ||||
|                 carrierStore.carriers[ | ||||
|                   language as keyof typeof carrierStore.carriers | ||||
|                 ].data?.length === 0 | ||||
|               } | ||||
|             > | ||||
|               <MenuItem value="">Не выбрано</MenuItem> | ||||
|               {carrierStore.carriers.data.map( | ||||
|                 (c: (typeof carrierStore.carriers.data)[number]) => ( | ||||
|                   <MenuItem key={c.id} value={c.id}> | ||||
|                     {c.full_name} | ||||
|                   </MenuItem> | ||||
|                 ) | ||||
|               )} | ||||
|               {carrierStore.carriers[ | ||||
|                 language as keyof typeof carrierStore.carriers | ||||
|               ].data?.map((carrier) => ( | ||||
|                 <MenuItem key={carrier.id} value={carrier.id}> | ||||
|                   {carrier.full_name} | ||||
|                 </MenuItem> | ||||
|               ))} | ||||
|             </Select> | ||||
|           </FormControl> | ||||
|           <TextField | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import { | ||||
|   MenuItem, | ||||
|   FormControl, | ||||
|   InputLabel, | ||||
|   Typography, | ||||
|   // Typography, | ||||
|   Box, | ||||
| } from "@mui/material"; | ||||
| import { LanguageSwitcher } from "@widgets"; | ||||
| @@ -19,22 +19,23 @@ import { carrierStore } from "../../../shared/store/CarrierStore"; | ||||
| import { articlesStore } from "../../../shared/store/ArticlesStore"; | ||||
| import { routeStore } from "../../../shared/store/RouteStore"; | ||||
| import { toast } from "react-toastify"; | ||||
| import { languageStore } from "@shared"; | ||||
|  | ||||
| export const RouteEditPage = observer(() => { | ||||
|   const navigate = useNavigate(); | ||||
|   const { id } = useParams(); | ||||
|   const { editRouteData } = routeStore; | ||||
|   const [isLoading, setIsLoading] = useState(false); | ||||
|  | ||||
|   const { language } = languageStore; | ||||
|   useEffect(() => { | ||||
|     const fetchData = async () => { | ||||
|       const response = await routeStore.getRoute(Number(id)); | ||||
|       routeStore.setEditRouteData(response); | ||||
|       carrierStore.getCarriers(); | ||||
|       carrierStore.getCarriers(language); | ||||
|       articlesStore.getArticleList(); | ||||
|     }; | ||||
|     fetchData(); | ||||
|   }, [id]); | ||||
|   }, [id, language]); | ||||
|  | ||||
|   const handleSave = async () => { | ||||
|     setIsLoading(true); | ||||
| @@ -67,21 +68,26 @@ export const RouteEditPage = observer(() => { | ||||
|                 routeStore.setEditRouteData({ | ||||
|                   carrier_id: Number(e.target.value), | ||||
|                   carrier: | ||||
|                     carrierStore.carriers.data.find( | ||||
|                       (c) => c.id === Number(e.target.value) | ||||
|                     )?.full_name || "", | ||||
|                     carrierStore.carriers[ | ||||
|                       language as keyof typeof carrierStore.carriers | ||||
|                     ].data?.find((c) => c.id === Number(e.target.value)) | ||||
|                       ?.full_name || "", | ||||
|                 }) | ||||
|               } | ||||
|               disabled={carrierStore.carriers.data.length === 0} | ||||
|               disabled={ | ||||
|                 carrierStore.carriers[ | ||||
|                   language as keyof typeof carrierStore.carriers | ||||
|                 ].data?.length === 0 | ||||
|               } | ||||
|             > | ||||
|               <MenuItem value="">Не выбрано</MenuItem> | ||||
|               {carrierStore.carriers.data.map( | ||||
|                 (c: (typeof carrierStore.carriers.data)[number]) => ( | ||||
|                   <MenuItem key={c.id} value={c.id}> | ||||
|                     {c.full_name} | ||||
|                   </MenuItem> | ||||
|                 ) | ||||
|               )} | ||||
|               {carrierStore.carriers[ | ||||
|                 language as keyof typeof carrierStore.carriers | ||||
|               ].data?.map((carrier) => ( | ||||
|                 <MenuItem key={carrier.id} value={carrier.id}> | ||||
|                   {carrier.full_name} | ||||
|                 </MenuItem> | ||||
|               ))} | ||||
|             </Select> | ||||
|           </FormControl> | ||||
|           <TextField | ||||
|   | ||||
| @@ -48,8 +48,8 @@ export const StationCreatePage = observer(() => { | ||||
|         </button> | ||||
|       </div> | ||||
|       <div className="flex flex-col gap-10 w-full items-end"> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%]"> | ||||
|           <h1 className="text-3xl break-words">Создание станции</h1> | ||||
|         <div className="flex gap-10 items-center mb-5 max-w-[80%] self-start"> | ||||
|           <h1 className="text-3xl break-words">{name}</h1> | ||||
|         </div> | ||||
|         <TextField | ||||
|           className="w-full" | ||||
|   | ||||
| @@ -7,7 +7,12 @@ import { | ||||
|   FormControl, | ||||
|   InputLabel, | ||||
| } from "@mui/material"; | ||||
| import { vehicleStore, VEHICLE_TYPES, carrierStore } from "@shared"; | ||||
| import { | ||||
|   vehicleStore, | ||||
|   VEHICLE_TYPES, | ||||
|   carrierStore, | ||||
|   languageStore, | ||||
| } from "@shared"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { ArrowLeft, Save } from "lucide-react"; | ||||
| import { Loader2 } from "lucide-react"; | ||||
| @@ -21,10 +26,11 @@ export const VehicleCreatePage = observer(() => { | ||||
|   const [type, setType] = useState(""); | ||||
|   const [carrierId, setCarrierId] = useState<number | null>(null); | ||||
|   const [isLoading, setIsLoading] = useState(false); | ||||
|   const { language } = languageStore; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     carrierStore.getCarriers(); | ||||
|   }, []); | ||||
|     carrierStore.getCarriers(language); | ||||
|   }, [language]); | ||||
|  | ||||
|   const handleCreate = async () => { | ||||
|     try { | ||||
| @@ -32,7 +38,8 @@ export const VehicleCreatePage = observer(() => { | ||||
|       await vehicleStore.createVehicle( | ||||
|         Number(tailNumber), | ||||
|         Number(type), | ||||
|         carrierStore.carriers.data.find((c) => c.id === carrierId)?.full_name!, | ||||
|         carrierStore.carriers[language].data?.find((c) => c.id === carrierId) | ||||
|           ?.full_name as string, | ||||
|         carrierId! | ||||
|       ); | ||||
|       toast.success("Транспорт успешно создан"); | ||||
| @@ -88,7 +95,7 @@ export const VehicleCreatePage = observer(() => { | ||||
|             required | ||||
|             onChange={(e) => setCarrierId(e.target.value as number)} | ||||
|           > | ||||
|             {carrierStore.carriers.data.map((carrier) => ( | ||||
|             {carrierStore.carriers[language].data?.map((carrier) => ( | ||||
|               <MenuItem key={carrier.id} value={carrier.id}> | ||||
|                 {carrier.full_name} | ||||
|               </MenuItem> | ||||
|   | ||||
| @@ -11,7 +11,12 @@ import { observer } from "mobx-react-lite"; | ||||
| import { ArrowLeft, Loader2, Save } from "lucide-react"; | ||||
| import { useNavigate, useParams } from "react-router-dom"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import { carrierStore, VEHICLE_TYPES, vehicleStore } from "@shared"; | ||||
| import { | ||||
|   carrierStore, | ||||
|   languageStore, | ||||
|   VEHICLE_TYPES, | ||||
|   vehicleStore, | ||||
| } from "@shared"; | ||||
| import { toast } from "react-toastify"; | ||||
|  | ||||
| export const VehicleEditPage = observer(() => { | ||||
| @@ -25,11 +30,12 @@ export const VehicleEditPage = observer(() => { | ||||
|     editVehicle, | ||||
|   } = vehicleStore; | ||||
|   const { getCarriers } = carrierStore; | ||||
|  | ||||
|   const { language } = languageStore; | ||||
|   useEffect(() => { | ||||
|     (async () => { | ||||
|       await getVehicle(Number(id)); | ||||
|       await getCarriers(); | ||||
|       await getCarriers(language); | ||||
|  | ||||
|       setEditVehicleData({ | ||||
|         tail_number: vehicle[Number(id)]?.vehicle.tail_number, | ||||
|         type: vehicle[Number(id)]?.vehicle.type, | ||||
| @@ -37,7 +43,7 @@ export const VehicleEditPage = observer(() => { | ||||
|         carrier_id: vehicle[Number(id)]?.vehicle.carrier_id, | ||||
|       }); | ||||
|     })(); | ||||
|   }, [id]); | ||||
|   }, [id, language]); | ||||
|   const [isLoading, setIsLoading] = useState(false); | ||||
|   const handleEdit = async () => { | ||||
|     try { | ||||
| @@ -108,7 +114,7 @@ export const VehicleEditPage = observer(() => { | ||||
|               }) | ||||
|             } | ||||
|           > | ||||
|             {carrierStore.carriers.data.map((carrier) => ( | ||||
|             {carrierStore.carriers[language].data?.map((carrier) => ( | ||||
|               <MenuItem key={carrier.id} value={carrier.id}> | ||||
|                 {carrier.full_name} | ||||
|               </MenuItem> | ||||
|   | ||||
| @@ -19,7 +19,7 @@ export const VehicleListPage = observer(() => { | ||||
|  | ||||
|   useEffect(() => { | ||||
|     getVehicles(); | ||||
|     getCarriers(); | ||||
|     getCarriers(language); | ||||
|   }, [language]); | ||||
|  | ||||
|   const columns: GridColDef[] = [ | ||||
| @@ -123,7 +123,7 @@ export const VehicleListPage = observer(() => { | ||||
|     tail_number: vehicle.vehicle.tail_number, | ||||
|     type: vehicle.vehicle.type, | ||||
|     carrier: vehicle.vehicle.carrier, | ||||
|     city: carriers.data?.find( | ||||
|     city: carriers[language].data?.find( | ||||
|       (carrier) => carrier.id === vehicle.vehicle.carrier_id | ||||
|     )?.city, | ||||
|   })); | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import { | ||||
|   Earth, | ||||
|   Landmark, | ||||
|   GitBranch, | ||||
|   Car, | ||||
|   // Car, | ||||
|   Table, | ||||
|   Notebook, | ||||
|   Split, | ||||
|   | ||||
| @@ -1,4 +1,10 @@ | ||||
| import { authInstance, cityStore, languageStore } from "@shared"; | ||||
| import { | ||||
|   authInstance, | ||||
|   cityStore, | ||||
|   languageStore, | ||||
|   languageInstance, | ||||
|   Language, | ||||
| } from "@shared"; | ||||
| import { makeAutoObservable, runInAction } from "mobx"; | ||||
|  | ||||
| export type Carrier = { | ||||
| @@ -14,17 +20,40 @@ export type Carrier = { | ||||
|   // right_color: string; | ||||
| }; | ||||
|  | ||||
| type Carriers = { | ||||
| type CarrierData = { | ||||
|   data: Carrier[]; | ||||
|   loaded: boolean; | ||||
| }; | ||||
|  | ||||
| type CashedCarrier = Record<number, Carrier>; | ||||
| type Carriers = { | ||||
|   ru: CarrierData; | ||||
|   en: CarrierData; | ||||
|   zh: CarrierData; | ||||
| }; | ||||
|  | ||||
| type CashedCarrier = Record< | ||||
|   number, | ||||
|   { | ||||
|     ru: Carrier | null; | ||||
|     en: Carrier | null; | ||||
|     zh: Carrier | null; | ||||
|   } | ||||
| >; | ||||
|  | ||||
| class CarrierStore { | ||||
|   carriers: Carriers = { | ||||
|     data: [], | ||||
|     loaded: false, | ||||
|     ru: { | ||||
|       data: [], | ||||
|       loaded: false, | ||||
|     }, | ||||
|     en: { | ||||
|       data: [], | ||||
|       loaded: false, | ||||
|     }, | ||||
|     zh: { | ||||
|       data: [], | ||||
|       loaded: false, | ||||
|     }, | ||||
|   }; | ||||
|   carrier: CashedCarrier = {}; | ||||
|  | ||||
| @@ -32,14 +61,14 @@ class CarrierStore { | ||||
|     makeAutoObservable(this); | ||||
|   } | ||||
|  | ||||
|   getCarriers = async () => { | ||||
|     if (this.carriers.loaded) return; | ||||
|   getCarriers = async (language: Language) => { | ||||
|     if (this.carriers[language as keyof Carriers].loaded) return; | ||||
|  | ||||
|     const response = await authInstance.get("/carrier"); | ||||
|  | ||||
|     runInAction(() => { | ||||
|       this.carriers.data = response.data; | ||||
|       this.carriers.loaded = true; | ||||
|       this.carriers[language as keyof Carriers].data = response.data; | ||||
|       this.carriers[language as keyof Carriers].loaded = true; | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -47,116 +76,163 @@ class CarrierStore { | ||||
|     await authInstance.delete(`/carrier/${id}`); | ||||
|  | ||||
|     runInAction(() => { | ||||
|       this.carriers.data = this.carriers.data.filter( | ||||
|         (carrier) => carrier.id !== id | ||||
|       ); | ||||
|       for (const language of ["ru", "en", "zh"] as const) { | ||||
|         this.carriers[language].data = this.carriers[language].data.filter( | ||||
|           (carrier: Carrier) => carrier.id !== id | ||||
|         ); | ||||
|       } | ||||
|       delete this.carrier[id]; | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   getCarrier = async (id: number) => { | ||||
|     if (this.carrier[id]) return; | ||||
|     const response = await authInstance.get(`/carrier/${id}`); | ||||
|     if (this.carrier[id]?.ru && this.carrier[id]?.en && this.carrier[id]?.zh) | ||||
|       return; | ||||
|  | ||||
|     const ruResponse = await languageInstance("ru").get(`/carrier/${id}`); | ||||
|     const enResponse = await languageInstance("en").get(`/carrier/${id}`); | ||||
|     const zhResponse = await languageInstance("zh").get(`/carrier/${id}`); | ||||
|  | ||||
|     runInAction(() => { | ||||
|       if (!this.carrier[id]) { | ||||
|         this.carrier[id] = { | ||||
|           id: 0, | ||||
|           short_name: "", | ||||
|           full_name: "", | ||||
|           slogan: "", | ||||
|           city: "", | ||||
|           city_id: 0, | ||||
|           logo: "", | ||||
|           // main_color: "", | ||||
|           // left_color: "", | ||||
|           // right_color: "", | ||||
|           ru: null, | ||||
|           en: null, | ||||
|           zh: null, | ||||
|         }; | ||||
|       } | ||||
|       this.carrier[id] = response.data; | ||||
|       this.carrier[id].ru = ruResponse.data; | ||||
|       this.carrier[id].en = enResponse.data; | ||||
|       this.carrier[id].zh = zhResponse.data; | ||||
|     }); | ||||
|     return response.data; | ||||
|     return this.carrier[id]; | ||||
|   }; | ||||
|  | ||||
|   createCarrier = async ( | ||||
|     fullName: string, | ||||
|     shortName: string, | ||||
|  | ||||
|     cityId: number, | ||||
|     // main_color: string, | ||||
|     // left_color: string, | ||||
|     // right_color: string, | ||||
|     slogan: string, | ||||
|     logoId: string | ||||
|   ) => { | ||||
|     const response = await authInstance.post("/carrier", { | ||||
|     const { language } = languageStore; | ||||
|     const cityName = | ||||
|       cityStore.cities[language].data.find((city) => city.id === cityId) | ||||
|         ?.name || ""; | ||||
|  | ||||
|     const response = await languageInstance(language).post("/carrier", { | ||||
|       full_name: fullName, | ||||
|       short_name: shortName, | ||||
|       city: "", | ||||
|       city: cityName, | ||||
|       city_id: cityId, | ||||
|       // main_color, | ||||
|       // left_color, | ||||
|       // right_color, | ||||
|       slogan, | ||||
|       logo: logoId, | ||||
|     }); | ||||
|  | ||||
|     const carrierId = response.data.id; | ||||
|  | ||||
|     // Create translations for other languages | ||||
|     for (const lang of ["ru", "en", "zh"].filter((l) => l !== language)) { | ||||
|       await languageInstance(lang as Language).patch(`/carrier/${carrierId}`, { | ||||
|         full_name: fullName, | ||||
|         short_name: shortName, | ||||
|         city: cityName, | ||||
|         city_id: cityId, | ||||
|         slogan, | ||||
|         logo: logoId, | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     runInAction(() => { | ||||
|       this.carriers.data.push(response.data); | ||||
|       for (const language of ["ru", "en", "zh"] as const) { | ||||
|         this.carriers[language].data.push(response.data); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   editCarrierData = { | ||||
|     full_name: "", | ||||
|     short_name: "", | ||||
|     ru: { | ||||
|       full_name: "", | ||||
|       short_name: "", | ||||
|  | ||||
|       // main_color: "", | ||||
|       // left_color: "", | ||||
|       // right_color: "", | ||||
|       slogan: "", | ||||
|     }, | ||||
|     en: { | ||||
|       full_name: "", | ||||
|       short_name: "", | ||||
|  | ||||
|       // main_color: "", | ||||
|       // left_color: "", | ||||
|       // right_color: "", | ||||
|       slogan: "", | ||||
|     }, | ||||
|     city_id: 0, | ||||
|     // main_color: "", | ||||
|     // left_color: "", | ||||
|     // right_color: "", | ||||
|     slogan: "", | ||||
|     logo: "", | ||||
|     zh: { | ||||
|       full_name: "", | ||||
|       short_name: "", | ||||
|  | ||||
|       // main_color: "", | ||||
|       // left_color: "", | ||||
|       // right_color: "", | ||||
|       slogan: "", | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   setEditCarrierData = ( | ||||
|     fullName: string, | ||||
|     shortName: string, | ||||
|  | ||||
|     cityId: number, | ||||
|     // main_color: string, | ||||
|     // left_color: string, | ||||
|     // right_color: string, | ||||
|     slogan: string, | ||||
|     logoId: string | ||||
|     logoId: string, | ||||
|     language: Language | ||||
|   ) => { | ||||
|     this.editCarrierData = { | ||||
|     this.editCarrierData.city_id = cityId; | ||||
|     this.editCarrierData.logo = logoId; | ||||
|     this.editCarrierData[language] = { | ||||
|       full_name: fullName, | ||||
|       short_name: shortName, | ||||
|  | ||||
|       city_id: cityId, | ||||
|       // main_color: main_color, | ||||
|       // left_color: left_color, | ||||
|       // right_color: right_color, | ||||
|       slogan: slogan, | ||||
|       logo: logoId, | ||||
|     }; | ||||
|   }; | ||||
|  | ||||
|   editCarrier = async (id: number) => { | ||||
|     const { language } = languageStore; | ||||
|     const response = await authInstance.patch(`/carrier/${id}`, { | ||||
|       ...this.editCarrierData, | ||||
|       city: cityStore.cities[language].data.find( | ||||
|     const cityName = | ||||
|       cityStore.cities[languageStore.language].data.find( | ||||
|         (city) => city.id === this.editCarrierData.city_id | ||||
|       )?.name, | ||||
|     }); | ||||
|       )?.name || ""; | ||||
|  | ||||
|     runInAction(() => { | ||||
|       this.carriers.data = this.carriers.data.map((carrier) => | ||||
|         carrier.id === id ? { ...carrier, ...response.data } : carrier | ||||
|     for (const language of ["ru", "en", "zh"] as const) { | ||||
|       const response = await languageInstance(language).patch( | ||||
|         `/carrier/${id}`, | ||||
|         { | ||||
|           ...this.editCarrierData[language], | ||||
|           city: cityName, | ||||
|           logo: this.editCarrierData.logo, | ||||
|         } | ||||
|       ); | ||||
|  | ||||
|       this.carrier[id] = response.data; | ||||
|     }); | ||||
|       runInAction(() => { | ||||
|         if (this.carrier[id]) { | ||||
|           this.carrier[id][language] = response.data; | ||||
|         } | ||||
|         for (const language of ["ru", "en", "zh"] as const) { | ||||
|           this.carriers[language].data = this.carriers[language].data.map( | ||||
|             (carrier: Carrier) => | ||||
|               carrier.id === id ? { ...carrier, ...response.data } : carrier | ||||
|           ); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -175,9 +175,10 @@ export const CreateInformationTab = observer( | ||||
|                 /> | ||||
|  | ||||
|                 <Autocomplete | ||||
|                   options={ruCities ?? []} | ||||
|                   options={ruCities.data ?? []} | ||||
|                   value={ | ||||
|                     ruCities.find((city) => city.id === sight.city_id) ?? null | ||||
|                     ruCities.data.find((city) => city.id === sight.city_id) ?? | ||||
|                     null | ||||
|                   } | ||||
|                   getOptionLabel={(option) => option.name} | ||||
|                   onChange={(_, value) => { | ||||
|   | ||||
| @@ -19,14 +19,7 @@ import { | ||||
|   MediaViewer, | ||||
|   DeleteModal, | ||||
| } from "@widgets"; | ||||
| import { | ||||
|   Trash2, | ||||
|   ImagePlus, | ||||
|   Unlink, | ||||
|   MousePointer, | ||||
|   Plus, | ||||
|   Save, | ||||
| } from "lucide-react"; | ||||
| import { Trash2, ImagePlus, Unlink, Plus, Save, Search } from "lucide-react"; | ||||
| import { useState, useCallback } from "react"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { toast } from "react-toastify"; | ||||
| @@ -160,7 +153,7 @@ export const CreateLeftTab = observer( | ||||
|                     variant="contained" | ||||
|                     color="primary" | ||||
|                     size="small" | ||||
|                     startIcon={<MousePointer color="white" size={18} />} | ||||
|                     startIcon={<Search color="white" size={18} />} | ||||
|                     onClick={() => setIsSelectArticleDialogOpen(true)} | ||||
|                   > | ||||
|                     Выбрать статью | ||||
|   | ||||
| @@ -551,6 +551,7 @@ export const CreateRightTab = observer( | ||||
|                           media={ | ||||
|                             sight[language].right[activeArticleIndex].media[0] | ||||
|                           } | ||||
|                           fullWidth | ||||
|                         /> | ||||
|                       </Box> | ||||
|                     ) : ( | ||||
|   | ||||
| @@ -18,14 +18,7 @@ import { | ||||
|   MediaViewer, | ||||
|   DeleteModal, | ||||
| } from "@widgets"; | ||||
| import { | ||||
|   Trash2, | ||||
|   ImagePlus, | ||||
|   Unlink, | ||||
|   Plus, | ||||
|   MousePointer, | ||||
|   Save, | ||||
| } from "lucide-react"; | ||||
| import { Trash2, ImagePlus, Unlink, Plus, Save, Search } from "lucide-react"; | ||||
| import { useState, useCallback } from "react"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { toast } from "react-toastify"; | ||||
| @@ -175,7 +168,7 @@ export const LeftWidgetTab = observer( | ||||
|                       variant="contained" | ||||
|                       color="primary" | ||||
|                       size="small" | ||||
|                       startIcon={<MousePointer color="white" size={18} />} | ||||
|                       startIcon={<Search color="white" size={18} />} | ||||
|                       onClick={() => setIsSelectArticleDialogOpen(true)} | ||||
|                     > | ||||
|                       Выбрать статью | ||||
|   | ||||
| @@ -493,6 +493,7 @@ export const RightWidgetTab = observer( | ||||
|                             media={ | ||||
|                               sight[language].right[activeArticleIndex].media[0] | ||||
|                             } | ||||
|                             fullWidth | ||||
|                           /> | ||||
|                         </Box> | ||||
|                       ) : ( | ||||
|   | ||||
| @@ -67,7 +67,7 @@ export const SightsTable = observer(() => { | ||||
|             </TableRow> | ||||
|           </TableHead> | ||||
|           <TableBody> | ||||
|             {rows(sights, cities[language])?.map((row) => ( | ||||
|             {rows(sights, cities[language]?.data ?? [])?.map((row) => ( | ||||
|               <TableRow | ||||
|                 key={row?.id} | ||||
|                 sx={{ "&:last-child td, &:last-child th": { border: 0 } }} | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user