diff --git a/frontend/src/components/maps/LocationMapStateViews.jsx b/frontend/src/components/maps/LocationMapStateViews.jsx index 26dd0de..38acb07 100644 --- a/frontend/src/components/maps/LocationMapStateViews.jsx +++ b/frontend/src/components/maps/LocationMapStateViews.jsx @@ -1,9 +1,16 @@ import LocationMapTopbar from "./LocationMapTopbar"; -export function LocationMapMessageState({ message }) { +export function LocationMapMessageState({ message, location, status = "Loading", onBack }) { + const showTopbar = Boolean(location && onBack); + return (
-

{message}

+ {showTopbar ? ( + + ) : null} +
+

{message}

+
); } diff --git a/frontend/src/pages/LocationMapManager.jsx b/frontend/src/pages/LocationMapManager.jsx index 958b476..26ca2c2 100644 --- a/frontend/src/pages/LocationMapManager.jsx +++ b/frontend/src/pages/LocationMapManager.jsx @@ -369,7 +369,14 @@ export default function LocationMapManager() { }); if (!hasLoaded || householdLoading || loading) { - return ; + return ( + + ); } if (!activeHousehold) { diff --git a/frontend/src/styles/pages/LocationMapManager.css b/frontend/src/styles/pages/LocationMapManager.css index 523d84d..4a878a5 100644 --- a/frontend/src/styles/pages/LocationMapManager.css +++ b/frontend/src/styles/pages/LocationMapManager.css @@ -76,6 +76,10 @@ background: rgba(148, 163, 184, 0.16); } +.location-map-status-loading { + background: rgba(148, 163, 184, 0.16); +} + .location-map-status-draft { background: rgba(245, 158, 11, 0.2); border-color: rgba(245, 158, 11, 0.36); @@ -902,6 +906,12 @@ color: #9fb3c8; } +.location-map-message-workspace { + display: flex; + align-items: center; + justify-content: center; +} + @media (min-width: 840px) { .location-map-workspace { grid-template-columns: minmax(0, 1fr) 360px; diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts index 40318d0..8301734 100644 --- a/frontend/tests/location-map-manager.spec.ts +++ b/frontend/tests/location-map-manager.spec.ts @@ -490,6 +490,68 @@ test("load failure shows retryable error instead of no-map setup", async ({ page expect(attempts).toBe(2); }); +test("loading map keeps location context visible", async ({ page }) => { + await mockMapShell(page); + + const mapState = publishedMapState([ + { + id: 461, + location_map_id: 901, + zone_id: 501, + zone_name: "Bakery", + type: "zone", + label: "Bakery", + x: 40, + y: 40, + width: 260, + height: 160, + rotation: 0, + locked: false, + visible: true, + sort_order: 1, + }, + ], true); + let resolveMapRequestStarted: () => void = () => {}; + let releaseMapResponse: () => void = () => {}; + let resolveMapResponseSent: () => void = () => {}; + const mapRequestStarted = new Promise((resolve) => { + resolveMapRequestStarted = resolve; + }); + const mapResponseGate = new Promise((resolve) => { + releaseMapResponse = resolve; + }); + const mapResponseSent = new Promise((resolve) => { + resolveMapResponseSent = resolve; + }); + + await page.route("**/households/1/locations/10/map", async (route) => { + resolveMapRequestStarted(); + await mapResponseGate; + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(mapState), + }); + resolveMapResponseSent(); + }); + + const navigation = page.goto("/stores/100/locations/10/map"); + await mapRequestStarted; + + await expect(page.getByRole("heading", { name: "Costco" })).toBeVisible(); + await expect(page.getByText("Eastvale")).toBeVisible(); + await expect(page.getByRole("button", { name: "Back to grocery list" })).toBeVisible(); + await expect(page.locator(".location-map-status")).toHaveText("Loading"); + await expect(page.getByText("Loading map...")).toBeVisible(); + + releaseMapResponse(); + await mapResponseSent; + await navigation; + + await expect(page.locator(".location-map-status")).toHaveText("Published"); + await expect(page.getByRole("button", { name: "Map area Bakery" })).toBeVisible(); +}); + test("ignores stale map responses after switching locations", async ({ page }) => { await mockMapShell(page, adminHousehold, [storeLocation, secondStoreLocation]);