From 525dc6ebd24a9771cacfc3f83f28e946c9ebbd24 Mon Sep 17 00:00:00 2001 From: Maurice Date: Wed, 8 Apr 2026 17:38:31 +0200 Subject: [PATCH 1/2] fix: budget member avatars lost after updating item fields loadItemMembers was returning raw avatar field without mapping to avatar_url, causing avatars to disappear when editing days/persons/etc. --- server/src/services/budgetService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/services/budgetService.ts b/server/src/services/budgetService.ts index 36927c2e..8fc82240 100644 --- a/server/src/services/budgetService.ts +++ b/server/src/services/budgetService.ts @@ -14,12 +14,13 @@ export function verifyTripAccess(tripId: string | number, userId: number) { } function loadItemMembers(itemId: number | string) { - return db.prepare(` + const rows = db.prepare(` SELECT bm.user_id, bm.paid, u.username, u.avatar FROM budget_item_members bm JOIN users u ON bm.user_id = u.id WHERE bm.budget_item_id = ? `).all(itemId) as BudgetItemMember[]; + return rows.map(m => ({ ...m, avatar_url: avatarUrl(m) })); } // --------------------------------------------------------------------------- From 741a8d3f09f78c2b69ffb684b2a2ab664dda8f85 Mon Sep 17 00:00:00 2001 From: Maurice Date: Wed, 8 Apr 2026 17:48:29 +0200 Subject: [PATCH 2/2] feat: collapsible day detail panel in planner Adds a collapse/expand toggle to the day detail panel header. Collapsed state persists across day switches. Clicking the header or the chevron button toggles between compact header-only view and the full detail panel. Closes #457 --- .../src/components/Planner/DayDetailPanel.tsx | 34 +++++++++++++------ client/src/pages/TripPlannerPage.tsx | 3 ++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/client/src/components/Planner/DayDetailPanel.tsx b/client/src/components/Planner/DayDetailPanel.tsx index 49cd6ff8..e841abde 100644 --- a/client/src/components/Planner/DayDetailPanel.tsx +++ b/client/src/components/Planner/DayDetailPanel.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react' import ReactDOM from 'react-dom' -import { X, Sun, Cloud, CloudRain, CloudSnow, CloudDrizzle, CloudLightning, Wind, Droplets, Sunrise, Sunset, Hotel, Calendar, MapPin, LogIn, LogOut, Hash, Pencil, Plane, Utensils, Train, Car, Ship, Ticket, FileText, Users } from 'lucide-react' +import { X, Sun, Cloud, CloudRain, CloudSnow, CloudDrizzle, CloudLightning, Wind, Droplets, Sunrise, Sunset, Hotel, Calendar, MapPin, LogIn, LogOut, Hash, Pencil, Plane, Utensils, Train, Car, Ship, Ticket, FileText, Users, ChevronsDown, ChevronsUp } from 'lucide-react' const RES_TYPE_ICONS = { flight: Plane, hotel: Hotel, restaurant: Utensils, train: Train, car: Car, cruise: Ship, event: Ticket, tour: Users, other: FileText } const RES_TYPE_COLORS = { flight: '#3b82f6', hotel: '#8b5cf6', restaurant: '#ef4444', train: '#06b6d4', car: '#6b7280', cruise: '#0ea5e9', event: '#f59e0b', tour: '#10b981', other: '#6b7280' } @@ -54,9 +54,11 @@ interface DayDetailPanelProps { onAccommodationChange: () => void leftWidth?: number rightWidth?: number + collapsed?: boolean + onToggleCollapse?: () => void } -export default function DayDetailPanel({ day, days, places, categories = [], tripId, assignments, reservations = [], lat, lng, onClose, onAccommodationChange, leftWidth = 0, rightWidth = 0 }: DayDetailPanelProps) { +export default function DayDetailPanel({ day, days, places, categories = [], tripId, assignments, reservations = [], lat, lng, onClose, onAccommodationChange, leftWidth = 0, rightWidth = 0, collapsed: collapsedProp = false, onToggleCollapse }: DayDetailPanelProps) { const { t, language, locale } = useTranslation() const can = useCanDo() const tripObj = useTripStore((s) => s.trip) @@ -66,6 +68,8 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri const blurCodes = useSettingsStore(s => s.settings.blur_booking_codes) const fmtTime = (v) => formatTime12(v, is12h) const unit = isFahrenheit ? '°F' : '°C' + const collapsed = collapsedProp + const toggleCollapse = () => onToggleCollapse?.() const [weather, setWeather] = useState(null) const [loading, setLoading] = useState(false) const [accommodation, setAccommodation] = useState(null) @@ -170,26 +174,36 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri WebkitBackdropFilter: 'blur(40px) saturate(180%)', borderRadius: 20, boxShadow: '0 8px 40px rgba(0,0,0,0.14), 0 0 0 1px rgba(0,0,0,0.06)', - overflow: 'hidden', maxHeight: '60vh', display: 'flex', flexDirection: 'column', + overflow: 'hidden', maxHeight: collapsed ? 'none' : '60vh', display: 'flex', flexDirection: 'column', }}> {/* Header */} -
-
- +
toggleCollapse()}> +
+
-
+
{day.title || t('planner.dayN', { n: (days.indexOf(day) + 1) || '?' })} + {collapsed && formattedDate && {formattedDate}}
- {formattedDate &&
{formattedDate}
} + {!collapsed && formattedDate &&
{formattedDate}
}
- +
{/* Scrollable content */} -
+
{/* ── Weather ── */} {day.date && lat && lng && ( diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index 365131ee..139c05cb 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -154,6 +154,7 @@ export default function TripPlannerPage(): React.ReactElement | null { const { leftWidth, rightWidth, leftCollapsed, rightCollapsed, setLeftCollapsed, setRightCollapsed, startResizeLeft, startResizeRight } = useResizablePanels() const { selectedPlaceId, selectedAssignmentId, setSelectedPlaceId, selectAssignment } = usePlaceSelection() const [showDayDetail, setShowDayDetail] = useState(null) + const [dayDetailCollapsed, setDayDetailCollapsed] = useState(false) const [showPlaceForm, setShowPlaceForm] = useState(false) const [editingPlace, setEditingPlace] = useState(null) const [prefillCoords, setPrefillCoords] = useState<{ lat: number; lng: number; name?: string; address?: string } | null>(null) @@ -766,6 +767,8 @@ export default function TripPlannerPage(): React.ReactElement | null { onAccommodationChange={loadAccommodations} leftWidth={isMobile ? 0 : (leftCollapsed ? 0 : leftWidth)} rightWidth={isMobile ? 0 : (rightCollapsed ? 0 : rightWidth)} + collapsed={dayDetailCollapsed} + onToggleCollapse={() => setDayDetailCollapsed(c => !c)} /> ) })()}