v2.5.7: Reservation overhaul, Day Detail Panel, i18n, paste support, auto dark mode

BREAKING: Reservations have been completely rebuilt. Existing place-level
reservations are no longer used. All reservations must be re-created via
the Bookings tab. Your trips, places, and other data are unaffected.

Reservation System (rebuilt from scratch):
- Reservations now link to specific day assignments instead of places
- Same place on different days can have independent reservations
- New assignment picker in booking modal (grouped by day, searchable)
- Removed day/place dropdowns from booking form
- Reservation badges in day plan sidebar with type-specific icons
- Reservation details in place inspector (only for selected assignment)
- Reservation summary in day detail panel

Day Detail Panel (new):
- Opens on day click in the sidebar
- Detailed weather: hourly forecast, precipitation, wind, sunrise/sunset
- Historical climate averages for dates beyond 16 days
- Accommodation management with check-in/check-out, confirmation number
- Hotel assignment across multiple days with day range picker
- Reservation overview for the day

Places:
- Places can now be assigned to the same day multiple times
- Start time + end time fields (replaces single time field)
- Map badges show multiple position numbers (e.g. "1 · 4")
- Route optimization fixed for duplicate places
- File attachments during place editing (not just creation)
- Cover image upload during trip creation (not just editing)
- Paste support (Ctrl+V) for images in trip, place, and file forms

Internationalization:
- 200+ hardcoded German strings translated to i18n (EN + DE)
- Server error messages in English
- Category seeds in English for new installations
- All planner, register, photo, packing components translated

UI/UX:
- Auto dark mode (follows system preference, configurable in settings)
- Navbar toggle switches light/dark (overrides auto)
- Sidebar minimize buttons z-index fixed
- Transport mode selector removed from day plan
- CustomSelect supports grouped headers (isHeader option)
- Optimistic updates for day notes (instant feedback)
- Booking cards redesigned with type-colored headers and structured details

Weather:
- Wind speed in mph when using Fahrenheit setting
- Weather description language matches app language

Admin:
- Weather info panel replaces OpenWeatherMap key input
- "Recommended" badge styling updated
This commit is contained in:
Maurice
2026-03-24 20:10:45 +01:00
parent e4607e426c
commit 0497032ed7
67 changed files with 2390 additions and 1322 deletions

View File

@@ -67,7 +67,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {} }) {
onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--text-faint)'}
onMouseLeave={e => { if (!open) e.currentTarget.style.borderColor = 'var(--border-primary)' }}>
<Calendar size={14} style={{ color: 'var(--text-faint)', flexShrink: 0 }} />
<span>{displayValue || placeholder || t('common.date')}</span>
<span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{displayValue || placeholder || t('common.date')}</span>
</button>
{open && ReactDOM.createPortal(

View File

@@ -51,7 +51,7 @@ export default function CustomSelect({
background: 'var(--bg-input)', color: 'var(--text-primary)',
fontSize: 13, fontWeight: 500, fontFamily: 'inherit',
cursor: 'pointer', outline: 'none', textAlign: 'left',
transition: 'border-color 0.15s',
transition: 'border-color 0.15s', overflow: 'hidden', minWidth: 0,
}}
onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--text-faint)'}
onMouseLeave={e => { if (!open) e.currentTarget.style.borderColor = 'var(--border-primary)' }}
@@ -105,6 +105,17 @@ export default function CustomSelect({
<div style={{ padding: '10px 12px', fontSize: 12, color: 'var(--text-faint)', textAlign: 'center' }}></div>
) : (
filtered.map(option => {
if (option.isHeader) {
return (
<div key={option.value} style={{
padding: '5px 10px', fontSize: 10, fontWeight: 700, color: 'var(--text-faint)',
textTransform: 'uppercase', letterSpacing: '0.03em',
background: 'var(--bg-tertiary)', borderRadius: 4, margin: '2px 0',
}}>
{option.label}
</div>
)
}
const isSelected = option.value === value
return (
<button

View File

@@ -25,17 +25,17 @@ export const CATEGORY_ICON_MAP = {
}
export const ICON_LABELS = {
MapPin: 'Pin', Building2: 'Gebäude', BedDouble: 'Hotel', UtensilsCrossed: 'Restaurant',
Landmark: 'Sehenswürdigkeit', ShoppingBag: 'Shopping', Bus: 'Bus', Train: 'Zug',
Car: 'Auto', Plane: 'Flugzeug', Ship: 'Schiff', Bike: 'Fahrrad',
Activity: 'Aktivität', Dumbbell: 'Fitness', Mountain: 'Berg', Tent: 'Camping',
Anchor: 'Hafen', Coffee: 'Café', Beer: 'Bar', Wine: 'Wein', Utensils: 'Essen',
Camera: 'Foto', Music: 'Musik', Theater: 'Theater', Ticket: 'Events',
TreePine: 'Natur', Waves: 'Strand', Leaf: 'Grün', Flower2: 'Garten', Sun: 'Sonne',
Globe: 'Welt', Compass: 'Erkundung', Flag: 'Flagge', Navigation: 'Navigation', Map: 'Karte',
Church: 'Kirche', Library: 'Museum', Store: 'Markt', Home: 'Unterkunft', Cross: 'Medizin',
Heart: 'Favorit', Star: 'Top', CreditCard: 'Bank', Wifi: 'Internet',
Luggage: 'Gepäck', Backpack: 'Rucksack', Zap: 'Abenteuer',
MapPin: 'Pin', Building2: 'Building', BedDouble: 'Hotel', UtensilsCrossed: 'Restaurant',
Landmark: 'Attraction', ShoppingBag: 'Shopping', Bus: 'Bus', Train: 'Train',
Car: 'Car', Plane: 'Plane', Ship: 'Ship', Bike: 'Bicycle',
Activity: 'Activity', Dumbbell: 'Fitness', Mountain: 'Mountain', Tent: 'Camping',
Anchor: 'Harbor', Coffee: 'Cafe', Beer: 'Bar', Wine: 'Wine', Utensils: 'Food',
Camera: 'Photo', Music: 'Music', Theater: 'Theater', Ticket: 'Events',
TreePine: 'Nature', Waves: 'Beach', Leaf: 'Green', Flower2: 'Garden', Sun: 'Sun',
Globe: 'World', Compass: 'Explore', Flag: 'Flag', Navigation: 'Navigation', Map: 'Map',
Church: 'Church', Library: 'Museum', Store: 'Market', Home: 'Accommodation', Cross: 'Medicine',
Heart: 'Favorite', Star: 'Top', CreditCard: 'Bank', Wifi: 'Internet',
Luggage: 'Luggage', Backpack: 'Backpack', Zap: 'Adventure',
}
export function getCategoryIcon(iconName) {