feat(tours): add non-motorized day tour planning

This commit is contained in:
elmocito
2026-10-04 12:59:55 +02:00
parent c93cf2bce9
commit 9e475f447a
202 changed files with 16129 additions and 383 deletions
+21 -2
View File
@@ -2,7 +2,7 @@ import { isEffectivelyOffline } from '../sync/networkMode'
import axios, { AxiosInstance } from 'axios'
import type { z } from 'zod'
import type { Day, Place, Trip } from '../types'
import type { MapsNearbyRequest, JourneyReorderEntryPhotosRequest, TransitProvider, GoogleQuotaUpdateRequest, NotificationDefaultsUpdateRequest } from '@trek/shared'
import type { MapsNearbyRequest, JourneyReorderEntryPhotosRequest, TransitProvider, GoogleQuotaUpdateRequest, NotificationDefaultsUpdateRequest, TourListItem, TourImportGpxResponse, TourCreateRequest, TourCreateResponse, TourDetailResponse } from '@trek/shared'
import { randomId } from '../utils/randomId'
import { postProviderPhotosInBatches } from './providerPhotoBatches'
import {
@@ -465,7 +465,8 @@ export const placesApi = {
apiClient.post(`/trips/${tripId}/places`, data).then(r => r.data),
get: (tripId: number | string, id: number | string) => apiClient.get(`/trips/${tripId}/places/${id}`).then(r => r.data),
update: (tripId: number | string, id: number | string, data: PlaceUpdateRequest) => apiClient.put(`/trips/${tripId}/places/${id}`, data).then(r => r.data),
delete: (tripId: number | string, id: number | string) => apiClient.delete(`/trips/${tripId}/places/${id}`).then(r => r.data),
delete: (tripId: number | string, id: number | string): Promise<{ success: boolean; tourPlaceIds?: number[] }> =>
apiClient.delete(`/trips/${tripId}/places/${id}`).then(r => r.data),
searchImage: (tripId: number | string, id: number | string) => apiClient.get(`/trips/${tripId}/places/${id}/image`).then(r => r.data),
uploadImage: (tripId: number | string, id: number | string, file: File) => {
const fd = new FormData()
@@ -513,6 +514,24 @@ export const placesApi = {
apiClient.post(`/trips/${tripId}/places/bulk-update`, { ids, ...data } satisfies PlaceBulkUpdateRequest).then(r => r.data),
}
export const toursApi = {
list: (tripId: number | string): Promise<{ tours: TourListItem[] }> =>
apiClient.get(`/trips/${tripId}/tours`).then(r => r.data),
detail: (tripId: number | string, placeId: number | string, signal?: AbortSignal): Promise<TourDetailResponse> =>
(signal
? apiClient.get(`/trips/${tripId}/tours/${placeId}`, { signal })
: apiClient.get(`/trips/${tripId}/tours/${placeId}`)).then(r => r.data),
create: (tripId: number | string, body: TourCreateRequest): Promise<TourCreateResponse> =>
apiClient.post(`/trips/${tripId}/tours`, body).then(r => r.data),
update: (tripId: number | string, placeId: number | string, body: TourCreateRequest): Promise<TourDetailResponse> =>
apiClient.put(`/trips/${tripId}/tours/${placeId}`, body).then(r => r.data),
importGpx: (tripId: number | string, file: File): Promise<TourImportGpxResponse> => {
const fd = new FormData()
fd.append('file', file)
return postMultipart(`/trips/${tripId}/tours/import/gpx`, fd)
},
}
export const assignmentsApi = {
list: (tripId: number | string, dayId: number | string) => apiClient.get(`/trips/${tripId}/days/${dayId}/assignments`).then(r => r.data),
create: (tripId: number | string, dayId: number | string, data: AssignmentCreateRequest) => apiClient.post(`/trips/${tripId}/days/${dayId}/assignments`, data).then(r => r.data),
+3
View File
@@ -16,6 +16,9 @@ import type { TrekWsEventName } from '@trek/shared'
* the entry (the event then needs a new home or an IGNORED_WS_EVENTS slot).
*/
export const HANDLED_OUTSIDE_TRIP_STORE = [
// Tours facet invalidation — hooks/useTripWebSocket forwards tours:changed to
// useTourPlaceIds so the active trip refreshes its Tours/Places read model.
'tours:changed',
// Collab — Collab/MCollab components + useTripWebSocket's collabFileSync
'collab:note:created',
'collab:note:updated',