route edit fixed with drag and drop tool
This commit is contained in:
		| @@ -27,6 +27,16 @@ import { axiosInstance } from "../providers/data"; | ||||
| import { TOKEN_KEY } from "../authProvider"; | ||||
| import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; | ||||
|  | ||||
| function insertAtPosition<T>(arr: T[], pos: number, value: T): T[] { | ||||
|   const index = pos - 1; | ||||
|   if (index >= arr.length) { | ||||
|     arr.push(value); | ||||
|   } else { | ||||
|     arr.splice(index, 0, value); | ||||
|   } | ||||
|   return arr; | ||||
| } | ||||
|  | ||||
| type Field<T> = { | ||||
|   label: string; | ||||
|   data: keyof T; | ||||
| @@ -49,6 +59,7 @@ type LinkedItemsProps<T> = { | ||||
|   type: "show" | "edit"; | ||||
|   extraField?: ExtraFieldConfig; | ||||
|   dragAllowed?: boolean; | ||||
|   onSave?: (items: T[]) => void; | ||||
| }; | ||||
|  | ||||
| const reorder = (list: any[], startIndex: number, endIndex: number) => { | ||||
| @@ -66,7 +77,9 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|   title, | ||||
|   dragAllowed = false, | ||||
|   type, | ||||
|   onSave, | ||||
| }: LinkedItemsProps<T>) => { | ||||
|   const [position, setPosition] = useState<number>(1); | ||||
|   const [items, setItems] = useState<T[]>([]); | ||||
|   const [linkedItems, setLinkedItems] = useState<T[]>([]); | ||||
|   const [selectedItemId, setSelectedItemId] = useState<number | null>(null); | ||||
| @@ -89,16 +102,11 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|     axiosInstance.post( | ||||
|       `${import.meta.env.VITE_KRBL_API}/route/${parentId}/station`, | ||||
|       { | ||||
|         after_station: 3, | ||||
|         offset_x: 90, | ||||
|         offset_y: 15, | ||||
|         station_id: linkedItems[result.destination.index].id + 1, | ||||
|         stations: reorderedItems.map((item) => ({ | ||||
|           id: item.id, | ||||
|         })), | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     // If you need to save the new order to the backend, you would add that here | ||||
|     // For example: | ||||
|     // saveNewOrder(reorderedItems); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
| @@ -158,9 +166,19 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|               [`${childResource}_id`]: selectedItemId, | ||||
|               media_order: mediaOrder, | ||||
|             } | ||||
|           : { | ||||
|               [`${childResource}_id`]: selectedItemId, | ||||
|             }; | ||||
|           : childResource === "station" | ||||
|           ? { | ||||
|               stations: insertAtPosition( | ||||
|                 linkedItems.map((item) => ({ | ||||
|                   id: item.id, | ||||
|                 })), | ||||
|                 position, | ||||
|                 { | ||||
|                   id: selectedItemId, | ||||
|                 } | ||||
|               ), | ||||
|             } | ||||
|           : { [`${childResource}_id`]: selectedItemId }; | ||||
|  | ||||
|       axiosInstance | ||||
|         .post( | ||||
| @@ -232,6 +250,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|                     {type === "edit" && dragAllowed && ( | ||||
|                       <TableCell width="40px"></TableCell> | ||||
|                     )} | ||||
|                     <TableCell key="id">№</TableCell> | ||||
|                     {fields.map((field) => ( | ||||
|                       <TableCell key={String(field.data)}> | ||||
|                         {field.label} | ||||
| @@ -259,6 +278,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|                             <TableRow | ||||
|                               ref={provided.innerRef} | ||||
|                               {...provided.draggableProps} | ||||
|                               {...provided.dragHandleProps} | ||||
|                               hover | ||||
|                             > | ||||
|                               {type === "edit" && dragAllowed && ( | ||||
| @@ -268,6 +288,9 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|                                   </IconButton> | ||||
|                                 </TableCell> | ||||
|                               )} | ||||
|                               <TableCell key={String(item.id)}> | ||||
|                                 {index + 1} | ||||
|                               </TableCell> | ||||
|                               {fields.map((field) => ( | ||||
|                                 <TableCell key={String(field.data)}> | ||||
|                                   {field.render | ||||
| @@ -389,6 +412,21 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({ | ||||
|               > | ||||
|                 Добавить | ||||
|               </Button> | ||||
|               {childResource == "station" && ( | ||||
|                 <TextField | ||||
|                   type="text" | ||||
|                   label="Позиция добавляемой остановки к маршруту" | ||||
|                   value={position} | ||||
|                   onChange={(e) => { | ||||
|                     const newValue = Number(e.target.value); | ||||
|                     setPosition( | ||||
|                       newValue > linkedItems.length + 1 | ||||
|                         ? linkedItems.length + 1 | ||||
|                         : newValue | ||||
|                     ); | ||||
|                   }} | ||||
|                 ></TextField> | ||||
|               )} | ||||
|             </Stack> | ||||
|           )} | ||||
|         </Stack> | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import { | ||||
|   stationFields, | ||||
|   vehicleFields, | ||||
| } from "./types"; | ||||
| import { useEffect } from "react"; | ||||
|  | ||||
| export const RouteEdit = () => { | ||||
|   const { | ||||
| @@ -24,10 +25,23 @@ export const RouteEdit = () => { | ||||
|     register, | ||||
|     control, | ||||
|     formState: { errors }, | ||||
|     refineCore: { queryResult }, | ||||
|     setValue, | ||||
|     watch, | ||||
|   } = useForm({}); | ||||
|  | ||||
|   const { id: routeId } = useParams<{ id: string }>(); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (queryResult?.data?.data && Array.isArray(queryResult.data.data.path)) { | ||||
|       const formattedPath = queryResult.data.data.path | ||||
|         .map((coords) => coords.join(" ")) | ||||
|         .join("\n"); | ||||
|  | ||||
|       setValue("path", formattedPath); | ||||
|     } | ||||
|   }, [queryResult?.data?.data, setValue]); | ||||
|  | ||||
|   const { autocompleteProps: carrierAutocompleteProps } = useAutocomplete({ | ||||
|     resource: "carrier", | ||||
|     onSearch: (value) => [ | ||||
| @@ -134,7 +148,6 @@ export const RouteEdit = () => { | ||||
|           {...register("path", { | ||||
|             required: "Это поле является обязательным", | ||||
|             setValueAs: (value: string) => { | ||||
|               // Преобразование строки в массив координат | ||||
|               try { | ||||
|                 const lines = value.trim().split("\n"); | ||||
|                 return lines.map((line) => { | ||||
|   | ||||
| @@ -13,11 +13,10 @@ import { LocalizedStringDefaults } from "@mt/common-types"; | ||||
| import { useParams } from "react-router"; | ||||
|  | ||||
| export const RoutePreviewContainer = () => { | ||||
|   const { id } = useParams(); | ||||
|   const routeId = id as string; | ||||
|   const { routeId } = useParams(); | ||||
|   const { routeData, mappedData, isLoading, isError } = | ||||
|     useGetRouteData(routeId); | ||||
|   // const updateRouteView = useUpdateRouteData(routeId, routeData); | ||||
|   const updateRouteView = useUpdateRouteData(routeId, routeData); | ||||
|   const [routeInfo, setRouteInfo] = useState<RouteInfoData>(); | ||||
|  | ||||
|   const { | ||||
| @@ -28,9 +27,9 @@ export const RoutePreviewContainer = () => { | ||||
|     getUpdatedStations, | ||||
|   } = useMapWidgetContext(); | ||||
|  | ||||
|   // const handleSubmit = (mapSettings: MapSettings) => { | ||||
|   //   updateRouteView(mapSettings, getUpdatedStations()); | ||||
|   // }; | ||||
|   const handleSubmit = (mapSettings: MapSettings) => { | ||||
|     updateRouteView(mapSettings, getUpdatedStations()); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (!mappedData) { | ||||
| @@ -42,7 +41,7 @@ export const RoutePreviewContainer = () => { | ||||
|     setIsEditMode(true); | ||||
|  | ||||
|     setRouteInfo({ | ||||
|       routeNumber: routeData.number, | ||||
|       routeNumber: routeData?.number ?? "", | ||||
|       firstStationName: | ||||
|         mappedData.stationsOnMap.at(0)?.shortName ?? LocalizedStringDefaults, | ||||
|       lastStationName: | ||||
| @@ -74,7 +73,7 @@ export const RoutePreviewContainer = () => { | ||||
|  | ||||
|   if (mappedData) { | ||||
|     return ( | ||||
|       <RoutePreviewDashboard routeInfo={routeInfo!}> | ||||
|       <RoutePreviewDashboard routeInfo={routeInfo}> | ||||
|         <MapWidget /> | ||||
|         {/* <SettingsPanel onSubmit={handleSubmit} /> */} | ||||
|       </RoutePreviewDashboard> | ||||
|   | ||||
| @@ -21,17 +21,24 @@ export const RoutePreviewDashboard = ({ children, routeInfo }: Props) => { | ||||
|  | ||||
|   return ( | ||||
|     <div className={styles.root}> | ||||
|       {/* <Drawer isOpen={openNav} onToggle={setOpenNav} onLocaleChange={setLocale} /> | ||||
|       <Drawer | ||||
|         isOpen={openNav} | ||||
|         onToggle={setOpenNav} | ||||
|         onLocaleChange={setLocale} | ||||
|       /> | ||||
|  | ||||
|       <div className={cn(styles.container, { [styles.pushed]: openNav })}> | ||||
|         <div className={styles.leftTopWrapper}> | ||||
|           <RouteInfoWidget className={styles.routeNumber} routeInfo={routeInfo} /> | ||||
|           <RouteInfoWidget | ||||
|             className={styles.routeNumber} | ||||
|             routeInfo={routeInfo} | ||||
|           /> | ||||
|           <WeatherWidget className={styles.weatherWidget} /> | ||||
|         </div> */} | ||||
|         </div> | ||||
|  | ||||
|       {children} | ||||
|         {children} | ||||
|  | ||||
|       {/* <div className={styles.transferPlaceholder}> | ||||
|         <div className={styles.transferPlaceholder}> | ||||
|           <Icons.InfoBtn | ||||
|             className={styles.toggleTransferBtn} | ||||
|             onClick={() => setOpenTransfer(!openTransfers)} | ||||
| @@ -41,7 +48,7 @@ export const RoutePreviewDashboard = ({ children, routeInfo }: Props) => { | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <div className={styles.rightSidebar} /> */} | ||||
|       <div className={styles.rightSidebar} /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,62 +1,58 @@ | ||||
| import { MapData } from "@mt/components"; | ||||
|  | ||||
| import { Route, Station } from "@mt/common-types"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import { mapRouteFromApi } from "../mappers/mapRouteFromApi"; | ||||
| import { useOne, useMany } from "@refinedev/core"; | ||||
| import { axiosInstance } from "../../../../providers/data"; | ||||
|  | ||||
| const fetchStations = async ( | ||||
|   routeId: string, | ||||
|   setStations: (stations: any[]) => void, | ||||
|   setSights: (sights: any[]) => void | ||||
| ) => { | ||||
|   const stations = (await axiosInstance.get(`/route/${routeId}/station`)).data; | ||||
|   const sights = (await axiosInstance.get(`/route/${routeId}/sight`)).data; | ||||
|   setStations(stations); | ||||
|   setSights(sights); | ||||
|  | ||||
|   const stationsPath = stations.map((station: any) => [ | ||||
|     station.latitude, | ||||
|     station.longitude, | ||||
|   ]); | ||||
|   const sightsPath = sights.map((sight: any) => [ | ||||
|     sight.latitude, | ||||
|     sight.longitude, | ||||
|   ]); | ||||
|  | ||||
|   console.log(stationsPath, sightsPath); | ||||
| }; | ||||
| import { useOn, Station, useOne, useMany } from "@refinedev/core"; | ||||
| import { Route } from "@mt/common-types"; | ||||
|  | ||||
| export const useGetRouteData = (routeId: string) => { | ||||
|   const [mappedData, setMappedData] = useState<MapData>(null); | ||||
|   const [mappedData, setMappedData] = useState<MapData | null>(null); | ||||
|   const [error, setError] = useState<string | null>(null); // Handle error state | ||||
|  | ||||
|   // Fetch route data | ||||
|   const { | ||||
|     data: routeData, | ||||
|     isSuccess, | ||||
|     isError, | ||||
|     isLoading, | ||||
|   } = useOne<Route>({ | ||||
|     resource: "route", | ||||
|     id: routeId, | ||||
|   } = useOne<Route>("routes", { id: routeId }); | ||||
|  | ||||
|   const { data: stations } = useMany<Station>("stations", { | ||||
|     ids: routeData?.stations.flatMap(({ stationId, transferStations }) => { | ||||
|       const transferIds = transferStations | ||||
|         .filter(({ isShowOnMap }) => isShowOnMap) | ||||
|         .map(({ stationId: id }) => id); | ||||
|       return [stationId, ...transferIds]; | ||||
|     }), | ||||
|   }); | ||||
|  | ||||
|   const [stations, setStations] = useState<any[]>([]); | ||||
|   const [sights, setSights] = useState<any[]>([]); | ||||
|  | ||||
|   // Handle loading state and errors | ||||
|   useEffect(() => { | ||||
|     fetchStations(routeId, setStations, setSights); | ||||
|   }, [routeData]); | ||||
|     if (isLoading || stationsLoading) { | ||||
|       setError(null); // Reset error during loading | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|   // useEffect(() => { | ||||
|   //   if (!routeData) { | ||||
|   //     return; | ||||
|   //   } | ||||
|     if (isError || stationsError) { | ||||
|       setError("Failed to fetch route or station data."); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|   //   // const data = mapRouteFromApi(routeData, []); | ||||
|     if (!routeData || !stations) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|   //   setMappedData(data); | ||||
|   // }, [routeData]); | ||||
|     try { | ||||
|       const data = mapRouteFromApi(routeData, stations); | ||||
|       setMappedData(data); // Set the mapped data | ||||
|     } catch (err) { | ||||
|       setError("Error mapping the route data."); | ||||
|     } | ||||
|   }, [routeData, stations, isLoading, stationsLoading, isError, stationsError]); | ||||
|  | ||||
|   return { routeData, mappedData, isLoading, isError }; | ||||
|   return { | ||||
|     routeData, | ||||
|     mappedData, | ||||
|     isLoading: isLoading || stationsLoading, // Combine loading states | ||||
|     isError: isError || stationsError, // Combine error states | ||||
|     error, // Provide the error message | ||||
|   }; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user