diff --git a/frontend/src/components/maps/LocationMapBottomSheet.jsx b/frontend/src/components/maps/LocationMapBottomSheet.jsx index 13747a6..0965a85 100644 --- a/frontend/src/components/maps/LocationMapBottomSheet.jsx +++ b/frontend/src/components/maps/LocationMapBottomSheet.jsx @@ -121,6 +121,10 @@ export default function LocationMapBottomSheet({ showEditorControls && hiddenAreaCount > 0 && filters.showZones && !hasNoVisibleAreas; const saveDraftLabel = savingAction === "save" ? "Saving..." : "Save Draft"; const publishLabel = savingAction === "publish" ? "Publishing..." : "Publish"; + const canPublishDraft = Boolean(mapState?.draft_map || hasUnsavedChanges); + const showSaveDraftAction = Boolean(savingAction === "save" || hasUnsavedChanges); + const showPublishAction = Boolean(savingAction === "publish" || canPublishDraft); + const showPreviewDraftAction = Boolean((mapState?.draft_map || hasUnsavedChanges) && !showSelectedObjectForm); const mappedItemSummary = assignedItemCount === visibleAssignedItemCount ? String(assignedItemCount) : `${visibleAssignedItemCount} shown`; @@ -152,25 +156,29 @@ export default function LocationMapBottomSheet({ showCompleted: true, })); }; - const primaryDraftActions = showEditorControls ? ( + const primaryDraftActions = showEditorControls && (showSaveDraftAction || showPublishAction) ? (
- - + {showSaveDraftAction ? ( + + ) : null} + {showPublishAction ? ( + + ) : null}
) : null; const secondaryDraftActions = showEditorControls ? (
- {!showSelectedObjectForm ? ( + {showPreviewDraftAction ? ( @@ -236,14 +244,16 @@ export default function LocationMapBottomSheet({ ) : null} - {showSelectedObjectForm && primaryDraftActions ? ( -
- {primaryDraftActions} -
+ {showSelectedObjectForm ? ( + primaryDraftActions ? ( +
+ {primaryDraftActions} +
+ ) : null ) : editorActions} {showHiddenAreaAction ? ( diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts index e26d887..16d7200 100644 --- a/frontend/tests/location-map-manager.spec.ts +++ b/frontend/tests/location-map-manager.spec.ts @@ -1229,6 +1229,53 @@ test("admin status follows the visible map when a saved draft exists", async ({ await expect(page.locator(".location-map-object", { hasText: "Live Bakery" })).toBeVisible(); }); +test("admin edit mode hides unavailable draft actions until changes exist", async ({ page }) => { + await mockMapShell(page); + + const mapState = publishedMapState([ + { + 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, + }, + ], 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 page.getByRole("button", { name: "Edit Draft" }).click(); + + const secondaryActions = page.locator(".location-map-secondary-actions"); + await expect(page.locator(".location-map-primary-actions")).toHaveCount(0); + await expect(secondaryActions.getByRole("button", { name: "Add Area" })).toBeVisible(); + await expect(secondaryActions.getByRole("button", { name: "Preview Draft" })).toHaveCount(0); + await expect(page.getByRole("button", { name: "Save Draft" })).toHaveCount(0); + await expect(page.getByRole("button", { name: "Publish", exact: true })).toHaveCount(0); + + await secondaryActions.getByRole("button", { name: "Add Area" }).click(); + + await expect(page.locator(".location-map-status")).toHaveText("Unsaved Draft"); + await expect(page.getByRole("button", { name: "Save Draft" })).toBeEnabled(); + await expect(page.getByRole("button", { name: "Publish", exact: true })).toBeEnabled(); +}); + test("mode switches close open map layers without resetting filters", async ({ page }) => { await mockMapShell(page); @@ -1561,7 +1608,7 @@ test("admin selecting an object does not mark a draft dirty until it changes", a await expect(page.getByRole("textbox", { name: "Label" })).toBeVisible(); 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" })).toHaveCount(0); await expect(page.getByRole("button", { name: "Undo" })).toBeDisabled(); const objectBox = await bakeryObject.boundingBox(); @@ -2098,7 +2145,7 @@ 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("button", { name: "Save Draft" })).toHaveCount(0); 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"); @@ -2611,7 +2658,7 @@ test("admin publish failure keeps successfully saved pending edits", async ({ pa ]) ); 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" })).toHaveCount(0); await expect(page.getByRole("button", { name: "Map area Saved Draft Bakery" })).toBeVisible(); await expect(page.getByRole("button", { name: "Map area Live Bakery" })).toHaveCount(0); }); @@ -3957,7 +4004,7 @@ test("mobile editor uses bottom sheet controls instead of desktop object toolbar await expect(historyActions.getByRole("button", { name: "Redo" })).toBeDisabled(); const primaryActions = page.locator(".location-map-primary-actions"); const secondaryActions = page.locator(".location-map-secondary-actions"); - await expect(primaryActions.getByRole("button", { name: "Save Draft" })).toBeVisible(); + await expect(primaryActions.getByRole("button", { name: "Save Draft" })).toHaveCount(0); await expect(primaryActions.getByRole("button", { name: "Publish" })).toBeVisible(); await expect(secondaryActions.getByRole("button", { name: "Add Area" })).toBeVisible(); await expect(secondaryActions.getByRole("button", { name: "Preview Draft" })).toBeVisible(); @@ -4017,7 +4064,7 @@ test("mobile editor uses bottom sheet controls instead of desktop object toolbar await expect(page.getByRole("button", { name: "Locked" })).toHaveAttribute("aria-pressed", "false"); const selectedPrimaryActions = page.locator(".location-map-selected-primary-actions"); const selectedSecondaryActions = page.locator(".location-map-selected-secondary-actions"); - await expect(selectedPrimaryActions.getByRole("button", { name: "Save Draft" })).toBeVisible(); + await expect(selectedPrimaryActions.getByRole("button", { name: "Save Draft" })).toHaveCount(0); await expect(selectedPrimaryActions.getByRole("button", { name: "Publish" })).toBeVisible(); await expect(selectedSecondaryActions.getByRole("button", { name: "Add Area" })).toBeVisible(); await expect(selectedSecondaryActions.getByRole("button", { name: "Preview Draft" })).toHaveCount(0); @@ -4049,7 +4096,7 @@ test("mobile editor uses bottom sheet controls instead of desktop object toolbar await closeSelectedButton.click(); await expect(page.getByRole("textbox", { name: "Label" })).toHaveCount(0); await expect(page.locator(".location-map-sheet-header strong")).toHaveText("Edit Areas"); - await expect(page.getByRole("button", { name: "Save Draft" })).toBeDisabled(); + await expect(page.getByRole("button", { name: "Save Draft" })).toHaveCount(0); await page.getByRole("button", { name: "View", exact: true }).click(); await expect(page.getByRole("textbox", { name: "Label" })).toHaveCount(0); });