diff --git a/client/src/components/Planner/DayPlanSidebar.tsx b/client/src/components/Planner/DayPlanSidebar.tsx index b83270e..c062584 100644 --- a/client/src/components/Planner/DayPlanSidebar.tsx +++ b/client/src/components/Planner/DayPlanSidebar.tsx @@ -211,13 +211,67 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ const TRANSPORT_TYPES = new Set(['flight', 'train', 'bus', 'car', 'cruise']) + // Determine if a reservation's end_time represents a different date (multi-day) + const getEndDate = (r: Reservation) => { + const endStr = r.reservation_end_time || '' + return endStr.includes('T') ? endStr.split('T')[0] : null + } + + // Get span phase: how a reservation relates to a specific day's date + const getSpanPhase = (r: Reservation, dayDate: string): 'single' | 'start' | 'middle' | 'end' => { + if (!r.reservation_time) return 'single' + const startDate = r.reservation_time.split('T')[0] + const endDate = getEndDate(r) || startDate + if (startDate === endDate) return 'single' + if (dayDate === startDate) return 'start' + if (dayDate === endDate) return 'end' + return 'middle' + } + + // Get the appropriate display time for a reservation on a specific day + const getDisplayTimeForDay = (r: Reservation, dayDate: string): string | null => { + const phase = getSpanPhase(r, dayDate) + if (phase === 'end') return r.reservation_end_time || null + if (phase === 'middle') return null + return r.reservation_time || null + } + + // Get phase label for multi-day badge + const getSpanLabel = (r: Reservation, phase: string): string | null => { + if (phase === 'single') return null + if (r.type === 'flight') return t(`reservations.span.${phase === 'start' ? 'departure' : phase === 'end' ? 'arrival' : 'inTransit'}`) + if (r.type === 'car') return t(`reservations.span.${phase === 'start' ? 'pickup' : phase === 'end' ? 'return' : 'active'}`) + return t(`reservations.span.${phase === 'start' ? 'start' : phase === 'end' ? 'end' : 'ongoing'}`) + } + const getTransportForDay = (dayId: number) => { const day = days.find(d => d.id === dayId) if (!day?.date) return [] return reservations.filter(r => { - if (!r.reservation_time || !TRANSPORT_TYPES.has(r.type)) return false - const resDate = r.reservation_time.split('T')[0] - return resDate === day.date + if (!r.reservation_time || r.type === 'hotel') return false + const startDate = r.reservation_time.split('T')[0] + const endDate = getEndDate(r) + + if (endDate && endDate !== startDate) { + // Multi-day: show on any day in range (car middle handled elsewhere) + return day.date >= startDate && day.date <= endDate + } else { + // Single-day: show all non-hotel reservations that match this day's date + return startDate === day.date + } + }) + } + + // Get car rentals that are in "active" (middle) phase for a day — shown in day header, not timeline + const getActiveRentalsForDay = (dayId: number) => { + const day = days.find(d => d.id === dayId) + if (!day?.date) return [] + return reservations.filter(r => { + if (r.type !== 'car' || !r.reservation_time) return false + const startDate = r.reservation_time.split('T')[0] + const endDate = getEndDate(r) + if (!endDate || endDate === startDate) return false + return day.date > startDate && day.date < endDate }) } @@ -279,6 +333,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ const da = getDayAssignments(dayId) const dn = (dayNotes[String(dayId)] || []).slice().sort((a, b) => a.sort_order - b.sort_order) const transport = getTransportForDay(dayId) + const dayDate = days.find(d => d.id === dayId)?.date || '' // Initialize positions for transports that don't have one yet if (transport.some(r => r.day_plan_position == null)) { @@ -295,9 +350,14 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ ].sort((a, b) => a.sortKey - b.sortKey) // Timed places + transports: compute sortKeys based on time, inserted among base items + // For multi-day transports, use the appropriate display time for this day const allTimed = [ ...timedPlaces.map(a => ({ type: 'place' as const, data: a, minutes: parseTimeToMinutes(a.place?.place_time)! })), - ...transport.map(r => ({ type: 'transport' as const, data: r, minutes: parseTimeToMinutes(r.reservation_time) ?? 0 })), + ...transport.map(r => ({ + type: 'transport' as const, + data: r, + minutes: parseTimeToMinutes(getDisplayTimeForDay(r, dayDate)) ?? 0, + })), ].sort((a, b) => a.minutes - b.minutes) if (allTimed.length === 0) return baseItems @@ -968,6 +1028,17 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ ) }) })()} + {/* Active rental car badges */} + {(() => { + const activeRentals = getActiveRentalsForDay(day.id) + if (activeRentals.length === 0) return null + return activeRentals.map(r => ( + { e.stopPropagation(); setTransportDetail(r) }} style={{ display: 'inline-flex', alignItems: 'center', gap: 3, padding: '2px 7px', borderRadius: 5, background: 'rgba(59,130,246,0.08)', border: '1px solid rgba(59,130,246,0.2)', flexShrink: 1, minWidth: 0, maxWidth: '40%', cursor: 'pointer' }}> + + {r.title} + + )) + })()} )}
@@ -1291,6 +1362,11 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ // Transport booking (flight, train, bus, car, cruise) if (item.type === 'transport') { const res = item.data + const spanPhase = getSpanPhase(res, day.date) + + // Car "active" (middle) days are shown in the day header, skip here + if (res.type === 'car' && spanPhase === 'middle') return null + const TransportIcon = RES_ICONS[res.type] || Ticket const color = '#3b82f6' const meta = typeof res.metadata === 'string' ? JSON.parse(res.metadata || '{}') : (res.metadata || {}) @@ -1307,8 +1383,12 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ subtitle = [meta.train_number, meta.platform ? `Gl. ${meta.platform}` : '', meta.seat ? `Sitz ${meta.seat}` : ''].filter(Boolean).join(' · ') } + // Multi-day span phase + const spanLabel = getSpanLabel(res, spanPhase) + const displayTime = getDisplayTimeForDay(res, day.date) + return ( - + {showDropLine &&
}
setTransportDetail(res)} @@ -1340,6 +1420,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ background: isTransportHovered ? `${color}12` : `${color}08`, cursor: 'pointer', userSelect: 'none', transition: 'background 0.1s', + opacity: spanPhase === 'middle' ? 0.65 : 1, }} >
+ {spanLabel && ( + + {spanLabel} + + )} {res.title} - {res.reservation_time?.includes('T') && ( + {displayTime?.includes('T') && ( - {new Date(res.reservation_time).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })} - {res.reservation_end_time?.includes('T') && ` – ${new Date(res.reservation_end_time).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })}`} + {new Date(displayTime).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })} + {spanPhase === 'single' && res.reservation_end_time && (() => { + const endStr = res.reservation_end_time.includes('T') ? res.reservation_end_time : (displayTime.split('T')[0] + 'T' + res.reservation_end_time) + return ` – ${new Date(endStr).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })}` + })()} + {meta.departure_timezone && spanPhase === 'start' && ` ${meta.departure_timezone}`} + {meta.arrival_timezone && spanPhase === 'end' && ` ${meta.arrival_timezone}`} )}
diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx index 8a376d7..6d7c7ee 100644 --- a/client/src/components/Planner/ReservationModal.tsx +++ b/client/src/components/Planner/ReservationModal.tsx @@ -73,9 +73,10 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p const [form, setForm] = useState({ title: '', type: 'other', status: 'pending', - reservation_time: '', reservation_end_time: '', location: '', confirmation_number: '', + reservation_time: '', reservation_end_time: '', end_date: '', location: '', confirmation_number: '', notes: '', assignment_id: '', accommodation_id: '', meta_airline: '', meta_flight_number: '', meta_departure_airport: '', meta_arrival_airport: '', + meta_departure_timezone: '', meta_arrival_timezone: '', meta_train_number: '', meta_platform: '', meta_seat: '', meta_check_in_time: '', meta_check_out_time: '', hotel_place_id: '', hotel_start_day: '', hotel_end_day: '', @@ -95,12 +96,21 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p useEffect(() => { if (reservation) { const meta = typeof reservation.metadata === 'string' ? JSON.parse(reservation.metadata || '{}') : (reservation.metadata || {}) + // Parse end_date from reservation_end_time if it's a full ISO datetime + const rawEnd = reservation.reservation_end_time || '' + let endDate = '' + let endTime = rawEnd + if (rawEnd.includes('T')) { + endDate = rawEnd.split('T')[0] + endTime = rawEnd.split('T')[1]?.slice(0, 5) || '' + } setForm({ title: reservation.title || '', type: reservation.type || 'other', status: reservation.status || 'pending', reservation_time: reservation.reservation_time ? reservation.reservation_time.slice(0, 16) : '', - reservation_end_time: reservation.reservation_end_time || '', + reservation_end_time: endTime, + end_date: endDate, location: reservation.location || '', confirmation_number: reservation.confirmation_number || '', notes: reservation.notes || '', @@ -110,6 +120,8 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p meta_flight_number: meta.flight_number || '', meta_departure_airport: meta.departure_airport || '', meta_arrival_airport: meta.arrival_airport || '', + meta_departure_timezone: meta.departure_timezone || '', + meta_arrival_timezone: meta.arrival_timezone || '', meta_train_number: meta.train_number || '', meta_platform: meta.platform || '', meta_seat: meta.seat || '', @@ -122,9 +134,10 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p } else { setForm({ title: '', type: 'other', status: 'pending', - reservation_time: '', reservation_end_time: '', location: '', confirmation_number: '', + reservation_time: '', reservation_end_time: '', end_date: '', location: '', confirmation_number: '', notes: '', assignment_id: '', accommodation_id: '', meta_airline: '', meta_flight_number: '', meta_departure_airport: '', meta_arrival_airport: '', + meta_departure_timezone: '', meta_arrival_timezone: '', meta_train_number: '', meta_platform: '', meta_seat: '', meta_check_in_time: '', meta_check_out_time: '', }) @@ -134,9 +147,21 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p const set = (field, value) => setForm(prev => ({ ...prev, [field]: value })) + // Validate that end datetime is after start datetime + const isEndBeforeStart = (() => { + if (!form.end_date || !form.reservation_time) return false + const startDate = form.reservation_time.split('T')[0] + const startTime = form.reservation_time.split('T')[1] || '00:00' + const endTime = form.reservation_end_time || '00:00' + const startFull = `${startDate}T${startTime}` + const endFull = `${form.end_date}T${endTime}` + return endFull <= startFull + })() + const handleSubmit = async (e) => { e.preventDefault() if (!form.title.trim()) return + if (isEndBeforeStart) { toast.error(t('reservations.validation.endBeforeStart')); return } setIsSaving(true) try { const metadata: Record = {} @@ -145,6 +170,8 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p if (form.meta_flight_number) metadata.flight_number = form.meta_flight_number if (form.meta_departure_airport) metadata.departure_airport = form.meta_departure_airport if (form.meta_arrival_airport) metadata.arrival_airport = form.meta_arrival_airport + if (form.meta_departure_timezone) metadata.departure_timezone = form.meta_departure_timezone + if (form.meta_arrival_timezone) metadata.arrival_timezone = form.meta_arrival_timezone } else if (form.type === 'hotel') { if (form.meta_check_in_time) metadata.check_in_time = form.meta_check_in_time if (form.meta_check_out_time) metadata.check_out_time = form.meta_check_out_time @@ -153,9 +180,14 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p if (form.meta_platform) metadata.platform = form.meta_platform if (form.meta_seat) metadata.seat = form.meta_seat } + // Combine end_date + end_time into reservation_end_time + let combinedEndTime = form.reservation_end_time + if (form.end_date) { + combinedEndTime = form.reservation_end_time ? `${form.end_date}T${form.reservation_end_time}` : form.end_date + } const saveData: Record = { title: form.title, type: form.type, status: form.status, - reservation_time: form.reservation_time, reservation_end_time: form.reservation_end_time, + reservation_time: form.reservation_time, reservation_end_time: combinedEndTime, location: form.location, confirmation_number: form.confirmation_number, notes: form.notes, assignment_id: form.assignment_id || null, @@ -257,10 +289,9 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p placeholder={t('reservations.titlePlaceholder')} style={inputStyle} />
- {/* Assignment Picker + Date (hidden for hotels) */} - {form.type !== 'hotel' && ( -
- {assignmentOptions.length > 0 && ( + {/* Assignment Picker (hidden for hotels) */} + {form.type !== 'hotel' && assignmentOptions.length > 0 && ( +
- )} -
- - { const [d] = (form.reservation_time || '').split('T'); return d || '' })()} - onChange={d => { - const [, t] = (form.reservation_time || '').split('T') - set('reservation_time', d ? (t ? `${d}T${t}` : d) : '') - }} - /> -
)} - {/* Start Time + End Time + Status */} -
- {form.type !== 'hotel' && ( - <> + {/* Start Date/Time + End Date/Time + Status (hidden for hotels) */} + {form.type !== 'hotel' && ( + <> +
+
+ + { const [d] = (form.reservation_time || '').split('T'); return d || '' })()} + onChange={d => { + const [, t] = (form.reservation_time || '').split('T') + set('reservation_time', d ? (t ? `${d}T${t}` : d) : '') + }} + /> +
+
+ + { const [, t] = (form.reservation_time || '').split('T'); return t || '' })()} + onChange={t => { + const [d] = (form.reservation_time || '').split('T') + const date = d || new Date().toISOString().split('T')[0] + set('reservation_time', t ? `${date}T${t}` : date) + }} + /> +
+ {form.type === 'flight' && (
- - { const [, t] = (form.reservation_time || '').split('T'); return t || '' })()} - onChange={t => { - const [d] = (form.reservation_time || '').split('T') - const date = d || new Date().toISOString().split('T')[0] - set('reservation_time', t ? `${date}T${t}` : date) - }} - /> + + set('meta_departure_timezone', e.target.value)} + placeholder="e.g. CET, UTC+1" style={inputStyle} />
-
- - set('reservation_end_time', v)} /> -
- - )} -
- - set('status', value)} - options={[ - { value: 'pending', label: t('reservations.pending') }, - { value: 'confirmed', label: t('reservations.confirmed') }, - ]} - size="sm" - /> + )}
-
+
+
+ + set('end_date', d || '')} + /> +
+
+ + set('reservation_end_time', v)} /> +
+ {form.type === 'flight' && ( +
+ + set('meta_arrival_timezone', e.target.value)} + placeholder="e.g. JST, UTC+9" style={inputStyle} /> +
+ )} +
+ {isEndBeforeStart && ( +
{t('reservations.validation.endBeforeStart')}
+ )} +
+
+ + set('status', value)} + options={[ + { value: 'pending', label: t('reservations.pending') }, + { value: 'confirmed', label: t('reservations.confirmed') }, + ]} + size="sm" + /> +
+
+ + )} {/* Location + Booking Code */}
@@ -422,8 +480,8 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p />
- {/* Check-in/out times */} -
+ {/* Check-in/out times + Status */} +
set('meta_check_in_time', v)} /> @@ -432,6 +490,18 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p set('meta_check_out_time', v)} />
+
+ + set('status', value)} + options={[ + { value: 'pending', label: t('reservations.pending') }, + { value: 'confirmed', label: t('reservations.confirmed') }, + ]} + size="sm" + /> +
)} @@ -561,7 +631,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p -
diff --git a/client/src/components/Planner/ReservationsPanel.tsx b/client/src/components/Planner/ReservationsPanel.tsx index 4d37fab..e517663 100644 --- a/client/src/components/Planner/ReservationsPanel.tsx +++ b/client/src/components/Planner/ReservationsPanel.tsx @@ -136,7 +136,12 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo {r.reservation_time && (
{t('reservations.date')}
-
{fmtDate(r.reservation_time)}
+
+ {fmtDate(r.reservation_time)} + {r.reservation_end_time?.includes('T') && r.reservation_end_time.split('T')[0] !== r.reservation_time.split('T')[0] && ( + <> – {fmtDate(r.reservation_end_time)} + )} +
)} {r.reservation_time?.includes('T') && ( diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts index a2523e7..10ed404 100644 --- a/client/src/i18n/translations/ar.ts +++ b/client/src/i18n/translations/ar.ts @@ -936,6 +936,27 @@ const ar: Record = { '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': 'الميزانية', @@ -1546,4 +1567,5 @@ const ar: Record = { 'notifications.test.tripText': 'إشعار تجريبي للرحلة "{trip}".', } -export default ar \ No newline at end of file +export default ar + diff --git a/client/src/i18n/translations/br.ts b/client/src/i18n/translations/br.ts index 4c2eb59..d60d0c9 100644 --- a/client/src/i18n/translations/br.ts +++ b/client/src/i18n/translations/br.ts @@ -917,6 +917,27 @@ const br: Record = { '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', @@ -1541,4 +1562,5 @@ const br: Record = { 'notifications.test.tripText': 'Notificação de teste para a viagem "{trip}".', } -export default br \ No newline at end of file +export default br + diff --git a/client/src/i18n/translations/cs.ts b/client/src/i18n/translations/cs.ts index 669d496..88d7024 100644 --- a/client/src/i18n/translations/cs.ts +++ b/client/src/i18n/translations/cs.ts @@ -934,6 +934,27 @@ const cs: Record = { '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', @@ -1546,4 +1567,5 @@ const cs: Record = { 'notifications.test.tripText': 'Testovací oznámení pro výlet "{trip}".', } -export default cs \ No newline at end of file +export default cs + diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts index a2980ba..c0cb4b1 100644 --- a/client/src/i18n/translations/de.ts +++ b/client/src/i18n/translations/de.ts @@ -933,6 +933,27 @@ const de: Record = { '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', @@ -1543,4 +1564,5 @@ const de: Record = { 'notifications.test.tripText': 'Testbenachrichtigung für Reise "{trip}".', } -export default de \ No newline at end of file +export default de + diff --git a/client/src/i18n/translations/en.ts b/client/src/i18n/translations/en.ts index c2107e6..fc2430c 100644 --- a/client/src/i18n/translations/en.ts +++ b/client/src/i18n/translations/en.ts @@ -930,6 +930,27 @@ const en: Record = { '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', diff --git a/client/src/i18n/translations/es.ts b/client/src/i18n/translations/es.ts index 4de28e2..454f480 100644 --- a/client/src/i18n/translations/es.ts +++ b/client/src/i18n/translations/es.ts @@ -893,6 +893,27 @@ const es: Record = { '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', @@ -1548,4 +1569,5 @@ const es: Record = { 'notifications.test.tripText': 'Notificación de prueba para el viaje "{trip}".', } -export default es \ No newline at end of file +export default es + diff --git a/client/src/i18n/translations/fr.ts b/client/src/i18n/translations/fr.ts index c37207d..a62832a 100644 --- a/client/src/i18n/translations/fr.ts +++ b/client/src/i18n/translations/fr.ts @@ -932,6 +932,27 @@ const fr: Record = { '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', @@ -1542,4 +1563,5 @@ const fr: Record = { 'notifications.test.tripText': 'Notification de test pour le voyage "{trip}".', } -export default fr \ No newline at end of file +export default fr + diff --git a/client/src/i18n/translations/hu.ts b/client/src/i18n/translations/hu.ts index 05c72e1..5c2b777 100644 --- a/client/src/i18n/translations/hu.ts +++ b/client/src/i18n/translations/hu.ts @@ -933,6 +933,27 @@ const hu: Record = { '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', @@ -1543,4 +1564,5 @@ const hu: Record = { 'notifications.test.tripText': 'Teszt értesítés a(z) "{trip}" utazáshoz.', } -export default hu \ No newline at end of file +export default hu + diff --git a/client/src/i18n/translations/it.ts b/client/src/i18n/translations/it.ts index e1d76e2..5c79190 100644 --- a/client/src/i18n/translations/it.ts +++ b/client/src/i18n/translations/it.ts @@ -933,6 +933,27 @@ const it: Record = { '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', @@ -1543,4 +1564,5 @@ const it: Record = { 'notifications.test.tripText': 'Notifica di test per il viaggio "{trip}".', } -export default it \ No newline at end of file +export default it + diff --git a/client/src/i18n/translations/nl.ts b/client/src/i18n/translations/nl.ts index c40f87a..5e46423 100644 --- a/client/src/i18n/translations/nl.ts +++ b/client/src/i18n/translations/nl.ts @@ -932,6 +932,27 @@ const nl: Record = { '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', @@ -1542,4 +1563,5 @@ const nl: Record = { 'notifications.test.tripText': 'Testmelding voor reis "{trip}".', } -export default nl \ No newline at end of file +export default nl + diff --git a/client/src/i18n/translations/pl.ts b/client/src/i18n/translations/pl.ts index ecd3b8a..c678795 100644 --- a/client/src/i18n/translations/pl.ts +++ b/client/src/i18n/translations/pl.ts @@ -888,6 +888,27 @@ const pl: Record = { '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', diff --git a/client/src/i18n/translations/ru.ts b/client/src/i18n/translations/ru.ts index 518c5e9..65be706 100644 --- a/client/src/i18n/translations/ru.ts +++ b/client/src/i18n/translations/ru.ts @@ -932,6 +932,27 @@ const ru: Record = { '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': 'Бюджет', @@ -1542,4 +1563,5 @@ const ru: Record = { 'notifications.test.tripText': 'Тестовое уведомление для поездки "{trip}".', } -export default ru \ No newline at end of file +export default ru + diff --git a/client/src/i18n/translations/zh.ts b/client/src/i18n/translations/zh.ts index 5a90e5b..eefeb3b 100644 --- a/client/src/i18n/translations/zh.ts +++ b/client/src/i18n/translations/zh.ts @@ -932,6 +932,27 @@ const zh: Record = { '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': '预算', @@ -1542,4 +1563,5 @@ const zh: Record = { 'notifications.test.tripText': '行程"{trip}"的测试通知。', } -export default zh \ No newline at end of file +export default zh +