Latest version #12
| @@ -11,6 +11,7 @@ | ||||
|     "@mui/lab": "^6.0.0-beta.14", | ||||
|     "@mui/material": "^6.1.7", | ||||
|     "@mui/x-data-grid": "^7.22.2", | ||||
|     "@photo-sphere-viewer/core": "^5.13.1", | ||||
|     "@react-three/drei": "^10.0.6", | ||||
|     "@react-three/fiber": "^9.1.2", | ||||
|     "@refinedev/cli": "^2.16.21", | ||||
| @@ -31,9 +32,9 @@ | ||||
|     "i18next": "^24.2.2", | ||||
|     "js-cookie": "^3.0.5", | ||||
|     "jwt-decode": "^4.0.0", | ||||
|     "react": "^18.0.0", | ||||
|     "react": "19.0.0", | ||||
|     "react-beautiful-dnd": "^13.1.1", | ||||
|     "react-dom": "^18.0.0", | ||||
|     "react-dom": "19.0.0", | ||||
|     "react-draggable": "^4.4.6", | ||||
|     "react-dropzone": "^14.3.8", | ||||
|     "react-hook-form": "^7.30.0", | ||||
|   | ||||
							
								
								
									
										8589
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8589
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										661
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										661
									
								
								src/App.tsx
									
									
									
									
									
								
							| @@ -75,352 +75,359 @@ import { | ||||
| } from "./components/ui/Icons"; | ||||
| import SidebarTitle from "./components/ui/SidebarTitle"; | ||||
| import { AdminOnly } from "./components/AdminOnly"; | ||||
| import { Dashboard } from "./preview/widgets/dashboard/Dashboard"; | ||||
| import { QueryClient } from "@tanstack/react-query"; | ||||
| import { QueryClientProvider } from "@tanstack/react-query"; | ||||
|  | ||||
| const queryClient = new QueryClient(); | ||||
| function App() { | ||||
|   return ( | ||||
|     <BrowserRouter> | ||||
|       <ColorModeContextProvider> | ||||
|         <CssBaseline /> | ||||
|         <GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} /> | ||||
|         <RefineSnackbarProvider> | ||||
|           <DevtoolsProvider> | ||||
|             <Refine | ||||
|               dataProvider={customDataProvider} | ||||
|               notificationProvider={useNotificationProvider} | ||||
|               routerProvider={routerBindings} | ||||
|               authProvider={authProvider} | ||||
|               i18nProvider={i18nProvider} | ||||
|               resources={[ | ||||
|                 { | ||||
|                   name: "country", | ||||
|                   list: "/country", | ||||
|                   create: "/country/create", | ||||
|                   edit: "/country/edit/:id", | ||||
|                   show: "/country/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Страны", | ||||
|                     icon: <CountryIcon />, | ||||
|     <QueryClientProvider client={queryClient}> | ||||
|       <BrowserRouter> | ||||
|         <ColorModeContextProvider> | ||||
|           <CssBaseline /> | ||||
|           <GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} /> | ||||
|           <RefineSnackbarProvider> | ||||
|             <DevtoolsProvider> | ||||
|               <Refine | ||||
|                 dataProvider={customDataProvider} | ||||
|                 notificationProvider={useNotificationProvider} | ||||
|                 routerProvider={routerBindings} | ||||
|                 authProvider={authProvider} | ||||
|                 i18nProvider={i18nProvider} | ||||
|                 resources={[ | ||||
|                   { | ||||
|                     name: "country", | ||||
|                     list: "/country", | ||||
|                     create: "/country/create", | ||||
|                     edit: "/country/edit/:id", | ||||
|                     show: "/country/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Страны", | ||||
|                       icon: <CountryIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "city", | ||||
|                   list: "/city", | ||||
|                   create: "/city/create", | ||||
|                   edit: "/city/edit/:id", | ||||
|                   show: "/city/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Города", | ||||
|                     icon: <CityIcon />, | ||||
|                   { | ||||
|                     name: "city", | ||||
|                     list: "/city", | ||||
|                     create: "/city/create", | ||||
|                     edit: "/city/edit/:id", | ||||
|                     show: "/city/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Города", | ||||
|                       icon: <CityIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "carrier", | ||||
|                   list: "/carrier", | ||||
|                   create: "/carrier/create", | ||||
|                   edit: "/carrier/edit/:id", | ||||
|                   show: "/carrier/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Перевозчики", | ||||
|                     icon: <CarrierIcon />, | ||||
|                   { | ||||
|                     name: "carrier", | ||||
|                     list: "/carrier", | ||||
|                     create: "/carrier/create", | ||||
|                     edit: "/carrier/edit/:id", | ||||
|                     show: "/carrier/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Перевозчики", | ||||
|                       icon: <CarrierIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "media", | ||||
|                   list: "/media", | ||||
|                   create: "/media/create", | ||||
|                   edit: "/media/edit/:id", | ||||
|                   show: "/media/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Медиа", | ||||
|                     icon: <MediaIcon />, | ||||
|                   { | ||||
|                     name: "media", | ||||
|                     list: "/media", | ||||
|                     create: "/media/create", | ||||
|                     edit: "/media/edit/:id", | ||||
|                     show: "/media/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Медиа", | ||||
|                       icon: <MediaIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "article", | ||||
|                   list: "/article", | ||||
|                   create: "/article/create", | ||||
|                   edit: "/article/edit/:id", | ||||
|                   show: "/article/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Статьи", | ||||
|                     icon: <ArticleIcon />, | ||||
|                   { | ||||
|                     name: "article", | ||||
|                     list: "/article", | ||||
|                     create: "/article/create", | ||||
|                     edit: "/article/edit/:id", | ||||
|                     show: "/article/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Статьи", | ||||
|                       icon: <ArticleIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "sight", | ||||
|                   list: "/sight", | ||||
|                   create: "/sight/create", | ||||
|                   edit: "/sight/edit/:id", | ||||
|                   show: "/sight/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Достопримечательности", | ||||
|                     icon: <SightIcon />, | ||||
|                   { | ||||
|                     name: "sight", | ||||
|                     list: "/sight", | ||||
|                     create: "/sight/create", | ||||
|                     edit: "/sight/edit/:id", | ||||
|                     show: "/sight/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Достопримечательности", | ||||
|                       icon: <SightIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "station", | ||||
|                   list: "/station", | ||||
|                   create: "/station/create", | ||||
|                   edit: "/station/edit/:id", | ||||
|                   show: "/station/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Остановки", | ||||
|                     icon: <StationIcon />, | ||||
|                   { | ||||
|                     name: "station", | ||||
|                     list: "/station", | ||||
|                     create: "/station/create", | ||||
|                     edit: "/station/edit/:id", | ||||
|                     show: "/station/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Остановки", | ||||
|                       icon: <StationIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "vehicle", | ||||
|                   list: "/vehicle", | ||||
|                   create: "/vehicle/create", | ||||
|                   edit: "/vehicle/edit/:id", | ||||
|                   show: "/vehicle/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Транспорт", | ||||
|                     icon: <VehicleIcon />, | ||||
|                   { | ||||
|                     name: "vehicle", | ||||
|                     list: "/vehicle", | ||||
|                     create: "/vehicle/create", | ||||
|                     edit: "/vehicle/edit/:id", | ||||
|                     show: "/vehicle/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Транспорт", | ||||
|                       icon: <VehicleIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "route", | ||||
|                   list: "/route", | ||||
|                   create: "/route/create", | ||||
|                   edit: "/route/edit/:id", | ||||
|                   show: "/route/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Маршруты", | ||||
|                     icon: <RouteIcon />, | ||||
|                   { | ||||
|                     name: "route", | ||||
|                     list: "/route", | ||||
|                     create: "/route/create", | ||||
|                     edit: "/route/edit/:id", | ||||
|                     show: "/route/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Маршруты", | ||||
|                       icon: <RouteIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "user", | ||||
|                   list: "/user", | ||||
|                   create: "/user/create", | ||||
|                   edit: "/user/edit/:id", | ||||
|                   show: "/user/show/:id", | ||||
|                   meta: { | ||||
|                     canDelete: true, | ||||
|                     label: "Пользователи", | ||||
|                     icon: <UsersIcon />, | ||||
|                   { | ||||
|                     name: "user", | ||||
|                     list: "/user", | ||||
|                     create: "/user/create", | ||||
|                     edit: "/user/edit/:id", | ||||
|                     show: "/user/show/:id", | ||||
|                     meta: { | ||||
|                       canDelete: true, | ||||
|                       label: "Пользователи", | ||||
|                       icon: <UsersIcon />, | ||||
|                     }, | ||||
|                   }, | ||||
|                 }, | ||||
|               ]} | ||||
|               options={{ | ||||
|                 syncWithLocation: true, | ||||
|                 warnWhenUnsavedChanges: true, // Включаем глобально | ||||
|                 useNewQueryKeys: true, | ||||
|                 projectId: "Wv044J-t53S3s-PcbJGe", | ||||
|               }} | ||||
|             > | ||||
|               <Routes> | ||||
|                 <Route | ||||
|                   element={ | ||||
|                     <Authenticated | ||||
|                       key="authenticated-inner" | ||||
|                       fallback={<CatchAllNavigate to="/login" />} | ||||
|                     > | ||||
|                       <ThemedLayoutV2 Header={Header} Title={SidebarTitle}> | ||||
|                         <Outlet /> | ||||
|                       </ThemedLayoutV2> | ||||
|                     </Authenticated> | ||||
|                   } | ||||
|                 > | ||||
|                 ]} | ||||
|                 options={{ | ||||
|                   syncWithLocation: true, | ||||
|                   warnWhenUnsavedChanges: true, // Включаем глобально | ||||
|                   useNewQueryKeys: true, | ||||
|                   projectId: "Wv044J-t53S3s-PcbJGe", | ||||
|                 }} | ||||
|               > | ||||
|                 <Routes> | ||||
|                   <Route | ||||
|                     index | ||||
|                     element={<NavigateToResource resource="country" />} | ||||
|                   /> | ||||
|  | ||||
|                   <Route path="/country"> | ||||
|                     <Route index element={<CountryList />} /> | ||||
|                     <Route | ||||
|                       path="create" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <CountryCreate /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route | ||||
|                       path="edit/:id" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <CountryEdit /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route path="show/:id" element={<CountryShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/city"> | ||||
|                     <Route index element={<CityList />} /> | ||||
|                     <Route | ||||
|                       path="create" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <CityCreate /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route | ||||
|                       path="edit/:id" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <CityEdit /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route path="show/:id" element={<CityShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/carrier"> | ||||
|                     <Route index element={<CarrierList />} /> | ||||
|                     <Route path="create" element={<CarrierCreate />} /> | ||||
|                     <Route path="edit/:id" element={<CarrierEdit />} /> | ||||
|                     <Route path="show/:id" element={<CarrierShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/media"> | ||||
|                     <Route index element={<MediaList />} /> | ||||
|                     <Route path="create" element={<MediaCreate />} /> | ||||
|                     <Route path="edit/:id" element={<MediaEdit />} /> | ||||
|                     <Route path="show/:id" element={<MediaShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/article"> | ||||
|                     <Route index element={<ArticleList />} /> | ||||
|                     <Route path="create" element={<ArticleCreate />} /> | ||||
|                     <Route path="edit/:id" element={<ArticleEdit />} /> | ||||
|                     <Route path="show/:id" element={<ArticleShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/sight"> | ||||
|                     <Route index element={<SightList />} /> | ||||
|                     <Route path="create" element={<SightCreate />} /> | ||||
|                     <Route path="edit/:id" element={<SightEdit />} /> | ||||
|                     <Route path="show/:id" element={<SightShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/station"> | ||||
|                     <Route index element={<StationList />} /> | ||||
|                     <Route | ||||
|                       path="create" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <StationCreate /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route | ||||
|                       path="edit/:id" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <StationEdit /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route path="show/:id" element={<StationShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/vehicle"> | ||||
|                     <Route index element={<VehicleList />} /> | ||||
|                     <Route path="create" element={<VehicleCreate />} /> | ||||
|                     <Route path="edit/:id" element={<VehicleEdit />} /> | ||||
|                     <Route path="show/:id" element={<VehicleShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/route"> | ||||
|                     <Route index element={<RouteList />} /> | ||||
|                     <Route | ||||
|                       path="create" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <RouteCreate /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route | ||||
|                       path="edit/:id" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <RouteEdit /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route path="show/:id" element={<RouteShow />} /> | ||||
|                   </Route> | ||||
|  | ||||
|                   <Route path="/user"> | ||||
|                     element={ | ||||
|                       <Authenticated | ||||
|                         key="authenticated-inner" | ||||
|                         fallback={<CatchAllNavigate to="/login" />} | ||||
|                       > | ||||
|                         <ThemedLayoutV2 Header={Header} Title={SidebarTitle}> | ||||
|                           <Outlet /> | ||||
|                         </ThemedLayoutV2> | ||||
|                       </Authenticated> | ||||
|                     } | ||||
|                   > | ||||
|                     <Route | ||||
|                       index | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <UserList /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route | ||||
|                       path="create" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <UserCreate /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route | ||||
|                       path="edit/:id" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <UserEdit /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                     /> | ||||
|                     <Route | ||||
|                       path="show/:id" | ||||
|                       element={ | ||||
|                         <AdminOnly> | ||||
|                           <UserShow /> | ||||
|                         </AdminOnly> | ||||
|                       } | ||||
|                       element={<NavigateToResource resource="country" />} | ||||
|                     /> | ||||
|  | ||||
|                     <Route path="/country"> | ||||
|                       <Route index element={<CountryList />} /> | ||||
|                       <Route | ||||
|                         path="create" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <CountryCreate /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route | ||||
|                         path="edit/:id" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <CountryEdit /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route path="show/:id" element={<CountryShow />} /> | ||||
|                     </Route> | ||||
|                     <Route path="dashboard" element={<Dashboard />} /> | ||||
|  | ||||
|                     <Route path="/city"> | ||||
|                       <Route index element={<CityList />} /> | ||||
|                       <Route | ||||
|                         path="create" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <CityCreate /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route | ||||
|                         path="edit/:id" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <CityEdit /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route path="show/:id" element={<CityShow />} /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="/carrier"> | ||||
|                       <Route index element={<CarrierList />} /> | ||||
|                       <Route path="create" element={<CarrierCreate />} /> | ||||
|                       <Route path="edit/:id" element={<CarrierEdit />} /> | ||||
|                       <Route path="show/:id" element={<CarrierShow />} /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="/media"> | ||||
|                       <Route index element={<MediaList />} /> | ||||
|                       <Route path="create" element={<MediaCreate />} /> | ||||
|                       <Route path="edit/:id" element={<MediaEdit />} /> | ||||
|                       <Route path="show/:id" element={<MediaShow />} /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="/article"> | ||||
|                       <Route index element={<ArticleList />} /> | ||||
|                       <Route path="create" element={<ArticleCreate />} /> | ||||
|                       <Route path="edit/:id" element={<ArticleEdit />} /> | ||||
|                       <Route path="show/:id" element={<ArticleShow />} /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="/sight"> | ||||
|                       <Route index element={<SightList />} /> | ||||
|                       <Route path="create" element={<SightCreate />} /> | ||||
|                       <Route path="edit/:id" element={<SightEdit />} /> | ||||
|                       <Route path="show/:id" element={<SightShow />} /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="/station"> | ||||
|                       <Route index element={<StationList />} /> | ||||
|                       <Route | ||||
|                         path="create" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <StationCreate /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route | ||||
|                         path="edit/:id" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <StationEdit /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route path="show/:id" element={<StationShow />} /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="/vehicle"> | ||||
|                       <Route index element={<VehicleList />} /> | ||||
|                       <Route path="create" element={<VehicleCreate />} /> | ||||
|                       <Route path="edit/:id" element={<VehicleEdit />} /> | ||||
|                       <Route path="show/:id" element={<VehicleShow />} /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="/route"> | ||||
|                       <Route index element={<RouteList />} /> | ||||
|                       <Route | ||||
|                         path="create" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <RouteCreate /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route | ||||
|                         path="edit/:id" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <RouteEdit /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route path="show/:id" element={<RouteShow />} /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="/user"> | ||||
|                       <Route | ||||
|                         index | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <UserList /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route | ||||
|                         path="create" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <UserCreate /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route | ||||
|                         path="edit/:id" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <UserEdit /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                       <Route | ||||
|                         path="show/:id" | ||||
|                         element={ | ||||
|                           <AdminOnly> | ||||
|                             <UserShow /> | ||||
|                           </AdminOnly> | ||||
|                         } | ||||
|                       /> | ||||
|                     </Route> | ||||
|  | ||||
|                     <Route path="*" element={<ErrorComponent />} /> | ||||
|                   </Route> | ||||
|                   <Route | ||||
|                     element={ | ||||
|                       <Authenticated | ||||
|                         key="authenticated-outer" | ||||
|                         fallback={<Outlet />} | ||||
|                       > | ||||
|                         <NavigateToResource /> | ||||
|                       </Authenticated> | ||||
|                     } | ||||
|                   > | ||||
|                     <Route path="/login" element={<Login />} /> | ||||
|                   </Route> | ||||
|                 </Routes> | ||||
|  | ||||
|                   <Route path="*" element={<ErrorComponent />} /> | ||||
|                 </Route> | ||||
|                 <Route | ||||
|                   element={ | ||||
|                     <Authenticated | ||||
|                       key="authenticated-outer" | ||||
|                       fallback={<Outlet />} | ||||
|                     > | ||||
|                       <NavigateToResource /> | ||||
|                     </Authenticated> | ||||
|                   } | ||||
|                 > | ||||
|                   <Route path="/login" element={<Login />} /> | ||||
|                 </Route> | ||||
|               </Routes> | ||||
|  | ||||
|               <UnsavedChangesNotifier /> | ||||
|               <DocumentTitleHandler | ||||
|                 handler={() => { | ||||
|                   // const cleanedTitle = title.autoGeneratedTitle.split('|')[0].trim() | ||||
|                   // return `${cleanedTitle} — Белые ночи` | ||||
|                   return "Белые ночи"; | ||||
|                 }} | ||||
|               /> | ||||
|             </Refine> | ||||
|             <DevtoolsPanel /> | ||||
|           </DevtoolsProvider> | ||||
|         </RefineSnackbarProvider> | ||||
|       </ColorModeContextProvider> | ||||
|     </BrowserRouter> | ||||
|                 <UnsavedChangesNotifier /> | ||||
|                 <DocumentTitleHandler | ||||
|                   handler={() => { | ||||
|                     // const cleanedTitle = title.autoGeneratedTitle.split('|')[0].trim() | ||||
|                     // return `${cleanedTitle} — Белые ночи` | ||||
|                     return "Белые ночи"; | ||||
|                   }} | ||||
|                 /> | ||||
|               </Refine> | ||||
|               <DevtoolsPanel /> | ||||
|             </DevtoolsProvider> | ||||
|           </RefineSnackbarProvider> | ||||
|         </ColorModeContextProvider> | ||||
|       </BrowserRouter> | ||||
|     </QueryClientProvider> | ||||
|   ); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -19,15 +19,13 @@ import { | ||||
|   TableRow, | ||||
|   Paper, | ||||
|   TableBody, | ||||
|   IconButton, | ||||
| } from "@mui/material"; | ||||
| import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; | ||||
| import DragIndicatorIcon from "@mui/icons-material/DragIndicator"; | ||||
| import { axiosInstance } from "../providers/data"; | ||||
|  | ||||
| import { Link } from "react-router"; | ||||
| import { TOKEN_KEY } from "../authProvider"; | ||||
| import { Droppable, Draggable, DragDropContext } from "@hello-pangea/dnd"; | ||||
|  | ||||
| // TODO: ДОДЕЛАТЬ | ||||
| import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; | ||||
|  | ||||
| type Field<T> = { | ||||
|   label: string; | ||||
| @@ -52,11 +50,10 @@ type LinkedItemsProps<T> = { | ||||
|   extraField?: ExtraFieldConfig; | ||||
| }; | ||||
|  | ||||
| const reorder = (list, startIndex, endIndex) => { | ||||
| const reorder = (list: any[], startIndex: number, endIndex: number) => { | ||||
|   const result = Array.from(list); | ||||
|   const [removed] = result.splice(startIndex, 1); | ||||
|   result.splice(endIndex, 0, removed); | ||||
|  | ||||
|   return result; | ||||
| }; | ||||
|  | ||||
| @@ -76,18 +73,20 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|   const [mediaOrder, setMediaOrder] = useState<number>(1); | ||||
|   const theme = useTheme(); | ||||
|  | ||||
|   const onDragEnd = (result) => { | ||||
|     // ドロップ先がない | ||||
|     if (!result.destination) { | ||||
|       return; | ||||
|     } | ||||
|     // 配列の順序を入れ替える | ||||
|     let movedItems = reorder( | ||||
|       linkedItems, // 順序を入れ変えたい配列 | ||||
|       result.source.index, // 元の配列の位置 | ||||
|       result.destination.index // 移動先の配列の位置 | ||||
|   const onDragEnd = (result: any) => { | ||||
|     if (!result.destination) return; | ||||
|  | ||||
|     const reorderedItems = reorder( | ||||
|       linkedItems, | ||||
|       result.source.index, | ||||
|       result.destination.index | ||||
|     ); | ||||
|     setLinkedItems(movedItems); | ||||
|  | ||||
|     setLinkedItems(reorderedItems); | ||||
|  | ||||
|     // If you need to save the new order to the backend, you would add that here | ||||
|     // For example: | ||||
|     // saveNewOrder(reorderedItems); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
| @@ -212,144 +211,88 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|       </AccordionSummary> | ||||
|  | ||||
|       <AccordionDetails sx={{ background: theme.palette.background.paper }}> | ||||
|         <TableContainer component={Paper}> | ||||
|           <Table aria-label="simple table"> | ||||
|             <TableHead> | ||||
|               <TableRow> | ||||
|                 <TableCell>ID</TableCell> | ||||
|                 <TableCell>Name</TableCell> | ||||
|                 <TableCell>Действие</TableCell> | ||||
|               </TableRow> | ||||
|             </TableHead> | ||||
|             <DragDropContext onDragEnd={onDragEnd}> | ||||
|               <Droppable droppableId="droppable"> | ||||
|                 {(provided) => ( | ||||
|                   <TableBody | ||||
|                     ref={provided.innerRef} | ||||
|                     {...provided.droppableProps} | ||||
|                   > | ||||
|                     {linkedItems.length > 0 && | ||||
|                       linkedItems.map((item, index) => ( | ||||
|         <Stack gap={2}> | ||||
|           <DragDropContext onDragEnd={onDragEnd}> | ||||
|             <TableContainer component={Paper}> | ||||
|               <Table> | ||||
|                 <TableHead> | ||||
|                   <TableRow> | ||||
|                     {type === "edit" && <TableCell width="40px"></TableCell>} | ||||
|                     {fields.map((field) => ( | ||||
|                       <TableCell key={String(field.data)}> | ||||
|                         {field.label} | ||||
|                       </TableCell> | ||||
|                     ))} | ||||
|                     {type === "edit" && ( | ||||
|                       <TableCell width="120px">Действие</TableCell> | ||||
|                     )} | ||||
|                   </TableRow> | ||||
|                 </TableHead> | ||||
|                 <Droppable droppableId="droppable"> | ||||
|                   {(provided) => ( | ||||
|                     <TableBody | ||||
|                       ref={provided.innerRef} | ||||
|                       {...provided.droppableProps} | ||||
|                     > | ||||
|                       {linkedItems.map((item, index) => ( | ||||
|                         <Draggable | ||||
|                           key={item.id} | ||||
|                           draggableId={"q" + item.id.toString()} | ||||
|                           draggableId={"q" + String(item.id)} | ||||
|                           index={index} | ||||
|                           isDragDisabled={type !== "edit"} | ||||
|                         > | ||||
|                           {(provided) => ( | ||||
|                             <TableRow | ||||
|                               ref={provided.innerRef} | ||||
|                               {...provided.dragHandleProps} | ||||
|                               {...provided.draggableProps} | ||||
|                               style={{ | ||||
|                                 ...provided.draggableProps.style, | ||||
|                               }} | ||||
|                               hover | ||||
|                             > | ||||
|                               <TableCell style={{ flex: 1, minWidth: "100px" }}> | ||||
|                                 {item.id} | ||||
|                               </TableCell> | ||||
|                               <TableCell style={{ flex: 1, minWidth: "100px" }}> | ||||
|                                 {item.name} | ||||
|                               </TableCell> | ||||
|                               <TableCell style={{ flex: 1, minWidth: "100px" }}> | ||||
|                                 <Button | ||||
|                                   variant="outlined" | ||||
|                                   color="error" | ||||
|                                   size="small" | ||||
|                                   onClick={(e) => { | ||||
|                                     e.preventDefault(); | ||||
|                                     deleteItem(item.id); | ||||
|                                   }} | ||||
|                                 > | ||||
|                                   Отвязать | ||||
|                                 </Button> | ||||
|                               </TableCell> | ||||
|                               {type === "edit" && ( | ||||
|                                 <TableCell {...provided.dragHandleProps}> | ||||
|                                   <IconButton size="small"> | ||||
|                                     <DragIndicatorIcon /> | ||||
|                                   </IconButton> | ||||
|                                 </TableCell> | ||||
|                               )} | ||||
|                               {fields.map((field) => ( | ||||
|                                 <TableCell key={String(field.data)}> | ||||
|                                   {field.render | ||||
|                                     ? field.render(item[field.data]) | ||||
|                                     : item[field.data]} | ||||
|                                 </TableCell> | ||||
|                               ))} | ||||
|                               {type === "edit" && ( | ||||
|                                 <TableCell> | ||||
|                                   <Button | ||||
|                                     variant="outlined" | ||||
|                                     color="error" | ||||
|                                     size="small" | ||||
|                                     onClick={() => deleteItem(item.id)} | ||||
|                                   > | ||||
|                                     Отвязать | ||||
|                                   </Button> | ||||
|                                 </TableCell> | ||||
|                               )} | ||||
|                             </TableRow> | ||||
|                           )} | ||||
|                         </Draggable> | ||||
|                       ))} | ||||
|                     {provided.placeholder} | ||||
|                   </TableBody> | ||||
|                 )} | ||||
|               </Droppable> | ||||
|             </DragDropContext> | ||||
|           </Table> | ||||
|         </TableContainer> | ||||
|                       {provided.placeholder} | ||||
|                     </TableBody> | ||||
|                   )} | ||||
|                 </Droppable> | ||||
|               </Table> | ||||
|             </TableContainer> | ||||
|           </DragDropContext> | ||||
|  | ||||
|         {/* <Stack gap={2}> | ||||
|           <Grid container gap={1.25}> | ||||
|             {isLoading ? ( | ||||
|               <Typography>Загрузка...</Typography> | ||||
|             ) : linkedItems.length > 0 ? ( | ||||
|               linkedItems.map((item, index) => ( | ||||
|                 <Box | ||||
|                   component={Link} | ||||
|                   to={`/${childResource}/show/${item.id}`} | ||||
|                   key={index} | ||||
|                   sx={{ | ||||
|                     marginTop: "8px", | ||||
|                     padding: "14px", | ||||
|                     borderRadius: 2, | ||||
|                     border: `2px solid ${theme.palette.divider}`, | ||||
|                     width: childResource === "article" ? "100%" : "auto", | ||||
|                     textDecoration: "none", | ||||
|                     color: "inherit", | ||||
|                     display: "block", | ||||
|                     "&:hover": { | ||||
|                       backgroundColor: theme.palette.action.hover, | ||||
|                     }, | ||||
|                   }} | ||||
|                 > | ||||
|                   <Stack gap={0.25}> | ||||
|                     {childResource === "media" && item.id && ( | ||||
|                       <img | ||||
|                         src={`${import.meta.env.VITE_KRBL_MEDIA}/${ | ||||
|                           item.iimport { DragDropContext } from 'react-beautiful-dnd'; | ||||
| d | ||||
|                         }/download?token=${localStorage.getItem(TOKEN_KEY)}`} | ||||
|                         alt={String(item.media_name)} | ||||
|                         style={{ | ||||
|                           width: "100%", | ||||
|                           height: "120px", | ||||
|                           objectFit: "contain", | ||||
|                           marginBottom: "8px", | ||||
|                           borderRadius: 4, | ||||
|                         }} | ||||
|                       /> | ||||
|                     )} | ||||
|                     {fields.map(({ label, data, render }) => ( | ||||
|                       <Typography | ||||
|                         variant="body2" | ||||
|                         color="textSecondary" | ||||
|                         key={String(data)} | ||||
|                       > | ||||
|                         <strong>{label}:</strong>{" "} | ||||
|                         {render ? render(item[data]) : item[data]} | ||||
|                       </Typography> | ||||
|                     ))} | ||||
|                     {type === "edit" && ( | ||||
|                       <Button | ||||
|                         variant="outlined" | ||||
|                         color="error" | ||||
|                         size="small" | ||||
|                         onClick={(e) => { | ||||
|                           e.preventDefault(); | ||||
|                           deleteItem(item.id); | ||||
|                         }} | ||||
|                         sx={{ mt: 1.5 }} | ||||
|                       > | ||||
|                         Отвязать | ||||
|                       </Button> | ||||
|                     )} | ||||
|                   </Stack> | ||||
|                 </Box> | ||||
|               )) | ||||
|             ) : ( | ||||
|               <Typography color="textSecondary">{title} не найдены</Typography> | ||||
|             )} | ||||
|           </Grid> | ||||
|           {linkedItems.length === 0 && !isLoading && ( | ||||
|             <Typography color="textSecondary" textAlign="center" py={2}> | ||||
|               {title} не найдены | ||||
|             </Typography> | ||||
|           )} | ||||
|  | ||||
|           {type === "edit" && ( | ||||
|             <Stack gap={2}> | ||||
|             <Stack gap={2} mt={2}> | ||||
|               <Typography variant="subtitle1">Добавить {title}</Typography> | ||||
|               <Autocomplete | ||||
|                 fullWidth | ||||
| @@ -373,7 +316,6 @@ d | ||||
|                   option.id === value?.id | ||||
|                 } | ||||
|                 filterOptions={(options, { inputValue }) => { | ||||
|                   // return options.filter((option) => String(option[fields[0].data]).toLowerCase().includes(inputValue.toLowerCase())) | ||||
|                   const searchWords = inputValue | ||||
|                     .toLowerCase() | ||||
|                     .split(" ") | ||||
| @@ -398,7 +340,7 @@ d | ||||
|                     value={pageNum} | ||||
|                     onChange={(e) => { | ||||
|                       const newValue = Number(e.target.value); | ||||
|                       const minValue = linkedItems.length + 1; // page number on articles lenght | ||||
|                       const minValue = linkedItems.length + 1; | ||||
|                       setPageNum(newValue < minValue ? minValue : newValue); | ||||
|                     }} | ||||
|                     fullWidth | ||||
| @@ -407,7 +349,7 @@ d | ||||
|                 </FormControl> | ||||
|               )} | ||||
|  | ||||
|               {childResource === "media" && type === "edit" && ( | ||||
|               {childResource === "media" && ( | ||||
|                 <FormControl fullWidth> | ||||
|                   <TextField | ||||
|                     type="number" | ||||
| @@ -429,12 +371,13 @@ d | ||||
|                 variant="contained" | ||||
|                 onClick={linkItem} | ||||
|                 disabled={!selectedItemId} | ||||
|                 sx={{ alignSelf: "flex-start" }} | ||||
|               > | ||||
|                 Добавить | ||||
|               </Button> | ||||
|             </Stack> | ||||
|           )} | ||||
|         </Stack> */} | ||||
|         </Stack> | ||||
|       </AccordionDetails> | ||||
|     </Accordion> | ||||
|   ); | ||||
|   | ||||
| @@ -1,44 +1,49 @@ | ||||
| import {Box, TextField} from '@mui/material' | ||||
| import {Edit} from '@refinedev/mui' | ||||
| import {useForm} from '@refinedev/react-hook-form' | ||||
| import { Icons } from "../../preview/components"; | ||||
| import { Box, TextField } from "@mui/material"; | ||||
| import { Edit } from "@refinedev/mui"; | ||||
| import { useForm } from "@refinedev/react-hook-form"; | ||||
|  | ||||
| export const CountryEdit = () => { | ||||
|   const { | ||||
|     saveButtonProps, | ||||
|     register, | ||||
|     formState: {errors}, | ||||
|   } = useForm({}) | ||||
|     formState: { errors }, | ||||
|   } = useForm({}); | ||||
|  | ||||
|   return ( | ||||
|     <Edit saveButtonProps={saveButtonProps}> | ||||
|       <Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off"> | ||||
|       <Box | ||||
|         component="form" | ||||
|         sx={{ display: "flex", flexDirection: "column" }} | ||||
|         autoComplete="off" | ||||
|       > | ||||
|         <TextField | ||||
|           {...register('code', { | ||||
|             required: 'Это поле является обязательным', | ||||
|           {...register("code", { | ||||
|             required: "Это поле является обязательным", | ||||
|           })} | ||||
|           error={!!(errors as any)?.code} | ||||
|           helperText={(errors as any)?.code?.message} | ||||
|           margin="normal" | ||||
|           fullWidth | ||||
|           InputLabelProps={{shrink: true}} | ||||
|           InputLabelProps={{ shrink: true }} | ||||
|           type="text" | ||||
|           label={'Код *'} | ||||
|           label={"Код *"} | ||||
|           name="code" | ||||
|         /> | ||||
|         <TextField | ||||
|           {...register('name', { | ||||
|             required: 'Это поле является обязательным', | ||||
|           {...register("name", { | ||||
|             required: "Это поле является обязательным", | ||||
|           })} | ||||
|           error={!!(errors as any)?.name} | ||||
|           helperText={(errors as any)?.name?.message} | ||||
|           margin="normal" | ||||
|           fullWidth | ||||
|           InputLabelProps={{shrink: true}} | ||||
|           InputLabelProps={{ shrink: true }} | ||||
|           type="text" | ||||
|           label={'Название *'} | ||||
|           label={"Название *"} | ||||
|           name="name" | ||||
|         /> | ||||
|       </Box> | ||||
|     </Edit> | ||||
|   ) | ||||
| } | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import cn from 'classnames'; | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import cn from "classnames"; | ||||
| import React, { useEffect, useState } from "react"; | ||||
|  | ||||
| import './AttractionMedia.css'; | ||||
| import ModelViewer from '../../model-viewer/ModelViewer'; | ||||
| import { Icons, useLightboxContext } from '@mt/components'; | ||||
| import { Object3DLightboxData } from '@mt/common-types'; | ||||
| import "./AttractionMedia.css"; | ||||
| import ModelViewer from "../../model-viewer/ModelViewer"; | ||||
| import { Icons, useLightboxContext } from "@mt/components"; | ||||
| import { Object3DLightboxData } from "@mt/common-types"; | ||||
|  | ||||
| interface Object3DMediaProps { | ||||
|   url: string; | ||||
| @@ -19,7 +19,7 @@ export const Object3DMedia = ({ url, watermarkUrl }: Object3DMediaProps) => { | ||||
|   const handle3DFullscreenOpen = () => { | ||||
|     setAutoRotate(false); | ||||
|     setData({ | ||||
|       type: 'OBJECT_3D', | ||||
|       type: "OBJECT_3D", | ||||
|       modelUrl: url, | ||||
|       watermarkUrl, | ||||
|     }); | ||||
| @@ -33,12 +33,14 @@ export const Object3DMedia = ({ url, watermarkUrl }: Object3DMediaProps) => { | ||||
|   return ( | ||||
|     <div className="widget-media__wrapper"> | ||||
|       <div | ||||
|         className={cn('widget-3d-model', { | ||||
|           'media-with-watermark': watermarkUrl !== null, | ||||
|         className={cn("widget-3d-model", { | ||||
|           "media-with-watermark": watermarkUrl !== null, | ||||
|         })} | ||||
|       > | ||||
|         <ModelViewer key={url} pathToModel={url} autoRotate={autoRotate} /> | ||||
|         {watermarkUrl && <img src={watermarkUrl} alt="Watermark" className="watermark" />} | ||||
|         {watermarkUrl && ( | ||||
|           <img src={watermarkUrl} alt="Watermark" className="watermark" /> | ||||
|         )} | ||||
|       </div> | ||||
|  | ||||
|       <Icons.FullscreenIcon | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import cn from 'classnames'; | ||||
| import { HTMLAttributes, ReactNode } from 'react'; | ||||
| import cn from "classnames"; | ||||
| import { HTMLAttributes, ReactNode } from "react"; | ||||
|  | ||||
| import { StyledDrawer } from './Drawer.styles'; | ||||
| import { Icons } from '@mt/components'; | ||||
| import { Locale, LocaleSwitcher } from '@mt/i18n'; | ||||
| import { StyledDrawer } from "./Drawer.styles"; | ||||
| import { Icons } from "@mt/components"; | ||||
| import { Locale, LocaleSwitcher } from "@mt/i18n"; | ||||
|  | ||||
| export interface DrawerProps extends HTMLAttributes<HTMLDivElement> { | ||||
|   onToggle: (isOpened: boolean) => void; | ||||
| @@ -24,11 +24,17 @@ export function Drawer({ | ||||
|   ...props | ||||
| }: DrawerProps) { | ||||
|   return ( | ||||
|     <StyledDrawer className={cn('g-flex-column', { 'nav-widget--opened': isOpen })} {...props}> | ||||
|     <StyledDrawer | ||||
|       className={cn("g-flex-column", { "nav-widget--opened": isOpen })} | ||||
|       {...props} | ||||
|     > | ||||
|       {children} | ||||
|  | ||||
|       <div className="g-flex actions"> | ||||
|         <div className="action-btn toggle-btn" onPointerUp={() => onToggle(!isOpen)}> | ||||
|         <div | ||||
|           className="action-btn toggle-btn" | ||||
|           onPointerUp={() => onToggle(!isOpen)} | ||||
|         > | ||||
|           <Icons.ArrowBtn /> | ||||
|         </div> | ||||
|  | ||||
|   | ||||
| @@ -5,9 +5,20 @@ import { | ||||
| } from "react-simple-maps"; | ||||
| import styles from "./MapWidget.module.css"; | ||||
| import { mapCanvasProps, useMapWidgetContext } from "../../MapWidgetContext"; | ||||
| import { useState } from "react"; | ||||
| import { useState, FC, ReactNode } from "react"; | ||||
| import { MapContent } from "./MapContent"; | ||||
|  | ||||
| // Create wrapper components to handle type issues | ||||
| const ComposableMapWrapper: FC<any> = (props) => { | ||||
|   // @ts-ignore - Ignore type issues with the ComposableMap component | ||||
|   return <ComposableMap {...props} />; | ||||
| }; | ||||
|  | ||||
| const ZoomableGroupWrapper: FC<ZoomableGroupProps> = (props) => { | ||||
|   // @ts-ignore - Ignore type issues with the ZoomableGroup component | ||||
|   return <ZoomableGroup {...props} />; | ||||
| }; | ||||
|  | ||||
| // default coordinates for 3a route: 59.943, 30.331 | ||||
| export const MapWidget = () => { | ||||
|   const { onMapCenterMoved, projection, isDragMode, rotateAngle } = | ||||
| @@ -34,13 +45,12 @@ export const MapWidget = () => { | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <ComposableMap | ||||
|       // Cast to any need due to error in react-simple-maps typings | ||||
|     <ComposableMapWrapper | ||||
|       projection={projection as any} | ||||
|       className={styles.mapWidget} | ||||
|       {...mapCanvasProps} | ||||
|     > | ||||
|       <ZoomableGroup | ||||
|       <ZoomableGroupWrapper | ||||
|         key={key} | ||||
|         center={projection.center()} | ||||
|         onMoveEnd={handleMoveEnd} | ||||
| @@ -49,7 +59,7 @@ export const MapWidget = () => { | ||||
|         maxZoom={1} | ||||
|       > | ||||
|         <MapContent /> | ||||
|       </ZoomableGroup> | ||||
|     </ComposableMap> | ||||
|       </ZoomableGroupWrapper> | ||||
|     </ComposableMapWrapper> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Marker, Point } from "react-simple-maps"; | ||||
| import { Point, Marker } from "react-simple-maps"; | ||||
| import { Icons } from "@mt/components"; | ||||
| import { AttractionGroupIconSizeType } from "@mt/common-types"; | ||||
|  | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/preview/components/MyComponent/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/preview/components/MyComponent/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| export const MyComponent = () => { | ||||
|   return ( | ||||
|     <div style={{ width: "100px", height: "100px", backgroundColor: "red" }}> | ||||
|       MyComponent | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { HTMLAttributes, ReactNode } from "react"; | ||||
|  | ||||
| import { Icons } from "../icons"; | ||||
| import { Icons } from "../Icons"; | ||||
| import { TransportType } from "@mt/common-types"; | ||||
|  | ||||
| const transportStopIcons: Record<TransportType, ReactNode> = { | ||||
|   | ||||
| @@ -8,4 +8,5 @@ export * from "./MapWidget"; | ||||
| export * from "./Drawer"; | ||||
| export * from "./lightbox"; | ||||
| export * from "./model-viewer"; | ||||
| export * from "./icons"; | ||||
| export { Icons } from "./Icons"; | ||||
| export * from "./MyComponent"; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import React from 'react'; | ||||
| import { useLightboxContext } from './LightboxContext'; | ||||
| import React from "react"; | ||||
| import { useLightboxContext } from "./LightboxContext"; | ||||
|  | ||||
| import './Lightbox.css'; | ||||
| import { LightboxData } from '@mt/common-types'; | ||||
| import { LightboxContent } from './LightboxContent'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import "./Lightbox.css"; | ||||
| import { LightboxData } from "@mt/common-types"; | ||||
| import { LightboxContent } from "./LightboxContent"; | ||||
| import { FormattedMessage } from "react-intl"; | ||||
|  | ||||
| export const Lightbox = () => { | ||||
|   // prettier-ignore | ||||
| @@ -13,14 +13,24 @@ export const Lightbox = () => { | ||||
|   return lightboxActive ? ( | ||||
|     <div className="lightbox-overlay"> | ||||
|       <div className="lightbox-content"> | ||||
|         <div className="lightbox-content__wrapper" style={{ height: '749px', width: '1220px' }}> | ||||
|         <div | ||||
|           className="lightbox-content__wrapper" | ||||
|           style={{ height: "749px", width: "1220px" }} | ||||
|         > | ||||
|           <LightboxContent /> | ||||
|  | ||||
|           {data.watermarkUrl && ( | ||||
|             <img src={data.watermarkUrl} alt="Watermark" className="watermark" /> | ||||
|             <img | ||||
|               src={data.watermarkUrl} | ||||
|               alt="Watermark" | ||||
|               className="watermark" | ||||
|             /> | ||||
|           )} | ||||
|  | ||||
|           <button className="lightbox-content__close-btn" onPointerUp={closeLightbox}> | ||||
|           <button | ||||
|             className="lightbox-content__close-btn" | ||||
|             onPointerUp={closeLightbox} | ||||
|           > | ||||
|             <FormattedMessage id="close" /> | ||||
|           </button> | ||||
|         </div> | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import React, { useCallback, useState } from 'react'; | ||||
| import React, { useCallback, useState } from "react"; | ||||
| // TODO: resolve circular deps (probably we should move icons to a separate lib) | ||||
| import { Icons } from '@mt/components'; | ||||
| import { Locale, localesMap } from '../i18n.interface'; | ||||
| import './LocaleSwitcher.css'; | ||||
| import { Icons } from "@mt/components"; | ||||
| import { Locale, localesMap } from "../i18n.interface"; | ||||
| import "./LocaleSwitcher.css"; | ||||
|  | ||||
| interface LocaleSwitcherProps { | ||||
|   onLocaleChange: (locale: Locale) => void; | ||||
| @@ -10,7 +10,7 @@ interface LocaleSwitcherProps { | ||||
|  | ||||
| export const LocaleSwitcher = ({ onLocaleChange }: LocaleSwitcherProps) => { | ||||
|   const [isOpen, setIsOpen] = useState(false); | ||||
|   const [selectedLocale, setSelectedLocale] = useState<Locale>('ru'); | ||||
|   const [selectedLocale, setSelectedLocale] = useState<Locale>("ru"); | ||||
|  | ||||
|   const handleLocaleChange = useCallback( | ||||
|     (locale: Locale) => { | ||||
| @@ -24,7 +24,10 @@ export const LocaleSwitcher = ({ onLocaleChange }: LocaleSwitcherProps) => { | ||||
|   return ( | ||||
|     <div className="locale-switcher"> | ||||
|       {!isOpen ? ( | ||||
|         <button className="locale-switcher__button" onPointerUp={() => setIsOpen(!isOpen)}> | ||||
|         <button | ||||
|           className="locale-switcher__button" | ||||
|           onPointerUp={() => setIsOpen(!isOpen)} | ||||
|         > | ||||
|           <Icons.I18NIcon /> | ||||
|         </button> | ||||
|       ) : ( | ||||
| @@ -32,7 +35,9 @@ export const LocaleSwitcher = ({ onLocaleChange }: LocaleSwitcherProps) => { | ||||
|           {Object.entries(localesMap).map(([label, locale]) => ( | ||||
|             <button | ||||
|               key={locale} | ||||
|               className={`locale-switcher__option ${selectedLocale === locale ? 'selected' : ''}`} | ||||
|               className={`locale-switcher__option ${ | ||||
|                 selectedLocale === locale ? "selected" : "" | ||||
|               }`} | ||||
|               onPointerUp={() => handleLocaleChange(locale)} | ||||
|             > | ||||
|               {label} | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import { | ||||
|   LongPollingQueryOptions, | ||||
|   useLongPollingQuery, | ||||
| } from "./useLongPollingQuery"; | ||||
| import { useId } from "react"; | ||||
|  | ||||
| export type EventQueryData<T> = T & { | ||||
|   ["@type"]: string; | ||||
|   | ||||
| @@ -1,19 +1,20 @@ | ||||
| import { MapWidget, useMapWidgetContext } from '@mt/components'; | ||||
| import { useGetMapData } from './useGetMapData'; | ||||
| import { EventQueryData, useEventQuery, useLoading } from '@mt/utils'; | ||||
| import { useEffect } from 'react'; | ||||
| import { MapWidget, useMapWidgetContext } from "@mt/components"; | ||||
| import { useGetMapData } from "./useGetMapData"; | ||||
| import { EventQueryData, useEventQuery, useLoading } from "@mt/utils"; | ||||
| import { useEffect } from "react"; | ||||
| // TODO: resolve circular deps | ||||
| import { Coordinates } from '@mt/common-types'; | ||||
| import { Coordinates } from "@mt/common-types"; | ||||
|  | ||||
| export const MapWidgetContainer = () => { | ||||
|   const { isLoading } = useLoading(); | ||||
|   const { data, refetch } = useGetMapData(); | ||||
|   const { currentPosition, setCurrentPosition, onMapDataFetched } = useMapWidgetContext(); | ||||
|   const { currentPosition, setCurrentPosition, onMapDataFetched } = | ||||
|     useMapWidgetContext(); | ||||
|  | ||||
|   const { data: events = [], isSuccess } = useEventQuery('/widgets/route-map/events', [ | ||||
|     'REFRESH_DATA', | ||||
|     'UPDATE_CURRENT_POINT_ON_TRACK', | ||||
|   ]); | ||||
|   const { data: events = [], isSuccess } = useEventQuery( | ||||
|     "/widgets/route-map/events", | ||||
|     ["REFRESH_DATA", "UPDATE_CURRENT_POINT_ON_TRACK"] | ||||
|   ); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (!data) { | ||||
| @@ -28,7 +29,7 @@ export const MapWidgetContainer = () => { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (events.some((e) => e['@type'] === 'REFRESH_DATA')) { | ||||
|     if (events.some((e) => e["@type"] === "REFRESH_DATA")) { | ||||
|       refetch(); | ||||
|  | ||||
|       return; | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| import styled from "@emotion/styled"; | ||||
| import { AttractionWidgetContainer } from "../attractions-widget/AttractionWidgetContainer"; | ||||
| import { WeatherWidgetContainer } from "../WeatherWidget/WeatherWidgetContainer"; | ||||
| import { OperativeInfoWidget } from "../operative-info-widget/operative-info-widget"; | ||||
| import { Drawer } from "@mt/components"; | ||||
| import { Locale } from "@mt/i18n"; | ||||
| import { NavWidgetContainer } from "../nav-widget/nav-widget-container"; | ||||
| import { MapWidgetContainer } from "../MapWidgetContainer"; | ||||
| import { RouteInfoWidgetContainer } from "../RouteInfoWidgetContainer/RouteInfoWidgetContainer"; | ||||
| import { WeatherWidgetContainer } from "../WeatherWidget/WeatherWidgetContainer"; | ||||
| import { MapWidgetContainer } from "../MapWidgetContainer/MapWidgetContainer"; | ||||
| import { OperativeInfoWidget } from "../operative-info-widget/operative-info-widget"; | ||||
| import { AttractionWidgetContainer } from "../attractions-widget/AttractionWidgetContainer"; | ||||
|  | ||||
| const StyledDashboard = styled.div` | ||||
|   background-color: #000; | ||||
| @@ -49,6 +51,16 @@ const StyledDashboard = styled.div` | ||||
| export function Dashboard() { | ||||
|   return ( | ||||
|     <StyledDashboard> | ||||
|       <Drawer | ||||
|         onToggle={function (isOpened: boolean): void { | ||||
|           throw new Error("Function not implemented."); | ||||
|         }} | ||||
|         isOpen={false} | ||||
|         onLocaleChange={function (locale: Locale): void { | ||||
|           throw new Error("Function not implemented."); | ||||
|         }} | ||||
|       ></Drawer> | ||||
|  | ||||
|       <NavWidgetContainer /> | ||||
|  | ||||
|       <div className="container"> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { HTMLAttributes } from 'react'; | ||||
| import { NavWidget } from './nav-widget'; | ||||
| import { useGetStations } from './hooks/useGetStations'; | ||||
| import { useGetAttractions } from './hooks'; | ||||
| import { HTMLAttributes } from "react"; | ||||
| import { NavWidget } from "./nav-widget"; | ||||
| import { useGetStations } from "./hooks/useGetStations"; | ||||
| import { useGetAttractions } from "./hooks"; | ||||
|  | ||||
| export function NavWidgetContainer(props: HTMLAttributes<HTMLDivElement>) { | ||||
|   const { data: stations } = useGetStations(); | ||||
|   | ||||
| @@ -1,16 +1,23 @@ | ||||
| import cn from 'classnames'; | ||||
| import { HTMLAttributes, ReactNode, useContext, useEffect, useMemo, useState } from 'react'; | ||||
| import { LocalizationContext, useServerLocalization } from '@mt/i18n'; | ||||
| import { Order, uuid } from '@mt/common-types'; | ||||
| import cn from "classnames"; | ||||
| import { | ||||
|   HTMLAttributes, | ||||
|   ReactNode, | ||||
|   useContext, | ||||
|   useEffect, | ||||
|   useMemo, | ||||
|   useState, | ||||
| } from "react"; | ||||
| import { LocalizationContext, useServerLocalization } from "@mt/i18n"; | ||||
| import { Order, uuid } from "@mt/common-types"; | ||||
|  | ||||
| import { AttractionCard, AccordionListTab, NestedItems } from './components'; | ||||
| import { Drawer, Icons } from '@mt/components'; | ||||
| import { AttractionCard, AccordionListTab, NestedItems } from "./components"; | ||||
| import { Drawer, Icons } from "@mt/components"; | ||||
|  | ||||
| import { Attraction, Station } from './nav-widget.interface'; | ||||
| import { HomeTab } from './components'; | ||||
| import { StyledNavWidget } from './nav-widget.styles'; | ||||
| import { Attraction, Station } from "./nav-widget.interface"; | ||||
| import { HomeTab } from "./components"; | ||||
| import { StyledNavWidget } from "./nav-widget.styles"; | ||||
|  | ||||
| export type NavTabs = 'stationsTab' | 'attractionsTab'; | ||||
| export type NavTabs = "stationsTab" | "attractionsTab"; | ||||
|  | ||||
| export interface NavWidgetProps extends HTMLAttributes<HTMLDivElement> { | ||||
|   stations: Station[]; | ||||
| @@ -22,14 +29,19 @@ export function NavWidget({ stations, attractions }: NavWidgetProps) { | ||||
|   const [isOpen, setIsOpen] = useState<boolean>(false); | ||||
|   const [openedTab, setOpenedTab] = useState<NavTabs | null>(null); | ||||
|   const [attractionId, setAttractionId] = useState<uuid | null>(null); | ||||
|   const [attractionOrder, setAttractionOrder] = useState<Order>('asc'); | ||||
|   const [attractionOrder, setAttractionOrder] = useState<Order>("asc"); | ||||
|  | ||||
|   const sortAttractionsBtn: ReactNode = useMemo(() => { | ||||
|     if (openedTab === 'attractionsTab') { | ||||
|     if (openedTab === "attractionsTab") { | ||||
|       return ( | ||||
|         <div | ||||
|           className={cn([{ 'order-btn-inverse': attractionOrder === 'desc' }, 'action-btn'])} | ||||
|           onPointerUp={() => setAttractionOrder((prev) => (prev === 'asc' ? 'desc' : 'asc'))} | ||||
|           className={cn([ | ||||
|             { "order-btn-inverse": attractionOrder === "desc" }, | ||||
|             "action-btn", | ||||
|           ])} | ||||
|           onPointerUp={() => | ||||
|             setAttractionOrder((prev) => (prev === "asc" ? "desc" : "asc")) | ||||
|           } | ||||
|         > | ||||
|           <Icons.SortIcon /> | ||||
|         </div> | ||||
| @@ -64,8 +76,10 @@ export function NavWidget({ stations, attractions }: NavWidgetProps) { | ||||
|       })) | ||||
|       .sort( | ||||
|         (a, b) => | ||||
|           localizeText(a.name).toLowerCase().localeCompare(localizeText(b.name).toLowerCase()) * | ||||
|           (attractionOrder === 'asc' ? 1 : -1) | ||||
|           localizeText(a.name) | ||||
|             .toLowerCase() | ||||
|             .localeCompare(localizeText(b.name).toLowerCase()) * | ||||
|           (attractionOrder === "asc" ? 1 : -1) | ||||
|       ); | ||||
|   }, [attractions, attractionOrder, locale]); | ||||
|  | ||||
| @@ -74,7 +88,7 @@ export function NavWidget({ stations, attractions }: NavWidgetProps) { | ||||
|  | ||||
|   return ( | ||||
|     <Drawer | ||||
|       className={cn({ 'nav-widget--opened': isOpen })} | ||||
|       className={cn({ "nav-widget--opened": isOpen })} | ||||
|       isOpen={isOpen} | ||||
|       onToggle={setIsOpen} | ||||
|       onHomeBtnClick={() => setOpenedTab(null)} | ||||
| @@ -86,14 +100,14 @@ export function NavWidget({ stations, attractions }: NavWidgetProps) { | ||||
|  | ||||
|         <AccordionListTab | ||||
|           titleId="stops" | ||||
|           isOpened={openedTab === 'stationsTab'} | ||||
|           isOpened={openedTab === "stationsTab"} | ||||
|           items={mappedStations} | ||||
|           onNestedItemClick={setAttractionId} | ||||
|         /> | ||||
|  | ||||
|         <AccordionListTab | ||||
|           titleId="attractions" | ||||
|           isOpened={openedTab === 'attractionsTab'} | ||||
|           isOpened={openedTab === "attractionsTab"} | ||||
|           items={mappedAttractions} | ||||
|           onExpandChange={handleExpandChange} | ||||
|         /> | ||||
|   | ||||
| @@ -5,22 +5,23 @@ | ||||
|     "lib": ["DOM", "DOM.Iterable", "ESNext"], | ||||
|     "allowJs": false, | ||||
|     "skipLibCheck": true, | ||||
|     "esModuleInterop": false, | ||||
|     "esModuleInterop": true, | ||||
|     "allowSyntheticDefaultImports": true, | ||||
|     "strict": true, | ||||
|     "forceConsistentCasingInFileNames": true, | ||||
|     "module": "ESNext", | ||||
|     "moduleResolution": "Node", | ||||
|     "resolveJsonModule": true, | ||||
|     "isolatedModules": true, | ||||
|     "isolatedModules": false, | ||||
|     "noEmit": true, | ||||
|     "jsx": "react-jsx", | ||||
|     "baseUrl": ".", | ||||
|     "paths": { | ||||
|       "@mt/common-types": ["./src/preview/types"], | ||||
|       "@mt/components": ["./src/preview/components"], | ||||
|       "@mt/i18n": ["./src/preview/i18n"], | ||||
|       "@mt/widgets": ["./src/preview/widgets"], | ||||
|       "@mt/utils": ["./src/preview/utils"] | ||||
|       "@mt/common-types": ["src/preview/types"], | ||||
|       "@mt/components": ["src/preview/components"], | ||||
|       "@mt/i18n": ["src/preview/i18n"], | ||||
|       "@mt/widgets": ["src/preview/widgets"], | ||||
|       "@mt/utils": ["src/preview/utils"] | ||||
|     } | ||||
|   }, | ||||
|   "include": ["src"], | ||||
|   | ||||
| @@ -2,7 +2,17 @@ | ||||
|   "compilerOptions": { | ||||
|     "composite": true, | ||||
|     "module": "ESNext", | ||||
|     "moduleResolution": "node" | ||||
|     "moduleResolution": "node", | ||||
|     "allowSyntheticDefaultImports": true, | ||||
|     "esModuleInterop": true, | ||||
|     "baseUrl": ".", | ||||
|     "paths": { | ||||
|       "@mt/common-types": ["src/preview/types"], | ||||
|       "@mt/components": ["src/preview/components"], | ||||
|       "@mt/i18n": ["src/preview/i18n"], | ||||
|       "@mt/widgets": ["src/preview/widgets"], | ||||
|       "@mt/utils": ["src/preview/utils"] | ||||
|     } | ||||
|   }, | ||||
|   "include": ["vite.config.ts"] | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { defineConfig } from "vite"; | ||||
| import react from "@vitejs/plugin-react"; | ||||
| import * as path from "path"; | ||||
| import svgr from "vite-plugin-svgr"; | ||||
| import react from "@vitejs/plugin-react"; | ||||
|  | ||||
| export default defineConfig({ | ||||
|   plugins: [react(), svgr()], | ||||
|   | ||||
		Reference in New Issue
	
	Block a user