diff --git a/client/src/db/offlineDb.ts b/client/src/db/offlineDb.ts index 57b33dfe..224794c6 100644 --- a/client/src/db/offlineDb.ts +++ b/client/src/db/offlineDb.ts @@ -1,5 +1,10 @@ import Dexie, { type Table } from 'dexie'; -import type { Trip, Day, Place, PackingItem, TodoItem, BudgetItem, Reservation, TripFile } from '../types'; +import type { Trip, Day, Place, PackingItem, TodoItem, BudgetItem, Reservation, TripFile, Accommodation, TripMember, Tag, Category } from '../types'; + +/** TripMember enriched with tripId so we can index by trip. */ +export interface CachedTripMember extends TripMember { + tripId: number; +} // ── Queue + sync types ──────────────────────────────────────────────────────── @@ -52,6 +57,10 @@ class TrekOfflineDb extends Dexie { budgetItems!: Table; reservations!: Table; tripFiles!: Table; + accommodations!: Table; + tripMembers!: Table; + tags!: Table; + categories!: Table; mutationQueue!: Table; syncMeta!: Table; blobCache!: Table; @@ -72,6 +81,13 @@ class TrekOfflineDb extends Dexie { syncMeta: 'tripId', blobCache: 'url, cachedAt', }); + + this.version(2).stores({ + accommodations: 'id, trip_id', + tripMembers: '[tripId+id], tripId', + tags: 'id', + categories: 'id', + }); } } @@ -111,6 +127,23 @@ export async function upsertTripFiles(files: TripFile[]): Promise { await offlineDb.tripFiles.bulkPut(files); } +export async function upsertAccommodations(items: Accommodation[]): Promise { + await offlineDb.accommodations.bulkPut(items); +} + +export async function upsertTripMembers(tripId: number, members: TripMember[]): Promise { + const rows: CachedTripMember[] = members.map(m => ({ ...m, tripId })); + await offlineDb.tripMembers.bulkPut(rows); +} + +export async function upsertTags(tags: Tag[]): Promise { + await offlineDb.tags.bulkPut(tags); +} + +export async function upsertCategories(categories: Category[]): Promise { + await offlineDb.categories.bulkPut(categories); +} + export async function upsertSyncMeta(meta: SyncMeta): Promise { await offlineDb.syncMeta.put(meta); } @@ -129,6 +162,8 @@ export async function clearTripData(tripId: number): Promise { offlineDb.budgetItems, offlineDb.reservations, offlineDb.tripFiles, + offlineDb.accommodations, + offlineDb.tripMembers, offlineDb.mutationQueue, offlineDb.syncMeta, ], @@ -140,6 +175,8 @@ export async function clearTripData(tripId: number): Promise { await offlineDb.budgetItems.where('trip_id').equals(tripId).delete(); await offlineDb.reservations.where('trip_id').equals(tripId).delete(); await offlineDb.tripFiles.where('trip_id').equals(tripId).delete(); + await offlineDb.accommodations.where('trip_id').equals(tripId).delete(); + await offlineDb.tripMembers.where('tripId').equals(tripId).delete(); await offlineDb.mutationQueue.where('tripId').equals(tripId).delete(); await offlineDb.syncMeta.where('tripId').equals(tripId).delete(); }, diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts index 3a37d8f5..33cd8b6f 100644 --- a/client/src/i18n/translations/ar.ts +++ b/client/src/i18n/translations/ar.ts @@ -149,6 +149,7 @@ const ar: Record = { 'settings.tabs.notifications': 'الإشعارات', 'settings.tabs.integrations': 'التكاملات', 'settings.tabs.account': 'الحساب', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'حول', 'settings.map': 'الخريطة', 'settings.mapTemplate': 'قالب الخريطة', diff --git a/client/src/i18n/translations/br.ts b/client/src/i18n/translations/br.ts index ef89acd7..5a46ba45 100644 --- a/client/src/i18n/translations/br.ts +++ b/client/src/i18n/translations/br.ts @@ -144,6 +144,7 @@ const br: Record = { 'settings.tabs.notifications': 'Notificações', 'settings.tabs.integrations': 'Integrações', 'settings.tabs.account': 'Conta', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'Sobre', 'settings.map': 'Mapa', 'settings.mapTemplate': 'Modelo de mapa', diff --git a/client/src/i18n/translations/cs.ts b/client/src/i18n/translations/cs.ts index 3a0cf375..56c08001 100644 --- a/client/src/i18n/translations/cs.ts +++ b/client/src/i18n/translations/cs.ts @@ -145,6 +145,7 @@ const cs: Record = { 'settings.tabs.notifications': 'Oznámení', 'settings.tabs.integrations': 'Integrace', 'settings.tabs.account': 'Účet', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'O aplikaci', 'settings.map': 'Mapy', 'settings.mapTemplate': 'Šablona mapy', diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts index 107b9903..3d8500c6 100644 --- a/client/src/i18n/translations/de.ts +++ b/client/src/i18n/translations/de.ts @@ -147,6 +147,7 @@ const de: Record = { 'settings.tabs.notifications': 'Benachrichtigungen', 'settings.tabs.integrations': 'Integrationen', 'settings.tabs.account': 'Konto', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'Über', 'settings.map': 'Karte', 'settings.mapTemplate': 'Karten-Vorlage', diff --git a/client/src/i18n/translations/en.ts b/client/src/i18n/translations/en.ts index 8acb2238..596b272c 100644 --- a/client/src/i18n/translations/en.ts +++ b/client/src/i18n/translations/en.ts @@ -147,6 +147,7 @@ const en: Record = { 'settings.tabs.notifications': 'Notifications', 'settings.tabs.integrations': 'Integrations', 'settings.tabs.account': 'Account', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'About', 'settings.map': 'Map', 'settings.mapTemplate': 'Map Template', diff --git a/client/src/i18n/translations/es.ts b/client/src/i18n/translations/es.ts index 518bac4e..efb64c3f 100644 --- a/client/src/i18n/translations/es.ts +++ b/client/src/i18n/translations/es.ts @@ -145,6 +145,7 @@ const es: Record = { 'settings.tabs.notifications': 'Notificaciones', 'settings.tabs.integrations': 'Integraciones', 'settings.tabs.account': 'Cuenta', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'Acerca de', 'settings.map': 'Mapa', 'settings.mapTemplate': 'Plantilla del mapa', diff --git a/client/src/i18n/translations/fr.ts b/client/src/i18n/translations/fr.ts index b5aaa41a..09dddc83 100644 --- a/client/src/i18n/translations/fr.ts +++ b/client/src/i18n/translations/fr.ts @@ -144,6 +144,7 @@ const fr: Record = { 'settings.tabs.notifications': 'Notifications', 'settings.tabs.integrations': 'Intégrations', 'settings.tabs.account': 'Compte', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'À propos', 'settings.map': 'Carte', 'settings.mapTemplate': 'Modèle de carte', diff --git a/client/src/i18n/translations/hu.ts b/client/src/i18n/translations/hu.ts index 816fe69d..00a29375 100644 --- a/client/src/i18n/translations/hu.ts +++ b/client/src/i18n/translations/hu.ts @@ -144,6 +144,7 @@ const hu: Record = { 'settings.tabs.notifications': 'Értesítések', 'settings.tabs.integrations': 'Integrációk', 'settings.tabs.account': 'Fiók', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'Névjegy', 'settings.map': 'Térkép', 'settings.mapTemplate': 'Térkép sablon', diff --git a/client/src/i18n/translations/it.ts b/client/src/i18n/translations/it.ts index 8c7d986e..6212d8ac 100644 --- a/client/src/i18n/translations/it.ts +++ b/client/src/i18n/translations/it.ts @@ -144,6 +144,7 @@ const it: Record = { 'settings.tabs.notifications': 'Notifiche', 'settings.tabs.integrations': 'Integrazioni', 'settings.tabs.account': 'Account', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'Informazioni', 'settings.map': 'Mappa', 'settings.mapTemplate': 'Modello Mappa', diff --git a/client/src/i18n/translations/nl.ts b/client/src/i18n/translations/nl.ts index ab7790bc..229ad70c 100644 --- a/client/src/i18n/translations/nl.ts +++ b/client/src/i18n/translations/nl.ts @@ -144,6 +144,7 @@ const nl: Record = { 'settings.tabs.notifications': 'Meldingen', 'settings.tabs.integrations': 'Integraties', 'settings.tabs.account': 'Account', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'Over', 'settings.map': 'Kaart', 'settings.mapTemplate': 'Kaartsjabloon', diff --git a/client/src/i18n/translations/pl.ts b/client/src/i18n/translations/pl.ts index a3a2953b..302801a7 100644 --- a/client/src/i18n/translations/pl.ts +++ b/client/src/i18n/translations/pl.ts @@ -130,6 +130,7 @@ const pl: Record = { 'settings.tabs.notifications': 'Powiadomienia', 'settings.tabs.integrations': 'Integracje', 'settings.tabs.account': 'Konto', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'O aplikacji', 'settings.map': 'Mapa', 'settings.mapTemplate': 'Szablon mapy', diff --git a/client/src/i18n/translations/ru.ts b/client/src/i18n/translations/ru.ts index 6842f015..40e093d4 100644 --- a/client/src/i18n/translations/ru.ts +++ b/client/src/i18n/translations/ru.ts @@ -144,6 +144,7 @@ const ru: Record = { 'settings.tabs.notifications': 'Уведомления', 'settings.tabs.integrations': 'Интеграции', 'settings.tabs.account': 'Аккаунт', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': 'О приложении', 'settings.map': 'Карта', 'settings.mapTemplate': 'Шаблон карты', diff --git a/client/src/i18n/translations/zh.ts b/client/src/i18n/translations/zh.ts index e78e4b94..d6aa8278 100644 --- a/client/src/i18n/translations/zh.ts +++ b/client/src/i18n/translations/zh.ts @@ -144,6 +144,7 @@ const zh: Record = { 'settings.tabs.notifications': '通知', 'settings.tabs.integrations': '集成', 'settings.tabs.account': '账户', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': '关于', 'settings.map': '地图', 'settings.mapTemplate': '地图模板', diff --git a/client/src/i18n/translations/zhTw.ts b/client/src/i18n/translations/zhTw.ts index e7393705..83d31c2e 100644 --- a/client/src/i18n/translations/zhTw.ts +++ b/client/src/i18n/translations/zhTw.ts @@ -144,6 +144,7 @@ const zhTw: Record = { 'settings.tabs.notifications': '通知', 'settings.tabs.integrations': '整合', 'settings.tabs.account': '帳戶', + 'settings.tabs.offline': 'Offline', 'settings.tabs.about': '關於', 'settings.map': '地圖', 'settings.mapTemplate': '地圖模板', diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index 9b02b285..fcc253a2 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState, useRef } from 'react' import { useNavigate } from 'react-router-dom' import { tripsApi } from '../api/client' +import { tripRepo } from '../repo/tripRepo' import { useAuthStore } from '../store/authStore' import { useSettingsStore } from '../store/settingsStore' import { useTranslation } from '../i18n' @@ -713,12 +714,9 @@ export default function DashboardPage(): React.ReactElement { const loadTrips = async () => { setIsLoading(true) try { - const [active, archived] = await Promise.all([ - tripsApi.list(), - tripsApi.list({ archived: 1 }), - ]) - setTrips(sortTrips(active.trips)) - setArchivedTrips(sortTrips(archived.trips)) + const { trips, archivedTrips } = await tripRepo.list() + setTrips(sortTrips(trips)) + setArchivedTrips(sortTrips(archivedTrips)) } catch { toast.error(t('dashboard.toast.loadError')) } finally { diff --git a/client/src/pages/FilesPage.tsx b/client/src/pages/FilesPage.tsx index 2a6c4eac..cb48d5e1 100644 --- a/client/src/pages/FilesPage.tsx +++ b/client/src/pages/FilesPage.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from 'react' import { useParams, useNavigate, Link } from 'react-router-dom' import { useTripStore } from '../store/tripStore' -import { tripsApi, placesApi } from '../api/client' +import { tripRepo } from '../repo/tripRepo' +import { placeRepo } from '../repo/placeRepo' import Navbar from '../components/Layout/Navbar' import FileManager from '../components/Files/FileManager' import { ArrowLeft } from 'lucide-react' @@ -27,8 +28,8 @@ export default function FilesPage(): React.ReactElement { setIsLoading(true) try { const [tripData, placesData] = await Promise.all([ - tripsApi.get(tripId), - placesApi.list(tripId), + tripRepo.get(tripId), + placeRepo.list(tripId), ]) setTrip(tripData.trip) setPlaces(placesData.places) diff --git a/client/src/pages/SettingsPage.tsx b/client/src/pages/SettingsPage.tsx index 1d740a25..fedcdb31 100644 --- a/client/src/pages/SettingsPage.tsx +++ b/client/src/pages/SettingsPage.tsx @@ -42,7 +42,7 @@ export default function SettingsPage(): React.ReactElement { { id: 'map', label: t('settings.tabs.map') }, { id: 'notifications', label: t('settings.tabs.notifications') }, ...(hasIntegrations ? [{ id: 'integrations', label: t('settings.tabs.integrations') }] : []), - { id: 'offline', label: t('settings.tabs.offline', 'Offline') }, + { id: 'offline', label: t('settings.tabs.offline') }, { id: 'account', label: t('settings.tabs.account') }, ...(appVersion ? [{ id: 'about', label: t('settings.tabs.about') }] : []), ] diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index 069fce14..d893e308 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -26,6 +26,8 @@ import { useToast } from '../components/shared/Toast' import { Map, X, PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen, Ticket, PackageCheck, Wallet, FolderOpen, Users } from 'lucide-react' import { useTranslation } from '../i18n' import { addonsApi, accommodationsApi, authApi, tripsApi, assignmentsApi, mapsApi } from '../api/client' +import { accommodationRepo } from '../repo/accommodationRepo' +import { offlineDb } from '../db/offlineDb' import ConfirmDialog from '../components/shared/ConfirmDialog' import { useResizablePanels } from '../hooks/useResizablePanels' import { useTripWebSocket } from '../hooks/useTripWebSocket' @@ -104,7 +106,7 @@ export default function TripPlannerPage(): React.ReactElement | null { const loadAccommodations = useCallback(() => { if (tripId) { - accommodationsApi.list(tripId).then(d => setTripAccommodations(d.accommodations || [])).catch(() => {}) + accommodationRepo.list(tripId).then(d => setTripAccommodations(d.accommodations || [])).catch(() => {}) tripActions.loadReservations(tripId) } }, [tripId]) @@ -192,11 +194,16 @@ export default function TripPlannerPage(): React.ReactElement | null { tripActions.loadTrip(tripId).catch(() => { toast.error(t('trip.toast.loadError')); navigate('/dashboard') }) tripActions.loadFiles(tripId) loadAccommodations() - tripsApi.getMembers(tripId).then(d => { - // Combine owner + members into one list - const all = [d.owner, ...(d.members || [])].filter(Boolean) - setTripMembers(all) - }).catch(() => {}) + if (!navigator.onLine) { + offlineDb.tripMembers.where('tripId').equals(Number(tripId)).toArray() + .then(rows => setTripMembers(rows)) + .catch(() => {}) + } else { + tripsApi.getMembers(tripId).then(d => { + const all = [d.owner, ...(d.members || [])].filter(Boolean) + setTripMembers(all) + }).catch(() => {}) + } } }, [tripId]) diff --git a/client/src/repo/accommodationRepo.ts b/client/src/repo/accommodationRepo.ts new file mode 100644 index 00000000..75e8c345 --- /dev/null +++ b/client/src/repo/accommodationRepo.ts @@ -0,0 +1,16 @@ +import { accommodationsApi } from '../api/client' +import { offlineDb, upsertAccommodations } from '../db/offlineDb' +import type { Accommodation } from '../types' + +export const accommodationRepo = { + async list(tripId: number | string): Promise<{ accommodations: Accommodation[] }> { + if (!navigator.onLine) { + const accommodations = await offlineDb.accommodations + .where('trip_id').equals(Number(tripId)).toArray() + return { accommodations } + } + const result = await accommodationsApi.list(tripId) + upsertAccommodations(result.accommodations || []).catch(() => {}) + return result + }, +} diff --git a/client/src/repo/tripRepo.ts b/client/src/repo/tripRepo.ts index 6b6c4696..082e346a 100644 --- a/client/src/repo/tripRepo.ts +++ b/client/src/repo/tripRepo.ts @@ -3,6 +3,23 @@ import { offlineDb, upsertTrip } from '../db/offlineDb' import type { Trip } from '../types' export const tripRepo = { + async list(): Promise<{ trips: Trip[]; archivedTrips: Trip[] }> { + if (!navigator.onLine) { + const all = await offlineDb.trips.toArray() + return { + trips: all.filter(t => !t.is_archived), + archivedTrips: all.filter(t => t.is_archived), + } + } + const [active, archived] = await Promise.all([ + tripsApi.list(), + tripsApi.list({ archived: 1 }), + ]) + active.trips.forEach(t => upsertTrip(t)) + archived.trips.forEach(t => upsertTrip(t)) + return { trips: active.trips, archivedTrips: archived.trips } + }, + async get(tripId: number | string): Promise<{ trip: Trip }> { if (!navigator.onLine) { const cached = await offlineDb.trips.get(Number(tripId)) diff --git a/client/src/store/slices/budgetSlice.ts b/client/src/store/slices/budgetSlice.ts index 04e58fda..9f63bc45 100644 --- a/client/src/store/slices/budgetSlice.ts +++ b/client/src/store/slices/budgetSlice.ts @@ -1,4 +1,5 @@ import { budgetApi } from '../../api/client' +import { budgetRepo } from '../../repo/budgetRepo' import type { StoreApi } from 'zustand' import type { TripStoreState } from '../tripStore' import type { BudgetItem, BudgetMember } from '../../types' @@ -21,7 +22,7 @@ export interface BudgetSlice { export const createBudgetSlice = (set: SetState, get: GetState): BudgetSlice => ({ loadBudgetItems: async (tripId) => { try { - const data = await budgetApi.list(tripId) + const data = await budgetRepo.list(tripId) set({ budgetItems: data.items }) } catch (err: unknown) { console.error('Failed to load budget items:', err) diff --git a/client/src/store/slices/filesSlice.ts b/client/src/store/slices/filesSlice.ts index 07ed0deb..13617515 100644 --- a/client/src/store/slices/filesSlice.ts +++ b/client/src/store/slices/filesSlice.ts @@ -1,4 +1,5 @@ import { filesApi } from '../../api/client' +import { fileRepo } from '../../repo/fileRepo' import type { StoreApi } from 'zustand' import type { TripStoreState } from '../tripStore' import type { TripFile } from '../../types' @@ -16,7 +17,7 @@ export interface FilesSlice { export const createFilesSlice = (set: SetState, get: GetState): FilesSlice => ({ loadFiles: async (tripId) => { try { - const data = await filesApi.list(tripId) + const data = await fileRepo.list(tripId) set({ files: data.files }) } catch (err: unknown) { console.error('Failed to load files:', err) diff --git a/client/src/store/slices/reservationsSlice.ts b/client/src/store/slices/reservationsSlice.ts index 536d4cb1..c020a593 100644 --- a/client/src/store/slices/reservationsSlice.ts +++ b/client/src/store/slices/reservationsSlice.ts @@ -1,4 +1,5 @@ import { reservationsApi } from '../../api/client' +import { reservationRepo } from '../../repo/reservationRepo' import type { StoreApi } from 'zustand' import type { TripStoreState } from '../tripStore' import type { Reservation } from '../../types' @@ -18,7 +19,7 @@ export interface ReservationsSlice { export const createReservationsSlice = (set: SetState, get: GetState): ReservationsSlice => ({ loadReservations: async (tripId) => { try { - const data = await reservationsApi.list(tripId) + const data = await reservationRepo.list(tripId) set({ reservations: data.reservations }) } catch (err: unknown) { console.error('Failed to load reservations:', err) diff --git a/client/src/store/tripStore.ts b/client/src/store/tripStore.ts index f7588442..5168c078 100644 --- a/client/src/store/tripStore.ts +++ b/client/src/store/tripStore.ts @@ -1,6 +1,7 @@ import { create } from 'zustand' import type { StoreApi } from 'zustand' import { tripsApi, tagsApi, categoriesApi } from '../api/client' +import { offlineDb } from '../db/offlineDb' import { tripRepo } from '../repo/tripRepo' import { dayRepo } from '../repo/dayRepo' import { placeRepo } from '../repo/placeRepo' @@ -94,8 +95,12 @@ export const useTripStore = create((set, get) => ({ placeRepo.list(tripId), packingRepo.list(tripId), todoRepo.list(tripId), - tagsApi.list().catch(() => ({ tags: [] })), - categoriesApi.list().catch(() => ({ categories: [] })), + navigator.onLine + ? tagsApi.list().catch(() => offlineDb.tags.toArray().then(tags => ({ tags }))) + : offlineDb.tags.toArray().then(tags => ({ tags })), + navigator.onLine + ? categoriesApi.list().catch(() => offlineDb.categories.toArray().then(categories => ({ categories }))) + : offlineDb.categories.toArray().then(categories => ({ categories })), ]) const assignmentsMap: AssignmentsMap = {} diff --git a/client/src/sync/tripSyncManager.ts b/client/src/sync/tripSyncManager.ts index dc868244..4129469b 100644 --- a/client/src/sync/tripSyncManager.ts +++ b/client/src/sync/tripSyncManager.ts @@ -10,7 +10,7 @@ * - trip list refresh (DashboardPage) * - WS reconnect (phase 7) */ -import { tripsApi } from '../api/client' +import { tripsApi, tagsApi, categoriesApi } from '../api/client' import { offlineDb, upsertTrip, @@ -21,12 +21,16 @@ import { upsertBudgetItems, upsertReservations, upsertTripFiles, + upsertAccommodations, + upsertTripMembers, + upsertTags, + upsertCategories, upsertSyncMeta, clearTripData, } from '../db/offlineDb' import { prefetchTilesForTrip } from './tilePrefetcher' import { useSettingsStore } from '../store/settingsStore' -import type { Trip, Day, Place, PackingItem, TodoItem, BudgetItem, Reservation, TripFile } from '../types' +import type { Trip, Day, Place, PackingItem, TodoItem, BudgetItem, Reservation, TripFile, Accommodation, TripMember } from '../types' // ── Types ───────────────────────────────────────────────────────────────────── @@ -39,6 +43,8 @@ interface TripBundle { budgetItems: BudgetItem[] reservations: Reservation[] files: TripFile[] + accommodations: Accommodation[] + members: TripMember[] } // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -77,6 +83,8 @@ async function syncTrip(tripId: number): Promise { await upsertBudgetItems(bundle.budgetItems) await upsertReservations(bundle.reservations) await upsertTripFiles(bundle.files) + await upsertAccommodations(bundle.accommodations || []) + await upsertTripMembers(tripId, bundle.members || []) await upsertSyncMeta({ tripId, lastSyncedAt: Date.now(), @@ -145,6 +153,10 @@ export const tripSyncManager = { } } + // Cache global user data (tags + categories) — fire-and-forget + tagsApi.list().then(d => upsertTags(d.tags)).catch(() => {}) + categoriesApi.list().then(d => upsertCategories(d.categories)).catch(() => {}) + // Cache file blobs + map tiles in background (don't block syncAll) const tileUrl = useSettingsStore.getState().settings.map_tile_url || undefined for (const trip of toSync) { diff --git a/server/src/routes/trips.ts b/server/src/routes/trips.ts index 9882e1f5..9b2413ba 100644 --- a/server/src/routes/trips.ts +++ b/server/src/routes/trips.ts @@ -29,7 +29,7 @@ import { ValidationError, TRIP_SELECT, } from '../services/tripService'; -import { listDays } from '../services/dayService'; +import { listDays, listAccommodations } from '../services/dayService'; import { listPlaces } from '../services/placeService'; import { listItems as listPackingItems } from '../services/packingService'; import { listItems as listTodoItems } from '../services/todoService'; @@ -318,6 +318,9 @@ router.get('/:id/bundle', authenticate, (req: Request, res: Response) => { const budgetItems = listBudgetItems(tripId); const reservations = listReservations(tripId); const files = listFiles(tripId, false); + const accommodations = listAccommodations(tripId); + const { owner, members } = listMembers(tripId, trip.user_id); + const allMembers = [owner, ...(members || [])].filter(Boolean); res.json({ trip, @@ -328,6 +331,8 @@ router.get('/:id/bundle', authenticate, (req: Request, res: Response) => { budgetItems, reservations, files, + accommodations, + members: allMembers, }); });