grocery-app/frontend/src/components/maps/LocationMapBottomSheet.jsx
2026-06-03 11:55:08 -07:00

254 lines
8.2 KiB
JavaScript

import { MAP_OBJECT_TYPES } from "../../lib/locationMapUtils";
function objectZoneId(object) {
return object?.zone_id ? String(object.zone_id) : "";
}
const DISPLAY_CONTROLS = [
["showZones", "Zones"],
["showLabels", "Labels"],
["showCounts", "Counts"],
["showPins", "Pins"],
["showMyItems", "Mine"],
["showOtherItems", "Others"],
["showCompleted", "Done"],
["showUnmapped", "Unmapped"],
];
export default function LocationMapBottomSheet({
mode,
editorTool,
canManage,
filters,
setFilters,
layersOpen,
setLayersOpen,
selectedObject,
selectedZoneItems,
visibleUnmappedItems,
history,
future,
saving,
hasUnsavedChanges,
mapState,
onAddObject,
onUndo,
onRedo,
onSaveDraft,
onPublish,
onPreviewDraft,
onPanMode,
onEditObjects,
onEditMode,
onObjectField,
onZoneLinkChange,
onDuplicateObject,
onDeleteObject,
}) {
const selectedTitle = selectedObject?.label || selectedObject?.zone_name || "Map Area";
const selectedItemCountLabel = `${selectedZoneItems.length} item${selectedZoneItems.length === 1 ? "" : "s"}`;
const sheetTitle = mode === "edit"
? selectedObject && editorTool === "edit"
? "Object Settings"
: editorTool === "edit"
? "Edit Objects"
: "Pan Mode"
: selectedObject
? selectedTitle
: "Map Details";
const showEditorControls = mode === "edit" && canManage;
const showSelectedObjectForm = Boolean(mode === "edit" && editorTool === "edit" && selectedObject);
const editorActions = showEditorControls ? (
<div className="location-map-editor-actions location-map-primary-actions">
<button type="button" onClick={onAddObject}>Add Area</button>
<button type="button" onClick={onPreviewDraft}>Preview Draft</button>
<button type="button" className="primary" onClick={onSaveDraft} disabled={saving || !hasUnsavedChanges}>Save Draft</button>
<button type="button" className="primary" onClick={onPublish} disabled={saving || !mapState?.draft_map}>Publish</button>
<button type="button" onClick={onUndo} disabled={history.length === 0}>Undo</button>
<button type="button" onClick={onRedo} disabled={future.length === 0}>Redo</button>
</div>
) : canManage && mapState?.published_map ? (
<div className="location-map-editor-actions">
<button type="button" onClick={onEditMode}>Edit Map</button>
</div>
) : null;
return (
<aside className="location-map-bottom-sheet">
<div className="location-map-sheet-header">
<div>
<strong>{sheetTitle}</strong>
{mode === "edit" && canManage ? (
<span>{editorTool === "edit" ? "Move, resize, and configure selected areas." : "Drag and zoom without changing objects."}</span>
) : null}
</div>
<button
type="button"
className={`location-map-layer-toggle ${layersOpen ? "active" : ""}`}
onClick={() => setLayersOpen((current) => !current)}
aria-expanded={layersOpen}
>
Layers
</button>
</div>
{layersOpen ? (
<div className="location-map-display-controls">
{DISPLAY_CONTROLS.map(([key, label]) => (
<label key={key}>
<input
type="checkbox"
checked={filters[key]}
onChange={(event) =>
setFilters((current) => ({ ...current, [key]: event.target.checked }))
}
/>
{label}
</label>
))}
</div>
) : null}
{showSelectedObjectForm ? null : editorActions}
{showEditorControls ? (
<div className="location-map-mobile-tool-switch" aria-label="Edit tool">
<button
type="button"
className={editorTool === "pan" ? "active" : ""}
onClick={onPanMode}
>
Pan
</button>
<button
type="button"
className={editorTool === "edit" ? "active" : ""}
onClick={onEditObjects}
>
Objects
</button>
</div>
) : null}
{mode === "edit" && editorTool === "edit" && selectedObject ? (
<div className="location-map-object-form">
<label>
Label
<input
value={selectedObject.label || ""}
onChange={(event) => onObjectField("label", event.target.value)}
/>
</label>
<label>
Type
<select
aria-label="Object type"
value={selectedObject.type}
onChange={(event) => onObjectField("type", event.target.value)}
>
{MAP_OBJECT_TYPES.map((type) => (
<option key={type} value={type}>{type}</option>
))}
</select>
</label>
<label>
Zone
<select
aria-label="Linked zone"
value={objectZoneId(selectedObject)}
onChange={(event) => onZoneLinkChange(event.target.value)}
>
<option value="">No linked zone</option>
{(mapState?.zones || []).map((zone) => (
<option key={zone.id} value={zone.id}>{zone.name}</option>
))}
</select>
</label>
<div className="location-map-object-grid">
<label>
X
<input
type="number"
value={Math.round(selectedObject.x)}
onChange={(event) => onObjectField("x", Number(event.target.value))}
/>
</label>
<label>
Y
<input
type="number"
value={Math.round(selectedObject.y)}
onChange={(event) => onObjectField("y", Number(event.target.value))}
/>
</label>
<label>
W
<input
type="number"
value={Math.round(selectedObject.width)}
onChange={(event) => onObjectField("width", Number(event.target.value))}
/>
</label>
<label>
H
<input
type="number"
value={Math.round(selectedObject.height)}
onChange={(event) => onObjectField("height", Number(event.target.value))}
/>
</label>
</div>
<div className="location-map-editor-actions">
<button type="button" onClick={onDuplicateObject}>Duplicate</button>
<button type="button" className="danger" onClick={onDeleteObject}>Delete</button>
</div>
</div>
) : mode === "edit" && editorTool === "edit" ? (
<p className="location-map-muted">
Tap an area to edit its name, linked zone, size, and position.
</p>
) : mode === "edit" ? (
<p className="location-map-muted">
Use Pan Mode to move around the map. Switch to Objects when you want to edit areas.
</p>
) : selectedObject ? (
<div className="location-map-zone-items">
<div className="location-map-zone-items-title">
<h2>{selectedTitle}</h2>
<span>{selectedItemCountLabel}</span>
</div>
{selectedZoneItems.length === 0 ? (
<p className="location-map-muted">No visible items assigned to this zone.</p>
) : (
<ul>
{selectedZoneItems.map((item) => (
<li key={item.id}>
<span>{item.item_name}</span>
<small>x{item.quantity}</small>
</li>
))}
</ul>
)}
</div>
) : (
<p className="location-map-muted">
Tap a zone to view assigned items. Item pins stay hidden unless you turn them on in Layers.
</p>
)}
{showSelectedObjectForm ? editorActions : null}
{filters.showUnmapped && visibleUnmappedItems.length > 0 ? (
<div className="location-map-unmapped-list">
<strong>Unmapped Items</strong>
<ul>
{visibleUnmappedItems.slice(0, 8).map((item) => (
<li key={item.id}>{item.item_name}</li>
))}
</ul>
</div>
) : null}
</aside>
);
}