feat(collections): detail redesign + categories, close-on-map, highlight fix (#1081)

- Rebuild the place detail as a clean, opaque, sectioned sheet (cover → meta →
  status segment → description → links) with a proper footer action bar — the
  loose "white lower half" is gone.
- Assign a place to a central (admin-defined) category, both in the detail edit
  and when adding a place; categories are fetched once for the page.
- Add-place now sets category + description (markdown) + links + status in the
  same step, closer to the trip's place form.
- Switching to the full-map view now closes the (list-docked) detail.
- Fix the selected-row highlight: it was clipped by the column's overflow — use
  an inset ring and only clip during the map-collapse animation; a picked row
  now scrolls into view above the detail sheet.
- New category strings across all 22 locales.
This commit is contained in:
Maurice
2026-07-01 12:34:35 +02:00
committed by Maurice
parent 0910c90e7f
commit f97e284592
27 changed files with 245 additions and 97 deletions
+9 -3
View File
@@ -48,7 +48,11 @@ export default function CollectionsPage(): React.ReactElement {
const mappable = mappablePlaces(c.visiblePlaces)
const openPlace = (id: number) => c.setSelectedPlaceId(c.selectedPlaceId === id ? null : id)
const deselect = () => c.setSelectedPlaceId(null)
const toggleView = () => c.setView(c.view === 'map' ? 'list' : 'map')
const toggleView = () => {
// Going to the full-map view closes the (list-docked) detail sheet.
if (c.view !== 'map') c.setSelectedPlaceId(null)
c.setView(c.view === 'map' ? 'list' : 'map')
}
// Clicking a marker in the full-map view drops back to the split so the list
// + detail come into view alongside the map.
const onMapSelect = (id: number) => { openPlace(id); if (c.view === 'map') c.setView('list') }
@@ -247,11 +251,12 @@ export default function CollectionsPage(): React.ReactElement {
visible + interactive). On the desktop split it docks over the list
column so the map stays clear; otherwise it's a full-width sheet.
Clicking another place / the map background re-points the selection. */}
{c.selectedPlace && (
{c.selectedPlace && c.view !== 'map' && (
<CollectionPlaceDetail
place={c.selectedPlace}
canEdit
anchorRect={desktopSplit && c.view === 'list' ? c.listColRect : null}
categories={c.categories}
anchorRect={desktopSplit ? c.listColRect : null}
onClose={c.handleCloseDetail}
onSetStatus={c.handleDetailStatus}
onSave={patch => c.updatePlace(c.selectedPlace!.id, patch)}
@@ -268,6 +273,7 @@ export default function CollectionsPage(): React.ReactElement {
isOpen={c.showAddPlace}
collectionId={c.activeId}
collectionName={c.activeCollection.name}
categories={c.categories}
onClose={() => c.setShowAddPlace(false)}
onAdded={c.handlePlaceAdded}
t={t}
@@ -9,6 +9,7 @@ import { getApiErrorMessage } from '../../types'
import { addListener, removeListener } from '../../api/websocket'
import { useCollectionStore, ALL_SAVED } from '../../store/collectionStore'
import type { ActiveCollectionId } from '../../store/collectionStore'
import { categoriesApi } from '../../api/client'
import type { Collection, CollectionStatus } from '@trek/shared'
import type { Category, Place } from '../../types'
import { filterPlaces, sortPlaces, statusCounts } from './collectionsModel'
@@ -70,6 +71,10 @@ export function useCollections() {
// Initial load.
useEffect(() => { loadAll() }, [])
// Central admin-defined categories, for assigning a category to a saved place.
const [categories, setCategories] = useState<Category[]>([])
useEffect(() => { categoriesApi.list().then((d: { categories: Category[] }) => setCategories(d.categories ?? [])).catch(() => {}) }, [])
// When the list↔map split toggles, its grid columns animate; keep nudging the
// mounted map to re-layout during the transition (Leaflet's trackResize hooks
// window resize; MapLibre observes its own box). Skip the initial mount — the
@@ -277,7 +282,7 @@ export function useCollections() {
return {
t, language, dark, navigate,
isWide, heroRef: hero.ref, heroHeight: hero.height,
listColRef: listCol.ref, listColRect: listCol.rect,
listColRef: listCol.ref, listColRect: listCol.rect, categories,
// store data
collections, ownedLists, sharedLists, activeCollection, isAllSaved, isOwner,
canShare, shareMemberCount,