290 lines
9.7 KiB
JavaScript
290 lines
9.7 KiB
JavaScript
import { useMemo } from "react";
|
|
import {
|
|
DEFAULT_MAP_FILTERS,
|
|
getMapObjectDisplayLabel,
|
|
objectZoneId,
|
|
} from "../../lib/locationMapUtils";
|
|
import {
|
|
LocationMapLayerPanel,
|
|
LocationMapOverview,
|
|
MAP_DISPLAY_CONTROLS,
|
|
SelectedMapObjectForm,
|
|
SelectedZoneItemsPanel,
|
|
UnmappedItemsPanel,
|
|
} from "./LocationMapBottomSheetSections";
|
|
|
|
const EMPTY_ITEMS = [];
|
|
|
|
export default function LocationMapBottomSheet({
|
|
mode,
|
|
editorTool,
|
|
canManage,
|
|
filters,
|
|
setFilters,
|
|
layersOpen,
|
|
setLayersOpen,
|
|
selectedObject,
|
|
mapObjects = [],
|
|
selectedZoneItems = EMPTY_ITEMS,
|
|
visibleAssignedItemCount = 0,
|
|
visibleUnmappedItems = EMPTY_ITEMS,
|
|
unmappedItemCount,
|
|
hiddenAreaCount,
|
|
saving,
|
|
savingAction,
|
|
hasUnsavedChanges,
|
|
mapState,
|
|
onAddObject,
|
|
onSaveDraft,
|
|
onPublish,
|
|
onPreviewDraft,
|
|
onObjectField,
|
|
onZoneLinkChange,
|
|
onDuplicateObject,
|
|
onDeleteObject,
|
|
onShowHiddenAreas,
|
|
}) {
|
|
const selectedTitle = getMapObjectDisplayLabel(selectedObject);
|
|
const selectedZoneId = objectZoneId(selectedObject);
|
|
const mapItems = mapState?.items || EMPTY_ITEMS;
|
|
const { assignedItemCount, selectedAssignedItems } = useMemo(() => {
|
|
const nextSelectedAssignedItems = [];
|
|
let nextAssignedItemCount = 0;
|
|
|
|
mapItems.forEach((item) => {
|
|
if (!item.zone_id) return;
|
|
nextAssignedItemCount += 1;
|
|
if (selectedZoneId && String(item.zone_id) === selectedZoneId) {
|
|
nextSelectedAssignedItems.push(item);
|
|
}
|
|
});
|
|
|
|
return {
|
|
assignedItemCount: nextAssignedItemCount,
|
|
selectedAssignedItems: nextSelectedAssignedItems,
|
|
};
|
|
}, [mapItems, selectedZoneId]);
|
|
const hiddenSelectedItemCount = Math.max(0, selectedAssignedItems.length - selectedZoneItems.length);
|
|
const hasHiddenSelectedItems = hiddenSelectedItemCount > 0;
|
|
const selectedItemCountLabel = hasHiddenSelectedItems
|
|
? `${selectedZoneItems.length} shown`
|
|
: `${selectedZoneItems.length} item${selectedZoneItems.length === 1 ? "" : "s"}`;
|
|
const sheetTitle = mode === "edit"
|
|
? selectedObject && editorTool === "edit"
|
|
? selectedTitle
|
|
: editorTool === "edit"
|
|
? "Edit Areas"
|
|
: "Move Map"
|
|
: selectedObject
|
|
? selectedTitle
|
|
: "Map Details";
|
|
const showHeaderItemCount = mode !== "edit" && Boolean(selectedObject);
|
|
const showEditorControls = mode === "edit" && canManage;
|
|
const showSelectedObjectForm = Boolean(mode === "edit" && editorTool === "edit" && selectedObject);
|
|
const changedLayerCount = useMemo(
|
|
() => MAP_DISPLAY_CONTROLS.filter(([key]) => filters[key] !== DEFAULT_MAP_FILTERS[key]).length,
|
|
[filters]
|
|
);
|
|
const totalAreaCount = mapObjects.length;
|
|
const visibleAreaCount = filters.showZones
|
|
? Math.max(0, totalAreaCount - hiddenAreaCount)
|
|
: 0;
|
|
const hasNoVisibleAreas =
|
|
filters.showZones && totalAreaCount > 0 && visibleAreaCount === 0 && hiddenAreaCount > 0;
|
|
const showMapOverview = mode !== "edit" && !selectedObject;
|
|
const unlinkedAreaCount = useMemo(
|
|
() => mapObjects.filter((object) => !objectZoneId(object)).length,
|
|
[mapObjects]
|
|
);
|
|
const showDraftOverview = mode === "edit" && editorTool === "edit" && !selectedObject;
|
|
const hiddenUnmappedItemCount = Math.max(0, unmappedItemCount - visibleUnmappedItems.length);
|
|
const hasHiddenUnmappedItems =
|
|
filters.showUnmapped && unmappedItemCount > 0 && visibleUnmappedItems.length === 0;
|
|
const hiddenAreaLabel = `Show Hidden Areas (${hiddenAreaCount})`;
|
|
const showHiddenAreaAction =
|
|
showEditorControls && hiddenAreaCount > 0 && filters.showZones && !hasNoVisibleAreas;
|
|
const saveDraftLabel = savingAction === "save" ? "Saving..." : "Save Draft";
|
|
const publishLabel = savingAction === "publish" ? "Publishing..." : "Publish";
|
|
const mappedItemSummary = assignedItemCount === visibleAssignedItemCount
|
|
? String(assignedItemCount)
|
|
: `${visibleAssignedItemCount} shown`;
|
|
const unmappedItemSummary = !filters.showUnmapped && unmappedItemCount > 0
|
|
? `${unmappedItemCount} hidden`
|
|
: visibleUnmappedItems.length === unmappedItemCount
|
|
? String(unmappedItemCount)
|
|
: `${visibleUnmappedItems.length} shown`;
|
|
const resetLayerFilters = () => setFilters({ ...DEFAULT_MAP_FILTERS });
|
|
const showSelectedZoneItems = () => {
|
|
setFilters((current) => ({
|
|
...current,
|
|
showMyItems: true,
|
|
showOtherItems: true,
|
|
showCompleted: true,
|
|
}));
|
|
};
|
|
const showUnmappedItems = () => {
|
|
setFilters((current) => ({
|
|
...current,
|
|
showUnmapped: true,
|
|
showMyItems: true,
|
|
showOtherItems: true,
|
|
showCompleted: true,
|
|
}));
|
|
};
|
|
const primaryDraftActions = showEditorControls ? (
|
|
<div className="location-map-editor-actions location-map-primary-actions">
|
|
<button type="button" className="primary" onClick={onSaveDraft} disabled={saving || !hasUnsavedChanges}>
|
|
{saveDraftLabel}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="primary"
|
|
onClick={onPublish}
|
|
disabled={saving || (!mapState?.draft_map && !hasUnsavedChanges)}
|
|
>
|
|
{publishLabel}
|
|
</button>
|
|
</div>
|
|
) : null;
|
|
const secondaryDraftActions = showEditorControls ? (
|
|
<div className="location-map-editor-actions location-map-secondary-actions">
|
|
<button type="button" onClick={onAddObject} disabled={saving}>Add Area</button>
|
|
{!showSelectedObjectForm ? (
|
|
<button type="button" onClick={onPreviewDraft} aria-label="Preview Draft" disabled={saving}>
|
|
Preview
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
) : null;
|
|
const editorActions = showEditorControls ? (
|
|
<div className="location-map-editor-action-stack" role="group" aria-label="Map draft actions">
|
|
{primaryDraftActions}
|
|
{secondaryDraftActions}
|
|
</div>
|
|
) : null;
|
|
|
|
return (
|
|
<aside className="location-map-bottom-sheet">
|
|
<div className="location-map-bottom-sheet-handle" aria-hidden="true" />
|
|
<div className="location-map-sheet-header">
|
|
<div>
|
|
<div className="location-map-sheet-heading">
|
|
<strong>{sheetTitle}</strong>
|
|
{showHeaderItemCount ? (
|
|
<span className="location-map-sheet-count">{selectedItemCountLabel}</span>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<div className="location-map-layer-actions">
|
|
{changedLayerCount ? (
|
|
<button
|
|
type="button"
|
|
className="location-map-layer-reset"
|
|
onClick={resetLayerFilters}
|
|
aria-label="Reset layer filters"
|
|
>
|
|
Reset
|
|
</button>
|
|
) : null}
|
|
<button
|
|
type="button"
|
|
className={`location-map-layer-toggle ${layersOpen ? "active" : ""}`}
|
|
onClick={() => setLayersOpen((current) => !current)}
|
|
aria-expanded={layersOpen}
|
|
aria-label={changedLayerCount ? `Layers, ${changedLayerCount} changed` : "Layers"}
|
|
>
|
|
Layers
|
|
{changedLayerCount ? (
|
|
<span className="location-map-layer-count" aria-hidden="true">{changedLayerCount}</span>
|
|
) : null}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{layersOpen ? (
|
|
<LocationMapLayerPanel filters={filters} setFilters={setFilters} />
|
|
) : null}
|
|
|
|
{showSelectedObjectForm && primaryDraftActions ? (
|
|
<div
|
|
className="location-map-editor-action-stack location-map-selected-primary-actions"
|
|
role="group"
|
|
aria-label="Map draft actions"
|
|
>
|
|
{primaryDraftActions}
|
|
</div>
|
|
) : editorActions}
|
|
|
|
{showHiddenAreaAction ? (
|
|
<div className="location-map-hidden-areas">
|
|
<button type="button" onClick={onShowHiddenAreas} disabled={saving}>
|
|
{hiddenAreaLabel}
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
|
|
{showMapOverview ? (
|
|
<LocationMapOverview
|
|
ariaLabel="Map summary"
|
|
rows={[
|
|
{ label: "Areas", value: filters.showZones ? `${visibleAreaCount} shown` : "Hidden" },
|
|
{ label: "Mapped items", value: mappedItemSummary },
|
|
{ label: "Unmapped", value: unmappedItemSummary },
|
|
]}
|
|
/>
|
|
) : null}
|
|
|
|
{showDraftOverview ? (
|
|
<LocationMapOverview
|
|
ariaLabel="Draft map summary"
|
|
rows={[
|
|
{ label: "Areas", value: totalAreaCount },
|
|
{ label: "Hidden", value: hiddenAreaCount },
|
|
{ label: "Unlinked", value: unlinkedAreaCount },
|
|
]}
|
|
/>
|
|
) : null}
|
|
|
|
{mode === "edit" && editorTool === "edit" && selectedObject ? (
|
|
<SelectedMapObjectForm
|
|
selectedObject={selectedObject}
|
|
zones={mapState?.zones || EMPTY_ITEMS}
|
|
saving={saving}
|
|
onObjectField={onObjectField}
|
|
onZoneLinkChange={onZoneLinkChange}
|
|
onDuplicateObject={onDuplicateObject}
|
|
onDeleteObject={onDeleteObject}
|
|
/>
|
|
) : mode === "edit" ? (
|
|
null
|
|
) : selectedObject ? (
|
|
<SelectedZoneItemsPanel
|
|
selectedZoneItems={selectedZoneItems}
|
|
hasHiddenSelectedItems={hasHiddenSelectedItems}
|
|
hiddenSelectedItemCount={hiddenSelectedItemCount}
|
|
onShowSelectedZoneItems={showSelectedZoneItems}
|
|
/>
|
|
) : null}
|
|
|
|
{showSelectedObjectForm && secondaryDraftActions ? (
|
|
<div
|
|
className="location-map-editor-action-stack location-map-selected-secondary-actions"
|
|
role="group"
|
|
aria-label="More map draft actions"
|
|
>
|
|
{secondaryDraftActions}
|
|
</div>
|
|
) : null}
|
|
|
|
{filters.showUnmapped ? (
|
|
<UnmappedItemsPanel
|
|
visibleUnmappedItems={visibleUnmappedItems}
|
|
hiddenUnmappedItemCount={hiddenUnmappedItemCount}
|
|
hasHiddenUnmappedItems={hasHiddenUnmappedItems}
|
|
onShowUnmappedItems={showUnmappedItems}
|
|
/>
|
|
) : null}
|
|
</aside>
|
|
);
|
|
}
|