fix: recover hidden map areas
This commit is contained in:
parent
5b8b7ee2b8
commit
daa283b160
@ -30,6 +30,7 @@ export default function LocationMapBottomSheet({
|
||||
selectedObject,
|
||||
selectedZoneItems,
|
||||
visibleUnmappedItems,
|
||||
hiddenAreaCount,
|
||||
history,
|
||||
future,
|
||||
saving,
|
||||
@ -51,6 +52,7 @@ export default function LocationMapBottomSheet({
|
||||
onClearSelection,
|
||||
onDuplicateObject,
|
||||
onDeleteObject,
|
||||
onShowHiddenAreas,
|
||||
}) {
|
||||
const selectedTitle = getMapObjectDisplayLabel(selectedObject);
|
||||
const selectedAssignedItems = selectedObject?.zone_id
|
||||
@ -82,6 +84,7 @@ export default function LocationMapBottomSheet({
|
||||
const hiddenUnmappedItemCount = visibleUnmappedItems.length - unmappedItemPreview.length;
|
||||
const selectedWidth = Number(selectedObject?.width) || 40;
|
||||
const selectedHeight = Number(selectedObject?.height) || 40;
|
||||
const hiddenAreaLabel = `Show Hidden Areas (${hiddenAreaCount})`;
|
||||
const maxX = Math.max(0, Math.round((mapSize?.width || selectedWidth) - selectedWidth));
|
||||
const maxY = Math.max(0, Math.round((mapSize?.height || selectedHeight) - selectedHeight));
|
||||
const maxWidth = Math.max(40, Math.round(mapSize?.width || selectedWidth));
|
||||
@ -217,6 +220,14 @@ export default function LocationMapBottomSheet({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showEditorControls && hiddenAreaCount > 0 ? (
|
||||
<div className="location-map-hidden-areas">
|
||||
<button type="button" onClick={onShowHiddenAreas} disabled={saving}>
|
||||
{hiddenAreaLabel}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{mode === "edit" && editorTool === "edit" && selectedObject ? (
|
||||
<div className="location-map-object-form">
|
||||
<label>
|
||||
@ -254,6 +265,26 @@ export default function LocationMapBottomSheet({
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<div className="location-map-object-flags">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedObject.visible !== false}
|
||||
disabled={saving}
|
||||
onChange={(event) => onObjectField("visible", event.target.checked)}
|
||||
/>
|
||||
Visible
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(selectedObject.locked)}
|
||||
disabled={saving}
|
||||
onChange={(event) => onObjectField("locked", event.target.checked)}
|
||||
/>
|
||||
Locked
|
||||
</label>
|
||||
</div>
|
||||
<div className="location-map-object-grid">
|
||||
<label>
|
||||
X
|
||||
|
||||
@ -81,6 +81,7 @@ export default function LocationMapManager() {
|
||||
? itemsForZone(mapState?.items || [], selectedObject.zone_id, filters, username)
|
||||
: [];
|
||||
const visibleUnmappedItems = unmappedItems(mapState?.items || [], filters, username);
|
||||
const hiddenAreaCount = objects.filter((object) => object.visible === false).length;
|
||||
const shouldGuardLeave = canManage && hasUnsavedChanges;
|
||||
|
||||
const mapSize = {
|
||||
@ -368,6 +369,17 @@ export default function LocationMapManager() {
|
||||
setPendingDeleteObject(null);
|
||||
};
|
||||
|
||||
const handleShowHiddenAreas = () => {
|
||||
if (!canManage || hiddenAreaCount === 0) return;
|
||||
remember();
|
||||
updateObjects((currentObjects) =>
|
||||
currentObjects.map((object) =>
|
||||
object.visible === false ? { ...object, visible: true } : object
|
||||
)
|
||||
);
|
||||
toast.success("Showed hidden areas", "Save the draft to keep this change.");
|
||||
};
|
||||
|
||||
const handleObjectField = (field, value) => {
|
||||
if (!selectedObject) return;
|
||||
remember();
|
||||
@ -567,6 +579,7 @@ export default function LocationMapManager() {
|
||||
selectedObject={selectedObject}
|
||||
selectedZoneItems={selectedZoneItems}
|
||||
visibleUnmappedItems={visibleUnmappedItems}
|
||||
hiddenAreaCount={hiddenAreaCount}
|
||||
history={history}
|
||||
future={future}
|
||||
saving={saving}
|
||||
@ -588,6 +601,7 @@ export default function LocationMapManager() {
|
||||
onClearSelection={() => setSelectedObjectKey(null)}
|
||||
onDuplicateObject={handleDuplicateObject}
|
||||
onDeleteObject={requestDeleteObject}
|
||||
onShowHiddenAreas={handleShowHiddenAreas}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -175,6 +175,7 @@
|
||||
.location-map-tool-buttons button:disabled,
|
||||
.location-map-zoom-controls button:disabled,
|
||||
.location-map-editor-actions button:disabled,
|
||||
.location-map-hidden-areas button:disabled,
|
||||
.location-map-mobile-tool-switch button:disabled,
|
||||
.location-map-setup-actions button:disabled {
|
||||
opacity: 0.55;
|
||||
@ -540,6 +541,22 @@
|
||||
background: rgba(20, 184, 166, 0.24);
|
||||
}
|
||||
|
||||
.location-map-hidden-areas {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.location-map-hidden-areas button {
|
||||
min-height: 40px;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.7rem;
|
||||
border: 1px dashed rgba(96, 165, 250, 0.5);
|
||||
border-radius: 8px;
|
||||
background: rgba(59, 130, 246, 0.14);
|
||||
color: #dbeafe;
|
||||
font-weight: 900;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.location-map-object-form {
|
||||
display: grid;
|
||||
gap: 0.6rem;
|
||||
@ -563,6 +580,31 @@
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.location-map-object-flags {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.location-map-object-flags label {
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: 8px;
|
||||
background: rgba(15, 23, 34, 0.58);
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.location-map-object-flags input {
|
||||
min-height: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.location-map-object-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
|
||||
@ -596,6 +596,80 @@ test("admin selecting an object does not mark a draft dirty until it changes", a
|
||||
await expect(page.getByRole("button", { name: "Undo" })).toBeEnabled();
|
||||
});
|
||||
|
||||
test("admin can recover hidden map areas from the edit sheet", async ({ page }) => {
|
||||
await mockMapShell(page);
|
||||
|
||||
let savedPayload: Record<string, unknown> | null = null;
|
||||
let mapState = draftMapState([
|
||||
{
|
||||
id: 1354,
|
||||
location_map_id: 900,
|
||||
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);
|
||||
|
||||
await page.route("**/households/1/locations/10/map", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(mapState),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route("**/households/1/locations/10/map/draft", async (route) => {
|
||||
savedPayload = await route.request().postDataJSON();
|
||||
const savedObjects = (savedPayload.objects as Array<Record<string, unknown>>).map((object, index) => ({
|
||||
id: 1370 + index,
|
||||
location_map_id: 900,
|
||||
zone_name: object.zone_id === 501 ? "Bakery" : null,
|
||||
...object,
|
||||
}));
|
||||
mapState = draftMapState(savedObjects, true);
|
||||
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: "Continue Editing" }).click();
|
||||
await page.getByRole("button", { name: "Edit Objects" }).click();
|
||||
await page.getByRole("button", { name: "Map area Bakery" }).click();
|
||||
await expect(page.getByLabel("Visible")).toBeChecked();
|
||||
await expect(page.getByLabel("Locked")).not.toBeChecked();
|
||||
|
||||
await page.getByLabel("Visible").click();
|
||||
await expect(page.getByRole("button", { name: "Map area Bakery" })).toHaveCount(0);
|
||||
await expect(page.locator(".location-map-sheet-header strong")).toHaveText("Edit Objects");
|
||||
await expect(page.getByText("No visible map areas")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Show Hidden Areas (1)" })).toBeVisible();
|
||||
await expect(page.locator(".location-map-status")).toHaveText("Unsaved Draft");
|
||||
|
||||
await page.getByRole("button", { name: "Show Hidden Areas (1)" }).click();
|
||||
await expect(page.getByRole("button", { name: "Map area Bakery" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Show Hidden Areas (1)" })).toHaveCount(0);
|
||||
|
||||
await page.getByRole("button", { name: "Save Draft" }).click();
|
||||
await expect(page.locator(".location-map-status")).toHaveText("Draft");
|
||||
expect(savedPayload).not.toBeNull();
|
||||
expect((savedPayload?.objects as Array<Record<string, unknown>>)[0]).toEqual(
|
||||
expect.objectContaining({ visible: true, zone_id: 501 })
|
||||
);
|
||||
});
|
||||
|
||||
test("admin cleared map area labels keep linked zone action names", async ({ page }) => {
|
||||
await mockMapShell(page);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user