feat: support multi-day spanning for reservations (flights, rental cars, events)

- ReservationModal: add separate departure/arrival date+time fields with
  type-specific labels (Departure/Arrival for flights, Pickup/Return for
  cars, Start/End for generic types), timezone fields for flights
- DayPlanSidebar: getTransportForDay now matches reservations across all
  days in their date range; shows phase badges (Departure/In Transit/
  Arrival etc.) with appropriate time display per day
- ReservationsPanel: show date range when end date differs from start
- All 13 translation files updated with new keys
This commit is contained in:
Luca
2026-04-03 15:47:06 +02:00
parent 64d4a20403
commit 0115987e52
16 changed files with 526 additions and 73 deletions
+23 -1
View File
@@ -935,6 +935,27 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'reservations.linkAssignment': 'ربط بخطة اليوم',
'reservations.pickAssignment': 'اختر عنصرًا من خطتك...',
'reservations.noAssignment': 'بلا ربط',
'reservations.departureDate': 'المغادرة',
'reservations.arrivalDate': 'الوصول',
'reservations.departureTime': 'وقت المغادرة',
'reservations.arrivalTime': 'وقت الوصول',
'reservations.pickupDate': 'الاستلام',
'reservations.returnDate': 'الإرجاع',
'reservations.pickupTime': 'وقت الاستلام',
'reservations.returnTime': 'وقت الإرجاع',
'reservations.endDate': 'تاريخ الانتهاء',
'reservations.meta.departureTimezone': 'TZ المغادرة',
'reservations.meta.arrivalTimezone': 'TZ الوصول',
'reservations.span.departure': 'المغادرة',
'reservations.span.arrival': 'الوصول',
'reservations.span.inTransit': 'في الطريق',
'reservations.span.pickup': 'الاستلام',
'reservations.span.return': 'الإرجاع',
'reservations.span.active': 'نشط',
'reservations.span.start': 'البداية',
'reservations.span.end': 'النهاية',
'reservations.span.ongoing': 'جارٍ',
'reservations.validation.endBeforeStart': 'يجب أن يكون تاريخ/وقت الانتهاء بعد تاريخ/وقت البدء',
// Budget
'budget.title': 'الميزانية',
@@ -1545,4 +1566,5 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'notifications.test.tripText': 'إشعار تجريبي للرحلة "{trip}".',
}
export default ar
export default ar
+23 -1
View File
@@ -916,6 +916,27 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'reservations.linkAssignment': 'Vincular à atribuição do dia',
'reservations.pickAssignment': 'Selecione uma atribuição do seu plano...',
'reservations.noAssignment': 'Sem vínculo (avulsa)',
'reservations.departureDate': 'Partida',
'reservations.arrivalDate': 'Chegada',
'reservations.departureTime': 'Hora partida',
'reservations.arrivalTime': 'Hora chegada',
'reservations.pickupDate': 'Retirada',
'reservations.returnDate': 'Devolução',
'reservations.pickupTime': 'Hora retirada',
'reservations.returnTime': 'Hora devolução',
'reservations.endDate': 'Data final',
'reservations.meta.departureTimezone': 'TZ partida',
'reservations.meta.arrivalTimezone': 'TZ chegada',
'reservations.span.departure': 'Partida',
'reservations.span.arrival': 'Chegada',
'reservations.span.inTransit': 'Em trânsito',
'reservations.span.pickup': 'Retirada',
'reservations.span.return': 'Devolução',
'reservations.span.active': 'Ativo',
'reservations.span.start': 'Início',
'reservations.span.end': 'Fim',
'reservations.span.ongoing': 'Em andamento',
'reservations.validation.endBeforeStart': 'A data/hora final deve ser posterior à data/hora inicial',
// Budget
'budget.title': 'Orçamento',
@@ -1540,4 +1561,5 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'notifications.test.tripText': 'Notificação de teste para a viagem "{trip}".',
}
export default br
export default br
+23 -1
View File
@@ -933,6 +933,27 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'reservations.linkAssignment': 'Propojit s přiřazením dne',
'reservations.pickAssignment': 'Vyberte přiřazení z vašeho plánu...',
'reservations.noAssignment': 'Bez propojení (samostatné)',
'reservations.departureDate': 'Odlet',
'reservations.arrivalDate': 'Přílet',
'reservations.departureTime': 'Čas odletu',
'reservations.arrivalTime': 'Čas příletu',
'reservations.pickupDate': 'Vyzvednutí',
'reservations.returnDate': 'Vrácení',
'reservations.pickupTime': 'Čas vyzvednutí',
'reservations.returnTime': 'Čas vrácení',
'reservations.endDate': 'Datum konce',
'reservations.meta.departureTimezone': 'TZ odletu',
'reservations.meta.arrivalTimezone': 'TZ příletu',
'reservations.span.departure': 'Odlet',
'reservations.span.arrival': 'Přílet',
'reservations.span.inTransit': 'Na cestě',
'reservations.span.pickup': 'Vyzvednutí',
'reservations.span.return': 'Vrácení',
'reservations.span.active': 'Aktivní',
'reservations.span.start': 'Začátek',
'reservations.span.end': 'Konec',
'reservations.span.ongoing': 'Probíhá',
'reservations.validation.endBeforeStart': 'Datum/čas konce musí být po datu/čase začátku',
// Rozpočet (Budget)
'budget.title': 'Rozpočet',
@@ -1545,4 +1566,5 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'notifications.test.tripText': 'Testovací oznámení pro výlet "{trip}".',
}
export default cs
export default cs
+23 -1
View File
@@ -932,6 +932,27 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'reservations.linkAssignment': 'Mit Tagesplanung verknüpfen',
'reservations.pickAssignment': 'Zuordnung aus dem Plan wählen...',
'reservations.noAssignment': 'Keine Verknüpfung',
'reservations.departureDate': 'Abflug',
'reservations.arrivalDate': 'Ankunft',
'reservations.departureTime': 'Abflugzeit',
'reservations.arrivalTime': 'Ankunftszeit',
'reservations.pickupDate': 'Abholung',
'reservations.returnDate': 'Rückgabe',
'reservations.pickupTime': 'Abholzeit',
'reservations.returnTime': 'Rückgabezeit',
'reservations.endDate': 'Enddatum',
'reservations.meta.departureTimezone': 'Abfl. TZ',
'reservations.meta.arrivalTimezone': 'Ank. TZ',
'reservations.span.departure': 'Abflug',
'reservations.span.arrival': 'Ankunft',
'reservations.span.inTransit': 'Unterwegs',
'reservations.span.pickup': 'Abholung',
'reservations.span.return': 'Rückgabe',
'reservations.span.active': 'Aktiv',
'reservations.span.start': 'Start',
'reservations.span.end': 'Ende',
'reservations.span.ongoing': 'Laufend',
'reservations.validation.endBeforeStart': 'Enddatum/-zeit muss nach dem Startdatum/-zeit liegen',
// Budget
'budget.title': 'Budget',
@@ -1542,4 +1563,5 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'notifications.test.tripText': 'Testbenachrichtigung für Reise "{trip}".',
}
export default de
export default de
+21
View File
@@ -929,6 +929,27 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'reservations.linkAssignment': 'Link to day assignment',
'reservations.pickAssignment': 'Select an assignment from your plan...',
'reservations.noAssignment': 'No link (standalone)',
'reservations.departureDate': 'Departure',
'reservations.arrivalDate': 'Arrival',
'reservations.departureTime': 'Dep. time',
'reservations.arrivalTime': 'Arr. time',
'reservations.pickupDate': 'Pickup',
'reservations.returnDate': 'Return',
'reservations.pickupTime': 'Pickup time',
'reservations.returnTime': 'Return time',
'reservations.endDate': 'End date',
'reservations.meta.departureTimezone': 'Dep. TZ',
'reservations.meta.arrivalTimezone': 'Arr. TZ',
'reservations.span.departure': 'Departure',
'reservations.span.arrival': 'Arrival',
'reservations.span.inTransit': 'In transit',
'reservations.span.pickup': 'Pickup',
'reservations.span.return': 'Return',
'reservations.span.active': 'Active',
'reservations.span.start': 'Start',
'reservations.span.end': 'End',
'reservations.span.ongoing': 'Ongoing',
'reservations.validation.endBeforeStart': 'End date/time must be after start date/time',
// Budget
'budget.title': 'Budget',
+23 -1
View File
@@ -892,6 +892,27 @@ const es: Record<string, string> = {
'reservations.linkAssignment': 'Vincular a una asignación del día',
'reservations.pickAssignment': 'Selecciona una asignación de tu plan...',
'reservations.noAssignment': 'Sin vínculo (independiente)',
'reservations.departureDate': 'Salida',
'reservations.arrivalDate': 'Llegada',
'reservations.departureTime': 'Hora salida',
'reservations.arrivalTime': 'Hora llegada',
'reservations.pickupDate': 'Recogida',
'reservations.returnDate': 'Devolución',
'reservations.pickupTime': 'Hora recogida',
'reservations.returnTime': 'Hora devolución',
'reservations.endDate': 'Fecha fin',
'reservations.meta.departureTimezone': 'TZ salida',
'reservations.meta.arrivalTimezone': 'TZ llegada',
'reservations.span.departure': 'Salida',
'reservations.span.arrival': 'Llegada',
'reservations.span.inTransit': 'En tránsito',
'reservations.span.pickup': 'Recogida',
'reservations.span.return': 'Devolución',
'reservations.span.active': 'Activo',
'reservations.span.start': 'Inicio',
'reservations.span.end': 'Fin',
'reservations.span.ongoing': 'En curso',
'reservations.validation.endBeforeStart': 'La fecha/hora de fin debe ser posterior a la de inicio',
// Budget
'budget.title': 'Presupuesto',
@@ -1547,4 +1568,5 @@ const es: Record<string, string> = {
'notifications.test.tripText': 'Notificación de prueba para el viaje "{trip}".',
}
export default es
export default es
+23 -1
View File
@@ -931,6 +931,27 @@ const fr: Record<string, string> = {
'reservations.linkAssignment': 'Lier à l\'affectation du jour',
'reservations.pickAssignment': 'Sélectionnez une affectation de votre plan…',
'reservations.noAssignment': 'Aucun lien (autonome)',
'reservations.departureDate': 'Départ',
'reservations.arrivalDate': 'Arrivée',
'reservations.departureTime': 'Heure dép.',
'reservations.arrivalTime': 'Heure arr.',
'reservations.pickupDate': 'Prise en charge',
'reservations.returnDate': 'Restitution',
'reservations.pickupTime': 'Heure prise en charge',
'reservations.returnTime': 'Heure restitution',
'reservations.endDate': 'Date de fin',
'reservations.meta.departureTimezone': 'TZ dép.',
'reservations.meta.arrivalTimezone': 'TZ arr.',
'reservations.span.departure': 'Départ',
'reservations.span.arrival': 'Arrivée',
'reservations.span.inTransit': 'En transit',
'reservations.span.pickup': 'Prise en charge',
'reservations.span.return': 'Restitution',
'reservations.span.active': 'Actif',
'reservations.span.start': 'Début',
'reservations.span.end': 'Fin',
'reservations.span.ongoing': 'En cours',
'reservations.validation.endBeforeStart': 'La date/heure de fin doit être postérieure à la date/heure de début',
// Budget
'budget.title': 'Budget',
@@ -1541,4 +1562,5 @@ const fr: Record<string, string> = {
'notifications.test.tripText': 'Notification de test pour le voyage "{trip}".',
}
export default fr
export default fr
+23 -1
View File
@@ -932,6 +932,27 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'reservations.linkAssignment': 'Összekapcsolás napi tervvel',
'reservations.pickAssignment': 'Válassz hozzárendelést a tervedből...',
'reservations.noAssignment': 'Nincs összekapcsolás (önálló)',
'reservations.departureDate': 'Indulás',
'reservations.arrivalDate': 'Érkezés',
'reservations.departureTime': 'Indulási idő',
'reservations.arrivalTime': 'Érkezési idő',
'reservations.pickupDate': 'Felvétel',
'reservations.returnDate': 'Visszaadás',
'reservations.pickupTime': 'Felvétel ideje',
'reservations.returnTime': 'Visszaadás ideje',
'reservations.endDate': 'Befejezés dátuma',
'reservations.meta.departureTimezone': 'TZ indulás',
'reservations.meta.arrivalTimezone': 'TZ érkezés',
'reservations.span.departure': 'Indulás',
'reservations.span.arrival': 'Érkezés',
'reservations.span.inTransit': 'Úton',
'reservations.span.pickup': 'Felvétel',
'reservations.span.return': 'Visszaadás',
'reservations.span.active': 'Aktív',
'reservations.span.start': 'Kezdés',
'reservations.span.end': 'Vége',
'reservations.span.ongoing': 'Folyamatban',
'reservations.validation.endBeforeStart': 'A befejezés dátuma/időpontja a kezdés utáni kell legyen',
// Költségvetés
'budget.title': 'Költségvetés',
@@ -1542,4 +1563,5 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'notifications.test.tripText': 'Teszt értesítés a(z) "{trip}" utazáshoz.',
}
export default hu
export default hu
+23 -1
View File
@@ -932,6 +932,27 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'reservations.linkAssignment': 'Collega all\'assegnazione del giorno',
'reservations.pickAssignment': 'Seleziona un\'assegnazione dal tuo programma...',
'reservations.noAssignment': 'Nessun collegamento (autonomo)',
'reservations.departureDate': 'Partenza',
'reservations.arrivalDate': 'Arrivo',
'reservations.departureTime': 'Ora part.',
'reservations.arrivalTime': 'Ora arr.',
'reservations.pickupDate': 'Ritiro',
'reservations.returnDate': 'Riconsegna',
'reservations.pickupTime': 'Ora ritiro',
'reservations.returnTime': 'Ora riconsegna',
'reservations.endDate': 'Data fine',
'reservations.meta.departureTimezone': 'TZ part.',
'reservations.meta.arrivalTimezone': 'TZ arr.',
'reservations.span.departure': 'Partenza',
'reservations.span.arrival': 'Arrivo',
'reservations.span.inTransit': 'In transito',
'reservations.span.pickup': 'Ritiro',
'reservations.span.return': 'Riconsegna',
'reservations.span.active': 'Attivo',
'reservations.span.start': 'Inizio',
'reservations.span.end': 'Fine',
'reservations.span.ongoing': 'In corso',
'reservations.validation.endBeforeStart': 'La data/ora di fine deve essere successiva alla data/ora di inizio',
// Budget
'budget.title': 'Budget',
@@ -1542,4 +1563,5 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'notifications.test.tripText': 'Notifica di test per il viaggio "{trip}".',
}
export default it
export default it
+23 -1
View File
@@ -931,6 +931,27 @@ const nl: Record<string, string> = {
'reservations.linkAssignment': 'Koppelen aan dagtoewijzing',
'reservations.pickAssignment': 'Selecteer een toewijzing uit je plan...',
'reservations.noAssignment': 'Geen koppeling (zelfstandig)',
'reservations.departureDate': 'Vertrek',
'reservations.arrivalDate': 'Aankomst',
'reservations.departureTime': 'Vertrektijd',
'reservations.arrivalTime': 'Aankomsttijd',
'reservations.pickupDate': 'Ophalen',
'reservations.returnDate': 'Inleveren',
'reservations.pickupTime': 'Ophaaltijd',
'reservations.returnTime': 'Inlevertijd',
'reservations.endDate': 'Einddatum',
'reservations.meta.departureTimezone': 'TZ vertrek',
'reservations.meta.arrivalTimezone': 'TZ aankomst',
'reservations.span.departure': 'Vertrek',
'reservations.span.arrival': 'Aankomst',
'reservations.span.inTransit': 'Onderweg',
'reservations.span.pickup': 'Ophalen',
'reservations.span.return': 'Inleveren',
'reservations.span.active': 'Actief',
'reservations.span.start': 'Start',
'reservations.span.end': 'Einde',
'reservations.span.ongoing': 'Lopend',
'reservations.validation.endBeforeStart': 'Einddatum/-tijd moet na de startdatum/-tijd liggen',
// Budget
'budget.title': 'Budget',
@@ -1541,4 +1562,5 @@ const nl: Record<string, string> = {
'notifications.test.tripText': 'Testmelding voor reis "{trip}".',
}
export default nl
export default nl
+21
View File
@@ -887,6 +887,27 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'reservations.linkAssignment': 'Przypisz do miejsca',
'reservations.pickAssignment': 'Wybierz miejsce z planu...',
'reservations.noAssignment': 'Brak przypisania (samodzielna)',
'reservations.departureDate': 'Wylot',
'reservations.arrivalDate': 'Przylot',
'reservations.departureTime': 'Godz. wylotu',
'reservations.arrivalTime': 'Godz. przylotu',
'reservations.pickupDate': 'Odbiór',
'reservations.returnDate': 'Zwrot',
'reservations.pickupTime': 'Godz. odbioru',
'reservations.returnTime': 'Godz. zwrotu',
'reservations.endDate': 'Data końca',
'reservations.meta.departureTimezone': 'TZ wylotu',
'reservations.meta.arrivalTimezone': 'TZ przylotu',
'reservations.span.departure': 'Wylot',
'reservations.span.arrival': 'Przylot',
'reservations.span.inTransit': 'W tranzycie',
'reservations.span.pickup': 'Odbiór',
'reservations.span.return': 'Zwrot',
'reservations.span.active': 'Aktywny',
'reservations.span.start': 'Start',
'reservations.span.end': 'Koniec',
'reservations.span.ongoing': 'W trakcie',
'reservations.validation.endBeforeStart': 'Data/godzina zakończenia musi być późniejsza niż data/godzina rozpoczęcia',
// Budget
'budget.title': 'Budżet',
+23 -1
View File
@@ -931,6 +931,27 @@ const ru: Record<string, string> = {
'reservations.linkAssignment': 'Привязать к назначению дня',
'reservations.pickAssignment': 'Выберите назначение из вашего плана...',
'reservations.noAssignment': 'Без привязки (самостоятельное)',
'reservations.departureDate': 'Вылет',
'reservations.arrivalDate': 'Прилёт',
'reservations.departureTime': 'Время вылета',
'reservations.arrivalTime': 'Время прилёта',
'reservations.pickupDate': 'Получение',
'reservations.returnDate': 'Возврат',
'reservations.pickupTime': 'Время получения',
'reservations.returnTime': 'Время возврата',
'reservations.endDate': 'Дата окончания',
'reservations.meta.departureTimezone': 'TZ вылета',
'reservations.meta.arrivalTimezone': 'TZ прилёта',
'reservations.span.departure': 'Вылет',
'reservations.span.arrival': 'Прилёт',
'reservations.span.inTransit': 'В пути',
'reservations.span.pickup': 'Получение',
'reservations.span.return': 'Возврат',
'reservations.span.active': 'Активно',
'reservations.span.start': 'Начало',
'reservations.span.end': 'Конец',
'reservations.span.ongoing': 'Продолжается',
'reservations.validation.endBeforeStart': 'Дата/время окончания должны быть позже даты/времени начала',
// Budget
'budget.title': 'Бюджет',
@@ -1541,4 +1562,5 @@ const ru: Record<string, string> = {
'notifications.test.tripText': 'Тестовое уведомление для поездки "{trip}".',
}
export default ru
export default ru
+23 -1
View File
@@ -931,6 +931,27 @@ const zh: Record<string, string> = {
'reservations.linkAssignment': '关联日程分配',
'reservations.pickAssignment': '从计划中选择一个分配...',
'reservations.noAssignment': '无关联(独立)',
'reservations.departureDate': '出发',
'reservations.arrivalDate': '到达',
'reservations.departureTime': '出发时间',
'reservations.arrivalTime': '到达时间',
'reservations.pickupDate': '取车',
'reservations.returnDate': '还车',
'reservations.pickupTime': '取车时间',
'reservations.returnTime': '还车时间',
'reservations.endDate': '结束日期',
'reservations.meta.departureTimezone': '出发时区',
'reservations.meta.arrivalTimezone': '到达时区',
'reservations.span.departure': '出发',
'reservations.span.arrival': '到达',
'reservations.span.inTransit': '途中',
'reservations.span.pickup': '取车',
'reservations.span.return': '还车',
'reservations.span.active': '使用中',
'reservations.span.start': '开始',
'reservations.span.end': '结束',
'reservations.span.ongoing': '进行中',
'reservations.validation.endBeforeStart': '结束日期/时间必须晚于开始日期/时间',
// Budget
'budget.title': '预算',
@@ -1541,4 +1562,5 @@ const zh: Record<string, string> = {
'notifications.test.tripText': '行程"{trip}"的测试通知。',
}
export default zh
export default zh