fix: keep selected map area after save
This commit is contained in:
parent
64b8071578
commit
a862eba0fa
@ -6,7 +6,7 @@ import {
|
|||||||
saveLocationMapDraft,
|
saveLocationMapDraft,
|
||||||
} from "../api/locationMaps";
|
} from "../api/locationMaps";
|
||||||
import getApiErrorMessage from "../lib/getApiErrorMessage";
|
import getApiErrorMessage from "../lib/getApiErrorMessage";
|
||||||
import { prepareObjectsForSave } from "../lib/locationMapUtils";
|
import { getObjectKey, prepareObjectsForSave } from "../lib/locationMapUtils";
|
||||||
|
|
||||||
export default function useLocationMapDraftActions({
|
export default function useLocationMapDraftActions({
|
||||||
activeHouseholdId,
|
activeHouseholdId,
|
||||||
@ -16,6 +16,7 @@ export default function useLocationMapDraftActions({
|
|||||||
locationId,
|
locationId,
|
||||||
mapDraft,
|
mapDraft,
|
||||||
objects,
|
objects,
|
||||||
|
selectedObjectKey,
|
||||||
setEditorTool,
|
setEditorTool,
|
||||||
setMapState,
|
setMapState,
|
||||||
setMode,
|
setMode,
|
||||||
@ -23,11 +24,15 @@ export default function useLocationMapDraftActions({
|
|||||||
syncMapDraftFromState,
|
syncMapDraftFromState,
|
||||||
toast,
|
toast,
|
||||||
}) {
|
}) {
|
||||||
const applyDraftResponse = useCallback((response, nextMode = "edit", nextPreviewDraft = true) => {
|
const selectedObjectIndex = selectedObjectKey
|
||||||
|
? objects.findIndex((object) => getObjectKey(object) === selectedObjectKey)
|
||||||
|
: -1;
|
||||||
|
|
||||||
|
const applyDraftResponse = useCallback((response, nextMode = "edit", nextPreviewDraft = true, options = {}) => {
|
||||||
setMapState(response.data);
|
setMapState(response.data);
|
||||||
setMode(nextMode);
|
setMode(nextMode);
|
||||||
setPreviewDraft(nextPreviewDraft);
|
setPreviewDraft(nextPreviewDraft);
|
||||||
syncMapDraftFromState(response.data, nextMode, nextPreviewDraft);
|
syncMapDraftFromState(response.data, nextMode, nextPreviewDraft, options);
|
||||||
}, [setMapState, setMode, setPreviewDraft, syncMapDraftFromState]);
|
}, [setMapState, setMode, setPreviewDraft, syncMapDraftFromState]);
|
||||||
|
|
||||||
const handleCreateBlank = useCallback(async () => {
|
const handleCreateBlank = useCallback(async () => {
|
||||||
@ -68,14 +73,17 @@ export default function useLocationMapDraftActions({
|
|||||||
map: mapDraft,
|
map: mapDraft,
|
||||||
objects: prepareObjectsForSave(objects),
|
objects: prepareObjectsForSave(objects),
|
||||||
});
|
});
|
||||||
applyDraftResponse(response);
|
applyDraftResponse(response, "edit", true, { preserveSelectedIndex: selectedObjectIndex });
|
||||||
toast.success("Saved draft", "Map draft saved");
|
toast.success("Saved draft", "Map draft saved");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Save draft failed", getApiErrorMessage(error, "Failed to save map draft"));
|
toast.error("Save draft failed", getApiErrorMessage(error, "Failed to save map draft"));
|
||||||
} finally {
|
} finally {
|
||||||
endSaving();
|
endSaving();
|
||||||
}
|
}
|
||||||
}, [activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId, mapDraft, objects, toast]);
|
}, [
|
||||||
|
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId,
|
||||||
|
mapDraft, objects, selectedObjectIndex, toast,
|
||||||
|
]);
|
||||||
|
|
||||||
const handlePublish = useCallback(async () => {
|
const handlePublish = useCallback(async () => {
|
||||||
if (!activeHouseholdId || !locationId) return;
|
if (!activeHouseholdId || !locationId) return;
|
||||||
@ -88,7 +96,7 @@ export default function useLocationMapDraftActions({
|
|||||||
objects: prepareObjectsForSave(objects),
|
objects: prepareObjectsForSave(objects),
|
||||||
});
|
});
|
||||||
savedPendingDraft = true;
|
savedPendingDraft = true;
|
||||||
applyDraftResponse(saveResponse);
|
applyDraftResponse(saveResponse, "edit", true, { preserveSelectedIndex: selectedObjectIndex });
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await publishLocationMapDraft(activeHouseholdId, locationId);
|
const response = await publishLocationMapDraft(activeHouseholdId, locationId);
|
||||||
@ -108,7 +116,7 @@ export default function useLocationMapDraftActions({
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, hasUnsavedChanges,
|
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, hasUnsavedChanges,
|
||||||
locationId, mapDraft, objects, setEditorTool, toast,
|
locationId, mapDraft, objects, selectedObjectIndex, setEditorTool, toast,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { handleCreateBlank, handleCreateFromZones, handlePublish, handleSaveDraft };
|
return { handleCreateBlank, handleCreateFromZones, handlePublish, handleSaveDraft };
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { getLocationMap } from "../api/locationMaps";
|
import { getLocationMap } from "../api/locationMaps";
|
||||||
import getApiErrorMessage from "../lib/getApiErrorMessage";
|
import getApiErrorMessage from "../lib/getApiErrorMessage";
|
||||||
import { DEFAULT_MAP_FILTERS, DEFAULT_MAP_SIZE, mapForMode, normalizeMapObject, objectsForMode } from "../lib/locationMapUtils";
|
import {
|
||||||
|
DEFAULT_MAP_FILTERS,
|
||||||
|
DEFAULT_MAP_SIZE,
|
||||||
|
getObjectKey,
|
||||||
|
mapForMode,
|
||||||
|
normalizeMapObject,
|
||||||
|
objectsForMode,
|
||||||
|
} from "../lib/locationMapUtils";
|
||||||
|
|
||||||
const DEFAULT_DRAFT_MAP = {
|
const DEFAULT_DRAFT_MAP = {
|
||||||
name: "Store Map",
|
name: "Store Map",
|
||||||
@ -46,18 +53,25 @@ export default function useLocationMapDraftState({
|
|||||||
setMapDraft(DEFAULT_DRAFT_MAP);
|
setMapDraft(DEFAULT_DRAFT_MAP);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft) => {
|
const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft, options = {}) => {
|
||||||
const nextMap = mapForMode(nextState, nextMode, nextPreviewDraft);
|
const nextMap = mapForMode(nextState, nextMode, nextPreviewDraft);
|
||||||
const nextObjects = objectsForMode(nextState, nextMode, nextPreviewDraft);
|
const nextObjects = objectsForMode(nextState, nextMode, nextPreviewDraft);
|
||||||
if (!nextMap) return;
|
if (!nextMap) return;
|
||||||
|
const normalizedObjects = nextObjects.map(normalizeMapObject);
|
||||||
|
const preserveSelectedIndex = Number.isInteger(options.preserveSelectedIndex)
|
||||||
|
? options.preserveSelectedIndex
|
||||||
|
: -1;
|
||||||
|
const preservedSelectedObject = preserveSelectedIndex >= 0
|
||||||
|
? normalizedObjects[preserveSelectedIndex]
|
||||||
|
: null;
|
||||||
|
|
||||||
setMapDraft({
|
setMapDraft({
|
||||||
name: nextMap.name || "Store Map",
|
name: nextMap.name || "Store Map",
|
||||||
width: nextMap.width || DEFAULT_MAP_SIZE.width,
|
width: nextMap.width || DEFAULT_MAP_SIZE.width,
|
||||||
height: nextMap.height || DEFAULT_MAP_SIZE.height,
|
height: nextMap.height || DEFAULT_MAP_SIZE.height,
|
||||||
});
|
});
|
||||||
setObjects(nextObjects.map(normalizeMapObject));
|
setObjects(normalizedObjects);
|
||||||
setSelectedObjectKey(null);
|
setSelectedObjectKey(preservedSelectedObject ? getObjectKey(preservedSelectedObject) : null);
|
||||||
setHistory([]);
|
setHistory([]);
|
||||||
setFuture([]);
|
setFuture([]);
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
|
|||||||
@ -254,6 +254,7 @@ export default function LocationMapManager() {
|
|||||||
locationId,
|
locationId,
|
||||||
mapDraft,
|
mapDraft,
|
||||||
objects,
|
objects,
|
||||||
|
selectedObjectKey,
|
||||||
setEditorTool,
|
setEditorTool,
|
||||||
setMapState,
|
setMapState,
|
||||||
setMode,
|
setMode,
|
||||||
|
|||||||
@ -1418,6 +1418,9 @@ test("admin save progress locks draft controls", async ({ page }) => {
|
|||||||
releaseSave();
|
releaseSave();
|
||||||
await expect(page.locator(".location-map-status")).toHaveText("Draft");
|
await expect(page.locator(".location-map-status")).toHaveText("Draft");
|
||||||
await expect(page.getByRole("button", { name: "Save Draft" })).toBeDisabled();
|
await expect(page.getByRole("button", { name: "Save Draft" })).toBeDisabled();
|
||||||
|
await expect(page.getByRole("textbox", { name: "Label" })).toBeEnabled();
|
||||||
|
await expect(page.getByRole("textbox", { name: "Label" })).toHaveValue("Saving Bakery");
|
||||||
|
await expect(page.getByRole("button", { name: "Map area Saving Bakery" })).toHaveAttribute("aria-pressed", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("admin save progress locks empty-canvas recovery actions", async ({ page }) => {
|
test("admin save progress locks empty-canvas recovery actions", async ({ page }) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user