254 lines
8.2 KiB
JavaScript
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>
|
|
);
|
|
}
|