Compare commits
2 Commits
64b8071578
...
10d442698b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d442698b | ||
|
|
a862eba0fa |
@ -6,7 +6,7 @@ import {
|
||||
saveLocationMapDraft,
|
||||
} from "../api/locationMaps";
|
||||
import getApiErrorMessage from "../lib/getApiErrorMessage";
|
||||
import { prepareObjectsForSave } from "../lib/locationMapUtils";
|
||||
import { getObjectKey, prepareObjectsForSave } from "../lib/locationMapUtils";
|
||||
|
||||
export default function useLocationMapDraftActions({
|
||||
activeHouseholdId,
|
||||
@ -16,6 +16,7 @@ export default function useLocationMapDraftActions({
|
||||
locationId,
|
||||
mapDraft,
|
||||
objects,
|
||||
selectedObjectKey,
|
||||
setEditorTool,
|
||||
setMapState,
|
||||
setMode,
|
||||
@ -23,11 +24,15 @@ export default function useLocationMapDraftActions({
|
||||
syncMapDraftFromState,
|
||||
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);
|
||||
setMode(nextMode);
|
||||
setPreviewDraft(nextPreviewDraft);
|
||||
syncMapDraftFromState(response.data, nextMode, nextPreviewDraft);
|
||||
syncMapDraftFromState(response.data, nextMode, nextPreviewDraft, options);
|
||||
}, [setMapState, setMode, setPreviewDraft, syncMapDraftFromState]);
|
||||
|
||||
const handleCreateBlank = useCallback(async () => {
|
||||
@ -68,14 +73,17 @@ export default function useLocationMapDraftActions({
|
||||
map: mapDraft,
|
||||
objects: prepareObjectsForSave(objects),
|
||||
});
|
||||
applyDraftResponse(response);
|
||||
applyDraftResponse(response, "edit", true, { preserveSelectedIndex: selectedObjectIndex });
|
||||
toast.success("Saved draft", "Map draft saved");
|
||||
} catch (error) {
|
||||
toast.error("Save draft failed", getApiErrorMessage(error, "Failed to save map draft"));
|
||||
} finally {
|
||||
endSaving();
|
||||
}
|
||||
}, [activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId, mapDraft, objects, toast]);
|
||||
}, [
|
||||
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId,
|
||||
mapDraft, objects, selectedObjectIndex, toast,
|
||||
]);
|
||||
|
||||
const handlePublish = useCallback(async () => {
|
||||
if (!activeHouseholdId || !locationId) return;
|
||||
@ -88,7 +96,7 @@ export default function useLocationMapDraftActions({
|
||||
objects: prepareObjectsForSave(objects),
|
||||
});
|
||||
savedPendingDraft = true;
|
||||
applyDraftResponse(saveResponse);
|
||||
applyDraftResponse(saveResponse, "edit", true, { preserveSelectedIndex: selectedObjectIndex });
|
||||
}
|
||||
|
||||
const response = await publishLocationMapDraft(activeHouseholdId, locationId);
|
||||
@ -108,7 +116,7 @@ export default function useLocationMapDraftActions({
|
||||
}
|
||||
}, [
|
||||
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, hasUnsavedChanges,
|
||||
locationId, mapDraft, objects, setEditorTool, toast,
|
||||
locationId, mapDraft, objects, selectedObjectIndex, setEditorTool, toast,
|
||||
]);
|
||||
|
||||
return { handleCreateBlank, handleCreateFromZones, handlePublish, handleSaveDraft };
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { getLocationMap } from "../api/locationMaps";
|
||||
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 = {
|
||||
name: "Store Map",
|
||||
@ -46,18 +53,25 @@ export default function useLocationMapDraftState({
|
||||
setMapDraft(DEFAULT_DRAFT_MAP);
|
||||
}, []);
|
||||
|
||||
const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft) => {
|
||||
const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft, options = {}) => {
|
||||
const nextMap = mapForMode(nextState, nextMode, nextPreviewDraft);
|
||||
const nextObjects = objectsForMode(nextState, nextMode, nextPreviewDraft);
|
||||
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({
|
||||
name: nextMap.name || "Store Map",
|
||||
width: nextMap.width || DEFAULT_MAP_SIZE.width,
|
||||
height: nextMap.height || DEFAULT_MAP_SIZE.height,
|
||||
});
|
||||
setObjects(nextObjects.map(normalizeMapObject));
|
||||
setSelectedObjectKey(null);
|
||||
setObjects(normalizedObjects);
|
||||
setSelectedObjectKey(preservedSelectedObject ? getObjectKey(preservedSelectedObject) : null);
|
||||
setHistory([]);
|
||||
setFuture([]);
|
||||
setHasUnsavedChanges(false);
|
||||
|
||||
@ -254,6 +254,7 @@ export default function LocationMapManager() {
|
||||
locationId,
|
||||
mapDraft,
|
||||
objects,
|
||||
selectedObjectKey,
|
||||
setEditorTool,
|
||||
setMapState,
|
||||
setMode,
|
||||
|
||||
@ -959,8 +959,8 @@
|
||||
}
|
||||
|
||||
.location-map-toolbar {
|
||||
--location-map-mode-slot: 148px;
|
||||
--location-map-history-slot: 80px;
|
||||
--location-map-mode-slot: 136px;
|
||||
--location-map-history-slot: 76px;
|
||||
display: grid;
|
||||
grid-template-columns: var(--location-map-mode-slot) minmax(0, 1fr);
|
||||
align-items: center;
|
||||
@ -1007,7 +1007,7 @@
|
||||
.location-map-history-buttons {
|
||||
grid-column: 2;
|
||||
gap: 0;
|
||||
width: 80px;
|
||||
width: var(--location-map-history-slot);
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
@ -1031,11 +1031,11 @@
|
||||
.location-map-zoom-controls button {
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
padding: 0.35rem;
|
||||
padding: 0.3rem;
|
||||
}
|
||||
|
||||
.location-map-zoom-controls span {
|
||||
min-width: 30px;
|
||||
min-width: 28px;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
@ -1050,15 +1050,15 @@
|
||||
|
||||
@media (max-width: 380px) {
|
||||
.location-map-toolbar {
|
||||
grid-template-columns: var(--location-map-mode-slot, 144px) var(--location-map-history-slot, 80px);
|
||||
grid-template-columns: var(--location-map-mode-slot, 136px) var(--location-map-history-slot, 76px);
|
||||
}
|
||||
|
||||
.location-map-toolbar.has-history-slot {
|
||||
grid-template-columns: var(--location-map-mode-slot, 144px) var(--location-map-history-slot, 80px);
|
||||
grid-template-columns: var(--location-map-mode-slot, 136px) var(--location-map-history-slot, 76px);
|
||||
}
|
||||
|
||||
.location-map-history-buttons {
|
||||
width: 80px;
|
||||
width: var(--location-map-history-slot, 76px);
|
||||
}
|
||||
|
||||
.location-map-history-buttons button,
|
||||
|
||||
@ -793,8 +793,8 @@ test("mobile keeps draft preview status compact in the topbar", async ({ page })
|
||||
expect(statusBox.y).toBeLessThan(titleBox.y + titleBox.height);
|
||||
expect(statusBox.x).toBeGreaterThan(titleBox.x);
|
||||
expect(toolbarBox.height).toBeLessThanOrEqual(64);
|
||||
expect(modeBox.width).toBeGreaterThanOrEqual(146);
|
||||
expect(modeBox.width).toBeLessThanOrEqual(150);
|
||||
expect(modeBox.width).toBeGreaterThanOrEqual(134);
|
||||
expect(modeBox.width).toBeLessThanOrEqual(138);
|
||||
expect(reservedHistoryBox.width).toBeGreaterThanOrEqual(70);
|
||||
expect(reservedHistoryBox.x).toBeGreaterThan(modeBox.x + modeBox.width - 1);
|
||||
await expect(historyActions.getByRole("button", { name: "Undo" })).toBeVisible();
|
||||
@ -824,8 +824,8 @@ test("mobile keeps draft preview status compact in the topbar", async ({ page })
|
||||
expect(Math.abs(editModeBox.x - modeBox.x)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(editModeGroupBox.width - modeGroupBox.width)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(editModeBox.width - modeBox.width)).toBeLessThanOrEqual(1);
|
||||
expect(editModeBox.width).toBeGreaterThanOrEqual(146);
|
||||
expect(editModeBox.width).toBeLessThanOrEqual(150);
|
||||
expect(editModeBox.width).toBeGreaterThanOrEqual(134);
|
||||
expect(editModeBox.width).toBeLessThanOrEqual(138);
|
||||
expect(Math.abs(editHistoryBox.width - reservedHistoryBox.width)).toBeLessThanOrEqual(1);
|
||||
expect(editHistoryBox.x).toBeGreaterThan(editModeBox.x + editModeBox.width);
|
||||
expect(editZoomBox.x).toBeGreaterThan(editHistoryBox.x + editHistoryBox.width);
|
||||
@ -884,8 +884,8 @@ test("narrow mobile toolbar keeps map controls tappable without overflow", async
|
||||
|
||||
expect(toolbarBox.x).toBeGreaterThanOrEqual(0);
|
||||
expect(toolbarBox.x + toolbarBox.width).toBeLessThanOrEqual(361);
|
||||
expect(modeBox.width).toBeGreaterThanOrEqual(146);
|
||||
expect(modeBox.width).toBeLessThanOrEqual(150);
|
||||
expect(modeBox.width).toBeGreaterThanOrEqual(134);
|
||||
expect(modeBox.width).toBeLessThanOrEqual(138);
|
||||
expect(modeBox.x + modeBox.width).toBeLessThanOrEqual(historyBox.x);
|
||||
expect(zoomBox.y).toBeGreaterThan(modeBox.y + modeBox.height - 1);
|
||||
expect(zoomBox.x).toBeGreaterThanOrEqual(toolbarBox.x);
|
||||
@ -1418,6 +1418,9 @@ test("admin save progress locks draft controls", async ({ page }) => {
|
||||
releaseSave();
|
||||
await expect(page.locator(".location-map-status")).toHaveText("Draft");
|
||||
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 }) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user