From 7bb76a8eb4ca9baddafd3a6ab15fb95ab8b6e48a Mon Sep 17 00:00:00 2001 From: Nico Date: Wed, 3 Jun 2026 11:25:20 -0700 Subject: [PATCH] fix: clarify visible map status --- .../src/components/maps/LocationMapTopbar.jsx | 4 +- frontend/src/lib/locationMapUtils.js | 8 +- frontend/src/pages/LocationMapManager.jsx | 2 +- .../src/styles/pages/LocationMapManager.css | 5 + frontend/tests/location-map-manager.spec.ts | 93 +++++++++++++++++++ 5 files changed, 108 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/maps/LocationMapTopbar.jsx b/frontend/src/components/maps/LocationMapTopbar.jsx index 744c667..187732b 100644 --- a/frontend/src/components/maps/LocationMapTopbar.jsx +++ b/frontend/src/components/maps/LocationMapTopbar.jsx @@ -1,6 +1,8 @@ import { getLocationName, getStoreName } from "../../lib/locationMapUtils"; export default function LocationMapTopbar({ location, status, onBack }) { + const statusClass = status.toLowerCase().replace(/\s+/g, "-"); + return (
diff --git a/frontend/src/lib/locationMapUtils.js b/frontend/src/lib/locationMapUtils.js index a56fc4c..8105534 100644 --- a/frontend/src/lib/locationMapUtils.js +++ b/frontend/src/lib/locationMapUtils.js @@ -40,10 +40,14 @@ export function getLocationName(location) { return location.location_name; } -export function getMapStatus(state) { +export function getMapStatus(state, options = {}) { + if (options.hasUnsavedChanges) return "Unsaved Draft"; if (!state?.draft_map && !state?.published_map) return "No Map"; + if (options.mode === "edit") return "Draft"; + if (options.mode === "view" && options.previewDraft && state?.draft_map) return "Draft Preview"; + if (state?.published_map) return "Published"; if (state?.draft_map) return "Draft"; - return "Published"; + return "No Map"; } export function objectZoneId(object) { diff --git a/frontend/src/pages/LocationMapManager.jsx b/frontend/src/pages/LocationMapManager.jsx index c6f641a..6fed4cd 100644 --- a/frontend/src/pages/LocationMapManager.jsx +++ b/frontend/src/pages/LocationMapManager.jsx @@ -427,7 +427,7 @@ export default function LocationMapManager() { } const hasAnyMap = Boolean(mapState?.draft_map || mapState?.published_map); - const status = hasUnsavedChanges ? "Unsaved Draft" : getMapStatus(mapState); + const status = getMapStatus(mapState, { mode, previewDraft, hasUnsavedChanges }); return (
diff --git a/frontend/src/styles/pages/LocationMapManager.css b/frontend/src/styles/pages/LocationMapManager.css index 7da36c6..662b1f9 100644 --- a/frontend/src/styles/pages/LocationMapManager.css +++ b/frontend/src/styles/pages/LocationMapManager.css @@ -78,6 +78,11 @@ border-color: rgba(245, 158, 11, 0.36); } +.location-map-status-draft-preview { + background: rgba(14, 165, 233, 0.2); + border-color: rgba(56, 189, 248, 0.4); +} + .location-map-status-unsaved-draft { background: rgba(244, 114, 182, 0.2); border-color: rgba(244, 114, 182, 0.42); diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts index 1d03453..e91dccb 100644 --- a/frontend/tests/location-map-manager.spec.ts +++ b/frontend/tests/location-map-manager.spec.ts @@ -113,6 +113,39 @@ function publishedMapState(objects: Array>, canManage = }; } +function draftAndPublishedMapState( + draftObjects: Array>, + publishedObjects: Array>, + canManage = true +) { + const draft = { + id: 900, + household_id: 1, + store_location_id: 10, + name: "Store Map", + width: 1000, + height: 700, + status: "draft", + version: 3, + }; + const published = { + ...draft, + id: 901, + status: "published", + version: 2, + }; + + return { + ...noMapState(canManage), + map: draft, + draft_map: draft, + published_map: published, + objects: draftObjects, + draft_objects: draftObjects, + published_objects: publishedObjects, + }; +} + async function mockMapShell(page: Page, household = adminHousehold) { await seedAuthStorage(page, { username: "map-user", role: household.role }); await mockConfig(page); @@ -330,6 +363,66 @@ test("admin creates a map from zones, saves a draft, and publishes it", async ({ await expect(page.getByText("loose batteries")).toBeVisible(); }); +test("admin status follows the visible map when a saved draft exists", async ({ page }) => { + await mockMapShell(page); + + const publishedObjects = [ + { + id: 1201, + location_map_id: 901, + zone_id: 501, + zone_name: "Bakery", + type: "zone", + label: "Live Bakery", + x: 40, + y: 40, + width: 260, + height: 160, + rotation: 0, + locked: false, + visible: true, + sort_order: 1, + }, + ]; + const draftObjects = [ + { + ...publishedObjects[0], + id: 1301, + location_map_id: 900, + label: "Draft Bakery", + x: 80, + y: 80, + }, + ]; + + const mapState = draftAndPublishedMapState(draftObjects, publishedObjects, true); + await page.route("**/households/1/locations/10/map", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(mapState), + }); + }); + + await page.goto("/stores/100/locations/10/map"); + + await expect(page.locator(".location-map-status")).toHaveText("Published"); + await expect(page.locator(".location-map-object", { hasText: "Live Bakery" })).toBeVisible(); + await expect(page.locator(".location-map-object", { hasText: "Draft Bakery" })).toHaveCount(0); + + await page.getByRole("button", { name: "Edit Draft" }).click(); + await expect(page.locator(".location-map-status")).toHaveText("Draft"); + await expect(page.locator(".location-map-object", { hasText: "Draft Bakery" })).toBeVisible(); + + await page.getByRole("button", { name: "Preview Draft" }).click(); + await expect(page.locator(".location-map-status")).toHaveText("Draft Preview"); + await expect(page.locator(".location-map-object", { hasText: "Draft Bakery" })).toBeVisible(); + + await page.getByRole("button", { name: "View" }).click(); + await expect(page.locator(".location-map-status")).toHaveText("Published"); + await expect(page.locator(".location-map-object", { hasText: "Live Bakery" })).toBeVisible(); +}); + test("members can view a published map but cannot edit it", async ({ page }) => { await mockMapShell(page, memberHousehold);