mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-30 18:46:00 +00:00
fix(pdf): show photos for OSM places in the trip PDF (#1130)
The PDF photo pre-fetch only fired for places with a google_place_id, so OSM/Nominatim places (osm_id only) fell back to category icons even though they show photos in-app. Recover osm_id from the full places pool (the assignment projection drops it) and key the photo off google_place_id || osm_id || coords, matching the UI.
This commit is contained in:
@@ -323,6 +323,28 @@ describe('downloadTripPDF', () => {
|
|||||||
expect(photoCalled).toBe(true)
|
expect(photoCalled).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('FE-COMP-TRIPPDF-019b: fetches photos for OSM places via osm_id recovered from the places pool (#1130)', async () => {
|
||||||
|
let fetchedId: string | null = null
|
||||||
|
server.use(
|
||||||
|
http.get('/api/maps/place-photo/:placeId', ({ params }) => {
|
||||||
|
fetchedId = params.placeId as string
|
||||||
|
return HttpResponse.json({ photoUrl: 'https://example.com/osm.jpg' })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// The assignment projection drops osm_id; the full place in `places` carries it.
|
||||||
|
const osmPlace = { ...placeWithDetails, id: 101, image_url: null, google_place_id: null, osm_id: 'node/240109189', lat: 41.89, lng: 12.49 }
|
||||||
|
const args = {
|
||||||
|
...richArgs,
|
||||||
|
places: [osmPlace],
|
||||||
|
assignments: {
|
||||||
|
'10': [{ ...assignmentForDay, id: 201, place_id: 101, place: { ...placeWithDetails, id: 101, image_url: null, google_place_id: null } }],
|
||||||
|
} as any,
|
||||||
|
}
|
||||||
|
await downloadTripPDF(args)
|
||||||
|
// osm_id is used as the photo key (not the coords fallback), proving the pool lookup works.
|
||||||
|
expect(fetchedId).toBe('node/240109189')
|
||||||
|
})
|
||||||
|
|
||||||
it('FE-COMP-TRIPPDF-020: renders empty day message when no items assigned', async () => {
|
it('FE-COMP-TRIPPDF-020: renders empty day message when no items assigned', async () => {
|
||||||
const args = {
|
const args = {
|
||||||
...minimalArgs,
|
...minimalArgs,
|
||||||
|
|||||||
@@ -97,21 +97,29 @@ function dayCost(assignments, dayId, locale) {
|
|||||||
return total > 0 ? `${total.toLocaleString(locale)} EUR` : null
|
return total > 0 ? `${total.toLocaleString(locale)} EUR` : null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-fetch Google Place photos for all assigned places
|
// Pre-fetch place photos for all assigned places.
|
||||||
async function fetchPlacePhotos(assignments: AssignmentsMap) {
|
// Assignment places are a server-side projection that drops osm_id, so we recover
|
||||||
|
// the full place from the trip's places pool and key the photo off the same id the
|
||||||
|
// app UI uses (google_place_id || osm_id || coords) — otherwise OSM/coords-only
|
||||||
|
// places fell back to category icons in the PDF even though they show photos in-app.
|
||||||
|
async function fetchPlacePhotos(assignments: AssignmentsMap, places: Place[]) {
|
||||||
const photoMap = {} // placeId → photoUrl
|
const photoMap = {} // placeId → photoUrl
|
||||||
|
// The assignment projection drops osm_id, so recover it from the full places pool.
|
||||||
|
const osmById = new Map((places || []).map(p => [p.id, p.osm_id]))
|
||||||
const allPlaces = Object.values(assignments).flatMap(a => a.map(x => x.place)).filter(Boolean)
|
const allPlaces = Object.values(assignments).flatMap(a => a.map(x => x.place)).filter(Boolean)
|
||||||
const unique = [...new Map(allPlaces.map(p => [p.id, p])).values()]
|
const unique = [...new Map(allPlaces.map(p => [p.id, p])).values()]
|
||||||
|
|
||||||
// Assignment places are a server-side projection that omits osm_id, so photo
|
const toFetch = unique
|
||||||
// pre-fetch keys off the google_place_id that the projection does carry.
|
.map(p => ({ p, osm_id: osmById.get(p.id) }))
|
||||||
const toFetch = unique.filter(p => !p.image_url && p.google_place_id)
|
.filter(({ p, osm_id }) => !p.image_url && (p.google_place_id || osm_id || (p.lat != null && p.lng != null)))
|
||||||
|
|
||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
toFetch.map(async (place) => {
|
toFetch.map(async ({ p, osm_id }) => {
|
||||||
|
// Same key the app UI uses: google_place_id || osm_id || coords.
|
||||||
|
const photoId = p.google_place_id || osm_id || `coords:${p.lat}:${p.lng}`
|
||||||
try {
|
try {
|
||||||
const data = await mapsApi.placePhoto(place.google_place_id, place.lat, place.lng, place.name)
|
const data = await mapsApi.placePhoto(photoId, p.lat, p.lng, p.name)
|
||||||
if (data.photoUrl) photoMap[place.id] = data.photoUrl
|
if (data.photoUrl) photoMap[p.id] = data.photoUrl
|
||||||
} catch {}
|
} catch {}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -141,8 +149,8 @@ export async function downloadTripPDF({ trip, days, places, assignments, categor
|
|||||||
//retrieve accommodations for the trip to display on the day sections and prefetch their photos if needed
|
//retrieve accommodations for the trip to display on the day sections and prefetch their photos if needed
|
||||||
const accommodations = await accommodationsApi.list(trip.id);
|
const accommodations = await accommodationsApi.list(trip.id);
|
||||||
|
|
||||||
// Pre-fetch place photos from Google
|
// Pre-fetch place photos (Google, OSM and coords-only places)
|
||||||
const photoMap = await fetchPlacePhotos(assignments)
|
const photoMap = await fetchPlacePhotos(assignments, places)
|
||||||
|
|
||||||
const totalAssigned = new Set(
|
const totalAssigned = new Set(
|
||||||
Object.values(assignments || {}).flatMap(a => a.map(x => x.place?.id)).filter(Boolean)
|
Object.values(assignments || {}).flatMap(a => a.map(x => x.place?.id)).filter(Boolean)
|
||||||
|
|||||||
Reference in New Issue
Block a user