Skip to content

Map Edit View Sidebar: Points of Interest List

Goal

In edit mode, show every point of interest on the current map in the left sidebar's existing "Points of interest" section, grouped by type and paginated per group, so a GM can find and select one without hunting for its marker on the map.

Decisions

Decision Choice Why
Plan file location docs/v0.0.2/map-edit-sidebar-poi-list.md next release
List location Existing left sidebar "Points of interest" section in project-map.tsx, below the "Add point of interest" button Confirmed with user — no new sidebar section
Selection behavior Clicking a list entry calls the same handleSelectRequest(poiId) already wired to marker clicks Confirmed with user — one selection path, not a second one
Grouping By PoiType, in POI_TYPES order (from poi.ts); a type with zero POIs renders no heading Confirmed with user
Pagination Per group, 10 items per page, using a new shadcn pagination component Confirmed with user; no pagination component exists in app/components/ui/ yet
Empty state "No points of interest yet." replaces all group headings when pois is empty Confirmed with user
Grouping logic placement A pure helper in app/lib/poi.ts, not a .server.ts module No filesystem access involved — grouping/pagination is in-memory list shaping over data the loader already returned, so it doesn't cross the server boundary described in the project instructions

Scope

In:

Out:

  • Configurable/custom POI types or a type "group" field — separate backlog item ("Point of Interest Types")
  • Dragging POIs or highlighting the selected one on the map itself — separate backlog item ("Edit mode: allow dragging points of interest")
  • Search/filter controls over the list
  • Any change to addPointOfInterest / updatePointOfInterest / removePointOfInterest in projects.server.ts, or to the loader/action shape in project-map.tsx — pois is already returned today

Why this shape

  • The grouping helper is a small pure function colocated with PoiType/POI_TYPES in poi.ts, which is already the shared module both the route and map.tsx import from — no new shared module needed for one function.
  • Pagination is per-group (not whole-list) state, kept as local useState in project-map.tsx alongside the route's other mode-local state (placingPoi, editingPoiId, etc.) — it's view-only state, not data that needs to survive a navigation or be shared with Map.
  • Using the shadcn CLI for pagination matches the project convention of never hand-writing app/components/ui/*.

Tasks

Ordered so each task type-checks and is shippable on its own; later tasks depend on earlier ones.

T1 — Grouping helper

Goal: A single pure function turns a flat PointOfInterest[] into ordered, non-empty groups by type.

Acceptance criteria:

  • app/lib/poi.ts exports a function, e.g. groupPointsOfInterest(pois: PointOfInterest[]): { type: PoiType; items: PointOfInterest[] }[].
  • Groups are ordered per POI_TYPES; a type with no matching entries is omitted from the result entirely (no empty-array group).
  • Within a group, items preserve the order they appear in the input array.
  • pnpm typecheck passes.

T2 — Add the shadcn pagination component

Goal: A Pagination component is available for the sidebar to use.

Acceptance criteria:

  • pnpm dlx shadcn@latest add pagination has been run, producing app/components/ui/pagination.tsx unmodified from the CLI output.
  • pnpm typecheck passes.

T3 — Render the grouped, paginated list in the sidebar

Goal: The left sidebar's "Points of interest" section shows every POI on the map, grouped and paginated, selectable via the existing click path.

Acceptance criteria:

  • Below the existing "Add point of interest" button, the section renders the output of groupPointsOfInterest(pois) — one heading per group (group label is the PoiType value), followed by that group's items.
  • When pois is empty, the section shows the text "No points of interest yet." and no group headings.
  • Each item renders as a clickable row (name, and visually distinguished by type consistent with existing marker styling); clicking it calls handleSelectRequest(poi.id) — the same function already invoked by Map's onSelectRequest.
  • A group with more than 10 items paginates using the Pagination component from T2, 10 items per page; each group tracks its own current page (e.g. Record<PoiType, number> local state, default page 1).
  • If a group's item count drops (e.g. after a remove) such that the tracked current page no longer exists, the page state clamps to the last valid page rather than rendering an empty page.
  • The list re-renders with updated data after any add/update/remove fetcher submit, consistent with the existing revalidation-driven behavior of pois in this route — no new fetcher or loader change needed.
  • The "Add point of interest" button's position and the button's own behavior are unchanged.
  • pnpm typecheck passes.
  • Manual check: pnpm dev, open a map with more than 10 POIs of one type, confirm that type's group paginates and other groups render in full; confirm clicking a list entry opens it in the right-hand edit form.