Compare commits

..

No commits in common. "10d442698b947ad4e79fa6e7b52d4c1cb5bc1a1d" and "64b80715784b3ebddf6ab8f786f06ad8b092660f" have entirely different histories.

5 changed files with 25 additions and 51 deletions

View File

@ -6,7 +6,7 @@ import {
saveLocationMapDraft, saveLocationMapDraft,
} from "../api/locationMaps"; } from "../api/locationMaps";
import getApiErrorMessage from "../lib/getApiErrorMessage"; import getApiErrorMessage from "../lib/getApiErrorMessage";
import { getObjectKey, prepareObjectsForSave } from "../lib/locationMapUtils"; import { prepareObjectsForSave } from "../lib/locationMapUtils";
export default function useLocationMapDraftActions({ export default function useLocationMapDraftActions({
activeHouseholdId, activeHouseholdId,
@ -16,7 +16,6 @@ export default function useLocationMapDraftActions({
locationId, locationId,
mapDraft, mapDraft,
objects, objects,
selectedObjectKey,
setEditorTool, setEditorTool,
setMapState, setMapState,
setMode, setMode,
@ -24,15 +23,11 @@ export default function useLocationMapDraftActions({
syncMapDraftFromState, syncMapDraftFromState,
toast, toast,
}) { }) {
const selectedObjectIndex = selectedObjectKey const applyDraftResponse = useCallback((response, nextMode = "edit", nextPreviewDraft = true) => {
? 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, options); syncMapDraftFromState(response.data, nextMode, nextPreviewDraft);
}, [setMapState, setMode, setPreviewDraft, syncMapDraftFromState]); }, [setMapState, setMode, setPreviewDraft, syncMapDraftFromState]);
const handleCreateBlank = useCallback(async () => { const handleCreateBlank = useCallback(async () => {
@ -73,17 +68,14 @@ export default function useLocationMapDraftActions({
map: mapDraft, map: mapDraft,
objects: prepareObjectsForSave(objects), objects: prepareObjectsForSave(objects),
}); });
applyDraftResponse(response, "edit", true, { preserveSelectedIndex: selectedObjectIndex }); applyDraftResponse(response);
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;
@ -96,7 +88,7 @@ export default function useLocationMapDraftActions({
objects: prepareObjectsForSave(objects), objects: prepareObjectsForSave(objects),
}); });
savedPendingDraft = true; savedPendingDraft = true;
applyDraftResponse(saveResponse, "edit", true, { preserveSelectedIndex: selectedObjectIndex }); applyDraftResponse(saveResponse);
} }
const response = await publishLocationMapDraft(activeHouseholdId, locationId); const response = await publishLocationMapDraft(activeHouseholdId, locationId);
@ -116,7 +108,7 @@ export default function useLocationMapDraftActions({
} }
}, [ }, [
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, hasUnsavedChanges, activeHouseholdId, applyDraftResponse, beginSaving, endSaving, hasUnsavedChanges,
locationId, mapDraft, objects, selectedObjectIndex, setEditorTool, toast, locationId, mapDraft, objects, setEditorTool, toast,
]); ]);
return { handleCreateBlank, handleCreateFromZones, handlePublish, handleSaveDraft }; return { handleCreateBlank, handleCreateFromZones, handlePublish, handleSaveDraft };

View File

