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 (
{label}
);
}
/**
* 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) => (
))}
);
}