mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 22:31: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:
@@ -26,7 +26,7 @@ export const useAuthStore = create((set, get) => ({
|
||||
connect(data.token)
|
||||
return data
|
||||
} catch (err) {
|
||||
const error = err.response?.data?.error || 'Anmeldung fehlgeschlagen'
|
||||
const error = err.response?.data?.error || 'Login failed'
|
||||
set({ isLoading: false, error })
|
||||
throw new Error(error)
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export const useAuthStore = create((set, get) => ({
|
||||
connect(data.token)
|
||||
return data
|
||||
} catch (err) {
|
||||
const error = err.response?.data?.error || 'Registrierung fehlgeschlagen'
|
||||
const error = err.response?.data?.error || 'Registration failed'
|
||||
set({ isLoading: false, error })
|
||||
throw new Error(error)
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export const useAuthStore = create((set, get) => ({
|
||||
user: { ...state.user, maps_api_key: key || null }
|
||||
}))
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Speichern des API-Schlüssels')
|
||||
throw new Error(err.response?.data?.error || 'Error saving API key')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -106,7 +106,7 @@ export const useAuthStore = create((set, get) => ({
|
||||
const data = await authApi.updateApiKeys(keys)
|
||||
set({ user: data.user })
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Speichern der API-Schlüssel')
|
||||
throw new Error(err.response?.data?.error || 'Error saving API keys')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -115,7 +115,7 @@ export const useAuthStore = create((set, get) => ({
|
||||
const data = await authApi.updateSettings(profileData)
|
||||
set({ user: data.user })
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren des Profils')
|
||||
throw new Error(err.response?.data?.error || 'Error updating profile')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -156,7 +156,7 @@ export const useAuthStore = create((set, get) => ({
|
||||
connect(data.token)
|
||||
return data
|
||||
} catch (err) {
|
||||
const error = err.response?.data?.error || 'Demo-Login fehlgeschlagen'
|
||||
const error = err.response?.data?.error || 'Demo login failed'
|
||||
set({ isLoading: false, error })
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const useSettingsStore = create((set, get) => ({
|
||||
await settingsApi.set(key, value)
|
||||
} catch (err) {
|
||||
console.error('Failed to save setting:', err)
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Speichern der Einstellung')
|
||||
throw new Error(err.response?.data?.error || 'Error saving setting')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -55,7 +55,7 @@ export const useSettingsStore = create((set, get) => ({
|
||||
await settingsApi.setBulk(settingsObj)
|
||||
} catch (err) {
|
||||
console.error('Failed to save settings:', err)
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Speichern der Einstellungen')
|
||||
throw new Error(err.response?.data?.error || 'Error saving settings')
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -76,6 +76,17 @@ export const useTripStore = create((set, get) => ({
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'assignment:updated': {
|
||||
const dayKey = String(payload.assignment.day_id)
|
||||
return {
|
||||
assignments: {
|
||||
...state.assignments,
|
||||
[dayKey]: (state.assignments[dayKey] || []).map(a =>
|
||||
a.id === payload.assignment.id ? { ...a, ...payload.assignment } : a
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'assignment:deleted': {
|
||||
const dayKey = String(payload.dayId)
|
||||
return {
|
||||
@@ -279,7 +290,7 @@ export const useTripStore = create((set, get) => ({
|
||||
set(state => ({ places: [data.place, ...state.places] }))
|
||||
return data.place
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Hinzufügen des Ortes')
|
||||
throw new Error(err.response?.data?.error || 'Error adding place')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -297,7 +308,7 @@ export const useTripStore = create((set, get) => ({
|
||||
}))
|
||||
return data.place
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren des Ortes')
|
||||
throw new Error(err.response?.data?.error || 'Error updating place')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -314,7 +325,7 @@ export const useTripStore = create((set, get) => ({
|
||||
),
|
||||
}))
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Löschen des Ortes')
|
||||
throw new Error(err.response?.data?.error || 'Error deleting place')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -323,9 +334,6 @@ export const useTripStore = create((set, get) => ({
|
||||
const place = state.places.find(p => p.id === parseInt(placeId))
|
||||
if (!place) return
|
||||
|
||||
const existing = (state.assignments[String(dayId)] || []).find(a => a.place?.id === parseInt(placeId))
|
||||
if (existing) return
|
||||
|
||||
const tempId = Date.now() * -1
|
||||
const current = [...(state.assignments[String(dayId)] || [])]
|
||||
const insertIdx = position != null ? position : current.length
|
||||
@@ -347,9 +355,11 @@ export const useTripStore = create((set, get) => ({
|
||||
|
||||
try {
|
||||
const data = await assignmentsApi.create(tripId, dayId, { place_id: placeId })
|
||||
const newAssignment = position != null
|
||||
? { ...data.assignment, order_index: insertIdx }
|
||||
: data.assignment
|
||||
const newAssignment = {
|
||||
...data.assignment,
|
||||
place: data.assignment.place || place,
|
||||
order_index: position != null ? insertIdx : data.assignment.order_index,
|
||||
}
|
||||
set(state => ({
|
||||
assignments: {
|
||||
...state.assignments,
|
||||
@@ -390,7 +400,7 @@ export const useTripStore = create((set, get) => ({
|
||||
[String(dayId)]: state.assignments[String(dayId)].filter(a => a.id !== tempId),
|
||||
}
|
||||
}))
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Zuweisen des Ortes')
|
||||
throw new Error(err.response?.data?.error || 'Error assigning place')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -408,7 +418,7 @@ export const useTripStore = create((set, get) => ({
|
||||
await assignmentsApi.delete(tripId, dayId, assignmentId)
|
||||
} catch (err) {
|
||||
set({ assignments: prevAssignments })
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Entfernen der Zuweisung')
|
||||
throw new Error(err.response?.data?.error || 'Error removing assignment')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -431,7 +441,7 @@ export const useTripStore = create((set, get) => ({
|
||||
await assignmentsApi.reorder(tripId, dayId, orderedIds)
|
||||
} catch (err) {
|
||||
set({ assignments: prevAssignments })
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Neuanordnen')
|
||||
throw new Error(err.response?.data?.error || 'Error reordering')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -464,7 +474,7 @@ export const useTripStore = create((set, get) => ({
|
||||
}
|
||||
} catch (err) {
|
||||
set({ assignments: prevAssignments })
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Verschieben der Zuweisung')
|
||||
throw new Error(err.response?.data?.error || 'Error moving assignment')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -498,7 +508,7 @@ export const useTripStore = create((set, get) => ({
|
||||
[String(fromDayId)]: [...(s.dayNotes[String(fromDayId)] || []), note],
|
||||
}
|
||||
}))
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Verschieben der Notiz')
|
||||
throw new Error(err.response?.data?.error || 'Error moving note')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -512,7 +522,7 @@ export const useTripStore = create((set, get) => ({
|
||||
set(state => ({ packingItems: [...state.packingItems, result.item] }))
|
||||
return result.item
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Hinzufügen des Artikels')
|
||||
throw new Error(err.response?.data?.error || 'Error adding item')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -524,7 +534,7 @@ export const useTripStore = create((set, get) => ({
|
||||
}))
|
||||
return result.item
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren des Artikels')
|
||||
throw new Error(err.response?.data?.error || 'Error updating item')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -535,7 +545,7 @@ export const useTripStore = create((set, get) => ({
|
||||
await packingApi.delete(tripId, id)
|
||||
} catch (err) {
|
||||
set({ packingItems: prev })
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Löschen des Artikels')
|
||||
throw new Error(err.response?.data?.error || 'Error deleting item')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -563,7 +573,7 @@ export const useTripStore = create((set, get) => ({
|
||||
days: state.days.map(d => d.id === parseInt(dayId) ? { ...d, notes } : d)
|
||||
}))
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren der Notizen')
|
||||
throw new Error(err.response?.data?.error || 'Error updating notes')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -574,7 +584,7 @@ export const useTripStore = create((set, get) => ({
|
||||
days: state.days.map(d => d.id === parseInt(dayId) ? { ...d, title } : d)
|
||||
}))
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren des Tagesnamens')
|
||||
throw new Error(err.response?.data?.error || 'Error updating day name')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -584,7 +594,7 @@ export const useTripStore = create((set, get) => ({
|
||||
set(state => ({ tags: [...state.tags, result.tag] }))
|
||||
return result.tag
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Erstellen des Tags')
|
||||
throw new Error(err.response?.data?.error || 'Error creating tag')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -594,7 +604,7 @@ export const useTripStore = create((set, get) => ({
|
||||
set(state => ({ categories: [...state.categories, result.category] }))
|
||||
return result.category
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Erstellen der Kategorie')
|
||||
throw new Error(err.response?.data?.error || 'Error creating category')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -612,7 +622,7 @@ export const useTripStore = create((set, get) => ({
|
||||
set({ days: daysData.days, assignments: assignmentsMap, dayNotes: dayNotesMap })
|
||||
return result.trip
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren der Reise')
|
||||
throw new Error(err.response?.data?.error || 'Error updating trip')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -631,7 +641,7 @@ export const useTripStore = create((set, get) => ({
|
||||
set(state => ({ budgetItems: [...state.budgetItems, result.item] }))
|
||||
return result.item
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Hinzufügen des Budget-Eintrags')
|
||||
throw new Error(err.response?.data?.error || 'Error adding budget item')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -643,7 +653,7 @@ export const useTripStore = create((set, get) => ({
|
||||
}))
|
||||
return result.item
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren des Budget-Eintrags')
|
||||
throw new Error(err.response?.data?.error || 'Error updating budget item')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -654,7 +664,7 @@ export const useTripStore = create((set, get) => ({
|
||||
await budgetApi.delete(tripId, id)
|
||||
} catch (err) {
|
||||
set({ budgetItems: prev })
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Löschen des Budget-Eintrags')
|
||||
throw new Error(err.response?.data?.error || 'Error deleting budget item')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -673,7 +683,7 @@ export const useTripStore = create((set, get) => ({
|
||||
set(state => ({ files: [data.file, ...state.files] }))
|
||||
return data.file
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Hochladen der Datei')
|
||||
throw new Error(err.response?.data?.error || 'Error uploading file')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -682,7 +692,7 @@ export const useTripStore = create((set, get) => ({
|
||||
await filesApi.delete(tripId, id)
|
||||
set(state => ({ files: state.files.filter(f => f.id !== id) }))
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Löschen der Datei')
|
||||
throw new Error(err.response?.data?.error || 'Error deleting file')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -701,7 +711,7 @@ export const useTripStore = create((set, get) => ({
|
||||
set(state => ({ reservations: [result.reservation, ...state.reservations] }))
|
||||
return result.reservation
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Erstellen der Reservierung')
|
||||
throw new Error(err.response?.data?.error || 'Error creating reservation')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -713,7 +723,7 @@ export const useTripStore = create((set, get) => ({
|
||||
}))
|
||||
return result.reservation
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren der Reservierung')
|
||||
throw new Error(err.response?.data?.error || 'Error updating reservation')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -737,22 +747,36 @@ export const useTripStore = create((set, get) => ({
|
||||
await reservationsApi.delete(tripId, id)
|
||||
set(state => ({ reservations: state.reservations.filter(r => r.id !== id) }))
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Löschen der Reservierung')
|
||||
throw new Error(err.response?.data?.error || 'Error deleting reservation')
|
||||
}
|
||||
},
|
||||
|
||||
addDayNote: async (tripId, dayId, data) => {
|
||||
const tempId = Date.now() * -1
|
||||
const tempNote = { id: tempId, day_id: dayId, ...data, created_at: new Date().toISOString() }
|
||||
set(state => ({
|
||||
dayNotes: {
|
||||
...state.dayNotes,
|
||||
[String(dayId)]: [...(state.dayNotes[String(dayId)] || []), tempNote],
|
||||
}
|
||||
}))
|
||||
try {
|
||||
const result = await dayNotesApi.create(tripId, dayId, data)
|
||||
set(state => ({
|
||||
dayNotes: {
|
||||
...state.dayNotes,
|
||||
[String(dayId)]: [...(state.dayNotes[String(dayId)] || []), result.note],
|
||||
[String(dayId)]: (state.dayNotes[String(dayId)] || []).map(n => n.id === tempId ? result.note : n),
|
||||
}
|
||||
}))
|
||||
return result.note
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Hinzufügen der Notiz')
|
||||
set(state => ({
|
||||
dayNotes: {
|
||||
...state.dayNotes,
|
||||
[String(dayId)]: (state.dayNotes[String(dayId)] || []).filter(n => n.id !== tempId),
|
||||
}
|
||||
}))
|
||||
throw new Error(err.response?.data?.error || 'Error adding note')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -767,7 +791,7 @@ export const useTripStore = create((set, get) => ({
|
||||
}))
|
||||
return result.note
|
||||
} catch (err) {
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Aktualisieren der Notiz')
|
||||
throw new Error(err.response?.data?.error || 'Error updating note')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -783,7 +807,7 @@ export const useTripStore = create((set, get) => ({
|
||||
await dayNotesApi.delete(tripId, dayId, id)
|
||||
} catch (err) {
|
||||
set({ dayNotes: prev })
|
||||
throw new Error(err.response?.data?.error || 'Fehler beim Löschen der Notiz')
|
||||
throw new Error(err.response?.data?.error || 'Error deleting note')
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user