import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import api from '../utils/api'
import { formatDistance, formatDuration, formatDate, formatPace, sportIcon } from '../utils/format'
export default function RoutesPage() {
const [selected, setSelected] = useState(null)
const [showCreate, setShowCreate] = useState(false)
const [newRoute, setNewRoute] = useState({ name: '', activity_id: '' })
const qc = useQueryClient()
const { data: routes } = useQuery({
queryKey: ['routes'],
queryFn: () => api.get('/routes/').then(r => r.data),
})
const { data: routeActivities } = useQuery({
queryKey: ['route-activities', selected?.id],
queryFn: () => api.get(`/routes/${selected.id}/activities`).then(r => r.data),
enabled: !!selected,
})
const { data: recentActivities } = useQuery({
queryKey: ['recent-activities-for-route'],
queryFn: () => api.get('/routes/recent-activities').then(r => r.data),
enabled: showCreate,
})
const createRoute = useMutation({
mutationFn: (data) => api.post('/routes/', data).then(r => r.data),
onSuccess: (route) => {
qc.invalidateQueries({ queryKey: ['routes'] })
setShowCreate(false)
setNewRoute({ name: '', activity_id: '' })
setSelected(route)
},
})
const fastest = routeActivities?.[0]
return (
Named Routes
Routes are auto-detected when you run the same path twice. You can also create them manually.
{/* Create route */}
{showCreate && (
Create named route
Select an activity to use as the reference GPS track. Future activities on the same route will be linked automatically.
setNewRoute(r => ({ ...r, name: e.target.value }))}
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="e.g. Morning park loop" />
{recentActivities?.length === 0 ? (
No recent activities found.
) : (
)}
)}
{/* Route list */}
{routes?.length === 0 && !showCreate && (
🗺️
No named routes yet
Routes are created automatically when you repeat a run, or create one manually above.
)}
{routes?.map(route => (
))}
{/* Route detail */}
{selected && (
{selected.name}
{selected.auto_detected && (
Auto-detected
)}
{fastest && (
Course record 🏆
{formatDuration(fastest.duration_s)}
{formatDate(fastest.start_time)} · {formatPace(fastest.avg_speed_ms, selected.sport_type)}
)}
All runs ({routeActivities?.length ?? 0})
{routeActivities?.map((act, i) => (
{i + 1}
{formatDate(act.start_time)}
{formatDuration(act.duration_s)}
{formatPace(act.avg_speed_ms, selected.sport_type)}
{act.avg_heart_rate && (
{Math.round(act.avg_heart_rate)} bpm
)}
{i === 0 && (
CR
)}
))}
)}
)
}