route edit fixed with drag and drop tool

This commit is contained in:
Илья Куприец 2025-04-21 15:34:11 +03:00
parent 9e34a71e14
commit 463c593a0e
5 changed files with 123 additions and 70 deletions

View File

@ -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>

View File

@ -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) => {

View File

@ -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>

View File

@ -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}
{/* <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>
);
};

View File

@ -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
};
};