import type { TourListItem } from '@trek/shared'; import { ArrowUp, Clock, Pause, Ruler, type LucideIcon } from 'lucide-react'; import { createElement, useId, useState } from 'react'; import { useTranslation } from '../../i18n'; import { useSettingsStore } from '../../store/settingsStore'; import { formatDistance, formatElevation } from '../../utils/units'; import { WhiteBadge } from '../Planner/planParts'; import { fs } from '../shared/DialogShell'; import { Tooltip } from '../shared/Tooltip'; import { paddleCheckFact } from './paddle/paddleCheckModel'; import { tourDayRowIcon } from './tourDayRowIcon'; import { movingTimeLabel } from './tourFacts'; import { formatPlannedTourDuration, tourPlannedTimes } from './tourPresentation'; export function TourDayRowIcon({ type, size = 27 }: { type: string; size?: number }) { const { t } = useTranslation(); const known = ['hike', 'bike', 'walk', 'kayak'].includes(type); return ( {createElement(tourDayRowIcon(type), { size: Math.round(size * 0.6), 'aria-hidden': 'true' })} ); } function Fact({ icon: Icon, value, label, testId, mobile, }: { icon: LucideIcon; value: string; label: string; testId: string; mobile: boolean; }) { const id = useId(); const [open, setOpen] = useState(false); return ( ); } /** * A tour's tile in the day plan and in its detail: the T-scale grade in a round * tile, the same tile the tour lists use, so a tour reads alike wherever it * shows. A GPX import has no grade and shows its type's icon instead. */ export function TourDayRowAvatar({ tour, size = 32, }: { tour: Pick; size?: number; }) { const { t } = useTranslation(); const level = tour.max_hiking_difficulty; const known = ['hike', 'bike', 'walk', 'kayak'].includes(tour.tour_type); const label = level != null ? t(`tours.planner.difficulty.t${level}`) : known ? t(`tourTypes.${tour.tour_type}`) : t('tours.mode.tours'); return ( = 44 ? 17 : 12, 'body') }} > {level != null ? `T${level}` : createElement(tourDayRowIcon(tour.tour_type), { size: Math.round(size * 0.46), strokeWidth: 1.9, className: 'text-content-secondary', 'aria-hidden': 'true', })} ); } /** One fact of a tour as the white badge a place's time sits in, named by its tooltip. */ export function TourDayRowPill({ icon: Icon, value, label, testId, }: { icon: LucideIcon; value: string; label: string; testId: string; }) { return ( }> {value} ); } /** A paddle tour's check bench as one fact (PAD-09), or null for a hike or a tour listed without one. */ function paddleCheckOf(tour: TourListItem, t: Parameters[1]) { return tour.tour_type === 'kayak' && tour.paddle_check ? paddleCheckFact(tour.paddle_check, t) : null; } /** * A tour's facts on its day-plan row, in the badges every other stop uses: the * time it takes (the planned total where there is one, otherwise the walking * time, the parts in the tooltip), how far, how much climbing and, for a paddle * tour, its check bench. */ export function TourDayRowPills({ tour }: { tour: TourListItem }) { const { t } = useTranslation(); const unit = useSettingsStore((state) => state.settings.distance_unit) || 'metric'; const times = tourPlannedTimes(tour); const shownMinutes = times.plannedTotalMinutes ?? times.walkingMinutes; const timeParts = [ times.walkingMinutes != null ? `${movingTimeLabel(tour, t('tours.planner.inspector.duration'), t)}: ${formatPlannedTourDuration(times.walkingMinutes)}` : null, times.breakMinutes ? `${t('tours.planner.breaksAdditional')}: ${formatPlannedTourDuration(times.breakMinutes)}` : null, times.plannedTotalMinutes != null ? `${t('tours.planner.plannedTotalDuration')}: ${formatPlannedTourDuration(times.plannedTotalMinutes)}${times.manuallyOverridden ? ` (${t('tours.planner.plannedTotalManual')})` : ''}` : null, ].filter((part): part is string => part !== null); const check = paddleCheckOf(tour, t); return ( <> {shownMinutes != null && ( )} {tour.distance != null && ( )} {tour.elevation_gain != null && ( )} {check && } ); } export default function TourDayRowFacts({ tour, mobile = false }: { tour: TourListItem; mobile?: boolean }) { const { t } = useTranslation(); const unit = useSettingsStore((state) => state.settings.distance_unit) || 'metric'; const times = tourPlannedTimes(tour); const facts: Array<{ icon: LucideIcon; value: string; label: string; testId: string }> = []; const add = (icon: LucideIcon, value: string, label: string, testId: string) => facts.push({ icon, value, label, testId }); if (tour.distance != null) add(Ruler, formatDistance(tour.distance, unit), t('tours.detail.distance'), 'plan-tour-distance'); if (tour.elevation_gain != null) add(ArrowUp, formatElevation(tour.elevation_gain, unit), t('tours.detail.ascent'), 'plan-tour-ascent'); if (times.walkingMinutes != null) add( tourDayRowIcon(tour.tour_type === 'kayak' ? 'kayak' : 'walk'), formatPlannedTourDuration(times.walkingMinutes), movingTimeLabel(tour, t('tours.planner.inspector.duration'), t), 'trip-plan-tour-walking-time' ); if (times.breakMinutes != null && times.breakMinutes > 0) add( Pause, formatPlannedTourDuration(times.breakMinutes), t('tours.planner.breaksAdditional'), 'trip-plan-tour-breaks' ); if (times.plannedTotalMinutes != null) add( Clock, formatPlannedTourDuration(times.plannedTotalMinutes), `${t('tours.planner.plannedTotalDuration')}${times.manuallyOverridden ? `, ${t('tours.planner.plannedTotalManual')}` : ''}`, 'trip-plan-tour-planned-duration' ); const check = paddleCheckOf(tour, t); if (check) add(check.icon, check.value, t('tours.paddle.check.title'), 'plan-tour-paddle-check'); return ( {facts.map((fact) => ( ))} ); }