mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
refactor(tours): share the planned tours filter between desktop and phone
The All, Unplanned and Planned filter of the tours list was copied into the phone selection list. It now lives in filterTours next to the other tour presentation helpers, and both lists call it.
This commit is contained in:
@@ -12,6 +12,7 @@ import { NEUTRAL_TINT, fs } from '../shared/DialogShell'
|
||||
import { Tooltip } from '../shared/Tooltip'
|
||||
import EmptyState from '../shared/EmptyState'
|
||||
import { TourDayMenu } from './tourParts'
|
||||
import { filterTours, type TourFilter } from './tourPresentation'
|
||||
|
||||
const MAX_FILE_BYTES = 10 * 1024 * 1024
|
||||
|
||||
@@ -44,7 +45,7 @@ export default function ToursSidebar({ tripId, days, tours, loading = false, sel
|
||||
const { canEdit, canAssign } = useTourPermissions({ tripId, canEdit: editPermission, canAssign: assignPermission })
|
||||
const toast = useToast()
|
||||
const loadTrip = useTripStore(s => s.loadTrip)
|
||||
const [filter, setFilter] = useState<'all' | 'unplanned' | 'planned'>('all')
|
||||
const [filter, setFilter] = useState<TourFilter>('all')
|
||||
const [importing, setImporting] = useState(false)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const filterRef = useRef<HTMLDivElement>(null)
|
||||
@@ -95,11 +96,7 @@ export default function ToursSidebar({ tripId, days, tours, loading = false, sel
|
||||
unplanned: tours.filter(tr => !tr.planned).length,
|
||||
planned: tours.filter(tr => tr.planned).length,
|
||||
}
|
||||
const filtered = tours.filter(tr => {
|
||||
if (filter === 'unplanned') return !tr.planned
|
||||
if (filter === 'planned') return tr.planned
|
||||
return true
|
||||
})
|
||||
const filtered = filterTours(tours, filter)
|
||||
|
||||
useEffect(() => {
|
||||
if (!filterOpen) return
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { hikeSourceBadgeLabel, tourSource } from './tourPresentation'
|
||||
import { filterTours, hikeSourceBadgeLabel, tourSource } from './tourPresentation'
|
||||
|
||||
describe('tourPresentation', () => {
|
||||
it('uses the source abstraction for TREK, GPX import, and wanderer badges', () => {
|
||||
@@ -12,4 +12,19 @@ describe('tourPresentation', () => {
|
||||
expect(tourSource({ wanderer_ref: 'wanderer:123' })).toBe('wanderer')
|
||||
expect(hikeSourceBadgeLabel({ wanderer_ref: 'wanderer:123' }, t)).toBe('tours.badge.wanderer')
|
||||
})
|
||||
|
||||
it('filters tours by All, Unplanned and Planned, keeping their order', () => {
|
||||
const tours = [
|
||||
{ id: 1, planned: true },
|
||||
{ id: 2, planned: false },
|
||||
{ id: 3, planned: true },
|
||||
{ id: 4, planned: false },
|
||||
]
|
||||
|
||||
expect(filterTours(tours, 'all')).toEqual(tours)
|
||||
expect(filterTours(tours, 'all')).not.toBe(tours)
|
||||
expect(filterTours(tours, 'unplanned').map(tour => tour.id)).toEqual([2, 4])
|
||||
expect(filterTours(tours, 'planned').map(tour => tour.id)).toEqual([1, 3])
|
||||
expect(filterTours([], 'planned')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TourListItem } from '@trek/shared'
|
||||
|
||||
export type TourSource = 'trek' | 'gpx' | 'wanderer'
|
||||
|
||||
/** All / Unplanned / Planned, the same three states as the Places pool. */
|
||||
export type TourFilter = 'all' | 'unplanned' | 'planned'
|
||||
|
||||
type Translate = (key: string, params?: Record<string, unknown>) => string
|
||||
|
||||
/**
|
||||
@@ -20,3 +23,12 @@ export function hikeSourceBadgeLabel(
|
||||
): string {
|
||||
return t(`tours.badge.${tourSource(tour)}`)
|
||||
}
|
||||
|
||||
/** The tours a list shows under the All / Unplanned / Planned filter, in their order. */
|
||||
export function filterTours<T extends Pick<TourListItem, 'planned'>>(tours: T[], filter: TourFilter): T[] {
|
||||
return tours.filter(tr => {
|
||||
if (filter === 'unplanned') return !tr.planned
|
||||
if (filter === 'planned') return tr.planned
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import MPlacesBulkCategorySheet from './MPlacesBulkCategorySheet'
|
||||
import MPlacesSaveToCollectionSheet from './MPlacesSaveToCollectionSheet'
|
||||
import MPlacesToursModeSwitch from './MPlacesToursModeSwitch'
|
||||
import MToursSelectionList from './MToursSelectionList'
|
||||
import type { TourFilter } from '../../../../components/Tours/tourPresentation'
|
||||
import { filterPool, firstPlannedDayNumbers, plannedPlaceIds } from './placesBrowserModel'
|
||||
import { MCategoryFilterList, MRatingFloorChips, SquareCheck } from './MPlacesFilterControls'
|
||||
import { countActivePlacesFilters } from '../../../../utils/placesFilter'
|
||||
@@ -46,7 +47,7 @@ export default function MPlacesBrowser({ planner, shell }: MPlacesBrowserProps)
|
||||
// Top level of the places browser, mirroring the desktop right add-panel's
|
||||
// Places <-> Tours switch placement.
|
||||
const [toursMode, setToursMode] = useState(false)
|
||||
const [toursFilter, setToursFilter] = useState<'all' | 'unplanned' | 'planned'>('all')
|
||||
const [toursFilter, setToursFilter] = useState<TourFilter>('all')
|
||||
|
||||
const poolPlaces = useMemo(
|
||||
// Places that are tours stay out of the Places pool while the addon is on.
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ArrowDown, ArrowUp, Mountain, AlertTriangle, CalendarDays } from 'lucid
|
||||
import type { TourListItem } from '@trek/shared'
|
||||
import { useSettingsStore } from '../../../../store/settingsStore'
|
||||
import { formatDistance, formatElevation } from '../../../../utils/units'
|
||||
import { hikeSourceBadgeLabel } from '../../../../components/Tours/tourPresentation'
|
||||
import { filterTours, hikeSourceBadgeLabel, type TourFilter } from '../../../../components/Tours/tourPresentation'
|
||||
import type { TripPlanner, MTripShellApi } from '../MTripShell'
|
||||
import { useTourPermissions, type TourPermissionProps } from '../../../../components/Tours/useTourPermissions'
|
||||
|
||||
@@ -10,7 +10,7 @@ interface MToursSelectionListProps extends TourPermissionProps {
|
||||
planner: TripPlanner
|
||||
shell: MTripShellApi
|
||||
/** All / Unplanned / Planned — same three states as the Places pool. */
|
||||
filter: 'all' | 'unplanned' | 'planned'
|
||||
filter: TourFilter
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,11 +28,7 @@ export default function MToursSelectionList({ planner, shell, filter, canAssign:
|
||||
const distanceUnit = useSettingsStore(s => s.settings.distance_unit)
|
||||
const tours: TourListItem[] = planner.tours
|
||||
|
||||
const filtered = tours.filter(tr => {
|
||||
if (filter === 'unplanned') return !tr.planned
|
||||
if (filter === 'planned') return tr.planned
|
||||
return true
|
||||
})
|
||||
const filtered = filterTours(tours, filter)
|
||||
|
||||
if (filtered.length === 0) {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user