feat: Корректировки 07.11.25

This commit is contained in:
2025-11-11 09:13:58 +03:00
parent b1ba3b4cd5
commit 0a6192c7da
11 changed files with 405 additions and 1958 deletions

View File

@@ -420,7 +420,15 @@ export const RouteCreatePage = observer(() => {
type="number" type="number"
value={scaleMin} value={scaleMin}
onChange={(e) => { onChange={(e) => {
const value = e.target.value; let value = e.target.value;
if (Number(value) > 297) {
value = "297";
}
if (Number(value) < 10) {
value = "10";
}
setScaleMin(value); setScaleMin(value);
if (value && scaleMax && Number(value) > Number(scaleMax)) { if (value && scaleMax && Number(value) > Number(scaleMax)) {
setScaleMax(value); setScaleMax(value);
@@ -447,6 +455,10 @@ export const RouteCreatePage = observer(() => {
value={scaleMax} value={scaleMax}
required required
onChange={(e) => { onChange={(e) => {
if (Number(e.target.value) > 300) {
e.target.value = "300";
}
const value = e.target.value; const value = e.target.value;
setScaleMax(value); setScaleMax(value);
}} }}

View File

@@ -393,20 +393,29 @@ export const RouteEditPage = observer(() => {
type="number" type="number"
value={editRouteData.scale_min ?? ""} value={editRouteData.scale_min ?? ""}
onChange={(e) => { onChange={(e) => {
const value = let value = e.target.value === "" ? null : e.target.value;
e.target.value === "" ? null : parseFloat(e.target.value);
if (value && Number(value) > 297) {
value = "297";
}
if (value && Number(value) < 10) {
value = "10";
}
routeStore.setEditRouteData({ routeStore.setEditRouteData({
scale_min: value, scale_min: value ? Number(value) : null,
}); });
// Если максимальный масштаб стал меньше минимального, обновляем его // Если максимальный масштаб стал меньше минимального, обновляем его
if ( if (
value !== null && value !== null &&
editRouteData.scale_max !== null && editRouteData.scale_max !== null &&
editRouteData.scale_max !== undefined && editRouteData.scale_max !== undefined &&
value > editRouteData.scale_max value &&
Number(value) > (editRouteData.scale_max ?? 0)
) { ) {
routeStore.setEditRouteData({ routeStore.setEditRouteData({
scale_max: value, scale_max: value ? Number(value) : null,
}); });
} }
}} }}
@@ -418,12 +427,17 @@ export const RouteEditPage = observer(() => {
label="Масштаб (макс)" label="Масштаб (макс)"
type="number" type="number"
value={editRouteData.scale_max ?? ""} value={editRouteData.scale_max ?? ""}
onChange={(e) => onChange={(e) => {
let value = e.target.value;
if (Number(value) > 300) {
value = "300";
}
routeStore.setEditRouteData({ routeStore.setEditRouteData({
scale_max: scale_max: value === "" ? null : parseFloat(value),
e.target.value === "" ? null : parseFloat(e.target.value), });
}) }}
}
error={ error={
editRouteData.scale_min !== null && editRouteData.scale_min !== null &&
editRouteData.scale_min !== undefined && editRouteData.scale_min !== undefined &&

View File

@@ -145,8 +145,8 @@ export function RightSidebar() {
onChange={(e) => { onChange={(e) => {
let newMinScale = Number(e.target.value); let newMinScale = Number(e.target.value);
if (newMinScale < 1) { if (newMinScale < 10) {
newMinScale = 1; newMinScale = 10;
} }
setMinScale(newMinScale); setMinScale(newMinScale);
@@ -189,8 +189,8 @@ export function RightSidebar() {
onChange={(e) => { onChange={(e) => {
let newMaxScale = Number(e.target.value); let newMaxScale = Number(e.target.value);
if (newMaxScale < 3) { if (newMaxScale < 13) {
newMaxScale = 3; newMaxScale = 13;
} }
if (newMaxScale > 300) { if (newMaxScale > 300) {

View File

@@ -1,6 +1,6 @@
import { useRef, useEffect, useState } from "react"; import { useRef, useEffect, useState } from "react";
import { Widgets } from "./Widgets"; import { Widgets } from "./Widgets";
import { Application, extend } from "@pixi/react"; import { extend } from "@pixi/react";
import { import {
Container, Container,
Graphics, Graphics,
@@ -12,22 +12,15 @@ import {
import { Box, Stack } from "@mui/material"; import { Box, Stack } from "@mui/material";
import { MapDataProvider, useMapData } from "./MapDataContext"; import { MapDataProvider, useMapData } from "./MapDataContext";
import { TransformProvider, useTransform } from "./TransformContext"; import { TransformProvider, useTransform } from "./TransformContext";
import { InfiniteCanvas } from "./InfiniteCanvas";
import { TravelPath } from "./TravelPath";
import { LeftSidebar } from "./LeftSidebar"; import { LeftSidebar } from "./LeftSidebar";
import { RightSidebar } from "./RightSidebar"; import { RightSidebar } from "./RightSidebar";
import { coordinatesToLocal } from "./utils"; import { coordinatesToLocal } from "./utils";
import { LanguageSwitcher } from "@widgets";
import { languageStore } from "@shared";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Sight } from "./Sight";
import { SightData } from "./types";
import { Station } from "./Station";
import { UP_SCALE } from "./Constants"; import { UP_SCALE } from "./Constants";
import { WebGLRouteMapPrototype } from "./webgl-prototype/WebGLRouteMapPrototype"; import { WebGLRouteMapPrototype } from "./webgl-prototype/WebGLRouteMapPrototype";
import CircularProgress from "@mui/material/CircularProgress"; import { CircularProgress } from "@mui/material";
extend({ extend({
Container, Container,
@@ -43,7 +36,7 @@ const Loading = () => {
if (isRouteLoading || isStationLoading || isSightLoading) { if (isRouteLoading || isStationLoading || isSightLoading) {
return ( return (
<div className="fixed flex z-1 items-center justify-center h-screen w-screen bg-[#111]"> <div className="fixed flex z-1000000000 items-center justify-center h-screen w-screen bg-[#111]">
<CircularProgress /> <CircularProgress />
</div> </div>
); );
@@ -91,15 +84,8 @@ export const RoutePreview = () => {
}; };
export const RouteMap = observer(() => { export const RouteMap = observer(() => {
const { language } = languageStore;
const { setPosition, setTransform, screenCenter } = useTransform(); const { setPosition, setTransform, screenCenter } = useTransform();
const { const { routeData, stationData, sightData, originalRouteData } = useMapData();
routeData,
stationData,
sightData,
originalRouteData,
originalSightData,
} = useMapData();
const [points, setPoints] = useState<{ x: number; y: number }[]>([]); const [points, setPoints] = useState<{ x: number; y: number }[]>([]);
const [isSetup, setIsSetup] = useState(false); const [isSetup, setIsSetup] = useState(false);

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState, type ReactElement } from "react"; import { useEffect, useRef, useState, type ReactElement } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { languageStore } from "@shared"; import { languageStore } from "@shared";
@@ -114,7 +114,7 @@ const ArrowIcon = ({ rotation }: { rotation: number }) => (
const LanguageSelector = observer( const LanguageSelector = observer(
({ onBack, isSidebarOpen = true }: LanguageSelectorProps) => { ({ onBack, isSidebarOpen = true }: LanguageSelectorProps) => {
const { language, setLanguage } = languageStore; const { setLanguage } = languageStore;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
@@ -145,20 +145,6 @@ const LanguageSelector = observer(
const toggle = () => setIsOpen((prev) => !prev); const toggle = () => setIsOpen((prev) => !prev);
const containerWidth = useMemo(() => {
const BUTTON_SIZE = 56;
const GAP = 8;
const backWidth = onBack ? BUTTON_SIZE + GAP : 0;
const toggleWidth = BUTTON_SIZE;
const collapsedWidth = backWidth + toggleWidth + BUTTON_SIZE;
const expandedWidth =
backWidth +
toggleWidth +
LANGUAGES.length * BUTTON_SIZE +
(LANGUAGES.length - 1) * GAP;
return isOpen ? expandedWidth : collapsedWidth;
}, [isOpen, onBack]);
return ( return (
<div <div
ref={containerRef} ref={containerRef}

File diff suppressed because it is too large Load Diff

View File

@@ -370,6 +370,7 @@ export const WebGLRouteMapPrototype = observer(() => {
setSelectedSight, setSelectedSight,
setStationOffset, setStationOffset,
setSightCoordinates, setSightCoordinates,
setMapCenter,
} = useMapData(); } = useMapData();
const { language } = languageStore; const { language } = languageStore;
const { setScale: setSharedScale, scale: sharedScale } = useTransform(); const { setScale: setSharedScale, scale: sharedScale } = useTransform();
@@ -446,6 +447,12 @@ export const WebGLRouteMapPrototype = observer(() => {
latitude: number | null; latitude: number | null;
longitude: number | null; longitude: number | null;
}>({ latitude: null, longitude: null }); }>({ latitude: null, longitude: null });
const pendingCenterRef = useRef<{
latitude: number;
longitude: number;
} | null>(null);
const isUserInteractingRef = useRef(false);
const commitCenterTimeoutRef = useRef<number | null>(null);
const getRelativePointerPosition = useCallback( const getRelativePointerPosition = useCallback(
(clientX: number, clientY: number) => { (clientX: number, clientY: number) => {
@@ -532,6 +539,58 @@ export const WebGLRouteMapPrototype = observer(() => {
[rotationAngle] [rotationAngle]
); );
const cancelScheduledCenterCommit = useCallback(() => {
if (commitCenterTimeoutRef.current !== null) {
window.clearTimeout(commitCenterTimeoutRef.current);
commitCenterTimeoutRef.current = null;
}
}, []);
const commitCenter = useCallback(() => {
const center = lastCenterRef.current;
if (
!center ||
center.latitude == null ||
center.longitude == null ||
!Number.isFinite(center.latitude) ||
!Number.isFinite(center.longitude)
) {
return;
}
const epsilon = 1e-7;
const prev = lastAppliedCenterRef.current;
if (
prev.latitude != null &&
prev.longitude != null &&
Math.abs(prev.latitude - center.latitude) < epsilon &&
Math.abs(prev.longitude - center.longitude) < epsilon
) {
return;
}
lastAppliedCenterRef.current = {
latitude: center.latitude,
longitude: center.longitude,
};
setMapCenter(center.latitude, center.longitude);
}, [setMapCenter]);
const scheduleCenterCommit = useCallback(() => {
cancelScheduledCenterCommit();
commitCenterTimeoutRef.current = window.setTimeout(() => {
commitCenterTimeoutRef.current = null;
isUserInteractingRef.current = false;
commitCenter();
}, 120);
}, [cancelScheduledCenterCommit, commitCenter]);
useEffect(() => {
return () => {
cancelScheduledCenterCommit();
};
}, [cancelScheduledCenterCommit]);
const updateTransform = useCallback( const updateTransform = useCallback(
(next: Transform) => { (next: Transform) => {
const adjusted = clampTransformScale(next); const adjusted = clampTransformScale(next);
@@ -1026,6 +1085,39 @@ export const WebGLRouteMapPrototype = observer(() => {
max: baseScale * 16, max: baseScale * 16,
}; };
} }
const centerLat =
routeData?.center_latitude ?? originalRouteData?.center_latitude;
const centerLon =
routeData?.center_longitude ?? originalRouteData?.center_longitude;
if (
Number.isFinite(centerLat) &&
Number.isFinite(centerLon) &&
canvas.width > 0 &&
canvas.height > 0
) {
const local = coordinatesToLocal(
centerLat as number,
centerLon as number
);
const baseX = local.x * UP_SCALE;
const baseY = local.y * UP_SCALE;
const cos = Math.cos(rotationAngle);
const sin = Math.sin(rotationAngle);
const rotatedX = baseX * cos - baseY * sin;
const rotatedY = baseX * sin + baseY * cos;
const scale = transform.scale || 1;
transform = {
scale,
translation: {
x: canvas.width / 2 - rotatedX * scale,
y: canvas.height / 2 - rotatedY * scale,
},
};
lastAppliedCenterRef.current = {
latitude: centerLat as number,
longitude: centerLon as number,
};
}
transform = clampTransformScale(transform); transform = clampTransformScale(transform);
updateTransform(transform); updateTransform(transform);
} else { } else {
@@ -1260,8 +1352,75 @@ export const WebGLRouteMapPrototype = observer(() => {
latitude: roundedLat, latitude: roundedLat,
longitude: roundedLon, longitude: roundedLon,
}; };
if (isUserInteractingRef.current) {
pendingCenterRef.current = {
latitude: roundedLat,
longitude: roundedLon,
};
return;
}
const transform =
transformRef.current ?? lastTransformRef.current ?? transformState;
const canvas = canvasRef.current;
if (!canvas || !transform) {
pendingCenterRef.current = {
latitude: roundedLat,
longitude: roundedLon,
};
return;
}
const width = canvas.width || canvas.clientWidth;
const height = canvas.height || canvas.clientHeight;
if (!width || !height) {
pendingCenterRef.current = {
latitude: roundedLat,
longitude: roundedLon,
};
return;
}
const local = coordinatesToLocal(roundedLat, roundedLon);
const baseX = local.x * UP_SCALE;
const baseY = local.y * UP_SCALE;
const cos = Math.cos(rotationAngle);
const sin = Math.sin(rotationAngle);
const rotatedX = baseX * cos - baseY * sin;
const rotatedY = baseX * sin + baseY * cos;
const scale = transform.scale || 1;
const targetTranslation = {
x: width / 2 - rotatedX * scale,
y: height / 2 - rotatedY * scale,
};
const currentTranslation = transform.translation;
const distance = Math.hypot(
targetTranslation.x - currentTranslation.x,
targetTranslation.y - currentTranslation.y
);
if (distance < 0.5) {
pendingCenterRef.current = null;
return;
}
const nextTransform: Transform = {
scale,
translation: targetTranslation,
};
transformRef.current = nextTransform;
lastTransformRef.current = nextTransform;
setTransformState(nextTransform);
drawSceneRef.current();
pendingCenterRef.current = null;
}, },
[] [rotationAngle, setTransformState, transformState]
); );
useEffect(() => { useEffect(() => {
@@ -1290,6 +1449,18 @@ export const WebGLRouteMapPrototype = observer(() => {
applyCenterFromCoordinates, applyCenterFromCoordinates,
]); ]);
useEffect(() => {
if (!pendingCenterRef.current || !transformRef.current) {
return;
}
const { latitude, longitude } = pendingCenterRef.current;
pendingCenterRef.current = null;
window.requestAnimationFrame(() => {
applyCenterFromCoordinates(latitude, longitude);
});
}, [transformState, applyCenterFromCoordinates]);
useEffect(() => { useEffect(() => {
const canvas = canvasRef.current; const canvas = canvasRef.current;
if (!canvas) { if (!canvas) {
@@ -1333,6 +1504,8 @@ export const WebGLRouteMapPrototype = observer(() => {
} }
event.preventDefault(); event.preventDefault();
isUserInteractingRef.current = true;
cancelScheduledCenterCommit();
const position = getEventPosition(event); const position = getEventPosition(event);
activePointersRef.current.set(event.pointerId, position); activePointersRef.current.set(event.pointerId, position);
canvas.setPointerCapture(event.pointerId); canvas.setPointerCapture(event.pointerId);
@@ -1420,6 +1593,7 @@ export const WebGLRouteMapPrototype = observer(() => {
dragStateRef.current = null; dragStateRef.current = null;
pinchStateRef.current = null; pinchStateRef.current = null;
canvas.style.cursor = "grab"; canvas.style.cursor = "grab";
scheduleCenterCommit();
} else if (activePointersRef.current.size === 1) { } else if (activePointersRef.current.size === 1) {
const remaining = Array.from(activePointersRef.current.values())[0]; const remaining = Array.from(activePointersRef.current.values())[0];
dragStateRef.current = { lastPos: remaining }; dragStateRef.current = { lastPos: remaining };
@@ -1432,6 +1606,8 @@ export const WebGLRouteMapPrototype = observer(() => {
event.preventDefault(); event.preventDefault();
const transform = transformRef.current; const transform = transformRef.current;
if (!transform) return; if (!transform) return;
isUserInteractingRef.current = true;
cancelScheduledCenterCommit();
const position = getEventPosition(event); const position = getEventPosition(event);
const delta = event.deltaY > 0 ? 0.9 : 1.1; const delta = event.deltaY > 0 ? 0.9 : 1.1;
@@ -1459,6 +1635,7 @@ export const WebGLRouteMapPrototype = observer(() => {
}, },
}); });
drawSceneRef.current(); drawSceneRef.current();
scheduleCenterCommit();
}; };
canvas.addEventListener("pointerdown", handlePointerDown); canvas.addEventListener("pointerdown", handlePointerDown);
@@ -1476,7 +1653,7 @@ export const WebGLRouteMapPrototype = observer(() => {
canvas.removeEventListener("pointerleave", handlePointerUp); canvas.removeEventListener("pointerleave", handlePointerUp);
canvas.removeEventListener("wheel", handleWheel as EventListener); canvas.removeEventListener("wheel", handleWheel as EventListener);
}; };
}, []); }, [cancelScheduledCenterCommit, scheduleCenterCommit, updateTransform]);
return ( return (
<div <div

View File

@@ -222,33 +222,55 @@ const LinkedStationsContentsInner = <
setSelectedItems(updated); setSelectedItems(updated);
}; };
const handleBulkLink = () => { const handleBulkLink = async () => {
if (selectedItems.size === 0) return; if (selectedItems.size === 0) return;
setError(null); setError(null);
setIsLinkingBulk(true); setIsLinkingBulk(true);
Promise.all( const idsToLink = Array.from(selectedItems);
Array.from(selectedItems).map((id) => const linkedIds: number[] = [];
authInstance.post(`/${parentResource}/${parentId}/${childResource}`, { const failedIds: number[] = [];
for (const id of idsToLink) {
try {
await authInstance.post(`/${parentResource}/${parentId}/${childResource}`, {
station_id: id, station_id: id,
}) });
) linkedIds.push(id);
) } catch (error) {
.then(() => { console.error("Error linking station:", error);
const newItems = allItems.filter((item) => failedIds.push(id);
selectedItems.has(item.id) }
); }
setLinkedItems([...linkedItems, ...newItems]);
setSelectedItems(new Set()); if (linkedIds.length > 0) {
onUpdate?.(); const newItems = allItems.filter((item) => linkedIds.includes(item.id));
}) setLinkedItems((prev) => {
.catch((error) => { const existingIds = new Set(prev.map((item) => item.id));
console.error("Error bulk linking stations:", error); const additions = newItems.filter((item) => !existingIds.has(item.id));
setError("Failed to link stations"); return [...prev, ...additions];
})
.finally(() => {
setIsLinkingBulk(false);
}); });
onUpdate?.();
}
setSelectedItems((prev) => {
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);
}; };
const toggleDetachSelection = (itemId: number) => { const toggleDetachSelection = (itemId: number) => {
@@ -269,7 +291,7 @@ const LinkedStationsContentsInner = <
setSelectedToDetach(new Set(linkedItems.map((item) => item.id))); setSelectedToDetach(new Set(linkedItems.map((item) => item.id)));
}; };
const handleBulkDetach = () => { const handleBulkDetach = async () => {
const idsToDetach = Array.from(selectedToDetach); const idsToDetach = Array.from(selectedToDetach);
if (idsToDetach.length === 0) return; if (idsToDetach.length === 0) return;
setError(null); setError(null);
@@ -281,32 +303,47 @@ const LinkedStationsContentsInner = <
return next; return next;
}); });
Promise.all( const detachedIds: number[] = [];
idsToDetach.map((itemId) => const failedIds: number[] = [];
authInstance.delete(`/${parentResource}/${parentId}/${childResource}`, {
for (const itemId of idsToDetach) {
try {
await authInstance.delete(`/${parentResource}/${parentId}/${childResource}`, {
data: { [`${childResource}_id`]: itemId }, data: { [`${childResource}_id`]: itemId },
})
)
)
.then(() => {
setLinkedItems(
linkedItems.filter((item) => !idsToDetach.includes(item.id))
);
setSelectedToDetach(new Set());
onUpdate?.();
})
.catch((error) => {
console.error("Error bulk deleting stations:", error);
setError("Failed to delete stations");
})
.finally(() => {
setDetachingIds((prev) => {
const next = new Set(prev);
idsToDetach.forEach((id) => next.delete(id));
return next;
}); });
setIsBulkDetaching(false); detachedIds.push(itemId);
} catch (error) {
console.error("Error deleting station:", error);
failedIds.push(itemId);
}
}
if (detachedIds.length > 0) {
setLinkedItems((prev) =>
prev.filter((item) => !detachedIds.includes(item.id))
);
setSelectedToDetach((prev) => {
const remaining = new Set(prev);
detachedIds.forEach((id) => remaining.delete(id));
return failedIds.length > 0 ? remaining : new Set();
}); });
onUpdate?.();
}
if (failedIds.length > 0) {
setError(
failedIds.length === idsToDetach.length
? "Failed to delete stations"
: "Some stations failed to delete"
);
}
setDetachingIds((prev) => {
const next = new Set(prev);
idsToDetach.forEach((id) => next.delete(id));
return next;
});
setIsBulkDetaching(false);
}; };
const allSelectedForDetach = const allSelectedForDetach =

View File

@@ -223,33 +223,58 @@ const LinkedSightsContentsInner = <
setSelectedItems(updated); setSelectedItems(updated);
}; };
const handleBulkLink = () => { const handleBulkLink = async () => {
if (selectedItems.size === 0) return; if (selectedItems.size === 0) return;
setError(null); setError(null);
setIsLinkingBulk(true); setIsLinkingBulk(true);
Promise.all( const idsToLink = Array.from(selectedItems);
Array.from(selectedItems).map((id) => const linkedIds: number[] = [];
authInstance.post(`/${parentResource}/${parentId}/${childResource}`, { const failedIds: number[] = [];
sight_id: id,
}) for (const id of idsToLink) {
) try {
) await authInstance.post(
.then(() => { `/${parentResource}/${parentId}/${childResource}`,
const newItems = allItems.filter((item) => {
selectedItems.has(item.id) sight_id: id,
}
); );
setLinkedItems([...linkedItems, ...newItems]); linkedIds.push(id);
setSelectedItems(new Set()); } catch (error) {
onUpdate?.(); console.error("Error linking sight:", error);
}) failedIds.push(id);
.catch((error) => { }
console.error("Error bulk linking sights:", error); }
setError("Failed to link sights");
}) if (linkedIds.length > 0) {
.finally(() => { const newItems = allItems.filter((item) => linkedIds.includes(item.id));
setIsLinkingBulk(false); setLinkedItems((prev) => {
const existingIds = new Set(prev.map((item) => item.id));
const additions = newItems.filter((item) => !existingIds.has(item.id));
return [...prev, ...additions];
}); });
onUpdate?.();
}
setSelectedItems((prev) => {
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);
}; };
const toggleDetachSelection = (itemId: number) => { const toggleDetachSelection = (itemId: number) => {
@@ -270,7 +295,7 @@ const LinkedSightsContentsInner = <
setSelectedToDetach(new Set(linkedItems.map((item) => item.id))); setSelectedToDetach(new Set(linkedItems.map((item) => item.id)));
}; };
const handleBulkDetach = () => { const handleBulkDetach = async () => {
const idsToDetach = Array.from(selectedToDetach); const idsToDetach = Array.from(selectedToDetach);
if (idsToDetach.length === 0) return; if (idsToDetach.length === 0) return;
setError(null); setError(null);
@@ -282,32 +307,50 @@ const LinkedSightsContentsInner = <
return next; return next;
}); });
Promise.all( const detachedIds: number[] = [];
idsToDetach.map((itemId) => const failedIds: number[] = [];
authInstance.delete(`/${parentResource}/${parentId}/${childResource}`, {
data: { [`${childResource}_id`]: itemId }, for (const itemId of idsToDetach) {
}) try {
) await authInstance.delete(
) `/${parentResource}/${parentId}/${childResource}`,
.then(() => { {
setLinkedItems( data: { [`${childResource}_id`]: itemId },
linkedItems.filter((item) => !idsToDetach.includes(item.id)) }
); );
setSelectedToDetach(new Set()); detachedIds.push(itemId);
onUpdate?.(); } catch (error) {
}) console.error("Error deleting sight:", error);
.catch((error) => { failedIds.push(itemId);
console.error("Error bulk deleting sights:", error); }
setError("Failed to delete sights"); }
})
.finally(() => { if (detachedIds.length > 0) {
setDetachingIds((prev) => { setLinkedItems((prev) =>
const next = new Set(prev); prev.filter((item) => !detachedIds.includes(item.id))
idsToDetach.forEach((id) => next.delete(id)); );
return next; setSelectedToDetach((prev) => {
}); const remaining = new Set(prev);
setIsBulkDetaching(false); detachedIds.forEach((id) => remaining.delete(id));
return failedIds.length > 0 ? remaining : new Set();
}); });
onUpdate?.();
}
if (failedIds.length > 0) {
setError(
failedIds.length === idsToDetach.length
? "Failed to delete sights"
: "Some sights failed to delete"
);
}
setDetachingIds((prev) => {
const next = new Set(prev);
idsToDetach.forEach((id) => next.delete(id));
return next;
});
setIsBulkDetaching(false);
}; };
const allSelectedForDetach = const allSelectedForDetach =
@@ -465,8 +508,9 @@ const LinkedSightsContentsInner = <
<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)

View File

@@ -132,12 +132,16 @@ class SightsStore {
common: boolean common: boolean
) => { ) => {
if (common) { if (common) {
// @ts-ignore
this.sight!.common = { this.sight!.common = {
// @ts-ignore
...this.sight!.common, ...this.sight!.common,
...content, ...content,
}; };
} else { } else {
// @ts-ignore
this.sight![language] = { this.sight![language] = {
// @ts-ignore
...this.sight![language], ...this.sight![language],
...content, ...content,
}; };

File diff suppressed because one or more lines are too long