diff --git a/client/src/components/Tours/poi/TourPoiSearchCard.tsx b/client/src/components/Tours/poi/TourPoiSearchCard.tsx
index b5871b654..497c64839 100644
--- a/client/src/components/Tours/poi/TourPoiSearchCard.tsx
+++ b/client/src/components/Tours/poi/TourPoiSearchCard.tsx
@@ -1,6 +1,7 @@
import type { TourPoi, WalkTheme } from '@trek/shared';
import { TOUR_POI_CORRIDORS_M } from '@trek/shared';
import {
+ Check,
ChevronDown,
Coffee,
Landmark,
@@ -22,13 +23,14 @@ import { useTourPoiStore } from '../../../store/tourPoiStore';
import { formatDistance } from '../../../utils/units';
import { TOUR_POI_LOOKS } from '../../Map/poiCategories';
import { PopoverItem } from '../../Packing/PackingPopover';
-import { WhiteBadge } from '../../Planner/planParts';
+import { SoftPill, WhiteBadge } from '../../Planner/planParts';
import { PopoverListPanel, Segmented } from '../../shared/dialogParts';
import { fs } from '../../shared/DialogShell';
import { usePopoverList } from '../../shared/usePopoverList';
import type { TourPlannerController } from '../planner/useTourPlanner';
import { RailCard, TourNotice } from '../tourParts';
import { formatPlannedTourDuration } from '../tourPresentation';
+import { placesInTour } from './poiInsertion';
import { SearchNotices } from './TourPoiSearchNotices';
import { usePoiInsertion, type InsertionOutcome } from './usePoiInsertion';
import { useSelectedPoi, useTourPoiSearch, type TourPoiSearch } from './useTourPoiSearch';
@@ -155,7 +157,7 @@ function ThemeGrid({
* and how far it lies from the route. A click opens its card over the map,
* where the rest is said and the place is taken in as a stop.
*/
-function PoiRow({ poi, selected, unit }: { poi: TourPoi; selected: boolean; unit: Unit }) {
+function PoiRow({ poi, selected, inTour, unit }: { poi: TourPoi; selected: boolean; inTour: boolean; unit: Unit }) {
const { t } = useTranslation();
const select = useTourPoiStore((state) => state.select);
const look = TOUR_POI_LOOKS[poi.category];
@@ -191,16 +193,25 @@ function PoiRow({ poi, selected, unit }: { poi: TourPoi; selected: boolean; unit
)}
-
+
{poi.name || t(look.labelKey)}
-
- {distance}
-
+ {inTour ? (
+ }>
+ {t('tours.placePicker.inTour')}
+
+ ) : (
+
+ {distance}
+
+ )}
);
@@ -288,6 +299,7 @@ export function TourPoiSearchCard({ planner }: { planner: TourPlannerController
const insertion = usePoiInsertion(planner);
const selected = useSelectedPoi();
const pois = search.answer?.pois ?? [];
+ const inTour = placesInTour(pois, planner.waypoints);
if (!planner.route || planner.route.length < 2) return null;
return (
{pois.map((poi) => (
-
+
))}
)}
diff --git a/client/src/components/Tours/poi/poiInsertion.test.ts b/client/src/components/Tours/poi/poiInsertion.test.ts
index 9ff26685c..8b0ca1037 100644
--- a/client/src/components/Tours/poi/poiInsertion.test.ts
+++ b/client/src/components/Tours/poi/poiInsertion.test.ts
@@ -6,6 +6,7 @@ import {
insertionBlock,
insertionPreview,
insertionSlots,
+ placesInTour,
straightDetourM,
withPointAfter,
} from './poiInsertion';
@@ -156,4 +157,13 @@ describe('the slots a found place can go into (PLN-04)', () => {
expect(chosenAfterId([A, B, C], null, place, { poiId: 'node:1', afterId: 'gone' })).toBe('b');
expect(chosenAfterId([A, B, C], null, place, null)).toBe('b');
});
+
+ it('FE-TOUR-POI-INSERT-010: tells the places that are a point of the tour already by their spot', () => {
+ const places = [
+ { id: 'node:1', lat: B.lat, lng: B.lng },
+ { id: 'node:2', lat: B.lat + 0.001, lng: B.lng },
+ ];
+ expect([...placesInTour(places, [A, B, C])]).toEqual(['node:1']);
+ expect(placesInTour(places, [A, C]).size).toBe(0);
+ });
});
diff --git a/client/src/components/Tours/poi/poiInsertion.ts b/client/src/components/Tours/poi/poiInsertion.ts
index 3c4147c29..dfcf49960 100644
--- a/client/src/components/Tours/poi/poiInsertion.ts
+++ b/client/src/components/Tours/poi/poiInsertion.ts
@@ -125,3 +125,14 @@ export function straightDetourM(line: ReadonlyArray<[number, number]>): number {
const replaced = line.length > 2 && first && last ? legs([first, last]) : 0;
return Math.max(0, legs(line) - replaced);
}
+
+/** The places of a search that are a point of the tour already, told by where they lie: a taken place keeps its spot. */
+export function placesInTour(
+ places: ReadonlyArray<{ id: string; lat: number; lng: number }>,
+ points: readonly RoutePoint[]
+): Set {
+ const spots = new Set(points.map((point) => `${point.lat.toFixed(6)},${point.lng.toFixed(6)}`));
+ return new Set(
+ places.filter((place) => spots.has(`${place.lat.toFixed(6)},${place.lng.toFixed(6)}`)).map((place) => place.id)
+ );
+}