Lots of improvements
This commit is contained in:
parent
3853b5dce7
commit
b94cf17d75
33 changed files with 2587 additions and 1866 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Slider } from '../ui/Slider';
|
||||
import { IconButton } from '../ui/IconButton';
|
||||
import { PillToggle } from '../ui/PillToggle';
|
||||
|
|
@ -8,9 +9,9 @@ import { TravelTimeInfoPopup } from '../ui/TravelTimeInfoPopup';
|
|||
import { CloseIcon } from '../ui/icons/CloseIcon';
|
||||
import { EyeIcon } from '../ui/icons/EyeIcon';
|
||||
import { InfoIcon } from '../ui/icons/InfoIcon';
|
||||
import { formatFilterValue } from '../../lib/format';
|
||||
import { formatFilterValue, formatNumber } from '../../lib/format';
|
||||
import { useTravelDestinations } from '../../hooks/useTravelDestinations';
|
||||
import { MODE_LABELS, MODE_ICONS, type TransportMode } from '../../hooks/useTravelTime';
|
||||
import { MODE_ICONS, useTranslatedModes, type TransportMode } from '../../hooks/useTravelTime';
|
||||
|
||||
interface TravelTimeCardProps {
|
||||
mode: TransportMode;
|
||||
|
|
@ -29,6 +30,7 @@ interface TravelTimeCardProps {
|
|||
onDragEnd: () => void;
|
||||
onToggleBest: () => void;
|
||||
onRemove: () => void;
|
||||
filterImpact?: number;
|
||||
}
|
||||
|
||||
export function TravelTimeCard({
|
||||
|
|
@ -48,7 +50,10 @@ export function TravelTimeCard({
|
|||
onDragEnd,
|
||||
onToggleBest,
|
||||
onRemove,
|
||||
filterImpact,
|
||||
}: TravelTimeCardProps) {
|
||||
const { t } = useTranslation();
|
||||
const modes = useTranslatedModes();
|
||||
const { destinations, loading: destinationsLoading } = useTravelDestinations(mode);
|
||||
const [showInfo, setShowInfo] = useState(false);
|
||||
const [showBestInfo, setShowBestInfo] = useState(false);
|
||||
|
|
@ -75,23 +80,23 @@ export function TravelTimeCard({
|
|||
<div className="flex items-center gap-1.5">
|
||||
<ModeIcon className="w-4 h-4 text-teal-600 dark:text-teal-400" />
|
||||
<span className="text-sm font-medium text-navy-950 dark:text-warm-100">
|
||||
Travel Time ({MODE_LABELS[mode]})
|
||||
{t('travel.travelTime', { mode: modes.label(mode) })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<IconButton onClick={() => setShowInfo(true)} title="Feature info">
|
||||
<IconButton onClick={() => setShowInfo(true)} title={t('filters.featureInfo')}>
|
||||
<InfoIcon className="w-3.5 h-3.5" />
|
||||
</IconButton>
|
||||
{slug && (
|
||||
<IconButton
|
||||
onClick={onTogglePin}
|
||||
active={isPinned}
|
||||
title={isPinned ? 'Stop previewing' : 'Preview on map'}
|
||||
title={isPinned ? t('travel.stopPreviewing') : t('travel.previewOnMap')}
|
||||
>
|
||||
<EyeIcon className="w-3.5 h-3.5" filled={isPinned} />
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton onClick={() => onRemove()} title="Remove travel time">
|
||||
<IconButton onClick={() => onRemove()} title={t('travel.removeTravelTime')}>
|
||||
<CloseIcon className="w-3.5 h-3.5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
|
@ -104,14 +109,14 @@ export function TravelTimeCard({
|
|||
onSelect={handleDestinationSelect}
|
||||
value={label || undefined}
|
||||
onClear={() => onSetDestination('', '', 0, 0)}
|
||||
placeholder="Select destination..."
|
||||
placeholder={t('travel.selectDestination')}
|
||||
/>
|
||||
|
||||
{/* Best-case toggle — transit only, shown when destination is set */}
|
||||
{slug && mode === 'transit' && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<PillToggle label="Best case" active={useBest} onClick={onToggleBest} size="xs" />
|
||||
<IconButton onClick={() => setShowBestInfo(true)} title="What is best case?">
|
||||
<PillToggle label={t('travel.bestCase')} active={useBest} onClick={onToggleBest} size="xs" />
|
||||
<IconButton onClick={() => setShowBestInfo(true)} title={t('travel.bestCaseTitle')}>
|
||||
<InfoIcon className="w-3 h-3" />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
|
@ -120,12 +125,11 @@ export function TravelTimeCard({
|
|||
{showInfo && <TravelTimeInfoPopup mode={mode} onClose={() => setShowInfo(false)} />}
|
||||
|
||||
{showBestInfo && (
|
||||
<InfoPopup title="Best case travel time" onClose={() => setShowBestInfo(false)}>
|
||||
<p className="text-sm text-warm-700 dark:text-warm-300 leading-relaxed">
|
||||
Uses the fastest realistic journey time (if you time your departure well and catch good
|
||||
connections). The default uses the <strong>median</strong>, representing a typical journey
|
||||
regardless of when you leave.
|
||||
</p>
|
||||
<InfoPopup title={t('travel.bestCaseTitle')} onClose={() => setShowBestInfo(false)}>
|
||||
<p
|
||||
className="text-sm text-warm-700 dark:text-warm-300 leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: t('travel.bestCaseDesc') }}
|
||||
/>
|
||||
</InfoPopup>
|
||||
)}
|
||||
|
||||
|
|
@ -133,7 +137,7 @@ export function TravelTimeCard({
|
|||
{slug && (
|
||||
<div>
|
||||
<span className="text-[10px] font-medium text-warm-500 dark:text-warm-400 uppercase tracking-wide">
|
||||
Max time
|
||||
{t('travel.maxTime')}
|
||||
</span>
|
||||
<Slider
|
||||
min={sliderMin}
|
||||
|
|
@ -145,9 +149,14 @@ export function TravelTimeCard({
|
|||
onPointerUp={() => onDragEnd()}
|
||||
/>
|
||||
<div className="relative h-4 mt-1 mx-2.5 text-[10px] text-warm-500 dark:text-warm-400 leading-tight">
|
||||
<span className="absolute left-0">{formatFilterValue(displayRange[0])} min</span>
|
||||
<span className="absolute right-0">{formatFilterValue(displayRange[1])} min</span>
|
||||
<span className="absolute left-0">{formatFilterValue(displayRange[0])} {t('common.min')}</span>
|
||||
<span className="absolute right-0">{formatFilterValue(displayRange[1])} {t('common.min')}</span>
|
||||
</div>
|
||||
{filterImpact != null && filterImpact > 0 && (
|
||||
<p className="text-[10px] text-warm-400 dark:text-warm-500 -mt-1 ml-2.5">
|
||||
+{formatNumber(filterImpact)} without this filter
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue