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);