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 { TOKEN_KEY } from "../authProvider";
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; 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> = { type Field<T> = {
label: string; label: string;
data: keyof T; data: keyof T;
@ -49,6 +59,7 @@ type LinkedItemsProps<T> = {
type: "show" | "edit"; type: "show" | "edit";
extraField?: ExtraFieldConfig; extraField?: ExtraFieldConfig;
dragAllowed?: boolean; dragAllowed?: boolean;
onSave?: (items: T[]) => void;
}; };
const reorder = (list: any[], startIndex: number, endIndex: number) => { const reorder = (list: any[], startIndex: number, endIndex: number) => {
@ -66,7 +77,9 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
title, title,
dragAllowed = false, dragAllowed = false,
type, type,
onSave,
}: LinkedItemsProps<T>) => { }: LinkedItemsProps<T>) => {
const [position, setPosition] = useState<number>(1);
const [items, setItems] = useState<T[]>([]); const [items, setItems] = useState<T[]>([]);
const [linkedItems, setLinkedItems] = useState<T[]>([]); const [linkedItems, setLinkedItems] = useState<T[]>([]);
const [selectedItemId, setSelectedItemId] = useState<number | null>(null); const [selectedItemId, setSelectedItemId] = useState<number | null>(null);
@ -89,16 +102,11 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
axiosInstance.post( axiosInstance.post(
`${import.meta.env.VITE_KRBL_API}/route/${parentId}/station`, `${import.meta.env.VITE_KRBL_API}/route/${parentId}/station`,
{ {
after_station: 3, stations: reorderedItems.map((item) => ({
offset_x: 90, id: item.id,
offset_y: 15, })),
station_id: linkedItems[result.destination.index].id + 1,
} }
); );
// If you need to save the new order to the backend, you would add that here
// For example:
// saveNewOrder(reorderedItems);
}; };
useEffect(() => { useEffect(() => {
@ -158,9 +166,19 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
[`${childResource}_id`]: selectedItemId, [`${childResource}_id`]: selectedItemId,
media_order: mediaOrder, media_order: mediaOrder,
} }
: { : childResource === "station"
[`${childResource}_id`]: selectedItemId, ? {
}; stations: insertAtPosition(
linkedItems.map((item) => ({
id: item.id,
})),
position,
{
id: selectedItemId,
}
),
}
: { [`${childResource}_id`]: selectedItemId };
axiosInstance axiosInstance
.post( .post(
@ -232,6 +250,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
{type === "edit" && dragAllowed && ( {type === "edit" && dragAllowed && (
<TableCell width="40px"></TableCell> <TableCell width="40px"></TableCell>
)} )}
<TableCell key="id"></TableCell>
{fields.map((field) => ( {fields.map((field) => (
<TableCell key={String(field.data)}> <TableCell key={String(field.data)}>
{field.label} {field.label}
@ -259,6 +278,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
<TableRow <TableRow
ref={provided.innerRef} ref={provided.innerRef}
{...provided.draggableProps} {...provided.draggableProps}
{...provided.dragHandleProps}
hover hover
> >
{type === "edit" && dragAllowed && ( {type === "edit" && dragAllowed && (
@ -268,6 +288,9 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
</IconButton> </IconButton>
</TableCell> </TableCell>
)} )}
<TableCell key={String(item.id)}>
{index + 1}
</TableCell>
{fields.map((field) => ( {fields.map((field) => (
<TableCell key={String(field.data)}> <TableCell key={String(field.data)}>
{field.render {field.render
@ -389,6 +412,21 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
> >
Добавить Добавить
</Button> </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>
)} )}
</Stack> </Stack>

View File

@ -17,6 +17,7 @@ import {
stationFields, stationFields,
vehicleFields, vehicleFields,
} from "./types"; } from "./types";
import { useEffect } from "react";
export const RouteEdit = () => { export const RouteEdit = () => {
const { const {
@ -24,10 +25,23 @@ export const RouteEdit = () => {
register, register,
control, control,
formState: { errors }, formState: { errors },
refineCore: { queryResult },
setValue,
watch,
} = useForm({}); } = useForm({});
const { id: routeId } = useParams<{ id: string }>(); 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({ const { autocompleteProps: carrierAutocompleteProps } = useAutocomplete({
resource: "carrier", resource: "carrier",
onSearch: (value) => [ onSearch: (value) => [
@ -134,7 +148,6 @@ export const RouteEdit = () => {
{...register("path", { {...register("path", {
required: "Это поле является обязательным", required: "Это поле является обязательным",
setValueAs: (value: string) => { setValueAs: (value: string) => {
// Преобразование строки в массив координат
try { try {
const lines = value.trim().split("\n"); const lines = value.trim().split("\n");
return lines.map((line) => { return lines.map((line) => {

View File

@ -13,11 +13,10 @@ import { LocalizedStringDefaults } from "@mt/common-types";
import { useParams } from "react-router"; import { useParams } from "react-router";
export const RoutePreviewContainer = () => { export const RoutePreviewContainer = () => {
const { id } = useParams(); const { routeId } = useParams();
const routeId = id as string;
const { routeData, mappedData, isLoading, isError } = const { routeData, mappedData, isLoading, isError } =
useGetRouteData(routeId); useGetRouteData(routeId);
// const updateRouteView = useUpdateRouteData(routeId, routeData); const updateRouteView = useUpdateRouteData(routeId, routeData);
const [routeInfo, setRouteInfo] = useState<RouteInfoData>(); const [routeInfo, setRouteInfo] = useState<RouteInfoData>();
const { const {
@ -28,9 +27,9 @@ export const RoutePreviewContainer = () => {
getUpdatedStations, getUpdatedStations,
} = useMapWidgetContext(); } = useMapWidgetContext();
// const handleSubmit = (mapSettings: MapSettings) => { const handleSubmit = (mapSettings: MapSettings) => {
// updateRouteView(mapSettings, getUpdatedStations()); updateRouteView(mapSettings, getUpdatedStations());
// }; };
useEffect(() => { useEffect(() => {
if (!mappedData) { if (!mappedData) {
@ -42,7 +41,7 @@ export const RoutePreviewContainer = () => {
setIsEditMode(true); setIsEditMode(true);
setRouteInfo({ setRouteInfo({
routeNumber: routeData.number, routeNumber: routeData?.number ?? "",
firstStationName: firstStationName:
mappedData.stationsOnMap.at(0)?.shortName ?? LocalizedStringDefaults, mappedData.stationsOnMap.at(0)?.shortName ?? LocalizedStringDefaults,
lastStationName: lastStationName:
@ -74,7 +73,7 @@ export const RoutePreviewContainer = () => {
if (mappedData) { if (mappedData) {
return ( return (
<RoutePreviewDashboard routeInfo={routeInfo!}> <RoutePreviewDashboard routeInfo={routeInfo}>
<MapWidget /> <MapWidget />
{/* <SettingsPanel onSubmit={handleSubmit} /> */} {/* <SettingsPanel onSubmit={handleSubmit} /> */}
</RoutePreviewDashboard> </RoutePreviewDashboard>

View File

@ -21,17 +21,24 @@ export const RoutePreviewDashboard = ({ children, routeInfo }: Props) => {
return ( return (
<div className={styles.root}> <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={cn(styles.container, { [styles.pushed]: openNav })}>
<div className={styles.leftTopWrapper}> <div className={styles.leftTopWrapper}>
<RouteInfoWidget className={styles.routeNumber} routeInfo={routeInfo} /> <RouteInfoWidget
className={styles.routeNumber}
routeInfo={routeInfo}
/>
<WeatherWidget className={styles.weatherWidget} /> <WeatherWidget className={styles.weatherWidget} />
</div> */} </div>
{children} {children}
{/* <div className={styles.transferPlaceholder}> <div className={styles.transferPlaceholder}>
<Icons.InfoBtn <Icons.InfoBtn
className={styles.toggleTransferBtn} className={styles.toggleTransferBtn}
onClick={() => setOpenTransfer(!openTransfers)} onClick={() => setOpenTransfer(!openTransfers)}
@ -41,7 +48,7 @@ export const RoutePreviewDashboard = ({ children, routeInfo }: Props) => {
</div> </div>
</div> </div>
<div className={styles.rightSidebar} /> */} <div className={styles.rightSidebar} />
</div> </div>
); );
}; };

View File

@ -1,62 +1,58 @@
import { MapData } from "@mt/components"; import { MapData } from "@mt/components";
import { Route, Station } from "@mt/common-types";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { mapRouteFromApi } from "../mappers/mapRouteFromApi"; import { mapRouteFromApi } from "../mappers/mapRouteFromApi";
import { useOne, useMany } from "@refinedev/core"; import { useOn, Station, useOne, useMany } from "@refinedev/core";
import { axiosInstance } from "../../../../providers/data"; import { Route } from "@mt/common-types";
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);
};
export const useGetRouteData = (routeId: string) => { 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 { const {
data: routeData, data: routeData,
isSuccess, isSuccess,
isError, isError,
isLoading, isLoading,
} = useOne<Route>({ } = useOne<Route>("routes", { id: routeId });
resource: "route",
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];
}),
}); });
// Handle loading state and errors
const [stations, setStations] = useState<any[]>([]);
const [sights, setSights] = useState<any[]>([]);
useEffect(() => { useEffect(() => {
fetchStations(routeId, setStations, setSights); if (isLoading || stationsLoading) {
}, [routeData]); setError(null); // Reset error during loading
return;
}
// useEffect(() => { if (isError || stationsError) {
// if (!routeData) { setError("Failed to fetch route or station data.");
// return; return;
// } }
// // const data = mapRouteFromApi(routeData, []); if (!routeData || !stations) {
return;
}
// setMappedData(data); try {
// }, [routeData]); 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
};
}; };