feat: Rewrite route edit and create page for new field
				
					
				
			This commit is contained in:
		| @@ -13,7 +13,7 @@ import { | |||||||
|   DialogContent, |   DialogContent, | ||||||
|   DialogActions, |   DialogActions, | ||||||
| } from "@mui/material"; | } from "@mui/material"; | ||||||
| import { MediaViewer } from "@widgets"; | import { MediaViewer, VideoPreviewCard } from "@widgets"; | ||||||
| import { observer } from "mobx-react-lite"; | import { observer } from "mobx-react-lite"; | ||||||
| import { ArrowLeft, Loader2, Save, Plus } from "lucide-react"; | import { ArrowLeft, Loader2, Save, Plus } from "lucide-react"; | ||||||
| import { useEffect, useState, useMemo } from "react"; | import { useEffect, useState, useMemo } from "react"; | ||||||
| @@ -24,9 +24,10 @@ import { articlesStore } from "../../../shared/store/ArticlesStore"; | |||||||
| import { Route, routeStore } from "../../../shared/store/RouteStore"; | import { Route, routeStore } from "../../../shared/store/RouteStore"; | ||||||
| import { | import { | ||||||
|   languageStore, |   languageStore, | ||||||
|   SelectArticleModal, |   ArticleSelectOrCreateDialog, | ||||||
|   SelectMediaDialog, |   SelectMediaDialog, | ||||||
|   selectedCityStore, |   selectedCityStore, | ||||||
|  |   UploadMediaDialog, | ||||||
| } from "@shared"; | } from "@shared"; | ||||||
|  |  | ||||||
| export const RouteCreatePage = observer(() => { | export const RouteCreatePage = observer(() => { | ||||||
| @@ -39,6 +40,7 @@ export const RouteCreatePage = observer(() => { | |||||||
|   const [direction, setDirection] = useState("backward"); |   const [direction, setDirection] = useState("backward"); | ||||||
|   const [scaleMin, setScaleMin] = useState(""); |   const [scaleMin, setScaleMin] = useState(""); | ||||||
|   const [scaleMax, setScaleMax] = useState(""); |   const [scaleMax, setScaleMax] = useState(""); | ||||||
|  |   const [routeName, setRouteName] = useState(""); | ||||||
|   const [turn, setTurn] = useState(""); |   const [turn, setTurn] = useState(""); | ||||||
|   const [centerLat, setCenterLat] = useState(""); |   const [centerLat, setCenterLat] = useState(""); | ||||||
|   const [centerLng, setCenterLng] = useState(""); |   const [centerLng, setCenterLng] = useState(""); | ||||||
| @@ -48,6 +50,8 @@ export const RouteCreatePage = observer(() => { | |||||||
|     useState(false); |     useState(false); | ||||||
|   const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false); |   const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false); | ||||||
|   const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false); |   const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false); | ||||||
|  |   const [isUploadVideoDialogOpen, setIsUploadVideoDialogOpen] = useState(false); | ||||||
|  |   const [fileToUpload, setFileToUpload] = useState<File | null>(null); | ||||||
|   const { language } = languageStore; |   const { language } = languageStore; | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
| @@ -110,6 +114,8 @@ export const RouteCreatePage = observer(() => { | |||||||
|   const handleArticleSelect = (articleId: number) => { |   const handleArticleSelect = (articleId: number) => { | ||||||
|     setGovernorAppeal(articleId.toString()); |     setGovernorAppeal(articleId.toString()); | ||||||
|     setIsSelectArticleDialogOpen(false); |     setIsSelectArticleDialogOpen(false); | ||||||
|  |     // Обновляем список статей после создания новой | ||||||
|  |     articlesStore.getArticleList(); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleVideoSelect = (media: { |   const handleVideoSelect = (media: { | ||||||
| @@ -122,6 +128,26 @@ export const RouteCreatePage = observer(() => { | |||||||
|     setIsSelectVideoDialogOpen(false); |     setIsSelectVideoDialogOpen(false); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const handleVideoFileSelect = (file?: File) => { | ||||||
|  |     if (file) { | ||||||
|  |       setFileToUpload(file); | ||||||
|  |       setIsUploadVideoDialogOpen(true); | ||||||
|  |     } else { | ||||||
|  |       setIsSelectVideoDialogOpen(true); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleVideoUpload = (media: { | ||||||
|  |     id: string; | ||||||
|  |     filename: string; | ||||||
|  |     media_name?: string; | ||||||
|  |     media_type: number; | ||||||
|  |   }) => { | ||||||
|  |     setVideoPreview(media.id); | ||||||
|  |     setIsUploadVideoDialogOpen(false); | ||||||
|  |     setFileToUpload(null); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const handleVideoPreviewClick = () => { |   const handleVideoPreviewClick = () => { | ||||||
|     setIsVideoPreviewOpen(true); |     setIsVideoPreviewOpen(true); | ||||||
|   }; |   }; | ||||||
| @@ -167,6 +193,7 @@ export const RouteCreatePage = observer(() => { | |||||||
|         route_number: routeNumber, |         route_number: routeNumber, | ||||||
|         route_sys_number: govRouteNumber, |         route_sys_number: govRouteNumber, | ||||||
|         governor_appeal, |         governor_appeal, | ||||||
|  |         route_name: routeName, | ||||||
|         route_direction, |         route_direction, | ||||||
|         scale_min, |         scale_min, | ||||||
|         scale_max, |         scale_max, | ||||||
| @@ -208,6 +235,13 @@ export const RouteCreatePage = observer(() => { | |||||||
|  |  | ||||||
|       <div className="flex flex-col gap-10 w-full items-end"> |       <div className="flex flex-col gap-10 w-full items-end"> | ||||||
|         <Box className="flex flex-col gap-6 w-full"> |         <Box className="flex flex-col gap-6 w-full"> | ||||||
|  |           <TextField | ||||||
|  |             className="w-full" | ||||||
|  |             label="Название маршрута" | ||||||
|  |             required | ||||||
|  |             value={routeName} | ||||||
|  |             onChange={(e) => setRouteName(e.target.value)} | ||||||
|  |           /> | ||||||
|           <FormControl fullWidth required> |           <FormControl fullWidth required> | ||||||
|             <InputLabel>Выберите перевозчика</InputLabel> |             <InputLabel>Выберите перевозчика</InputLabel> | ||||||
|             <Select |             <Select | ||||||
| @@ -279,6 +313,7 @@ export const RouteCreatePage = observer(() => { | |||||||
|               }, |               }, | ||||||
|             }} |             }} | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|           <TextField |           <TextField | ||||||
|             className="w-full" |             className="w-full" | ||||||
|             label="Номер маршрута в Говорящем Городе" |             label="Номер маршрута в Говорящем Городе" | ||||||
| @@ -287,99 +322,43 @@ export const RouteCreatePage = observer(() => { | |||||||
|             onChange={(e) => setGovRouteNumber(e.target.value)} |             onChange={(e) => setGovRouteNumber(e.target.value)} | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|           {/* Заменяем Select на кнопку для выбора статьи */} |           <Typography variant="subtitle1" sx={{ fontWeight: 600 }}> | ||||||
|           <Box className="flex flex-col gap-2"> |             Обращение к пассажирам | ||||||
|             <label className="text-sm font-medium text-gray-700"> |           </Typography> | ||||||
|               Обращение к пассажирам |           <Box className="flex gap-2"> | ||||||
|             </label> |             <TextField | ||||||
|             <Box className="flex gap-2"> |               className="flex-1" | ||||||
|               <TextField |               value={selectedArticle?.heading || "Статья не выбрана"} | ||||||
|                 className="flex-1" |               placeholder="Выберите статью" | ||||||
|                 value={selectedArticle?.heading || "Статья не выбрана"} |               disabled | ||||||
|                 placeholder="Выберите статью" |               fullWidth | ||||||
|                 disabled |               sx={{ | ||||||
|                 sx={{ |                 "& .MuiInputBase-input": { | ||||||
|                   "& .MuiInputBase-input": { |                   color: selectedArticle ? "inherit" : "#999", | ||||||
|                     color: selectedArticle ? "inherit" : "#999", |                 }, | ||||||
|                   }, |               }} | ||||||
|                 }} |             /> | ||||||
|               /> |             <Button | ||||||
|               <Button |               variant="outlined" | ||||||
|                 variant="outlined" |               onClick={() => setIsSelectArticleDialogOpen(true)} | ||||||
|                 onClick={() => setIsSelectArticleDialogOpen(true)} |               startIcon={<Plus size={16} />} | ||||||
|                 startIcon={<Plus size={16} />} |               sx={{ minWidth: "auto", px: 2 }} | ||||||
|                 sx={{ minWidth: "auto", px: 2 }} |             > | ||||||
|               > |               Выбрать | ||||||
|                 Выбрать |             </Button> | ||||||
|               </Button> |  | ||||||
|             </Box> |  | ||||||
|           </Box> |           </Box> | ||||||
|  |  | ||||||
|           {/* Селектор видеозаставки */} |           {/* Видео-превью как на странице редактирования */} | ||||||
|           <Box className="flex flex-col gap-2"> |           <VideoPreviewCard | ||||||
|             <label className="text-sm font-medium text-gray-700"> |             title="Видеозаставка" | ||||||
|               Видеозаставка |             videoId={videoPreview} | ||||||
|             </label> |             onVideoClick={handleVideoPreviewClick} | ||||||
|             <Box className="flex gap-2"> |             onDeleteVideoClick={() => { | ||||||
|               <Box |               setVideoPreview(""); | ||||||
|                 className="flex-1" |             }} | ||||||
|                 onClick={handleVideoPreviewClick} |             onSelectVideoClick={handleVideoFileSelect} | ||||||
|                 sx={{ |             className="w-full" | ||||||
|                   cursor: |           /> | ||||||
|                     videoPreview && videoPreview !== "" ? "pointer" : "default", |  | ||||||
|                 }} |  | ||||||
|               > |  | ||||||
|                 <Box |  | ||||||
|                   className="w-full h-[50px] border border-gray-400 rounded-sm flex items-center justify-between px-4" |  | ||||||
|                   sx={{ |  | ||||||
|                     "& .MuiInputBase-input": { |  | ||||||
|                       color: |  | ||||||
|                         videoPreview && videoPreview !== "" |  | ||||||
|                           ? "inherit" |  | ||||||
|                           : "#999", |  | ||||||
|                       cursor: |  | ||||||
|                         videoPreview && videoPreview !== "" |  | ||||||
|                           ? "pointer" |  | ||||||
|                           : "default", |  | ||||||
|                     }, |  | ||||||
|                   }} |  | ||||||
|                 > |  | ||||||
|                   <Typography variant="body1" className="text-sm"> |  | ||||||
|                     {videoPreview && videoPreview !== "" |  | ||||||
|                       ? "Видео выбрано" |  | ||||||
|                       : "Видео не выбрано"} |  | ||||||
|                   </Typography> |  | ||||||
|                   {videoPreview && videoPreview !== "" && ( |  | ||||||
|                     <Box |  | ||||||
|                       onClick={(e) => { |  | ||||||
|                         e.stopPropagation(); |  | ||||||
|                         setVideoPreview(""); |  | ||||||
|                       }} |  | ||||||
|                       sx={{ |  | ||||||
|                         cursor: "pointer", |  | ||||||
|                         color: "#999", |  | ||||||
|                         "&:hover": { |  | ||||||
|                           color: "#666", |  | ||||||
|                         }, |  | ||||||
|                       }} |  | ||||||
|                     > |  | ||||||
|                       <Typography variant="body1" className="text-lg font-bold"> |  | ||||||
|                         × |  | ||||||
|                       </Typography> |  | ||||||
|                     </Box> |  | ||||||
|                   )} |  | ||||||
|                 </Box> |  | ||||||
|               </Box> |  | ||||||
|               <Button |  | ||||||
|                 variant="outlined" |  | ||||||
|                 onClick={() => setIsSelectVideoDialogOpen(true)} |  | ||||||
|                 startIcon={<Plus size={16} />} |  | ||||||
|                 sx={{ minWidth: "auto", px: 2 }} |  | ||||||
|               > |  | ||||||
|                 Выбрать |  | ||||||
|               </Button> |  | ||||||
|             </Box> |  | ||||||
|           </Box> |  | ||||||
|  |  | ||||||
|           <FormControl fullWidth required> |           <FormControl fullWidth required> | ||||||
|             <InputLabel>Прямой/обратный маршрут</InputLabel> |             <InputLabel>Прямой/обратный маршрут</InputLabel> | ||||||
| @@ -404,6 +383,7 @@ export const RouteCreatePage = observer(() => { | |||||||
|             value={scaleMax} |             value={scaleMax} | ||||||
|             onChange={(e) => setScaleMax(e.target.value)} |             onChange={(e) => setScaleMax(e.target.value)} | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|           <TextField |           <TextField | ||||||
|             className="w-full" |             className="w-full" | ||||||
|             label="Поворот" |             label="Поворот" | ||||||
| @@ -441,8 +421,8 @@ export const RouteCreatePage = observer(() => { | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       {/* Модальное окно выбора статьи */} |       {/* Модальное окно выбора или создания статьи */} | ||||||
|       <SelectArticleModal |       <ArticleSelectOrCreateDialog | ||||||
|         open={isSelectArticleDialogOpen} |         open={isSelectArticleDialogOpen} | ||||||
|         onClose={() => setIsSelectArticleDialogOpen(false)} |         onClose={() => setIsSelectArticleDialogOpen(false)} | ||||||
|         onSelectArticle={handleArticleSelect} |         onSelectArticle={handleArticleSelect} | ||||||
| @@ -483,6 +463,20 @@ export const RouteCreatePage = observer(() => { | |||||||
|           </DialogActions> |           </DialogActions> | ||||||
|         </Dialog> |         </Dialog> | ||||||
|       )} |       )} | ||||||
|  |  | ||||||
|  |       {/* Модальное окно загрузки видео */} | ||||||
|  |       <UploadMediaDialog | ||||||
|  |         open={isUploadVideoDialogOpen} | ||||||
|  |         onClose={() => { | ||||||
|  |           setIsUploadVideoDialogOpen(false); | ||||||
|  |           setFileToUpload(null); | ||||||
|  |         }} | ||||||
|  |         hardcodeType="video_preview" | ||||||
|  |         contextObjectName={routeName || "Маршрут"} | ||||||
|  |         contextType="sight" | ||||||
|  |         initialFile={fileToUpload || undefined} | ||||||
|  |         afterUpload={handleVideoUpload} | ||||||
|  |       /> | ||||||
|     </Paper> |     </Paper> | ||||||
|   ); |   ); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ import { | |||||||
|   DialogContent, |   DialogContent, | ||||||
|   DialogActions, |   DialogActions, | ||||||
| } from "@mui/material"; | } from "@mui/material"; | ||||||
| import { MediaViewer } from "@widgets"; | import { MediaViewer, VideoPreviewCard } from "@widgets"; | ||||||
| import { observer } from "mobx-react-lite"; | import { observer } from "mobx-react-lite"; | ||||||
| import { ArrowLeft, Copy, Save, Plus } from "lucide-react"; | import { ArrowLeft, Copy, Save, Plus } from "lucide-react"; | ||||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| @@ -24,8 +24,9 @@ import { articlesStore } from "../../../shared/store/ArticlesStore"; | |||||||
| import { | import { | ||||||
|   routeStore, |   routeStore, | ||||||
|   languageStore, |   languageStore, | ||||||
|   SelectArticleModal, |   ArticleSelectOrCreateDialog, | ||||||
|   SelectMediaDialog, |   SelectMediaDialog, | ||||||
|  |   UploadMediaDialog, | ||||||
| } from "@shared"; | } from "@shared"; | ||||||
| import { toast } from "react-toastify"; | import { toast } from "react-toastify"; | ||||||
| import { stationsStore } from "@shared"; | import { stationsStore } from "@shared"; | ||||||
| @@ -40,6 +41,8 @@ export const RouteEditPage = observer(() => { | |||||||
|     useState(false); |     useState(false); | ||||||
|   const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false); |   const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false); | ||||||
|   const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false); |   const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false); | ||||||
|  |   const [isUploadVideoDialogOpen, setIsUploadVideoDialogOpen] = useState(false); | ||||||
|  |   const [fileToUpload, setFileToUpload] = useState<File | null>(null); | ||||||
|   const { language } = languageStore; |   const { language } = languageStore; | ||||||
|   const [coordinates, setCoordinates] = useState<string>(""); |   const [coordinates, setCoordinates] = useState<string>(""); | ||||||
|  |  | ||||||
| @@ -125,6 +128,8 @@ export const RouteEditPage = observer(() => { | |||||||
|       governor_appeal: articleId, |       governor_appeal: articleId, | ||||||
|     }); |     }); | ||||||
|     setIsSelectArticleDialogOpen(false); |     setIsSelectArticleDialogOpen(false); | ||||||
|  |     // Обновляем список статей после создания новой | ||||||
|  |     articlesStore.getArticleList(); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleVideoSelect = (media: { |   const handleVideoSelect = (media: { | ||||||
| @@ -139,6 +144,28 @@ export const RouteEditPage = observer(() => { | |||||||
|     setIsSelectVideoDialogOpen(false); |     setIsSelectVideoDialogOpen(false); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const handleVideoFileSelect = (file?: File) => { | ||||||
|  |     if (file) { | ||||||
|  |       setFileToUpload(file); | ||||||
|  |       setIsUploadVideoDialogOpen(true); | ||||||
|  |     } else { | ||||||
|  |       setIsSelectVideoDialogOpen(true); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleVideoUpload = (media: { | ||||||
|  |     id: string; | ||||||
|  |     filename: string; | ||||||
|  |     media_name?: string; | ||||||
|  |     media_type: number; | ||||||
|  |   }) => { | ||||||
|  |     routeStore.setEditRouteData({ | ||||||
|  |       video_preview: media.id, | ||||||
|  |     }); | ||||||
|  |     setIsUploadVideoDialogOpen(false); | ||||||
|  |     setFileToUpload(null); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const handleVideoPreviewClick = () => { |   const handleVideoPreviewClick = () => { | ||||||
|     if (editRouteData.video_preview && editRouteData.video_preview !== "") { |     if (editRouteData.video_preview && editRouteData.video_preview !== "") { | ||||||
|       setIsVideoPreviewOpen(true); |       setIsVideoPreviewOpen(true); | ||||||
| @@ -164,6 +191,17 @@ export const RouteEditPage = observer(() => { | |||||||
|  |  | ||||||
|       <div className="flex flex-col gap-10 w-full items-end"> |       <div className="flex flex-col gap-10 w-full items-end"> | ||||||
|         <Box className="flex flex-col gap-6 w-full"> |         <Box className="flex flex-col gap-6 w-full"> | ||||||
|  |           <TextField | ||||||
|  |             className="w-full" | ||||||
|  |             label="Название маршрута" | ||||||
|  |             required | ||||||
|  |             value={editRouteData.route_name || ""} | ||||||
|  |             onChange={(e) => | ||||||
|  |               routeStore.setEditRouteData({ | ||||||
|  |                 route_name: e.target.value, | ||||||
|  |               }) | ||||||
|  |             } | ||||||
|  |           /> | ||||||
|           <FormControl fullWidth required> |           <FormControl fullWidth required> | ||||||
|             <InputLabel>Выберите перевозчика</InputLabel> |             <InputLabel>Выберите перевозчика</InputLabel> | ||||||
|             <Select |             <Select | ||||||
| @@ -279,110 +317,6 @@ export const RouteEditPage = observer(() => { | |||||||
|             } |             } | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|           {/* Заменяем Select на кнопку для выбора статьи */} |  | ||||||
|           <Box className="flex flex-col gap-2"> |  | ||||||
|             <label className="text-sm font-medium text-gray-700"> |  | ||||||
|               Обращение к пассажирам |  | ||||||
|             </label> |  | ||||||
|             <Box className="flex gap-2"> |  | ||||||
|               <TextField |  | ||||||
|                 className="flex-1" |  | ||||||
|                 value={selectedArticle?.heading || "Статья не выбрана"} |  | ||||||
|                 placeholder="Выберите статью" |  | ||||||
|                 disabled |  | ||||||
|                 sx={{ |  | ||||||
|                   "& .MuiInputBase-input": { |  | ||||||
|                     color: selectedArticle ? "inherit" : "#999", |  | ||||||
|                   }, |  | ||||||
|                 }} |  | ||||||
|               /> |  | ||||||
|               <Button |  | ||||||
|                 variant="outlined" |  | ||||||
|                 onClick={() => setIsSelectArticleDialogOpen(true)} |  | ||||||
|                 startIcon={<Plus size={16} />} |  | ||||||
|                 sx={{ minWidth: "auto", px: 2 }} |  | ||||||
|               > |  | ||||||
|                 Выбрать |  | ||||||
|               </Button> |  | ||||||
|             </Box> |  | ||||||
|           </Box> |  | ||||||
|  |  | ||||||
|           {/* Селектор видеозаставки */} |  | ||||||
|           <Box className="flex flex-col gap-2"> |  | ||||||
|             <label className="text-sm font-medium text-gray-700"> |  | ||||||
|               Видеозаставка |  | ||||||
|             </label> |  | ||||||
|             <Box className="flex gap-2"> |  | ||||||
|               <Box |  | ||||||
|                 className="flex-1" |  | ||||||
|                 onClick={handleVideoPreviewClick} |  | ||||||
|                 sx={{ |  | ||||||
|                   cursor: |  | ||||||
|                     editRouteData.video_preview && |  | ||||||
|                     editRouteData.video_preview !== "" |  | ||||||
|                       ? "pointer" |  | ||||||
|                       : "default", |  | ||||||
|                 }} |  | ||||||
|               > |  | ||||||
|                 <Box |  | ||||||
|                   className="w-full h-[50px] border border-gray-400 rounded-sm flex items-center justify-between px-4" |  | ||||||
|                   sx={{ |  | ||||||
|                     "& .MuiInputBase-input": { |  | ||||||
|                       color: |  | ||||||
|                         editRouteData.video_preview && |  | ||||||
|                         editRouteData.video_preview !== "" |  | ||||||
|                           ? "inherit" |  | ||||||
|                           : "#999", |  | ||||||
|                       cursor: |  | ||||||
|                         editRouteData.video_preview && |  | ||||||
|                         editRouteData.video_preview !== "" |  | ||||||
|                           ? "pointer" |  | ||||||
|                           : "default", |  | ||||||
|                     }, |  | ||||||
|                   }} |  | ||||||
|                 > |  | ||||||
|                   <Typography variant="body1" className="text-sm"> |  | ||||||
|                     {editRouteData.video_preview && |  | ||||||
|                     editRouteData.video_preview !== "" |  | ||||||
|                       ? "Видео выбрано" |  | ||||||
|                       : "Видео не выбрано"} |  | ||||||
|                   </Typography> |  | ||||||
|                   {editRouteData.video_preview && |  | ||||||
|                     editRouteData.video_preview !== "" && ( |  | ||||||
|                       <Box |  | ||||||
|                         onClick={(e) => { |  | ||||||
|                           e.stopPropagation(); |  | ||||||
|                           routeStore.setEditRouteData({ video_preview: "" }); |  | ||||||
|                         }} |  | ||||||
|                         sx={{ |  | ||||||
|                           cursor: "pointer", |  | ||||||
|                           color: "#999", |  | ||||||
|                           "&:hover": { |  | ||||||
|                             color: "#666", |  | ||||||
|                           }, |  | ||||||
|                         }} |  | ||||||
|                       > |  | ||||||
|                         <Typography |  | ||||||
|                           variant="body1" |  | ||||||
|                           className="text-lg font-bold" |  | ||||||
|                         > |  | ||||||
|                           × |  | ||||||
|                         </Typography> |  | ||||||
|                       </Box> |  | ||||||
|                     )} |  | ||||||
|                 </Box> |  | ||||||
|               </Box> |  | ||||||
|               <Button |  | ||||||
|                 variant="outlined" |  | ||||||
|                 onClick={() => setIsSelectVideoDialogOpen(true)} |  | ||||||
|                 startIcon={<Plus size={16} />} |  | ||||||
|                 sx={{ minWidth: "auto", px: 2 }} |  | ||||||
|               > |  | ||||||
|                 Выбрать |  | ||||||
|               </Button> |  | ||||||
|             </Box> |  | ||||||
|           </Box> |  | ||||||
|  |  | ||||||
|           <FormControl fullWidth required> |           <FormControl fullWidth required> | ||||||
|             <InputLabel>Прямой/обратный маршрут</InputLabel> |             <InputLabel>Прямой/обратный маршрут</InputLabel> | ||||||
|             <Select |             <Select | ||||||
| @@ -453,6 +387,45 @@ export const RouteEditPage = observer(() => { | |||||||
|               }) |               }) | ||||||
|             } |             } | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|  |           <Typography variant="subtitle1" sx={{ fontWeight: 600 }}> | ||||||
|  |             Обращение к пассажирам | ||||||
|  |           </Typography> | ||||||
|  |           <Box className="flex gap-2"> | ||||||
|  |             <TextField | ||||||
|  |               className="flex-1" | ||||||
|  |               value={selectedArticle?.heading || "Статья не выбрана"} | ||||||
|  |               placeholder="Выберите статью" | ||||||
|  |               disabled | ||||||
|  |               fullWidth | ||||||
|  |               sx={{ | ||||||
|  |                 "& .MuiInputBase-input": { | ||||||
|  |                   color: selectedArticle ? "inherit" : "#999", | ||||||
|  |                 }, | ||||||
|  |               }} | ||||||
|  |             /> | ||||||
|  |             <Button | ||||||
|  |               variant="outlined" | ||||||
|  |               onClick={() => setIsSelectArticleDialogOpen(true)} | ||||||
|  |               startIcon={<Plus size={16} />} | ||||||
|  |               sx={{ minWidth: "auto", px: 2 }} | ||||||
|  |             > | ||||||
|  |               Выбрать | ||||||
|  |             </Button> | ||||||
|  |           </Box> | ||||||
|  |  | ||||||
|  |           {/* Правая часть - Видео (30%) */} | ||||||
|  |  | ||||||
|  |           <VideoPreviewCard | ||||||
|  |             title="Видеозаставка" | ||||||
|  |             videoId={editRouteData.video_preview} | ||||||
|  |             onVideoClick={handleVideoPreviewClick} | ||||||
|  |             onDeleteVideoClick={() => { | ||||||
|  |               routeStore.setEditRouteData({ video_preview: "" }); | ||||||
|  |             }} | ||||||
|  |             onSelectVideoClick={handleVideoFileSelect} | ||||||
|  |             className="w-full" | ||||||
|  |           /> | ||||||
|         </Box> |         </Box> | ||||||
|  |  | ||||||
|         <LinkedItems |         <LinkedItems | ||||||
| @@ -494,8 +467,8 @@ export const RouteEditPage = observer(() => { | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       {/* Модальное окно выбора статьи */} |       {/* Модальное окно выбора или создания статьи */} | ||||||
|       <SelectArticleModal |       <ArticleSelectOrCreateDialog | ||||||
|         open={isSelectArticleDialogOpen} |         open={isSelectArticleDialogOpen} | ||||||
|         onClose={() => setIsSelectArticleDialogOpen(false)} |         onClose={() => setIsSelectArticleDialogOpen(false)} | ||||||
|         onSelectArticle={handleArticleSelect} |         onSelectArticle={handleArticleSelect} | ||||||
| @@ -519,19 +492,35 @@ export const RouteEditPage = observer(() => { | |||||||
|         <DialogTitle>Предпросмотр видео</DialogTitle> |         <DialogTitle>Предпросмотр видео</DialogTitle> | ||||||
|         <DialogContent> |         <DialogContent> | ||||||
|           <Box className="flex justify-center items-center p-4"> |           <Box className="flex justify-center items-center p-4"> | ||||||
|             <MediaViewer |             {editRouteData.video_preview && ( | ||||||
|               media={{ |               <MediaViewer | ||||||
|                 id: editRouteData.video_preview, |                 media={{ | ||||||
|                 media_type: 2, |                   id: editRouteData.video_preview, | ||||||
|                 filename: "video_preview", |                   media_type: 2, | ||||||
|               }} |                   filename: "video_preview", | ||||||
|             /> |                 }} | ||||||
|  |               /> | ||||||
|  |             )} | ||||||
|           </Box> |           </Box> | ||||||
|         </DialogContent> |         </DialogContent> | ||||||
|         <DialogActions> |         <DialogActions> | ||||||
|           <Button onClick={() => setIsVideoPreviewOpen(false)}>Закрыть</Button> |           <Button onClick={() => setIsVideoPreviewOpen(false)}>Закрыть</Button> | ||||||
|         </DialogActions> |         </DialogActions> | ||||||
|       </Dialog> |       </Dialog> | ||||||
|  |  | ||||||
|  |       {/* Модальное окно загрузки видео */} | ||||||
|  |       <UploadMediaDialog | ||||||
|  |         open={isUploadVideoDialogOpen} | ||||||
|  |         onClose={() => { | ||||||
|  |           setIsUploadVideoDialogOpen(false); | ||||||
|  |           setFileToUpload(null); | ||||||
|  |         }} | ||||||
|  |         hardcodeType="video_preview" | ||||||
|  |         contextObjectName={editRouteData.route_name || "Маршрут"} | ||||||
|  |         contextType="sight" | ||||||
|  |         initialFile={fileToUpload || undefined} | ||||||
|  |         afterUpload={handleVideoUpload} | ||||||
|  |       /> | ||||||
|     </Paper> |     </Paper> | ||||||
|   ); |   ); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -50,6 +50,22 @@ export const RouteListPage = observer(() => { | |||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       field: "route_name", | ||||||
|  |       headerName: "Название маршрута", | ||||||
|  |       flex: 1, | ||||||
|  |       renderCell: (params: GridRenderCellParams) => { | ||||||
|  |         return ( | ||||||
|  |           <div className="w-full h-full flex items-center"> | ||||||
|  |             {params.value ? ( | ||||||
|  |               params.value | ||||||
|  |             ) : ( | ||||||
|  |               <Minus size={20} className="text-red-500" /> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       field: "route_number", |       field: "route_number", | ||||||
|       headerName: "Номер маршрута", |       headerName: "Номер маршрута", | ||||||
| @@ -122,6 +138,7 @@ export const RouteListPage = observer(() => { | |||||||
|     carrier_id: route.carrier_id, |     carrier_id: route.carrier_id, | ||||||
|     route_number: route.route_number, |     route_number: route.route_number, | ||||||
|     route_direction: route.route_direction ? "Прямой" : "Обратный", |     route_direction: route.route_direction ? "Прямой" : "Обратный", | ||||||
|  |     route_name: route.route_name, | ||||||
|   })); |   })); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|   | |||||||
							
								
								
									
										1083
									
								
								src/shared/modals/ArticleSelectOrCreateDialog/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1083
									
								
								src/shared/modals/ArticleSelectOrCreateDialog/index.tsx
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,3 +2,4 @@ export * from "./SelectArticleDialog"; | |||||||
| export * from "./SelectMediaDialog"; | export * from "./SelectMediaDialog"; | ||||||
| export * from "./PreviewMediaDialog"; | export * from "./PreviewMediaDialog"; | ||||||
| export * from "./UploadMediaDialog"; | export * from "./UploadMediaDialog"; | ||||||
|  | export * from "./ArticleSelectOrCreateDialog"; | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import { makeAutoObservable, runInAction } from "mobx"; | |||||||
| import { authInstance } from "@shared"; | import { authInstance } from "@shared"; | ||||||
|  |  | ||||||
| export type Route = { | export type Route = { | ||||||
|  |   route_name: string; | ||||||
|   carrier: string; |   carrier: string; | ||||||
|   carrier_id: number; |   carrier_id: number; | ||||||
|   center_latitude: number; |   center_latitude: number; | ||||||
| @@ -97,6 +98,7 @@ class RouteStore { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   editRouteData = { |   editRouteData = { | ||||||
|  |     route_name: "", | ||||||
|     carrier: "", |     carrier: "", | ||||||
|     carrier_id: 0, |     carrier_id: 0, | ||||||
|     center_latitude: "", |     center_latitude: "", | ||||||
| @@ -110,7 +112,7 @@ class RouteStore { | |||||||
|     route_sys_number: "", |     route_sys_number: "", | ||||||
|     scale_max: 0, |     scale_max: 0, | ||||||
|     scale_min: 0, |     scale_min: 0, | ||||||
|     video_preview: "", |     video_preview: "" as string | undefined, | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   setEditRouteData = (data: any) => { |   setEditRouteData = (data: any) => { | ||||||
| @@ -118,6 +120,9 @@ class RouteStore { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   editRoute = async (id: number) => { |   editRoute = async (id: number) => { | ||||||
|  |     if (!this.editRouteData.video_preview) { | ||||||
|  |       delete this.editRouteData.video_preview; | ||||||
|  |     } | ||||||
|     const response = await authInstance.patch(`/route/${id}`, { |     const response = await authInstance.patch(`/route/${id}`, { | ||||||
|       ...this.editRouteData, |       ...this.editRouteData, | ||||||
|       center_latitude: parseFloat(this.editRouteData.center_latitude), |       center_latitude: parseFloat(this.editRouteData.center_latitude), | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ interface VideoPreviewCardProps { | |||||||
|   onDeleteVideoClick: () => void; |   onDeleteVideoClick: () => void; | ||||||
|   onSelectVideoClick: (file?: File) => void; |   onSelectVideoClick: (file?: File) => void; | ||||||
|   tooltipText?: string; |   tooltipText?: string; | ||||||
|  |   className?: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({ | export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({ | ||||||
| @@ -20,6 +21,7 @@ export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({ | |||||||
|   onDeleteVideoClick, |   onDeleteVideoClick, | ||||||
|   onSelectVideoClick, |   onSelectVideoClick, | ||||||
|   tooltipText, |   tooltipText, | ||||||
|  |   className, | ||||||
| }) => { | }) => { | ||||||
|   const fileInputRef = useRef<HTMLInputElement>(null); |   const fileInputRef = useRef<HTMLInputElement>(null); | ||||||
|   const [isDragOver, setIsDragOver] = useState(false); |   const [isDragOver, setIsDragOver] = useState(false); | ||||||
| @@ -89,7 +91,10 @@ export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({ | |||||||
|         gap: 1, |         gap: 1, | ||||||
|         flex: 1, |         flex: 1, | ||||||
|         minWidth: 150, |         minWidth: 150, | ||||||
|  |         width: "min-content", | ||||||
|  |         mx: "auto", | ||||||
|       }} |       }} | ||||||
|  |       className={className} | ||||||
|     > |     > | ||||||
|       <Box sx={{ display: "flex", alignItems: "center" }}> |       <Box sx={{ display: "flex", alignItems: "center" }}> | ||||||
|         <Typography variant="subtitle2" gutterBottom sx={{ mb: 0, mr: 0.5 }}> |         <Typography variant="subtitle2" gutterBottom sx={{ mb: 0, mr: 0.5 }}> | ||||||
| @@ -127,7 +132,10 @@ export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({ | |||||||
|           </button> |           </button> | ||||||
|         )} |         )} | ||||||
|         {videoId ? ( |         {videoId ? ( | ||||||
|           <Box sx={{ position: "relative", width: "100%", height: "100%" }}> |           <Box | ||||||
|  |             sx={{ position: "relative", width: "100%", height: "100%" }} | ||||||
|  |             className={className} | ||||||
|  |           > | ||||||
|             <video |             <video | ||||||
|               src={`${ |               src={`${ | ||||||
|                 import.meta.env.VITE_KRBL_MEDIA |                 import.meta.env.VITE_KRBL_MEDIA | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user