feat: support check-in time ranges for hotel accommodations

- Add check_in_end column to day_accommodations (Migration 102)
- Server: create/update accommodation accepts check_in_end
- Bidirectional sync: check_in_end synced between accommodation
  and linked reservation metadata (check_in_end_time)
- DayDetailPanel: shows check-in range (e.g. "14:00 – 22:00"),
  new "Until" time picker in hotel form
- ReservationModal: new check-in-until field for hotel bookings
- ReservationsPanel: displays check-in range in metadata cells
- i18n: checkInUntil keys in all 15 languages

Closes #366
This commit is contained in:
Maurice
2026-04-16 00:23:00 +02:00
parent 125436fa87
commit 409a63633c
24 changed files with 84 additions and 27 deletions
@@ -78,7 +78,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
const [showHotelPicker, setShowHotelPicker] = useState(false) const [showHotelPicker, setShowHotelPicker] = useState(false)
const [hotelDayRange, setHotelDayRange] = useState({ start: day?.id, end: day?.id }) const [hotelDayRange, setHotelDayRange] = useState({ start: day?.id, end: day?.id })
const [hotelCategoryFilter, setHotelCategoryFilter] = useState('') const [hotelCategoryFilter, setHotelCategoryFilter] = useState('')
const [hotelForm, setHotelForm] = useState({ check_in: '', check_out: '', confirmation: '', place_id: null }) const [hotelForm, setHotelForm] = useState({ check_in: '', check_in_end: '', check_out: '', confirmation: '', place_id: null })
useEffect(() => { useEffect(() => {
if (!day?.date || !lat || !lng) { setWeather(null); return } if (!day?.date || !lat || !lng) { setWeather(null); return }
@@ -117,6 +117,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
start_day_id: hotelDayRange.start, start_day_id: hotelDayRange.start,
end_day_id: hotelDayRange.end, end_day_id: hotelDayRange.end,
check_in: hotelForm.check_in || null, check_in: hotelForm.check_in || null,
check_in_end: hotelForm.check_in_end || null,
check_out: hotelForm.check_out || null, check_out: hotelForm.check_out || null,
confirmation: hotelForm.confirmation || null, confirmation: hotelForm.confirmation || null,
}) })
@@ -128,7 +129,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
days.some(d => d.id >= a.start_day_id && d.id <= a.end_day_id && d.id === day?.id) days.some(d => d.id >= a.start_day_id && d.id <= a.end_day_id && d.id === day?.id)
)) ))
setShowHotelPicker(false) setShowHotelPicker(false)
setHotelForm({ check_in: '', check_out: '', confirmation: '', place_id: null }) setHotelForm({ check_in: '', check_in_end: '', check_out: '', confirmation: '', place_id: null })
onAccommodationChange?.() onAccommodationChange?.()
} catch {} } catch {}
} }
@@ -356,7 +357,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{acc.place_name}</div> <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{acc.place_name}</div>
{acc.place_address && <div style={{ fontSize: 10, color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{acc.place_address}</div>} {acc.place_address && <div style={{ fontSize: 10, color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{acc.place_address}</div>}
</div> </div>
{canEditDays && <button onClick={() => { setAccommodation(acc); setHotelForm({ check_in: acc.check_in || '', check_out: acc.check_out || '', confirmation: acc.confirmation || '', place_id: acc.place_id }); setHotelDayRange({ start: acc.start_day_id, end: acc.end_day_id }); setShowHotelPicker('edit') }} {canEditDays && <button onClick={() => { setAccommodation(acc); setHotelForm({ check_in: acc.check_in || '', check_in_end: acc.check_in_end || '', check_out: acc.check_out || '', confirmation: acc.confirmation || '', place_id: acc.place_id }); setHotelDayRange({ start: acc.start_day_id, end: acc.end_day_id }); setShowHotelPicker('edit') }}
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 3, flexShrink: 0 }}> style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 3, flexShrink: 0 }}>
<Pencil size={12} style={{ color: 'var(--text-faint)' }} /> <Pencil size={12} style={{ color: 'var(--text-faint)' }} />
</button>} </button>}
@@ -368,7 +369,9 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
<div style={{ display: 'flex', gap: 0, margin: '0 12px 8px', borderRadius: 10, overflow: 'hidden', border: '1px solid var(--border-faint)' }}> <div style={{ display: 'flex', gap: 0, margin: '0 12px 8px', borderRadius: 10, overflow: 'hidden', border: '1px solid var(--border-faint)' }}>
{acc.check_in && ( {acc.check_in && (
<div style={{ flex: 1, padding: '8px 10px', borderRight: '1px solid var(--border-faint)', textAlign: 'center' }}> <div style={{ flex: 1, padding: '8px 10px', borderRight: '1px solid var(--border-faint)', textAlign: 'center' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.2 }}>{fmtTime(acc.check_in)}</div> <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.2 }}>
{fmtTime(acc.check_in)}{acc.check_in_end ? ` ${fmtTime(acc.check_in_end)}` : ''}
</div>
<div style={{ fontSize: 9, color: 'var(--text-faint)', fontWeight: 500, marginTop: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3 }}> <div style={{ fontSize: 9, color: 'var(--text-faint)', fontWeight: 500, marginTop: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3 }}>
<LogIn size={8} /> {t('day.checkIn')} <LogIn size={8} /> {t('day.checkIn')}
</div> </div>
@@ -488,11 +491,15 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
{/* Check-in / Check-out / Confirmation */} {/* Check-in / Check-out / Confirmation */}
<div style={{ padding: '10px 18px', borderBottom: '1px solid var(--border-faint)', display: 'flex', gap: 8, flexWrap: 'wrap' }}> <div style={{ padding: '10px 18px', borderBottom: '1px solid var(--border-faint)', display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: 100 }}> <div style={{ flex: 1, minWidth: 80 }}>
<label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkIn')}</label> <label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkIn')}</label>
<CustomTimePicker value={hotelForm.check_in} onChange={v => setHotelForm(f => ({ ...f, check_in: v }))} placeholder="14:00" /> <CustomTimePicker value={hotelForm.check_in} onChange={v => setHotelForm(f => ({ ...f, check_in: v }))} placeholder="14:00" />
</div> </div>
<div style={{ flex: 1, minWidth: 100 }}> <div style={{ flex: 1, minWidth: 80 }}>
<label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkInUntil')}</label>
<CustomTimePicker value={hotelForm.check_in_end} onChange={v => setHotelForm(f => ({ ...f, check_in_end: v }))} placeholder="22:00" />
</div>
<div style={{ flex: 1, minWidth: 80 }}>
<label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkOut')}</label> <label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkOut')}</label>
<CustomTimePicker value={hotelForm.check_out} onChange={v => setHotelForm(f => ({ ...f, check_out: v }))} placeholder="11:00" /> <CustomTimePicker value={hotelForm.check_out} onChange={v => setHotelForm(f => ({ ...f, check_out: v }))} placeholder="11:00" />
</div> </div>
@@ -570,11 +577,12 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
start_day_id: hotelDayRange.start, start_day_id: hotelDayRange.start,
end_day_id: hotelDayRange.end, end_day_id: hotelDayRange.end,
check_in: hotelForm.check_in || null, check_in: hotelForm.check_in || null,
check_in_end: hotelForm.check_in_end || null,
check_out: hotelForm.check_out || null, check_out: hotelForm.check_out || null,
confirmation: hotelForm.confirmation || null, confirmation: hotelForm.confirmation || null,
}) })
setShowHotelPicker(false) setShowHotelPicker(false)
setHotelForm({ check_in: '', check_out: '', confirmation: '', place_id: null }) setHotelForm({ check_in: '', check_in_end: '', check_out: '', confirmation: '', place_id: null })
// Reload // Reload
accommodationsApi.list(tripId).then(d => { accommodationsApi.list(tripId).then(d => {
const all = d.accommodations || [] const all = d.accommodations || []
@@ -89,7 +89,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
meta_airline: '', meta_flight_number: '', meta_departure_airport: '', meta_arrival_airport: '', meta_airline: '', meta_flight_number: '', meta_departure_airport: '', meta_arrival_airport: '',
meta_departure_timezone: '', meta_arrival_timezone: '', meta_departure_timezone: '', meta_arrival_timezone: '',
meta_train_number: '', meta_platform: '', meta_seat: '', meta_train_number: '', meta_platform: '', meta_seat: '',
meta_check_in_time: '', meta_check_out_time: '', meta_check_in_time: '', meta_check_in_end_time: '', meta_check_out_time: '',
hotel_place_id: '', hotel_start_day: '', hotel_end_day: '', hotel_place_id: '', hotel_start_day: '', hotel_end_day: '',
}) })
const [isSaving, setIsSaving] = useState(false) const [isSaving, setIsSaving] = useState(false)
@@ -140,6 +140,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
meta_platform: meta.platform || '', meta_platform: meta.platform || '',
meta_seat: meta.seat || '', meta_seat: meta.seat || '',
meta_check_in_time: meta.check_in_time || '', meta_check_in_time: meta.check_in_time || '',
meta_check_in_end_time: meta.check_in_end_time || '',
meta_check_out_time: meta.check_out_time || '', meta_check_out_time: meta.check_out_time || '',
hotel_place_id: (() => { const acc = accommodations.find(a => a.id == reservation.accommodation_id); return acc?.place_id || '' })(), hotel_place_id: (() => { const acc = accommodations.find(a => a.id == reservation.accommodation_id); return acc?.place_id || '' })(),
hotel_start_day: (() => { const acc = accommodations.find(a => a.id == reservation.accommodation_id); return acc?.start_day_id || '' })(), hotel_start_day: (() => { const acc = accommodations.find(a => a.id == reservation.accommodation_id); return acc?.start_day_id || '' })(),
@@ -156,7 +157,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
meta_airline: '', meta_flight_number: '', meta_departure_airport: '', meta_arrival_airport: '', meta_airline: '', meta_flight_number: '', meta_departure_airport: '', meta_arrival_airport: '',
meta_departure_timezone: '', meta_arrival_timezone: '', meta_departure_timezone: '', meta_arrival_timezone: '',
meta_train_number: '', meta_platform: '', meta_seat: '', meta_train_number: '', meta_platform: '', meta_seat: '',
meta_check_in_time: '', meta_check_out_time: '', meta_check_in_time: '', meta_check_in_end_time: '', meta_check_out_time: '',
}) })
setPendingFiles([]) setPendingFiles([])
} }
@@ -207,6 +208,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
if (form.meta_arrival_timezone) metadata.arrival_timezone = form.meta_arrival_timezone if (form.meta_arrival_timezone) metadata.arrival_timezone = form.meta_arrival_timezone
} else if (form.type === 'hotel') { } else if (form.type === 'hotel') {
if (form.meta_check_in_time) metadata.check_in_time = form.meta_check_in_time if (form.meta_check_in_time) metadata.check_in_time = form.meta_check_in_time
if (form.meta_check_in_end_time) metadata.check_in_end_time = form.meta_check_in_end_time
if (form.meta_check_out_time) metadata.check_out_time = form.meta_check_out_time if (form.meta_check_out_time) metadata.check_out_time = form.meta_check_out_time
} else if (form.type === 'train') { } else if (form.type === 'train') {
if (form.meta_train_number) metadata.train_number = form.meta_train_number if (form.meta_train_number) metadata.train_number = form.meta_train_number
@@ -245,6 +247,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
start_day_id: form.hotel_start_day, start_day_id: form.hotel_start_day,
end_day_id: form.hotel_end_day, end_day_id: form.hotel_end_day,
check_in: form.meta_check_in_time || null, check_in: form.meta_check_in_time || null,
check_in_end: form.meta_check_in_end_time || null,
check_out: form.meta_check_out_time || null, check_out: form.meta_check_out_time || null,
confirmation: form.confirmation_number || null, confirmation: form.confirmation_number || null,
} }
@@ -526,11 +529,15 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
</div> </div>
</div> </div>
{/* Check-in/out times + Status */} {/* Check-in/out times + Status */}
<div className="grid grid-cols-3 gap-3"> <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div> <div>
<label style={labelStyle}>{t('reservations.meta.checkIn')}</label> <label style={labelStyle}>{t('reservations.meta.checkIn')}</label>
<CustomTimePicker value={form.meta_check_in_time} onChange={v => set('meta_check_in_time', v)} /> <CustomTimePicker value={form.meta_check_in_time} onChange={v => set('meta_check_in_time', v)} />
</div> </div>
<div>
<label style={labelStyle}>{t('reservations.meta.checkInUntil')}</label>
<CustomTimePicker value={form.meta_check_in_end_time} onChange={v => set('meta_check_in_end_time', v)} />
</div>
<div> <div>
<label style={labelStyle}>{t('reservations.meta.checkOut')}</label> <label style={labelStyle}>{t('reservations.meta.checkOut')}</label>
<CustomTimePicker value={form.meta_check_out_time} onChange={v => set('meta_check_out_time', v)} /> <CustomTimePicker value={form.meta_check_out_time} onChange={v => set('meta_check_out_time', v)} />
@@ -230,7 +230,7 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
if (meta.train_number) cells.push({ label: t('reservations.meta.trainNumber'), value: meta.train_number }) if (meta.train_number) cells.push({ label: t('reservations.meta.trainNumber'), value: meta.train_number })
if (meta.platform) cells.push({ label: t('reservations.meta.platform'), value: meta.platform }) if (meta.platform) cells.push({ label: t('reservations.meta.platform'), value: meta.platform })
if (meta.seat) cells.push({ label: t('reservations.meta.seat'), value: meta.seat }) if (meta.seat) cells.push({ label: t('reservations.meta.seat'), value: meta.seat })
if (meta.check_in_time) cells.push({ label: t('reservations.meta.checkIn'), value: fmtTime('2000-01-01T' + meta.check_in_time) }) if (meta.check_in_time) cells.push({ label: t('reservations.meta.checkIn'), value: fmtTime('2000-01-01T' + meta.check_in_time) + (meta.check_in_end_time ? ` ${fmtTime('2000-01-01T' + meta.check_in_end_time)}` : '') })
if (meta.check_out_time) cells.push({ label: t('reservations.meta.checkOut'), value: fmtTime('2000-01-01T' + meta.check_out_time) }) if (meta.check_out_time) cells.push({ label: t('reservations.meta.checkOut'), value: fmtTime('2000-01-01T' + meta.check_out_time) })
if (cells.length === 0) return null if (cells.length === 0) return null
return ( return (
+2
View File
@@ -1015,6 +1015,7 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'المنصة', 'reservations.meta.platform': 'المنصة',
'reservations.meta.seat': 'المقعد', 'reservations.meta.seat': 'المقعد',
'reservations.meta.checkIn': 'تسجيل الوصول', 'reservations.meta.checkIn': 'تسجيل الوصول',
'reservations.meta.checkInUntil': 'تسجيل الدخول حتى',
'reservations.meta.checkOut': 'تسجيل المغادرة', 'reservations.meta.checkOut': 'تسجيل المغادرة',
'reservations.meta.linkAccommodation': 'الإقامة', 'reservations.meta.linkAccommodation': 'الإقامة',
'reservations.meta.pickAccommodation': 'ربط بالإقامة', 'reservations.meta.pickAccommodation': 'ربط بالإقامة',
@@ -1499,6 +1500,7 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'أضف أماكن إلى رحلتك أولًا', 'day.noPlacesForHotel': 'أضف أماكن إلى رحلتك أولًا',
'day.allDays': 'الكل', 'day.allDays': 'الكل',
'day.checkIn': 'تسجيل الوصول', 'day.checkIn': 'تسجيل الوصول',
'day.checkInUntil': 'حتى',
'day.checkOut': 'تسجيل المغادرة', 'day.checkOut': 'تسجيل المغادرة',
'day.confirmation': 'التأكيد', 'day.confirmation': 'التأكيد',
'day.editAccommodation': 'تعديل الإقامة', 'day.editAccommodation': 'تعديل الإقامة',
+2
View File
@@ -984,6 +984,7 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'Plataforma', 'reservations.meta.platform': 'Plataforma',
'reservations.meta.seat': 'Assento', 'reservations.meta.seat': 'Assento',
'reservations.meta.checkIn': 'Check-in', 'reservations.meta.checkIn': 'Check-in',
'reservations.meta.checkInUntil': 'Check-in até',
'reservations.meta.checkOut': 'Check-out', 'reservations.meta.checkOut': 'Check-out',
'reservations.meta.linkAccommodation': 'Hospedagem', 'reservations.meta.linkAccommodation': 'Hospedagem',
'reservations.meta.pickAccommodation': 'Vincular à hospedagem', 'reservations.meta.pickAccommodation': 'Vincular à hospedagem',
@@ -1468,6 +1469,7 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'Adicione lugares à viagem primeiro', 'day.noPlacesForHotel': 'Adicione lugares à viagem primeiro',
'day.allDays': 'Todos', 'day.allDays': 'Todos',
'day.checkIn': 'Check-in', 'day.checkIn': 'Check-in',
'day.checkInUntil': 'Até',
'day.checkOut': 'Check-out', 'day.checkOut': 'Check-out',
'day.confirmation': 'Confirmação', 'day.confirmation': 'Confirmação',
'day.editAccommodation': 'Editar hospedagem', 'day.editAccommodation': 'Editar hospedagem',
+2
View File
@@ -1013,6 +1013,7 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'Nástupiště', 'reservations.meta.platform': 'Nástupiště',
'reservations.meta.seat': 'Sedadlo', 'reservations.meta.seat': 'Sedadlo',
'reservations.meta.checkIn': 'Check-in', 'reservations.meta.checkIn': 'Check-in',
'reservations.meta.checkInUntil': 'Check-in do',
'reservations.meta.checkOut': 'Check-out', 'reservations.meta.checkOut': 'Check-out',
'reservations.meta.linkAccommodation': 'Ubytování', 'reservations.meta.linkAccommodation': 'Ubytování',
'reservations.meta.pickAccommodation': 'Propojit s ubytováním', 'reservations.meta.pickAccommodation': 'Propojit s ubytováním',
@@ -1497,6 +1498,7 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'Nejprve přidejte místa ke své cestě', 'day.noPlacesForHotel': 'Nejprve přidejte místa ke své cestě',
'day.allDays': 'Vše', 'day.allDays': 'Vše',
'day.checkIn': 'Check-in', 'day.checkIn': 'Check-in',
'day.checkInUntil': 'Do',
'day.checkOut': 'Check-out', 'day.checkOut': 'Check-out',
'day.confirmation': 'Potvrzení', 'day.confirmation': 'Potvrzení',
'day.editAccommodation': 'Upravit ubytování', 'day.editAccommodation': 'Upravit ubytování',
+2
View File
@@ -1015,6 +1015,7 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'Gleis', 'reservations.meta.platform': 'Gleis',
'reservations.meta.seat': 'Sitzplatz', 'reservations.meta.seat': 'Sitzplatz',
'reservations.meta.checkIn': 'Check-in', 'reservations.meta.checkIn': 'Check-in',
'reservations.meta.checkInUntil': 'Check-in bis',
'reservations.meta.checkOut': 'Check-out', 'reservations.meta.checkOut': 'Check-out',
'reservations.meta.linkAccommodation': 'Unterkunft', 'reservations.meta.linkAccommodation': 'Unterkunft',
'reservations.meta.pickAccommodation': 'Mit Unterkunft verknüpfen', 'reservations.meta.pickAccommodation': 'Mit Unterkunft verknüpfen',
@@ -1499,6 +1500,7 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'Füge zuerst Orte zu deiner Reise hinzu', 'day.noPlacesForHotel': 'Füge zuerst Orte zu deiner Reise hinzu',
'day.allDays': 'Alle', 'day.allDays': 'Alle',
'day.checkIn': 'Check-in', 'day.checkIn': 'Check-in',
'day.checkInUntil': 'Bis',
'day.checkOut': 'Check-out', 'day.checkOut': 'Check-out',
'day.confirmation': 'Bestätigung', 'day.confirmation': 'Bestätigung',
'day.editAccommodation': 'Unterkunft bearbeiten', 'day.editAccommodation': 'Unterkunft bearbeiten',
+2
View File
@@ -1068,6 +1068,7 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'Platform', 'reservations.meta.platform': 'Platform',
'reservations.meta.seat': 'Seat', 'reservations.meta.seat': 'Seat',
'reservations.meta.checkIn': 'Check-in', 'reservations.meta.checkIn': 'Check-in',
'reservations.meta.checkInUntil': 'Check-in until',
'reservations.meta.checkOut': 'Check-out', 'reservations.meta.checkOut': 'Check-out',
'reservations.meta.linkAccommodation': 'Accommodation', 'reservations.meta.linkAccommodation': 'Accommodation',
'reservations.meta.pickAccommodation': 'Link to accommodation', 'reservations.meta.pickAccommodation': 'Link to accommodation',
@@ -1552,6 +1553,7 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'Add places to your trip first', 'day.noPlacesForHotel': 'Add places to your trip first',
'day.allDays': 'All', 'day.allDays': 'All',
'day.checkIn': 'Check-in', 'day.checkIn': 'Check-in',
'day.checkInUntil': 'Until',
'day.checkOut': 'Check-out', 'day.checkOut': 'Check-out',
'day.confirmation': 'Confirmation', 'day.confirmation': 'Confirmation',
'day.editAccommodation': 'Edit accommodation', 'day.editAccommodation': 'Edit accommodation',
+2
View File
@@ -1448,6 +1448,7 @@ const es: Record<string, string> = {
'day.noPlacesForHotel': 'Añade primero lugares al viaje', 'day.noPlacesForHotel': 'Añade primero lugares al viaje',
'day.allDays': 'Todos', 'day.allDays': 'Todos',
'day.checkIn': 'Registro de entrada', 'day.checkIn': 'Registro de entrada',
'day.checkInUntil': 'Hasta',
'day.checkOut': 'Registro de salida', 'day.checkOut': 'Registro de salida',
'day.confirmation': 'Confirmación', 'day.confirmation': 'Confirmación',
'day.editAccommodation': 'Editar alojamiento', 'day.editAccommodation': 'Editar alojamiento',
@@ -1615,6 +1616,7 @@ const es: Record<string, string> = {
'reservations.meta.platform': 'Andén', 'reservations.meta.platform': 'Andén',
'reservations.meta.seat': 'Asiento', 'reservations.meta.seat': 'Asiento',
'reservations.meta.checkIn': 'Registro de entrada', 'reservations.meta.checkIn': 'Registro de entrada',
'reservations.meta.checkInUntil': 'Check-in hasta',
'reservations.meta.checkOut': 'Registro de salida', 'reservations.meta.checkOut': 'Registro de salida',
'reservations.meta.linkAccommodation': 'Alojamiento', 'reservations.meta.linkAccommodation': 'Alojamiento',
'reservations.meta.pickAccommodation': 'Vincular con alojamiento', 'reservations.meta.pickAccommodation': 'Vincular con alojamiento',
+2
View File
@@ -1011,6 +1011,7 @@ const fr: Record<string, string> = {
'reservations.meta.platform': 'Quai', 'reservations.meta.platform': 'Quai',
'reservations.meta.seat': 'Place', 'reservations.meta.seat': 'Place',
'reservations.meta.checkIn': 'Arrivée', 'reservations.meta.checkIn': 'Arrivée',
'reservations.meta.checkInUntil': "Check-in jusqu'à",
'reservations.meta.checkOut': 'Départ', 'reservations.meta.checkOut': 'Départ',
'reservations.meta.linkAccommodation': 'Hébergement', 'reservations.meta.linkAccommodation': 'Hébergement',
'reservations.meta.pickAccommodation': 'Lier à un hébergement', 'reservations.meta.pickAccommodation': 'Lier à un hébergement',
@@ -1495,6 +1496,7 @@ const fr: Record<string, string> = {
'day.noPlacesForHotel': 'Ajoutez d\'abord des lieux à votre voyage', 'day.noPlacesForHotel': 'Ajoutez d\'abord des lieux à votre voyage',
'day.allDays': 'Tous', 'day.allDays': 'Tous',
'day.checkIn': 'Arrivée', 'day.checkIn': 'Arrivée',
'day.checkInUntil': "Jusqu'à",
'day.checkOut': 'Départ', 'day.checkOut': 'Départ',
'day.confirmation': 'Confirmation', 'day.confirmation': 'Confirmation',
'day.editAccommodation': 'Modifier l\'hébergement', 'day.editAccommodation': 'Modifier l\'hébergement',
+2
View File
@@ -1013,6 +1013,7 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'Vágány', 'reservations.meta.platform': 'Vágány',
'reservations.meta.seat': 'Ülés', 'reservations.meta.seat': 'Ülés',
'reservations.meta.checkIn': 'Bejelentkezés', 'reservations.meta.checkIn': 'Bejelentkezés',
'reservations.meta.checkInUntil': 'Bejelentkezés eddig',
'reservations.meta.checkOut': 'Kijelentkezés', 'reservations.meta.checkOut': 'Kijelentkezés',
'reservations.meta.linkAccommodation': 'Szállás', 'reservations.meta.linkAccommodation': 'Szállás',
'reservations.meta.pickAccommodation': 'Szállás hozzárendelése', 'reservations.meta.pickAccommodation': 'Szállás hozzárendelése',
@@ -1496,6 +1497,7 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'Először adj hozzá helyeket az utazásodhoz', 'day.noPlacesForHotel': 'Először adj hozzá helyeket az utazásodhoz',
'day.allDays': 'Összes', 'day.allDays': 'Összes',
'day.checkIn': 'Bejelentkezés', 'day.checkIn': 'Bejelentkezés',
'day.checkInUntil': 'Eddig',
'day.checkOut': 'Kijelentkezés', 'day.checkOut': 'Kijelentkezés',
'day.confirmation': 'Visszaigazolás', 'day.confirmation': 'Visszaigazolás',
'day.editAccommodation': 'Szállás szerkesztése', 'day.editAccommodation': 'Szállás szerkesztése',
+2
View File
@@ -1068,6 +1068,7 @@ const id: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'Peron', 'reservations.meta.platform': 'Peron',
'reservations.meta.seat': 'Kursi', 'reservations.meta.seat': 'Kursi',
'reservations.meta.checkIn': 'Check-in', 'reservations.meta.checkIn': 'Check-in',
'reservations.meta.checkInUntil': 'Check-in sampai',
'reservations.meta.checkOut': 'Check-out', 'reservations.meta.checkOut': 'Check-out',
'reservations.meta.linkAccommodation': 'Akomodasi', 'reservations.meta.linkAccommodation': 'Akomodasi',
'reservations.meta.pickAccommodation': 'Hubungkan ke akomodasi', 'reservations.meta.pickAccommodation': 'Hubungkan ke akomodasi',
@@ -1552,6 +1553,7 @@ const id: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'Tambahkan tempat ke perjalananmu terlebih dahulu', 'day.noPlacesForHotel': 'Tambahkan tempat ke perjalananmu terlebih dahulu',
'day.allDays': 'Semua', 'day.allDays': 'Semua',
'day.checkIn': 'Check-in', 'day.checkIn': 'Check-in',
'day.checkInUntil': 'Sampai',
'day.checkOut': 'Check-out', 'day.checkOut': 'Check-out',
'day.confirmation': 'Konfirmasi', 'day.confirmation': 'Konfirmasi',
'day.editAccommodation': 'Edit akomodasi', 'day.editAccommodation': 'Edit akomodasi',
+2
View File
@@ -1012,6 +1012,7 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'Binario', 'reservations.meta.platform': 'Binario',
'reservations.meta.seat': 'Posto', 'reservations.meta.seat': 'Posto',
'reservations.meta.checkIn': 'Check-in', 'reservations.meta.checkIn': 'Check-in',
'reservations.meta.checkInUntil': 'Check-in fino a',
'reservations.meta.checkOut': 'Check-out', 'reservations.meta.checkOut': 'Check-out',
'reservations.meta.linkAccommodation': 'Alloggio', 'reservations.meta.linkAccommodation': 'Alloggio',
'reservations.meta.pickAccommodation': 'Collega a un alloggio', 'reservations.meta.pickAccommodation': 'Collega a un alloggio',
@@ -1496,6 +1497,7 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'Aggiungi prima i luoghi al tuo viaggio', 'day.noPlacesForHotel': 'Aggiungi prima i luoghi al tuo viaggio',
'day.allDays': 'Tutti', 'day.allDays': 'Tutti',
'day.checkIn': 'Check-in', 'day.checkIn': 'Check-in',
'day.checkInUntil': 'Fino a',
'day.checkOut': 'Check-out', 'day.checkOut': 'Check-out',
'day.confirmation': 'Conferma', 'day.confirmation': 'Conferma',
'day.editAccommodation': 'Modifica alloggio', 'day.editAccommodation': 'Modifica alloggio',
+2
View File
@@ -1011,6 +1011,7 @@ const nl: Record<string, string> = {
'reservations.meta.platform': 'Perron', 'reservations.meta.platform': 'Perron',
'reservations.meta.seat': 'Stoel', 'reservations.meta.seat': 'Stoel',
'reservations.meta.checkIn': 'Inchecken', 'reservations.meta.checkIn': 'Inchecken',
'reservations.meta.checkInUntil': 'Check-in tot',
'reservations.meta.checkOut': 'Uitchecken', 'reservations.meta.checkOut': 'Uitchecken',
'reservations.meta.linkAccommodation': 'Accommodatie', 'reservations.meta.linkAccommodation': 'Accommodatie',
'reservations.meta.pickAccommodation': 'Koppel aan accommodatie', 'reservations.meta.pickAccommodation': 'Koppel aan accommodatie',
@@ -1495,6 +1496,7 @@ const nl: Record<string, string> = {
'day.noPlacesForHotel': 'Voeg eerst plaatsen toe aan je reis', 'day.noPlacesForHotel': 'Voeg eerst plaatsen toe aan je reis',
'day.allDays': 'Alle', 'day.allDays': 'Alle',
'day.checkIn': 'Inchecken', 'day.checkIn': 'Inchecken',
'day.checkInUntil': 'Tot',
'day.checkOut': 'Uitchecken', 'day.checkOut': 'Uitchecken',
'day.confirmation': 'Bevestiging', 'day.confirmation': 'Bevestiging',
'day.editAccommodation': 'Accommodatie bewerken', 'day.editAccommodation': 'Accommodatie bewerken',
+2
View File
@@ -968,6 +968,7 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'reservations.meta.platform': 'Peron', 'reservations.meta.platform': 'Peron',
'reservations.meta.seat': 'Miejsce', 'reservations.meta.seat': 'Miejsce',
'reservations.meta.checkIn': 'Zameldowanie', 'reservations.meta.checkIn': 'Zameldowanie',
'reservations.meta.checkInUntil': 'Check-in do',
'reservations.meta.checkOut': 'Wymeldowanie', 'reservations.meta.checkOut': 'Wymeldowanie',
'reservations.meta.linkAccommodation': 'Zakwaterowanie', 'reservations.meta.linkAccommodation': 'Zakwaterowanie',
'reservations.meta.pickAccommodation': 'Link do zakwaterowania', 'reservations.meta.pickAccommodation': 'Link do zakwaterowania',
@@ -1450,6 +1451,7 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'day.noPlacesForHotel': 'Najpierw dodaj miejsca do swojej podróży', 'day.noPlacesForHotel': 'Najpierw dodaj miejsca do swojej podróży',
'day.allDays': 'Wszystkie', 'day.allDays': 'Wszystkie',
'day.checkIn': 'Zameldowanie', 'day.checkIn': 'Zameldowanie',
'day.checkInUntil': 'Do',
'day.checkOut': 'Wymeldowanie', 'day.checkOut': 'Wymeldowanie',
'day.confirmation': 'Potwierdzenie', 'day.confirmation': 'Potwierdzenie',
'day.editAccommodation': 'Edytuj zakwaterowanie', 'day.editAccommodation': 'Edytuj zakwaterowanie',
+2
View File
@@ -1011,6 +1011,7 @@ const ru: Record<string, string> = {
'reservations.meta.platform': 'Платформа', 'reservations.meta.platform': 'Платформа',
'reservations.meta.seat': 'Место', 'reservations.meta.seat': 'Место',
'reservations.meta.checkIn': 'Заезд', 'reservations.meta.checkIn': 'Заезд',
'reservations.meta.checkInUntil': 'Заселение до',
'reservations.meta.checkOut': 'Выезд', 'reservations.meta.checkOut': 'Выезд',
'reservations.meta.linkAccommodation': 'Жильё', 'reservations.meta.linkAccommodation': 'Жильё',
'reservations.meta.pickAccommodation': 'Привязать к жилью', 'reservations.meta.pickAccommodation': 'Привязать к жилью',
@@ -1495,6 +1496,7 @@ const ru: Record<string, string> = {
'day.noPlacesForHotel': 'Сначала добавьте места в поездку', 'day.noPlacesForHotel': 'Сначала добавьте места в поездку',
'day.allDays': 'Все', 'day.allDays': 'Все',
'day.checkIn': 'Заезд', 'day.checkIn': 'Заезд',
'day.checkInUntil': 'До',
'day.checkOut': 'Выезд', 'day.checkOut': 'Выезд',
'day.confirmation': 'Подтверждение', 'day.confirmation': 'Подтверждение',
'day.editAccommodation': 'Редактировать жильё', 'day.editAccommodation': 'Редактировать жильё',
+2
View File
@@ -1011,6 +1011,7 @@ const zh: Record<string, string> = {
'reservations.meta.platform': '站台', 'reservations.meta.platform': '站台',
'reservations.meta.seat': '座位', 'reservations.meta.seat': '座位',
'reservations.meta.checkIn': '入住', 'reservations.meta.checkIn': '入住',
'reservations.meta.checkInUntil': '入住截止',
'reservations.meta.checkOut': '退房', 'reservations.meta.checkOut': '退房',
'reservations.meta.linkAccommodation': '住宿', 'reservations.meta.linkAccommodation': '住宿',
'reservations.meta.pickAccommodation': '关联住宿', 'reservations.meta.pickAccommodation': '关联住宿',
@@ -1495,6 +1496,7 @@ const zh: Record<string, string> = {
'day.noPlacesForHotel': '请先在旅行中添加地点', 'day.noPlacesForHotel': '请先在旅行中添加地点',
'day.allDays': '全部', 'day.allDays': '全部',
'day.checkIn': '入住', 'day.checkIn': '入住',
'day.checkInUntil': '截止',
'day.checkOut': '退房', 'day.checkOut': '退房',
'day.confirmation': '确认号', 'day.confirmation': '确认号',
'day.editAccommodation': '编辑住宿', 'day.editAccommodation': '编辑住宿',
+2
View File
@@ -1067,6 +1067,7 @@ const zhTw: Record<string, string> = {
'reservations.meta.platform': '站臺', 'reservations.meta.platform': '站臺',
'reservations.meta.seat': '座位', 'reservations.meta.seat': '座位',
'reservations.meta.checkIn': '入住', 'reservations.meta.checkIn': '入住',
'reservations.meta.checkInUntil': '入住截止',
'reservations.meta.checkOut': '退房', 'reservations.meta.checkOut': '退房',
'reservations.meta.linkAccommodation': '住宿', 'reservations.meta.linkAccommodation': '住宿',
'reservations.meta.pickAccommodation': '關聯住宿', 'reservations.meta.pickAccommodation': '關聯住宿',
@@ -1551,6 +1552,7 @@ const zhTw: Record<string, string> = {
'day.noPlacesForHotel': '請先在旅行中新增地點', 'day.noPlacesForHotel': '請先在旅行中新增地點',
'day.allDays': '全部', 'day.allDays': '全部',
'day.checkIn': '入住', 'day.checkIn': '入住',
'day.checkInUntil': '截止',
'day.checkOut': '退房', 'day.checkOut': '退房',
'day.confirmation': '確認號', 'day.confirmation': '確認號',
'day.editAccommodation': '編輯住宿', 'day.editAccommodation': '編輯住宿',
+1
View File
@@ -241,6 +241,7 @@ export interface Accommodation {
name: string name: string
address: string | null address: string | null
check_in: string | null check_in: string | null
check_in_end: string | null
check_out: string | null check_out: string | null
confirmation_number: string | null confirmation_number: string | null
notes: string | null notes: string | null
+5
View File
@@ -1610,6 +1610,11 @@ function runMigrations(db: Database.Database): void {
() => { () => {
db.prepare("UPDATE addons SET enabled = 1 WHERE id = 'naver_list_import'").run(); db.prepare("UPDATE addons SET enabled = 1 WHERE id = 'naver_list_import'").run();
}, },
// Migration 102: Add check_in_end column for check-in time ranges
() => {
try { db.exec('ALTER TABLE day_accommodations ADD COLUMN check_in_end TEXT'); } catch (err: any) { if (!err.message?.includes('duplicate column name')) throw err; }
},
]; ];
if (currentVersion < migrations.length) { if (currentVersion < migrations.length) {
+1
View File
@@ -334,6 +334,7 @@ function createTables(db: Database.Database): void {
start_day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE, start_day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE,
end_day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE, end_day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE,
check_in TEXT, check_in TEXT,
check_in_end TEXT,
check_out TEXT, check_out TEXT,
confirmation TEXT, confirmation TEXT,
notes TEXT, notes TEXT,
+4 -4
View File
@@ -73,7 +73,7 @@ accommodationsRouter.post('/', authenticate, requireTripAccess, (req: Request, r
return res.status(403).json({ error: 'No permission' }); return res.status(403).json({ error: 'No permission' });
const { tripId } = req.params; const { tripId } = req.params;
const { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes } = req.body; const { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes } = req.body;
if (!place_id || !start_day_id || !end_day_id) { if (!place_id || !start_day_id || !end_day_id) {
return res.status(400).json({ error: 'place_id, start_day_id, and end_day_id are required' }); return res.status(400).json({ error: 'place_id, start_day_id, and end_day_id are required' });
@@ -82,7 +82,7 @@ accommodationsRouter.post('/', authenticate, requireTripAccess, (req: Request, r
const errors = dayService.validateAccommodationRefs(tripId, place_id, start_day_id, end_day_id); const errors = dayService.validateAccommodationRefs(tripId, place_id, start_day_id, end_day_id);
if (errors.length > 0) return res.status(404).json({ error: errors[0].message }); if (errors.length > 0) return res.status(404).json({ error: errors[0].message });
const accommodation = dayService.createAccommodation(tripId, { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes }); const accommodation = dayService.createAccommodation(tripId, { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes });
res.status(201).json({ accommodation }); res.status(201).json({ accommodation });
broadcast(tripId, 'accommodation:created', { accommodation }, req.headers['x-socket-id'] as string); broadcast(tripId, 'accommodation:created', { accommodation }, req.headers['x-socket-id'] as string);
broadcast(tripId, 'reservation:created', {}, req.headers['x-socket-id'] as string); broadcast(tripId, 'reservation:created', {}, req.headers['x-socket-id'] as string);
@@ -98,12 +98,12 @@ accommodationsRouter.put('/:id', authenticate, requireTripAccess, (req: Request,
const existing = dayService.getAccommodation(id, tripId); const existing = dayService.getAccommodation(id, tripId);
if (!existing) return res.status(404).json({ error: 'Accommodation not found' }); if (!existing) return res.status(404).json({ error: 'Accommodation not found' });
const { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes } = req.body; const { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes } = req.body;
const errors = dayService.validateAccommodationRefs(tripId, place_id, start_day_id, end_day_id); const errors = dayService.validateAccommodationRefs(tripId, place_id, start_day_id, end_day_id);
if (errors.length > 0) return res.status(404).json({ error: errors[0].message }); if (errors.length > 0) return res.status(404).json({ error: errors[0].message });
const accommodation = dayService.updateAccommodation(id, existing, { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes }); const accommodation = dayService.updateAccommodation(id, existing, { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes });
res.json({ accommodation }); res.json({ accommodation });
broadcast(tripId, 'accommodation:updated', { accommodation }, req.headers['x-socket-id'] as string); broadcast(tripId, 'accommodation:updated', { accommodation }, req.headers['x-socket-id'] as string);
}); });
+11 -6
View File
@@ -170,6 +170,7 @@ export interface DayAccommodation {
start_day_id: number; start_day_id: number;
end_day_id: number; end_day_id: number;
check_in: string | null; check_in: string | null;
check_in_end: string | null;
check_out: string | null; check_out: string | null;
confirmation: string | null; confirmation: string | null;
notes: string | null; notes: string | null;
@@ -220,17 +221,18 @@ interface CreateAccommodationData {
start_day_id: number; start_day_id: number;
end_day_id: number; end_day_id: number;
check_in?: string; check_in?: string;
check_in_end?: string;
check_out?: string; check_out?: string;
confirmation?: string; confirmation?: string;
notes?: string; notes?: string;
} }
export function createAccommodation(tripId: string | number, data: CreateAccommodationData) { export function createAccommodation(tripId: string | number, data: CreateAccommodationData) {
const { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes } = data; const { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes } = data;
const result = db.prepare( const result = db.prepare(
'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' 'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
).run(tripId, place_id, start_day_id, end_day_id, check_in || null, check_out || null, confirmation || null, notes || null); ).run(tripId, place_id, start_day_id, end_day_id, check_in || null, check_in_end || null, check_out || null, confirmation || null, notes || null);
const accommodationId = result.lastInsertRowid; const accommodationId = result.lastInsertRowid;
@@ -239,6 +241,7 @@ export function createAccommodation(tripId: string | number, data: CreateAccommo
const startDayDate = (db.prepare('SELECT date FROM days WHERE id = ?').get(start_day_id) as { date: string } | undefined)?.date || null; const startDayDate = (db.prepare('SELECT date FROM days WHERE id = ?').get(start_day_id) as { date: string } | undefined)?.date || null;
const meta: Record<string, string> = {}; const meta: Record<string, string> = {};
if (check_in) meta.check_in_time = check_in; if (check_in) meta.check_in_time = check_in;
if (check_in_end) meta.check_in_end_time = check_in_end;
if (check_out) meta.check_out_time = check_out; if (check_out) meta.check_out_time = check_out;
db.prepare(` db.prepare(`
INSERT INTO reservations (trip_id, day_id, title, reservation_time, location, confirmation_number, notes, status, type, accommodation_id, metadata) INSERT INTO reservations (trip_id, day_id, title, reservation_time, location, confirmation_number, notes, status, type, accommodation_id, metadata)
@@ -258,25 +261,27 @@ export function getAccommodation(id: string | number, tripId: string | number) {
export function updateAccommodation(id: string | number, existing: DayAccommodation, fields: { export function updateAccommodation(id: string | number, existing: DayAccommodation, fields: {
place_id?: number; start_day_id?: number; end_day_id?: number; place_id?: number; start_day_id?: number; end_day_id?: number;
check_in?: string; check_out?: string; confirmation?: string; notes?: string; check_in?: string; check_in_end?: string; check_out?: string; confirmation?: string; notes?: string;
}) { }) {
const newPlaceId = fields.place_id !== undefined ? fields.place_id : existing.place_id; const newPlaceId = fields.place_id !== undefined ? fields.place_id : existing.place_id;
const newStartDayId = fields.start_day_id !== undefined ? fields.start_day_id : existing.start_day_id; const newStartDayId = fields.start_day_id !== undefined ? fields.start_day_id : existing.start_day_id;
const newEndDayId = fields.end_day_id !== undefined ? fields.end_day_id : existing.end_day_id; const newEndDayId = fields.end_day_id !== undefined ? fields.end_day_id : existing.end_day_id;
const newCheckIn = fields.check_in !== undefined ? fields.check_in : existing.check_in; const newCheckIn = fields.check_in !== undefined ? fields.check_in : existing.check_in;
const newCheckInEnd = fields.check_in_end !== undefined ? fields.check_in_end : existing.check_in_end;
const newCheckOut = fields.check_out !== undefined ? fields.check_out : existing.check_out; const newCheckOut = fields.check_out !== undefined ? fields.check_out : existing.check_out;
const newConfirmation = fields.confirmation !== undefined ? fields.confirmation : existing.confirmation; const newConfirmation = fields.confirmation !== undefined ? fields.confirmation : existing.confirmation;
const newNotes = fields.notes !== undefined ? fields.notes : existing.notes; const newNotes = fields.notes !== undefined ? fields.notes : existing.notes;
db.prepare( db.prepare(
'UPDATE day_accommodations SET place_id = ?, start_day_id = ?, end_day_id = ?, check_in = ?, check_out = ?, confirmation = ?, notes = ? WHERE id = ?' 'UPDATE day_accommodations SET place_id = ?, start_day_id = ?, end_day_id = ?, check_in = ?, check_in_end = ?, check_out = ?, confirmation = ?, notes = ? WHERE id = ?'
).run(newPlaceId, newStartDayId, newEndDayId, newCheckIn, newCheckOut, newConfirmation, newNotes, id); ).run(newPlaceId, newStartDayId, newEndDayId, newCheckIn, newCheckInEnd, newCheckOut, newConfirmation, newNotes, id);
// Sync check-in/out/confirmation to linked reservation // Sync check-in/out/confirmation to linked reservation
const linkedRes = db.prepare('SELECT id, metadata FROM reservations WHERE accommodation_id = ?').get(Number(id)) as { id: number; metadata: string | null } | undefined; const linkedRes = db.prepare('SELECT id, metadata FROM reservations WHERE accommodation_id = ?').get(Number(id)) as { id: number; metadata: string | null } | undefined;
if (linkedRes) { if (linkedRes) {
const meta = linkedRes.metadata ? JSON.parse(linkedRes.metadata) : {}; const meta = linkedRes.metadata ? JSON.parse(linkedRes.metadata) : {};
if (newCheckIn) meta.check_in_time = newCheckIn; if (newCheckIn) meta.check_in_time = newCheckIn;
if (newCheckInEnd) meta.check_in_end_time = newCheckInEnd;
if (newCheckOut) meta.check_out_time = newCheckOut; if (newCheckOut) meta.check_out_time = newCheckOut;
db.prepare('UPDATE reservations SET metadata = ?, confirmation_number = COALESCE(?, confirmation_number) WHERE id = ?') db.prepare('UPDATE reservations SET metadata = ?, confirmation_number = COALESCE(?, confirmation_number) WHERE id = ?')
.run(JSON.stringify(meta), newConfirmation || null, linkedRes.id); .run(JSON.stringify(meta), newConfirmation || null, linkedRes.id);
+6 -6
View File
@@ -123,9 +123,9 @@ export function createReservation(tripId: string | number, data: CreateReservati
// Sync check-in/out to accommodation if linked // Sync check-in/out to accommodation if linked
if (accommodation_id && metadata) { if (accommodation_id && metadata) {
const meta = typeof metadata === 'string' ? JSON.parse(metadata) : metadata; const meta = typeof metadata === 'string' ? JSON.parse(metadata) : metadata;
if (meta.check_in_time || meta.check_out_time) { if (meta.check_in_time || meta.check_in_end_time || meta.check_out_time) {
db.prepare('UPDATE day_accommodations SET check_in = COALESCE(?, check_in), check_out = COALESCE(?, check_out) WHERE id = ?') db.prepare('UPDATE day_accommodations SET check_in = COALESCE(?, check_in), check_in_end = COALESCE(?, check_in_end), check_out = COALESCE(?, check_out) WHERE id = ?')
.run(meta.check_in_time || null, meta.check_out_time || null, accommodation_id); .run(meta.check_in_time || null, meta.check_in_end_time || null, meta.check_out_time || null, accommodation_id);
} }
if (confirmation_number) { if (confirmation_number) {
db.prepare('UPDATE day_accommodations SET confirmation = COALESCE(?, confirmation) WHERE id = ?') db.prepare('UPDATE day_accommodations SET confirmation = COALESCE(?, confirmation) WHERE id = ?')
@@ -257,9 +257,9 @@ export function updateReservation(id: string | number, tripId: string | number,
const resolvedMeta = metadata !== undefined ? metadata : (current.metadata ? JSON.parse(current.metadata as string) : null); const resolvedMeta = metadata !== undefined ? metadata : (current.metadata ? JSON.parse(current.metadata as string) : null);
if (resolvedAccId && resolvedMeta) { if (resolvedAccId && resolvedMeta) {
const meta = typeof resolvedMeta === 'string' ? JSON.parse(resolvedMeta) : resolvedMeta; const meta = typeof resolvedMeta === 'string' ? JSON.parse(resolvedMeta) : resolvedMeta;
if (meta.check_in_time || meta.check_out_time) { if (meta.check_in_time || meta.check_in_end_time || meta.check_out_time) {
db.prepare('UPDATE day_accommodations SET check_in = COALESCE(?, check_in), check_out = COALESCE(?, check_out) WHERE id = ?') db.prepare('UPDATE day_accommodations SET check_in = COALESCE(?, check_in), check_in_end = COALESCE(?, check_in_end), check_out = COALESCE(?, check_out) WHERE id = ?')
.run(meta.check_in_time || null, meta.check_out_time || null, resolvedAccId); .run(meta.check_in_time || null, meta.check_in_end_time || null, meta.check_out_time || null, resolvedAccId);
} }
const resolvedConf = confirmation_number !== undefined ? confirmation_number : current.confirmation_number; const resolvedConf = confirmation_number !== undefined ? confirmation_number : current.confirmation_number;
if (resolvedConf) { if (resolvedConf) {