@ -1,14 +1,7 @@
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 { import { DEFAULT_MAP_FILTERS, DEFAULT_MAP_SIZE, mapForMode, normalizeMapObject, objectsForMode } from "../lib/locationMapUtils";
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",
@ -53,25 +46,18 @@ export default function useLocationMapDraftState({
setMapDraft(DEFAULT_DRAFT_MAP); setMapDraft(DEFAULT_DRAFT_MAP);
}, []); }, []);
const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft, options = {}) => { const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft) => {
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(normalizedObjects); setObjects(nextObjects.map(normalizeMapObject));
setSelectedObjectKey(preservedSelectedObject ? getObjectKey(preservedSelectedObject) : null); setSelectedObjectKey(null);
setHistory([]); setHistory([]);
setFuture([]); setFuture([]);
setHasUnsavedChanges(false); setHasUnsavedChanges(false);

View File

@ -254,7 +254,6 @@ export default function LocationMapManager() {
locationId, locationId,
mapDraft, mapDraft,
objects, objects,
selectedObjectKey,
setEditorTool, setEditorTool,
setMapState, setMapState,
setMode, setMode,

View File

@ -959,8 +959,8 @@
} }
.location-map-toolbar { .location-map-toolbar {
--location-map-mode-slot: 136px; --location-map-mode-slot: 148px;
--location-map-history-slot: 76px; --location-map-history-slot: 80px;
display: grid; display: grid;
grid-template-columns: var(--location-map-mode-slot) minmax(0, 1fr); grid-template-columns: var(--location-map-mode-slot) minmax(0, 1fr);
align-items: center; align-items: center;
@ -1007,7 +1007,7 @@
.location-map-history-buttons { .location-map-history-buttons {
grid-column: 2; grid-column: 2;
gap: 0; gap: 0;
width: var(--location-map-history-slot); width: 80px;
justify-self: center; justify-self: center;
} }
@ -1031,11 +1031,11 @@
.location-map-zoom-controls button { .location-map-zoom-controls button {
min-width: 40px; min-width: 40px;
min-height: 40px; min-height: 40px;
padding: 0.3rem; padding: 0.35rem;
} }
.location-map-zoom-controls span { .location-map-zoom-controls span {
min-width: 28px; min-width: 30px;
font-size: 0.82rem; font-size: 0.82rem;
} }
@ -1050,15 +1050,15 @@
@media (max-width: 380px) { @media (max-width: 380px) {
.location-map-toolbar { .location-map-toolbar {
grid-template-columns: var(--location-map-mode-slot, 136px) var(--location-map-history-slot, 76px); grid-template-columns: var(--location-map-mode-slot, 144px) var(--location-map-history-slot, 80px);
} }
.location-map-toolbar.has-history-slot { .location-map-toolbar.has-history-slot {
grid-template-columns: var(--location-map-mode-slot, 136px) var(--location-map-history-slot, 76px); grid-template-columns: var(--location-map-mode-slot, 144px) var(--location-map-history-slot, 80px);
} }
.location-map-history-buttons { .location-map-history-buttons {
width: var(--location-map-history-slot, 76px); width: 80px;
} }
.location-map-history-buttons button, .location-map-history-buttons button,

View File

@ -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.y).toBeLessThan(titleBox.y + titleBox.height);
expect(statusBox.x).toBeGreaterThan(titleBox.x); expect(statusBox.x).toBeGreaterThan(titleBox.x);
expect(toolbarBox.height).toBeLessThanOrEqual(64); expect(toolbarBox.height).toBeLessThanOrEqual(64);
expect(modeBox.width).toBeGreaterThanOrEqual(134); expect(modeBox.width).toBeGreaterThanOrEqual(146);
expect(modeBox.width).toBeLessThanOrEqual(138); expect(modeBox.width).toBeLessThanOrEqual(150);
expect(reservedHistoryBox.width).toBeGreaterThanOrEqual(70); expect(reservedHistoryBox.width).toBeGreaterThanOrEqual(70);
expect(reservedHistoryBox.x).toBeGreaterThan(modeBox.x + modeBox.width - 1); expect(reservedHistoryBox.x).toBeGreaterThan(modeBox.x + modeBox.width - 1);
await expect(historyActions.getByRole("button", { name: "Undo" })).toBeVisible(); 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(editModeBox.x - modeBox.x)).toBeLessThanOrEqual(1);
expect(Math.abs(editModeGroupBox.width - modeGroupBox.width)).toBeLessThanOrEqual(1); expect(Math.abs(editModeGroupBox.width - modeGroupBox.width)).toBeLessThanOrEqual(1);
expect(Math.abs(editModeBox.width - modeBox.width)).toBeLessThanOrEqual(1); expect(Math.abs(editModeBox.width - modeBox.width)).toBeLessThanOrEqual(1);
expect(editModeBox.width).toBeGreaterThanOrEqual(134); expect(editModeBox.width).toBeGreaterThanOrEqual(146);
expect(editModeBox.width).toBeLessThanOrEqual(138); expect(editModeBox.width).toBeLessThanOrEqual(150);
expect(Math.abs(editHistoryBox.width - reservedHistoryBox.width)).toBeLessThanOrEqual(1); expect(Math.abs(editHistoryBox.width - reservedHistoryBox.width)).toBeLessThanOrEqual(1);
expect(editHistoryBox.x).toBeGreaterThan(editModeBox.x + editModeBox.width); expect(editHistoryBox.x).toBeGreaterThan(editModeBox.x + editModeBox.width);
expect(editZoomBox.x).toBeGreaterThan(editHistoryBox.x + editHistoryBox.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).toBeGreaterThanOrEqual(0);
expect(toolbarBox.x + toolbarBox.width).toBeLessThanOrEqual(361); expect(toolbarBox.x + toolbarBox.width).toBeLessThanOrEqual(361);
expect(modeBox.width).toBeGreaterThanOrEqual(134); expect(modeBox.width).toBeGreaterThanOrEqual(146);
expect(modeBox.width).toBeLessThanOrEqual(138); expect(modeBox.width).toBeLessThanOrEqual(150);
expect(modeBox.x + modeBox.width).toBeLessThanOrEqual(historyBox.x); expect(modeBox.x + modeBox.width).toBeLessThanOrEqual(historyBox.x);
expect(zoomBox.y).toBeGreaterThan(modeBox.y + modeBox.height - 1); expect(zoomBox.y).toBeGreaterThan(modeBox.y + modeBox.height - 1);
expect(zoomBox.x).toBeGreaterThanOrEqual(toolbarBox.x); expect(zoomBox.x).toBeGreaterThanOrEqual(toolbarBox.x);
@ -1418,9 +1418,6 @@ 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 }) => {