feat: Update left sidebar and fetch queries
This commit is contained in:
@@ -61,6 +61,7 @@ export const LeftSidebar = observer(({ open, onToggle }: LeftSidebarProps) => {
|
|||||||
width="100%"
|
width="100%"
|
||||||
spacing={4}
|
spacing={4}
|
||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
|
justifyContent="space-between"
|
||||||
sx={{
|
sx={{
|
||||||
opacity: open ? 1 : 0,
|
opacity: open ? 1 : 0,
|
||||||
transition: "opacity 0.25s ease",
|
transition: "opacity 0.25s ease",
|
||||||
@@ -68,71 +69,72 @@ export const LeftSidebar = observer(({ open, onToggle }: LeftSidebarProps) => {
|
|||||||
display: open ? "flex" : "none",
|
display: open ? "flex" : "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<div>
|
||||||
onClick={handleBack}
|
<Button
|
||||||
variant="contained"
|
onClick={handleBack}
|
||||||
color="primary"
|
variant="contained"
|
||||||
sx={{
|
color="primary"
|
||||||
backgroundColor: "#222",
|
sx={{
|
||||||
color: "#fff",
|
backgroundColor: "#222",
|
||||||
borderRadius: 1.5,
|
color: "#fff",
|
||||||
px: 2,
|
borderRadius: 1.5,
|
||||||
py: 1,
|
px: 2,
|
||||||
"&:hover": {
|
py: 1,
|
||||||
backgroundColor: "#2d2d2d",
|
marginBottom: 10,
|
||||||
},
|
"&:hover": {
|
||||||
}}
|
backgroundColor: "#2d2d2d",
|
||||||
fullWidth
|
},
|
||||||
startIcon={<ArrowBackIcon />}
|
|
||||||
>
|
|
||||||
Назад
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Stack
|
|
||||||
direction="column"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
spacing={3}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
maxWidth: 200,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 10,
|
|
||||||
}}
|
}}
|
||||||
|
fullWidth
|
||||||
|
startIcon={<ArrowBackIcon />}
|
||||||
>
|
>
|
||||||
{carrierThumbnail && (
|
Назад
|
||||||
<MediaViewer
|
</Button>
|
||||||
media={{
|
|
||||||
id: carrierThumbnail,
|
|
||||||
media_type: 1, // Тип "Фото" для логотипа
|
|
||||||
filename: "route_thumbnail",
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
fullHeight
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Typography sx={{ color: "#fff" }} textAlign="center">
|
|
||||||
При поддержке Правительства
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
direction="column"
|
direction="column"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
spacing={2}
|
spacing={3}
|
||||||
>
|
>
|
||||||
<Button variant="outlined" color="warning" fullWidth>
|
<div
|
||||||
Достопримечательности
|
style={{
|
||||||
</Button>
|
maxWidth: 150,
|
||||||
<Button variant="outlined" color="warning" fullWidth>
|
display: "flex",
|
||||||
Остановки
|
flexDirection: "column",
|
||||||
</Button>
|
alignItems: "center",
|
||||||
</Stack>
|
gap: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{carrierThumbnail && (
|
||||||
|
<MediaViewer
|
||||||
|
media={{
|
||||||
|
id: carrierThumbnail,
|
||||||
|
media_type: 1, // Тип "Фото" для логотипа
|
||||||
|
filename: "route_thumbnail",
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
fullHeight
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Typography sx={{ color: "#fff" }} textAlign="center">
|
||||||
|
При поддержке Правительства
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center justify-center gap-2 mt-10">
|
||||||
|
<button className="bg-[#fcd500] text-black px-4 py-2 rounded-md w-full font-medium my-10">
|
||||||
|
Обращение губернатора
|
||||||
|
</button>
|
||||||
|
<button className="bg-white text-black px-4 py-2 rounded-md w-full font-medium mx-5">
|
||||||
|
Достопримечательности
|
||||||
|
</button>
|
||||||
|
<button className="bg-white text-black px-4 py-2 rounded-md w-full font-medium mx-5">
|
||||||
|
Остановки
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
direction="column"
|
direction="column"
|
||||||
@@ -153,31 +155,14 @@ export const LeftSidebar = observer(({ open, onToggle }: LeftSidebarProps) => {
|
|||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Typography variant="h6" textAlign="center" sx={{ color: "#fff" }}>
|
|
||||||
#ВсемПоПути
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{!open && (
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="h6"
|
||||||
sx={{
|
textAlign="center"
|
||||||
color: "rgba(255,255,255,0.6)",
|
sx={{ color: "#fff", marginTop: "auto" }}
|
||||||
writingMode: "vertical-rl",
|
|
||||||
transform: "rotate(180deg)",
|
|
||||||
letterSpacing: 4,
|
|
||||||
position: "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transformOrigin: "center",
|
|
||||||
translate: "-50% -50%",
|
|
||||||
opacity: 0.6,
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
#ВсемПоПути
|
#ВсемПоПути
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
</Stack>
|
||||||
|
|
||||||
<div className="absolute bottom-[20px] -right-[520px] z-10">
|
<div className="absolute bottom-[20px] -right-[520px] z-10">
|
||||||
<LanguageSelector onBack={onToggle} isSidebarOpen={open} />
|
<LanguageSelector onBack={onToggle} isSidebarOpen={open} />
|
||||||
|
|||||||
@@ -447,6 +447,21 @@ export function RightSidebar() {
|
|||||||
>
|
>
|
||||||
Сохранить изменения
|
Сохранить изменения
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
fill="none"
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
className="absolute bottom-5 left-[-68px] z-100"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M24.0013 0C23.3513 0 22.7013 0.03 22.0413 0.08C10.4213 1 1.01127 10.41 0.0812683 22.03C-0.428732 28.39 1.55127 34.28 5.14127 38.83C5.60127 39.42 5.76127 40.21 5.46127 40.89C4.76127 42.43 3.63127 43.64 3.05127 44.23C2.50127 44.78 1.97127 45.27 1.38127 45.7C0.791268 46.13 0.841268 47.03 1.45127 47.42C2.08127 47.82 3.01127 47.99 4.12127 47.99C6.84127 47.99 10.5813 46.99 13.3013 46.06C13.5013 45.99 13.7013 45.96 13.9113 45.96C14.1813 45.96 14.4613 46.02 14.7113 46.13C17.5713 47.33 20.7113 48 24.0013 48C24.6513 48 25.3213 47.97 25.9813 47.92C37.6313 46.98 47.0613 37.51 47.9313 25.85C48.9913 11.76 37.8713 0 24.0013 0ZM29.5113 37.71C29.4813 37.82 29.3413 37.94 29.2313 37.98C27.7413 38.48 26.2713 39.12 24.7313 39.42C22.9513 39.77 21.1413 39.68 19.5513 38.58C18.2213 37.66 17.7313 36.36 17.8113 34.8C17.9013 32.91 18.5113 31.13 19.0013 29.33C19.5213 27.42 20.1113 25.53 20.4613 23.59C20.9413 20.94 19.7813 20.48 17.3913 20.74C16.8013 20.8 16.2313 21.04 15.5813 21.22C15.7213 20.62 15.8313 20.08 15.9913 19.55C16.0213 19.45 19.6313 17.94 21.4413 17.78C23.3513 17.61 25.2013 17.8 26.6013 19.32C27.3913 20.17 27.6113 21.21 27.5913 22.33C27.5413 24.8 26.5813 27.07 25.9813 29.42C25.6113 30.86 25.2513 32.3 24.9313 33.75C24.8413 34.15 24.8413 34.59 24.8813 35C24.9613 35.97 25.4413 36.39 26.4313 36.57C27.6213 36.78 28.7213 36.45 29.9313 36.04C29.7813 36.66 29.6613 37.19 29.5213 37.7L29.5113 37.71ZM26.8513 15.21C26.6513 15.23 26.4613 15.23 26.2013 15.25C24.4013 15.27 22.7313 14.15 22.2013 12.52C21.5913 10.65 22.5613 8.71 24.5313 7.86C26.7913 6.88 29.5813 8.07 30.2713 10.33C31.0513 12.87 29.0613 14.95 26.8613 15.21H26.8513Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { flushSync } from "react-dom";
|
||||||
import type { PointerEvent as ReactPointerEvent } from "react";
|
import type { PointerEvent as ReactPointerEvent } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
@@ -592,11 +593,18 @@ export const WebGLRouteMapPrototype = observer(() => {
|
|||||||
}, [cancelScheduledCenterCommit]);
|
}, [cancelScheduledCenterCommit]);
|
||||||
|
|
||||||
const updateTransform = useCallback(
|
const updateTransform = useCallback(
|
||||||
(next: Transform) => {
|
(next: Transform, options?: { immediate?: boolean }) => {
|
||||||
const adjusted = clampTransformScale(next);
|
const adjusted = clampTransformScale(next);
|
||||||
transformRef.current = adjusted;
|
transformRef.current = adjusted;
|
||||||
setTransformState(adjusted);
|
if (options?.immediate) {
|
||||||
setSharedScale(adjusted.scale);
|
flushSync(() => {
|
||||||
|
setTransformState(adjusted);
|
||||||
|
setSharedScale(adjusted.scale);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTransformState(adjusted);
|
||||||
|
setSharedScale(adjusted.scale);
|
||||||
|
}
|
||||||
computeCenterCoordinates(adjusted);
|
computeCenterCoordinates(adjusted);
|
||||||
},
|
},
|
||||||
[clampTransformScale, setSharedScale, computeCenterCoordinates]
|
[clampTransformScale, setSharedScale, computeCenterCoordinates]
|
||||||
@@ -1129,13 +1137,16 @@ export const WebGLRouteMapPrototype = observer(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { scale, translation } = transform;
|
const { scale, translation } = transform;
|
||||||
const pointOuterSizePx = clamp(scale * 13.3333, 6, 120);
|
|
||||||
|
const desiredRouteWidthCss = 13;
|
||||||
|
const desiredStationDiameterCss = 30;
|
||||||
|
const pointOuterSizePx = desiredStationDiameterCss * dpr;
|
||||||
const pointInnerSizePx = pointOuterSizePx * 0.8;
|
const pointInnerSizePx = pointOuterSizePx * 0.8;
|
||||||
|
|
||||||
if (rotatedRouteVertices.length >= 4) {
|
if (rotatedRouteVertices.length >= 4) {
|
||||||
gl.useProgram(lineProgram.program);
|
gl.useProgram(lineProgram.program);
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, lineBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, lineBuffer);
|
||||||
const lineWidth = pointInnerSizePx / scale;
|
const lineWidth = (desiredRouteWidthCss * dpr) / scale;
|
||||||
const thickVertices = generateThickLineGeometry(
|
const thickVertices = generateThickLineGeometry(
|
||||||
rotatedRouteVertices,
|
rotatedRouteVertices,
|
||||||
lineWidth
|
lineWidth
|
||||||
@@ -1494,7 +1505,7 @@ export const WebGLRouteMapPrototype = observer(() => {
|
|||||||
y: transform.translation.y + dy,
|
y: transform.translation.y + dy,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
updateTransform(next);
|
updateTransform(next, { immediate: true });
|
||||||
drawSceneRef.current();
|
drawSceneRef.current();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1570,13 +1581,16 @@ export const WebGLRouteMapPrototype = observer(() => {
|
|||||||
scaleLimitsRef.current.max
|
scaleLimitsRef.current.max
|
||||||
);
|
);
|
||||||
|
|
||||||
updateTransform({
|
updateTransform(
|
||||||
scale: clampedScale,
|
{
|
||||||
translation: {
|
scale: clampedScale,
|
||||||
x: midpoint.x - worldMidpoint.x * clampedScale,
|
translation: {
|
||||||
y: midpoint.y - worldMidpoint.y * clampedScale,
|
x: midpoint.x - worldMidpoint.x * clampedScale,
|
||||||
|
y: midpoint.y - worldMidpoint.y * clampedScale,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
{ immediate: true }
|
||||||
|
);
|
||||||
drawSceneRef.current();
|
drawSceneRef.current();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1627,13 +1641,16 @@ export const WebGLRouteMapPrototype = observer(() => {
|
|||||||
y: (position.y - transform.translation.y) / transform.scale,
|
y: (position.y - transform.translation.y) / transform.scale,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateTransform({
|
updateTransform(
|
||||||
scale: clampedScale,
|
{
|
||||||
translation: {
|
scale: clampedScale,
|
||||||
x: position.x - worldPoint.x * clampedScale,
|
translation: {
|
||||||
y: position.y - worldPoint.y * clampedScale,
|
x: position.x - worldPoint.x * clampedScale,
|
||||||
|
y: position.y - worldPoint.y * clampedScale,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
{ immediate: true }
|
||||||
|
);
|
||||||
drawSceneRef.current();
|
drawSceneRef.current();
|
||||||
scheduleCenterCommit();
|
scheduleCenterCommit();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ const LinkedStationsContentsInner = <
|
|||||||
const parentResource = "sight";
|
const parentResource = "sight";
|
||||||
const childResource = "station";
|
const childResource = "station";
|
||||||
|
|
||||||
|
const buildPayload = (ids: number[]) => ({
|
||||||
|
[`${childResource}_ids`]: ids,
|
||||||
|
});
|
||||||
|
|
||||||
const availableItems = allItems
|
const availableItems = allItems
|
||||||
.filter((item) => !linkedItems.some((linked) => linked.id === item.id))
|
.filter((item) => !linkedItems.some((linked) => linked.id === item.id))
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
@@ -159,9 +163,7 @@ const LinkedStationsContentsInner = <
|
|||||||
const linkItem = () => {
|
const linkItem = () => {
|
||||||
if (selectedItemId !== null) {
|
if (selectedItemId !== null) {
|
||||||
setError(null);
|
setError(null);
|
||||||
const requestData = {
|
const requestData = buildPayload([selectedItemId]);
|
||||||
station_id: selectedItemId,
|
|
||||||
};
|
|
||||||
|
|
||||||
setIsLinkingSingle(true);
|
setIsLinkingSingle(true);
|
||||||
authInstance
|
authInstance
|
||||||
@@ -193,7 +195,7 @@ const LinkedStationsContentsInner = <
|
|||||||
});
|
});
|
||||||
authInstance
|
authInstance
|
||||||
.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
||||||
data: { [`${childResource}_id`]: itemId },
|
data: buildPayload([itemId]),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setLinkedItems(linkedItems.filter((item) => item.id !== itemId));
|
setLinkedItems(linkedItems.filter((item) => item.id !== itemId));
|
||||||
@@ -228,46 +230,28 @@ const LinkedStationsContentsInner = <
|
|||||||
|
|
||||||
setIsLinkingBulk(true);
|
setIsLinkingBulk(true);
|
||||||
const idsToLink = Array.from(selectedItems);
|
const idsToLink = Array.from(selectedItems);
|
||||||
const linkedIds: number[] = [];
|
|
||||||
const failedIds: number[] = [];
|
|
||||||
|
|
||||||
for (const id of idsToLink) {
|
try {
|
||||||
try {
|
await authInstance.post(
|
||||||
await authInstance.post(`/${parentResource}/${parentId}/${childResource}`, {
|
`/${parentResource}/${parentId}/${childResource}`,
|
||||||
station_id: id,
|
buildPayload(idsToLink)
|
||||||
});
|
);
|
||||||
linkedIds.push(id);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error linking station:", error);
|
|
||||||
failedIds.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (linkedIds.length > 0) {
|
const newItems = allItems.filter((item) => idsToLink.includes(item.id));
|
||||||
const newItems = allItems.filter((item) => linkedIds.includes(item.id));
|
|
||||||
setLinkedItems((prev) => {
|
setLinkedItems((prev) => {
|
||||||
const existingIds = new Set(prev.map((item) => item.id));
|
const existingIds = new Set(prev.map((item) => item.id));
|
||||||
const additions = newItems.filter((item) => !existingIds.has(item.id));
|
const additions = newItems.filter((item) => !existingIds.has(item.id));
|
||||||
return [...prev, ...additions];
|
return [...prev, ...additions];
|
||||||
});
|
});
|
||||||
|
setSelectedItems((prev) => {
|
||||||
|
const remaining = new Set(prev);
|
||||||
|
idsToLink.forEach((id) => remaining.delete(id));
|
||||||
|
return remaining;
|
||||||
|
});
|
||||||
onUpdate?.();
|
onUpdate?.();
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error("Error linking stations:", error);
|
||||||
setSelectedItems((prev) => {
|
setError("Failed to link stations");
|
||||||
if (linkedIds.length === 0) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
const remaining = new Set(prev);
|
|
||||||
linkedIds.forEach((id) => remaining.delete(id));
|
|
||||||
return failedIds.length > 0 ? remaining : new Set();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (failedIds.length > 0) {
|
|
||||||
setError(
|
|
||||||
failedIds.length === idsToLink.length
|
|
||||||
? "Failed to link stations"
|
|
||||||
: "Some stations failed to link"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLinkingBulk(false);
|
setIsLinkingBulk(false);
|
||||||
@@ -303,39 +287,26 @@ const LinkedStationsContentsInner = <
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
|
|
||||||
const detachedIds: number[] = [];
|
try {
|
||||||
const failedIds: number[] = [];
|
await authInstance.delete(
|
||||||
|
`/${parentResource}/${parentId}/${childResource}`,
|
||||||
|
{
|
||||||
|
data: buildPayload(idsToDetach),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
for (const itemId of idsToDetach) {
|
|
||||||
try {
|
|
||||||
await authInstance.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
|
||||||
data: { [`${childResource}_id`]: itemId },
|
|
||||||
});
|
|
||||||
detachedIds.push(itemId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error deleting station:", error);
|
|
||||||
failedIds.push(itemId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (detachedIds.length > 0) {
|
|
||||||
setLinkedItems((prev) =>
|
setLinkedItems((prev) =>
|
||||||
prev.filter((item) => !detachedIds.includes(item.id))
|
prev.filter((item) => !idsToDetach.includes(item.id))
|
||||||
);
|
);
|
||||||
setSelectedToDetach((prev) => {
|
setSelectedToDetach((prev) => {
|
||||||
const remaining = new Set(prev);
|
const remaining = new Set(prev);
|
||||||
detachedIds.forEach((id) => remaining.delete(id));
|
idsToDetach.forEach((id) => remaining.delete(id));
|
||||||
return failedIds.length > 0 ? remaining : new Set();
|
return remaining;
|
||||||
});
|
});
|
||||||
onUpdate?.();
|
onUpdate?.();
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error("Error deleting stations:", error);
|
||||||
if (failedIds.length > 0) {
|
setError("Failed to delete stations");
|
||||||
setError(
|
|
||||||
failedIds.length === idsToDetach.length
|
|
||||||
? "Failed to delete stations"
|
|
||||||
: "Some stations failed to delete"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setDetachingIds((prev) => {
|
setDetachingIds((prev) => {
|
||||||
@@ -499,8 +470,9 @@ const LinkedStationsContentsInner = <
|
|||||||
<Autocomplete
|
<Autocomplete
|
||||||
fullWidth
|
fullWidth
|
||||||
value={
|
value={
|
||||||
availableItems?.find((item) => item.id === selectedItemId) ||
|
availableItems?.find(
|
||||||
null
|
(item) => item.id === selectedItemId
|
||||||
|
) || null
|
||||||
}
|
}
|
||||||
onChange={(_, newValue) =>
|
onChange={(_, newValue) =>
|
||||||
setSelectedItemId(newValue?.id || null)
|
setSelectedItemId(newValue?.id || null)
|
||||||
@@ -508,7 +480,11 @@ const LinkedStationsContentsInner = <
|
|||||||
options={availableItems}
|
options={availableItems}
|
||||||
getOptionLabel={(item) => String(item.name)}
|
getOptionLabel={(item) => String(item.name)}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField {...params} label="Выберите остановку" fullWidth />
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Выберите остановку"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
isOptionEqualToValue={(option, value) =>
|
isOptionEqualToValue={(option, value) =>
|
||||||
option.id === value?.id
|
option.id === value?.id
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ const LinkedSightsContentsInner = <
|
|||||||
const parentResource = "station";
|
const parentResource = "station";
|
||||||
const childResource = "sight";
|
const childResource = "sight";
|
||||||
|
|
||||||
|
const buildPayload = (ids: number[]) => ({
|
||||||
|
[`${childResource}_ids`]: ids,
|
||||||
|
});
|
||||||
|
|
||||||
const availableItems = allItems
|
const availableItems = allItems
|
||||||
.filter((item) => !linkedItems.some((linked) => linked.id === item.id))
|
.filter((item) => !linkedItems.some((linked) => linked.id === item.id))
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
@@ -160,9 +164,7 @@ const LinkedSightsContentsInner = <
|
|||||||
const linkItem = () => {
|
const linkItem = () => {
|
||||||
if (selectedItemId !== null) {
|
if (selectedItemId !== null) {
|
||||||
setError(null);
|
setError(null);
|
||||||
const requestData = {
|
const requestData = buildPayload([selectedItemId]);
|
||||||
sight_id: selectedItemId,
|
|
||||||
};
|
|
||||||
|
|
||||||
setIsLinkingSingle(true);
|
setIsLinkingSingle(true);
|
||||||
authInstance
|
authInstance
|
||||||
@@ -194,7 +196,7 @@ const LinkedSightsContentsInner = <
|
|||||||
});
|
});
|
||||||
authInstance
|
authInstance
|
||||||
.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
||||||
data: { [`${childResource}_id`]: itemId },
|
data: buildPayload([itemId]),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setLinkedItems(linkedItems.filter((item) => item.id !== itemId));
|
setLinkedItems(linkedItems.filter((item) => item.id !== itemId));
|
||||||
@@ -229,49 +231,28 @@ const LinkedSightsContentsInner = <
|
|||||||
|
|
||||||
setIsLinkingBulk(true);
|
setIsLinkingBulk(true);
|
||||||
const idsToLink = Array.from(selectedItems);
|
const idsToLink = Array.from(selectedItems);
|
||||||
const linkedIds: number[] = [];
|
|
||||||
const failedIds: number[] = [];
|
|
||||||
|
|
||||||
for (const id of idsToLink) {
|
try {
|
||||||
try {
|
await authInstance.post(
|
||||||
await authInstance.post(
|
`/${parentResource}/${parentId}/${childResource}`,
|
||||||
`/${parentResource}/${parentId}/${childResource}`,
|
buildPayload(idsToLink)
|
||||||
{
|
);
|
||||||
sight_id: id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
linkedIds.push(id);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error linking sight:", error);
|
|
||||||
failedIds.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (linkedIds.length > 0) {
|
const newItems = allItems.filter((item) => idsToLink.includes(item.id));
|
||||||
const newItems = allItems.filter((item) => linkedIds.includes(item.id));
|
|
||||||
setLinkedItems((prev) => {
|
setLinkedItems((prev) => {
|
||||||
const existingIds = new Set(prev.map((item) => item.id));
|
const existingIds = new Set(prev.map((item) => item.id));
|
||||||
const additions = newItems.filter((item) => !existingIds.has(item.id));
|
const additions = newItems.filter((item) => !existingIds.has(item.id));
|
||||||
return [...prev, ...additions];
|
return [...prev, ...additions];
|
||||||
});
|
});
|
||||||
|
setSelectedItems((prev) => {
|
||||||
|
const remaining = new Set(prev);
|
||||||
|
idsToLink.forEach((id) => remaining.delete(id));
|
||||||
|
return remaining;
|
||||||
|
});
|
||||||
onUpdate?.();
|
onUpdate?.();
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error("Error linking sights:", error);
|
||||||
setSelectedItems((prev) => {
|
setError("Failed to link sights");
|
||||||
if (linkedIds.length === 0) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
const remaining = new Set(prev);
|
|
||||||
linkedIds.forEach((id) => remaining.delete(id));
|
|
||||||
return failedIds.length > 0 ? remaining : new Set();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (failedIds.length > 0) {
|
|
||||||
setError(
|
|
||||||
failedIds.length === idsToLink.length
|
|
||||||
? "Failed to link sights"
|
|
||||||
: "Some sights failed to link"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLinkingBulk(false);
|
setIsLinkingBulk(false);
|
||||||
@@ -307,42 +288,26 @@ const LinkedSightsContentsInner = <
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
|
|
||||||
const detachedIds: number[] = [];
|
try {
|
||||||
const failedIds: number[] = [];
|
await authInstance.delete(
|
||||||
|
`/${parentResource}/${parentId}/${childResource}`,
|
||||||
|
{
|
||||||
|
data: buildPayload(idsToDetach),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
for (const itemId of idsToDetach) {
|
|
||||||
try {
|
|
||||||
await authInstance.delete(
|
|
||||||
`/${parentResource}/${parentId}/${childResource}`,
|
|
||||||
{
|
|
||||||
data: { [`${childResource}_id`]: itemId },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
detachedIds.push(itemId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error deleting sight:", error);
|
|
||||||
failedIds.push(itemId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (detachedIds.length > 0) {
|
|
||||||
setLinkedItems((prev) =>
|
setLinkedItems((prev) =>
|
||||||
prev.filter((item) => !detachedIds.includes(item.id))
|
prev.filter((item) => !idsToDetach.includes(item.id))
|
||||||
);
|
);
|
||||||
setSelectedToDetach((prev) => {
|
setSelectedToDetach((prev) => {
|
||||||
const remaining = new Set(prev);
|
const remaining = new Set(prev);
|
||||||
detachedIds.forEach((id) => remaining.delete(id));
|
idsToDetach.forEach((id) => remaining.delete(id));
|
||||||
return failedIds.length > 0 ? remaining : new Set();
|
return remaining;
|
||||||
});
|
});
|
||||||
onUpdate?.();
|
onUpdate?.();
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error("Error deleting sights:", error);
|
||||||
if (failedIds.length > 0) {
|
setError("Failed to delete sights");
|
||||||
setError(
|
|
||||||
failedIds.length === idsToDetach.length
|
|
||||||
? "Failed to delete sights"
|
|
||||||
: "Some sights failed to delete"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setDetachingIds((prev) => {
|
setDetachingIds((prev) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user