mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
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:
@@ -3,8 +3,10 @@ import { PhotoLightbox } from './PhotoLightbox'
|
||||
import { PhotoUpload } from './PhotoUpload'
|
||||
import { Upload, Camera } from 'lucide-react'
|
||||
import Modal from '../shared/Modal'
|
||||
import { useTranslation } from '../../i18n'
|
||||
|
||||
export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, places, days, tripId }) {
|
||||
const { t } = useTranslation()
|
||||
const [lightboxIndex, setLightboxIndex] = useState(null)
|
||||
const [showUpload, setShowUpload] = useState(false)
|
||||
const [filterDayId, setFilterDayId] = useState('')
|
||||
@@ -49,7 +51,7 @@ export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, pla
|
||||
onChange={e => setFilterDayId(e.target.value)}
|
||||
className="border border-gray-200 rounded-lg px-3 py-1.5 text-sm text-gray-600 focus:outline-none focus:ring-2 focus:ring-slate-900"
|
||||
>
|
||||
<option value="">Alle Tage</option>
|
||||
<option value="">{t('photos.allDays')}</option>
|
||||
{(days || []).map(day => (
|
||||
<option key={day.id} value={day.id}>
|
||||
Tag {day.day_number}{day.date ? ` · ${formatDate(day.date)}` : ''}
|
||||
@@ -62,7 +64,7 @@ export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, pla
|
||||
onClick={() => setFilterDayId('')}
|
||||
className="text-xs text-gray-500 hover:text-gray-700 underline"
|
||||
>
|
||||
Zurücksetzen
|
||||
{t('common.reset')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -80,8 +82,8 @@ export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, pla
|
||||
{filteredPhotos.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '60px 20px', color: '#9ca3af' }}>
|
||||
<Camera size={40} style={{ color: '#d1d5db', display: 'block', margin: '0 auto 12px' }} />
|
||||
<p style={{ fontSize: 14, fontWeight: 600, color: '#374151', margin: '0 0 4px' }}>Noch keine Fotos</p>
|
||||
<p style={{ fontSize: 13, color: '#9ca3af', margin: '0 0 20px' }}>Lade deine Reisefotos hoch</p>
|
||||
<p style={{ fontSize: 14, fontWeight: 600, color: '#374151', margin: '0 0 4px' }}>{t('photos.noPhotos')}</p>
|
||||
<p style={{ fontSize: 13, color: '#9ca3af', margin: '0 0 20px' }}>{t('photos.uploadHint')}</p>
|
||||
<button
|
||||
onClick={() => setShowUpload(true)}
|
||||
className="flex items-center gap-2 bg-slate-900 text-white px-6 py-3 rounded-xl hover:bg-slate-700 font-medium"
|
||||
@@ -109,7 +111,7 @@ export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, pla
|
||||
className="aspect-square rounded-xl border-2 border-dashed border-gray-200 hover:border-slate-400 flex flex-col items-center justify-center gap-2 text-gray-400 hover:text-slate-700 transition-colors"
|
||||
>
|
||||
<Upload className="w-6 h-6" />
|
||||
<span className="text-xs">Hinzufügen</span>
|
||||
<span className="text-xs">{t('common.add')}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { X, ChevronLeft, ChevronRight, Edit2, Trash2, Check } from 'lucide-react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
|
||||
export function PhotoLightbox({ photos, initialIndex, onClose, onUpdate, onDelete, days, places, tripId }) {
|
||||
const { t } = useTranslation()
|
||||
const [index, setIndex] = useState(initialIndex || 0)
|
||||
const [editCaption, setEditCaption] = useState(false)
|
||||
const [caption, setCaption] = useState('')
|
||||
@@ -81,7 +83,7 @@ export function PhotoLightbox({ photos, initialIndex, onClose, onUpdate, onDelet
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="p-2 text-white/60 hover:text-red-400 hover:bg-white/10 rounded-lg transition-colors"
|
||||
title="Löschen"
|
||||
title={t('common.delete')}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
import { Upload, X, Image } from 'lucide-react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
|
||||
export function PhotoUpload({ tripId, days, places, onUpload, onClose }) {
|
||||
const { t } = useTranslation()
|
||||
const [files, setFiles] = useState([])
|
||||
const [dayId, setDayId] = useState('')
|
||||
const [placeId, setPlaceId] = useState('')
|
||||
@@ -78,7 +80,7 @@ export function PhotoUpload({ tripId, days, places, onUpload, onClose }) {
|
||||
) : (
|
||||
<>
|
||||
<p className="text-gray-600 font-medium">Fotos hier ablegen</p>
|
||||
<p className="text-gray-400 text-sm mt-1">oder klicken zum Auswählen</p>
|
||||
<p className="text-gray-400 text-sm mt-1">{t('photos.clickToSelect')}</p>
|
||||
<p className="text-gray-400 text-xs mt-2">JPG, PNG, WebP · max. 10 MB · bis zu 30 Fotos</p>
|
||||
</>
|
||||
)}
|
||||
@@ -128,13 +130,13 @@ export function PhotoUpload({ tripId, days, places, onUpload, onClose }) {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Ort verknüpfen</label>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">{t('photos.linkPlace')}</label>
|
||||
<select
|
||||
value={placeId}
|
||||
onChange={e => setPlaceId(e.target.value)}
|
||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-slate-900"
|
||||
>
|
||||
<option value="">Kein Ort</option>
|
||||
<option value="">{t('photos.noPlace')}</option>
|
||||
{(places || []).map(place => (
|
||||
<option key={place.id} value={place.id}>{place.name}</option>
|
||||
))}
|
||||
@@ -175,7 +177,7 @@ export function PhotoUpload({ tripId, days, places, onUpload, onClose }) {
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm text-gray-600 border border-gray-200 rounded-lg hover:bg-gray-50"
|
||||
>
|
||||
Abbrechen
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
@@ -183,7 +185,7 @@ export function PhotoUpload({ tripId, days, places, onUpload, onClose }) {
|
||||
className="flex items-center gap-2 px-6 py-2 bg-slate-900 text-white text-sm rounded-lg hover:bg-slate-700 disabled:opacity-60 font-medium"
|
||||
>
|
||||
<Upload className="w-4 h-4" />
|
||||
{uploading ? 'Hochladen...' : `${files.length} Foto${files.length !== 1 ? 's' : ''} hochladen`}
|
||||
{uploading ? t('common.uploading') : t('photos.uploadN', { n: files.length })}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user