fix: hide unavailable map draft actions

This commit is contained in:
Nico 2026-06-04 19:29:17 -07:00
parent 15324c25f2
commit 0c8f18e1e5
2 changed files with 84 additions and 27 deletions

View File

@ -121,6 +121,10 @@ export default function LocationMapBottomSheet({
showEditorControls && hiddenAreaCount > 0 && filters.showZones && !hasNoVisibleAreas; showEditorControls && hiddenAreaCount > 0 && filters.showZones && !hasNoVisibleAreas;
const saveDraftLabel = savingAction === "save" ? "Saving..." : "Save Draft"; const saveDraftLabel = savingAction === "save" ? "Saving..." : "Save Draft";
const publishLabel = savingAction === "publish" ? "Publishing..." : "Publish"; 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 const mappedItemSummary = assignedItemCount === visibleAssignedItemCount
? String(assignedItemCount) ? String(assignedItemCount)
: `${visibleAssignedItemCount} shown`; : `${visibleAssignedItemCount} shown`;
@ -152,25 +156,29 @@ export default function LocationMapBottomSheet({
showCompleted: true, showCompleted: true,
})); }));
}; };
const primaryDraftActions = showEditorControls ? ( const primaryDraftActions = showEditorControls && (showSaveDraftAction || showPublishAction) ? (
<div className="location-map-editor-actions location-map-primary-actions"> <div className="location-map-editor-actions location-map-primary-actions">
<button type="button" className="primary" onClick={onSaveDraft} disabled={saving || !hasUnsavedChanges}> {showSaveDraftAction ? (
{saveDraftLabel} <button type="button" className="primary" onClick={onSaveDraft} disabled={saving || !hasUnsavedChanges}>
</button> {saveDraftLabel}
<button </button>
type="button" ) : null}
className="primary" {showPublishAction ? (
onClick={onPublish} <button
disabled={saving || (!mapState?.draft_map && !hasUnsavedChanges)} type="button"
> className="primary"
{publishLabel} onClick={onPublish}
</button> disabled={saving || !canPublishDraft}
>
{publishLabel}
</button>
) : null}
</div> </div>
) : null; ) : null;
const secondaryDraftActions = showEditorControls ? ( const secondaryDraftActions = showEditorControls ? (
<div className="location-map-editor-actions location-map-secondary-actions"> <div className="location-map-editor-actions location-map-secondary-actions">
<button type="button" onClick={onAddObject} disabled={saving}>Add Area</button> <button type="button" onClick={onAddObject} disabled={saving}>Add Area</button>
{!showSelectedObjectForm ? ( {showPreviewDraftAction ? (
<button type="button" onClick={onPreviewDraft} aria-label="Preview Draft" disabled={saving}> <button type="button" onClick={onPreviewDraft} aria-label="Preview Draft" disabled={saving}>
Preview Preview
</button> </button>
@ -236,14 +244,16 @@ export default function LocationMapBottomSheet({
<LocationMapLayerPanel filters={filters} setFilters={setFilters} /> <LocationMapLayerPanel filters={filters} setFilters={setFilters} />
) : null} ) : null}
{showSelectedObjectForm && primaryDraftActions ? ( {showSelectedObjectForm ? (
<div primaryDraftActions ? (
className="location-map-editor-action-stack location-map-selected-primary-actions" <div
role="group" className="location-map-editor-action-stack location-map-selected-primary-actions"
aria-label="Map draft actions" role="group"
> aria-label="Map draft actions"
{primaryDraftActions} >
</div> {primaryDraftActions}
</div>
) : null
) : editorActions} ) : editorActions}
{showHiddenAreaAction ? ( {showHiddenAreaAction ? (

View File

@ -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(); 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 }) => { test("mode switches close open map layers without resetting filters", async ({ page }) => {
await mockMapShell(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.getByRole("textbox", { name: "Label" })).toBeVisible();
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" })).toHaveCount(0);
await expect(page.getByRole("button", { name: "Undo" })).toBeDisabled(); await expect(page.getByRole("button", { name: "Undo" })).toBeDisabled();
const objectBox = await bakeryObject.boundingBox(); const objectBox = await bakeryObject.boundingBox();
@ -2098,7 +2145,7 @@ 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" })).toHaveCount(0);
await expect(page.getByRole("textbox", { name: "Label" })).toBeEnabled(); await expect(page.getByRole("textbox", { name: "Label" })).toBeEnabled();
await expect(page.getByRole("textbox", { name: "Label" })).toHaveValue("Saving Bakery"); await expect(page.getByRole("textbox", { name: "Label" })).toHaveValue("Saving Bakery");
await expect(page.getByRole("button", { name: "Map area Saving Bakery" })).toHaveAttribute("aria-pressed", "true"); 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.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 Saved Draft Bakery" })).toBeVisible();
await expect(page.getByRole("button", { name: "Map area Live Bakery" })).toHaveCount(0); 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(); await expect(historyActions.getByRole("button", { name: "Redo" })).toBeDisabled();
const primaryActions = page.locator(".location-map-primary-actions"); const primaryActions = page.locator(".location-map-primary-actions");
const secondaryActions = page.locator(".location-map-secondary-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(primaryActions.getByRole("button", { name: "Publish" })).toBeVisible();
await expect(secondaryActions.getByRole("button", { name: "Add Area" })).toBeVisible(); await expect(secondaryActions.getByRole("button", { name: "Add Area" })).toBeVisible();
await expect(secondaryActions.getByRole("button", { name: "Preview Draft" })).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"); await expect(page.getByRole("button", { name: "Locked" })).toHaveAttribute("aria-pressed", "false");
const selectedPrimaryActions = page.locator(".location-map-selected-primary-actions"); const selectedPrimaryActions = page.locator(".location-map-selected-primary-actions");
const selectedSecondaryActions = page.locator(".location-map-selected-secondary-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(selectedPrimaryActions.getByRole("button", { name: "Publish" })).toBeVisible();
await expect(selectedSecondaryActions.getByRole("button", { name: "Add Area" })).toBeVisible(); await expect(selectedSecondaryActions.getByRole("button", { name: "Add Area" })).toBeVisible();
await expect(selectedSecondaryActions.getByRole("button", { name: "Preview Draft" })).toHaveCount(0); 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 closeSelectedButton.click();
await expect(page.getByRole("textbox", { name: "Label" })).toHaveCount(0); await expect(page.getByRole("textbox", { name: "Label" })).toHaveCount(0);
await expect(page.locator(".location-map-sheet-header strong")).toHaveText("Edit Areas"); 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 page.getByRole("button", { name: "View", exact: true }).click();
await expect(page.getByRole("textbox", { name: "Label" })).toHaveCount(0); await expect(page.getByRole("textbox", { name: "Label" })).toHaveCount(0);
}); });