fix(collections): pins on the map show their place's category (#3073)

The server nests a saved place's category, while the markers and the hover
card read the flat fields a trip place carries, so every pin on a
collection's map was the grey default. The map now hands its places on with
the category's colour, icon and name where the markers look for them.
This commit is contained in:
Maurice
2026-11-12 22:02:05 +01:00
parent 10d1a61614
commit f13a0aa6ee
3 changed files with 45 additions and 2 deletions
@@ -12,6 +12,7 @@ import L from 'leaflet';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { act, render, waitFor } from '../../../tests/helpers/render';
import { resetAllStores } from '../../../tests/helpers/store';
import { withMarkerCategory } from '../../pages/collections/collectionsModel';
import CollectionMap from './CollectionMap';
vi.mock('../../hooks/useGeolocation', () => ({
@@ -258,3 +259,26 @@ describe('CollectionMap framing', () => {
expect(wrappedIn(map, '[--bottom-nav-h:0px]')).toBe(true);
});
});
describe('CollectionMap pins (#3073)', () => {
const MUSEUM = { id: 4, name: 'Museum', color: '#7c3aed', icon: 'Landmark' };
it('FE-COMP-COLMAP-008: a saved place carries its category where the markers read it', () => {
const pin = withMarkerCategory({ ...place(1, 'Belvedere', 48.1915, 16.3809), category_id: 4, category: MUSEUM });
expect(pin).toMatchObject({ category_name: 'Museum', category_color: '#7c3aed', category_icon: 'Landmark' });
expect(withMarkerCategory(place(2, 'Somewhere', 48.2, 16.37))).toMatchObject({
category_name: null,
category_color: null,
category_icon: null,
});
});
it('FE-COMP-COLMAP-009: the pin of a place with a category wears its colour, not the grey default', async () => {
const { view } = await renderMap([
{ ...place(1, 'Belvedere', 48.1915, 16.3809), category_id: 4, category: MUSEUM },
]);
await waitFor(() => expect(view.container.querySelector('.leaflet-marker-icon')).not.toBeNull());
const html = view.container.querySelector('.leaflet-marker-icon')?.innerHTML ?? '';
expect(html.toLowerCase()).toContain('#7c3aed');
});
});
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react'
import { MapViewAuto } from '../Map/MapViewAuto'
import type { CollectionPlace } from '@trek/shared'
import { mappablePlaces } from '../../pages/collections/collectionsModel'
import { mappablePlaces, withMarkerCategory } from '../../pages/collections/collectionsModel'
import { useTileUrl } from '../../hooks/useTileUrl'
import { OFM_DARK, OFM_POSITRON } from '../../constants/mapDefaults'
import type { ViewportPadding } from '../../utils/mapViewport'
@@ -70,7 +70,7 @@ function framePoints(places: CollectionPlace[]): [number, number][] {
* while the map is built rather than flying in.
*/
export default function CollectionMap({ places, selectedPlaceId, onOpenPlace, onDeselect, dark, underBar = false }: CollectionMapProps): React.ReactElement {
const pts = useMemo(() => mappablePlaces(places), [places])
const pts = useMemo(() => mappablePlaces(places).map(withMarkerCategory), [places])
const tileUrl = useTileUrl(dark ? OFM_DARK : OFM_POSITRON)
const focusPoints = useMemo(() => framePoints(pts), [pts])
// The places as a set: another list, a filter or a new place frames again, while a
@@ -93,6 +93,25 @@ export function mappablePlaces(places: CollectionPlace[]): CollectionPlace[] {
return places.filter(p => p && typeof p.lat === 'number' && typeof p.lng === 'number')
}
/**
* A saved place with its category where the trip map reads it. The server nests a
* collection place's category (`category.color`), the markers and the hover card
* read the flat fields a trip place carries, so every pin here was the grey default
* (#3073).
*/
export function withMarkerCategory(place: CollectionPlace): CollectionPlace & {
category_name: string | null
category_color: string | null
category_icon: string | null
} {
return {
...place,
category_name: place.category?.name ?? null,
category_color: place.category?.color ?? null,
category_icon: place.category?.icon ?? null,
}
}
/**
* Normalise a user-typed link: prepend https:// when there's no scheme so the
* href is absolute (a bare "booking.com" would otherwise resolve relative to the