From 4680aa254df440966e60385ca36c2fc5d2538e61 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sun, 12 Apr 2026 23:20:13 +0200 Subject: [PATCH] Fix map tooltips, journey creation, and contributor avatars - Map tooltips now respect light/dark mode via CSS variables - Journey creation inherits cover image from first selected trip - Only day-assigned places are synced to journey (no unplanned places) - Place count in trip picker reflects assigned places only - Contributor avatars shown in journey detail page - Suggestion banner button visible in dark mode (!important override) - Dashboard list view uses correct trips array and status label --- client/src/index.css | 4 ++-- client/src/pages/DashboardPage.tsx | 6 ++--- client/src/pages/JourneyDetailPage.tsx | 10 +++++--- client/src/pages/JourneyPage.tsx | 8 +++++-- server/src/routes/assignments.ts | 2 ++ server/src/services/journeyService.ts | 32 ++++++++++++++++++-------- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/client/src/index.css b/client/src/index.css index bb3d3f03..711bbb8b 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -299,7 +299,7 @@ body { /* ── iOS-style map tooltip ─────────────────────── */ .leaflet-tooltip.map-tooltip { - background: rgba(9, 9, 11, 0.85); + background: var(--tooltip-bg); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border: none; @@ -310,7 +310,7 @@ body { font-size: 11px; font-weight: 500; pointer-events: none; - color: #fff; + color: var(--text-primary); } .leaflet-tooltip.map-tooltip::before, .leaflet-tooltip-left.map-tooltip::before, diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index 830dae12..ebcf53b3 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -524,7 +524,7 @@ function TripListItem({ trip, onEdit, onCopy, onDelete, onArchive, onClick, t, l : status === 'today' ? t('dashboard.status.today') : status === 'tomorrow' ? t('dashboard.status.tomorrow') : status === 'future' ? t('dashboard.status.daysLeft', { count: daysUntil(trip.start_date) }) - : t('dashboard.status.past')} + : t('dashboard.mobile.completed')} )} @@ -1065,7 +1065,7 @@ export default function DashboardPage(): React.ReactElement { )} {/* Trips — desktop grid or list */} - {!isLoading && (viewMode === 'grid' ? rest : rest).length > 0 && ( + {!isLoading && (viewMode === 'grid' ? rest : trips).length > 0 && ( viewMode === 'grid' ? (
{rest.map(trip => ( @@ -1083,7 +1083,7 @@ export default function DashboardPage(): React.ReactElement {
) : (
- {rest.map(trip => ( + {trips.map(trip => ( {current.contributors.map((c: any) => (
-
- {(c.username || '?')[0].toUpperCase()} -
+ {c.avatar_url ? ( + + ) : ( +
+ {(c.username || '?')[0].toUpperCase()} +
+ )}
{c.username}
diff --git a/client/src/pages/JourneyPage.tsx b/client/src/pages/JourneyPage.tsx index 790849f5..564cdddd 100644 --- a/client/src/pages/JourneyPage.tsx +++ b/client/src/pages/JourneyPage.tsx @@ -158,7 +158,7 @@ export default function JourneyPage() { @@ -336,7 +336,11 @@ export default function JourneyPage() { }`}> {selected && }
-
+
+ {trip.cover_image && ( + + )} +
{trip.title}
diff --git a/server/src/routes/assignments.ts b/server/src/routes/assignments.ts index 6559fa69..ae7abef8 100644 --- a/server/src/routes/assignments.ts +++ b/server/src/routes/assignments.ts @@ -18,6 +18,7 @@ import { updateTime, setParticipants, } from '../services/assignmentService'; +import { onPlaceCreated } from '../services/journeyService'; import { AuthRequest } from '../types'; const router = express.Router({ mergeParams: true }); @@ -45,6 +46,7 @@ router.post('/trips/:tripId/days/:dayId/assignments', authenticate, requireTripA const assignment = createAssignment(dayId, place_id, notes); res.status(201).json({ assignment }); broadcast(tripId, 'assignment:created', { assignment }, req.headers['x-socket-id'] as string); + try { onPlaceCreated(Number(tripId), Number(place_id)); } catch {} }); router.delete('/trips/:tripId/days/:dayId/assignments/:id', authenticate, requireTripAccess, (req: Request, res: Response) => { diff --git a/server/src/services/journeyService.ts b/server/src/services/journeyService.ts index 18121ad9..75cc6e34 100644 --- a/server/src/services/journeyService.ts +++ b/server/src/services/journeyService.ts @@ -83,6 +83,14 @@ export function createJourney(userId: number, data: { for (const tripId of data.trip_ids) { addTripToJourney(journeyId, tripId, userId); } + + // inherit cover image from first selected trip + const firstTrip = db.prepare('SELECT cover_image FROM trips WHERE id = ?').get(data.trip_ids[0]) as { cover_image: string | null } | undefined; + if (firstTrip?.cover_image) { + // trip stores full path (/uploads/covers/x.jpg), journey stores relative (covers/x.jpg) + const relativePath = firstTrip.cover_image.replace(/^\/uploads\//, ''); + db.prepare('UPDATE journeys SET cover_image = ? WHERE id = ?').run(relativePath, journeyId); + } } return db.prepare('SELECT * FROM journeys WHERE id = ?').get(journeyId) as Journey; @@ -125,11 +133,15 @@ export function getJourneyFull(journeyId: number, userId: number) { `).all(journeyId); // contributors - const contributors = db.prepare(` + const contributorsRaw = db.prepare(` SELECT jc.journey_id, jc.user_id, jc.role, jc.added_at, u.username, u.avatar FROM journey_contributors jc JOIN users u ON jc.user_id = u.id WHERE jc.journey_id = ? ORDER BY jc.added_at - `).all(journeyId); + `).all(journeyId) as any[]; + const contributors = contributorsRaw.map(c => ({ + ...c, + avatar_url: c.avatar ? `/uploads/avatars/${c.avatar}` : null, + })); // stats const entryCount = entries.filter(e => e.type === 'entry').length; @@ -221,8 +233,8 @@ export function syncTripPlaces(journeyId: number, tripId: number, authorId: numb const places = db.prepare(` SELECT p.*, da.day_id, d.date as day_date, da.assignment_time, da.assignment_end_time, d.day_number FROM places p - LEFT JOIN day_assignments da ON da.place_id = p.id - LEFT JOIN days d ON da.day_id = d.id + INNER JOIN day_assignments da ON da.place_id = p.id + INNER JOIN days d ON da.day_id = d.id WHERE p.trip_id = ? ORDER BY d.day_number ASC, da.order_index ASC `).all(tripId) as any[]; @@ -303,11 +315,11 @@ export function onPlaceCreated(tripId: number, placeId: number) { const place = db.prepare(` SELECT p.*, da.day_id, d.date as day_date, da.assignment_time, d.day_number FROM places p - LEFT JOIN day_assignments da ON da.place_id = p.id - LEFT JOIN days d ON da.day_id = d.id + INNER JOIN day_assignments da ON da.place_id = p.id + INNER JOIN days d ON da.day_id = d.id WHERE p.id = ? `).get(placeId) as any; - if (!place) return; + if (!place) return; // not assigned to a day yet — skip const now = ts(); for (const link of links) { @@ -317,7 +329,7 @@ export function onPlaceCreated(tripId: number, placeId: number) { if (already) continue; const journey = db.prepare('SELECT user_id FROM journeys WHERE id = ?').get(link.journey_id) as { user_id: number }; - const entryDate = place.day_date || new Date().toISOString().split('T')[0]; + const entryDate = place.day_date; db.prepare(` INSERT INTO journey_entries (journey_id, source_trip_id, source_place_id, author_id, type, title, entry_date, entry_time, location_name, location_lat, location_lng, sort_order, created_at, updated_at) @@ -701,7 +713,7 @@ export function getSuggestions(userId: number) { const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; return db.prepare(` SELECT t.id, t.title, t.start_date, t.end_date, t.cover_image, - (SELECT COUNT(*) FROM places WHERE trip_id = t.id) as place_count + (SELECT COUNT(*) FROM places p INNER JOIN day_assignments da ON da.place_id = p.id WHERE p.trip_id = t.id) as place_count FROM trips t LEFT JOIN trip_members tm ON t.id = tm.trip_id AND tm.user_id = ? WHERE (t.user_id = ? OR tm.user_id = ?) @@ -718,7 +730,7 @@ export function getSuggestions(userId: number) { export function listUserTrips(userId: number) { return db.prepare(` SELECT t.id, t.title, t.start_date, t.end_date, t.cover_image, - (SELECT COUNT(*) FROM places WHERE trip_id = t.id) as place_count + (SELECT COUNT(*) FROM places p INNER JOIN day_assignments da ON da.place_id = p.id WHERE p.trip_id = t.id) as place_count FROM trips t LEFT JOIN trip_members tm ON t.id = tm.trip_id AND tm.user_id = ? WHERE t.user_id = ? OR tm.user_id = ?