fix(assistant): a tour in a change row runs the card's width with room above it, and the latest start is short

- The tour's sketch, figures and profile in a change row take the whole width
  of the card and sit apart from the row above (wideBody), instead of under
  its text pressed against it.
- "Start by 16:08" says the latest start in three words; the reason (to be
  back before sunset) is its tooltip.
This commit is contained in:
Maurice
2026-10-23 16:19:07 +02:00
parent 8043b7952d
commit 7f9a4da52a
32 changed files with 41 additions and 7 deletions
@@ -134,7 +134,7 @@ function BodySlot({ item, settled }: { item: ChangeItem; settled: boolean }) {
const view = item.card ? viewOf(item.card) : null;
if (!view?.Body || !item.card) return null;
return (
<div className={`pb-1.5 ps-[38px] ${settled ? 'opacity-60' : ''}`}>
<div className={`mt-1.5 pb-1.5 ${view.wideBody ? '' : 'ps-[38px]'} ${settled ? 'opacity-60' : ''}`}>
<Slot item={item} view={view} name="Body" />
</div>
);
@@ -126,6 +126,8 @@ export interface CardView<K extends TrekCardKind> {
Facts?: ComponentType<CardSlotProps<K>>;
Body?: ComponentType<CardSlotProps<K>>;
ownsSubject?: boolean;
/** The body runs the whole width of the change card instead of under the row's text (a tour's drawing). */
wideBody?: boolean;
Group?: ComponentType<CardRunProps & { place: 'head' | 'foot' }>;
Run?: ComponentType<CardRunProps>;
runnable?(card: TrekCardOf<K>): boolean;
@@ -377,7 +377,7 @@ describe('TourRouteCard', () => {
it('FE-AI-TOUR-042: the day shows the latest start when none is set, and the walk in the dark in red', () => {
const schedule = HIKE.schedule as NonNullable<Route['schedule']>;
renderCard({ ...HIKE, schedule: { ...schedule, start: null, arrive: null, latestStart: '10:30' } });
expect(screen.getByText('Start by 10:30 to finish before sunset')).toBeInTheDocument();
expect(screen.getByText('Start by 10:30')).toBeInTheDocument();
const { container } = renderCard({
...HIKE,
@@ -385,6 +385,6 @@ describe('TourRouteCard', () => {
schedule: { ...schedule, start: '06:00', arrive: '19:00', latestStart: '10:30' },
});
expect(container.querySelectorAll('[data-testid="tour-day"] .bg-danger')).toHaveLength(2);
expect(screen.getAllByText('Start by 10:30 to finish before sunset')).toHaveLength(1);
expect(screen.getAllByText('Start by 10:30')).toHaveLength(1);
});
});
@@ -4,6 +4,7 @@ import { createElement } from 'react';
import { useTranslation } from '../../../../i18n';
import { SoftPill } from '../../../Planner/planParts';
import { Tooltip } from '../../../shared/Tooltip';
import { weatherIconFor } from '../../../Weather/weatherIcons';
import { useCardFormat } from '../parts/useCardFormat';
import { type BarSpan, dayBarOf, forecastHours, RAIN_SHOWN_PCT } from './tourRouteDayModel';
@@ -93,9 +94,13 @@ export default function TourRouteDay({ card }: { card: TourRouteCard }) {
)}
{!schedule.start && schedule.latestStart && (
<div className="mt-1 flex">
<SoftPill icon={<Clock size={10} strokeWidth={2.2} aria-hidden="true" />}>
{t('assistant.card.tour.latestStart', { time: format.clock(schedule.latestStart) })}
</SoftPill>
<Tooltip label={t('assistant.card.tour.latestStart', { time: format.clock(schedule.latestStart) })}>
<span className="inline-flex">
<SoftPill icon={<Clock size={10} strokeWidth={2.2} aria-hidden="true" />}>
{t('assistant.card.tour.latestStartShort', { time: format.clock(schedule.latestStart) })}
</SoftPill>
</span>
</Tooltip>
</div>
)}
{schedule.weather === 'climate' && (
@@ -14,6 +14,6 @@ import TourRouteInRow, { TourRouteLead } from './TourRouteInRow';
* file.
*/
export const TOUR_VIEWS = {
tourRoute: { Block: TourRouteBlock, Lead: TourRouteLead, Body: TourRouteInRow },
tourRoute: { Block: TourRouteBlock, Lead: TourRouteLead, Body: TourRouteInRow, wideBody: true },
tourList: { Block: TourListCard },
} satisfies { [K in 'tourRoute' | 'tourList']: CardView<K> };
+1
View File
@@ -67,6 +67,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'الغروب {time}',
'assistant.card.tour.nextDay': 'اليوم التالي',
'assistant.card.tour.latestStart': 'انطلق قبل {time} لتنتهي قبل الغروب',
'assistant.card.tour.latestStartShort': 'الانطلاق قبل {time}',
'assistant.card.tour.climate': 'طقس معتاد لهذا التاريخ، لا توقعات بعد',
'assistant.card.tour.weatherNear': 'الطقس قرب أعلى نقطة',
'assistant.card.tour.warn.thunder': 'عواصف رعدية محتملة من {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Gün batır {time}',
'assistant.card.tour.nextDay': 'növbəti gün',
'assistant.card.tour.latestStart': 'Gün batmazdan əvvəl bitirmək üçün ən gec {time} yola düşün',
'assistant.card.tour.latestStartShort': 'Ən gec {time}-da başla',
'assistant.card.tour.climate': 'Tarix üçün tipik hava, hələ proqnoz yoxdur',
'assistant.card.tour.weatherNear': 'Ən yüksək nöqtə yaxınlığında hava',
'assistant.card.tour.warn.thunder': '{time} saatdan ildırım mümkündür',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Pôr do sol {time}',
'assistant.card.tour.nextDay': 'no dia seguinte',
'assistant.card.tour.latestStart': 'Saia até as {time} para terminar antes do pôr do sol',
'assistant.card.tour.latestStartShort': 'Saída até {time}',
'assistant.card.tour.climate': 'Clima típico para a data, ainda sem previsão',
'assistant.card.tour.weatherNear': 'Clima perto do ponto mais alto',
'assistant.card.tour.warn.thunder': 'Possíveis trovoadas a partir das {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Posta de sol {time}',
'assistant.card.tour.nextDay': 'l’endemà',
'assistant.card.tour.latestStart': 'Surt abans de les {time} per acabar abans de la posta de sol',
'assistant.card.tour.latestStartShort': 'Sortida abans de les {time}',
'assistant.card.tour.climate': 'Temps típic per a la data, encara sense previsió',
'assistant.card.tour.weatherNear': 'Temps a prop del punt més alt',
'assistant.card.tour.warn.thunder': 'Possibles tempestes a partir de les {time}',
+1
View File
@@ -61,6 +61,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Západ slunce {time}',
'assistant.card.tour.nextDay': 'další den',
'assistant.card.tour.latestStart': 'Vyraz nejpozději v {time}, abys skončil před západem slunce',
'assistant.card.tour.latestStartShort': 'Vyrazit do {time}',
'assistant.card.tour.climate': 'Typické počasí pro datum, zatím bez předpovědi',
'assistant.card.tour.weatherNear': 'Počasí u nejvyššího bodu',
'assistant.card.tour.warn.thunder': 'Možné bouřky od {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Sonnenuntergang {time}',
'assistant.card.tour.nextDay': 'am nächsten Tag',
'assistant.card.tour.latestStart': 'Spätestens um {time} losgehen, um vor Sonnenuntergang fertig zu sein',
'assistant.card.tour.latestStartShort': 'Start spätestens {time}',
'assistant.card.tour.climate': 'Typisches Wetter für das Datum, noch keine Vorhersage',
'assistant.card.tour.weatherNear': 'Wetter nahe dem höchsten Punkt',
'assistant.card.tour.warn.thunder': 'Gewitter möglich ab {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Sunset {time}',
'assistant.card.tour.nextDay': 'next day',
'assistant.card.tour.latestStart': 'Start by {time} to finish before sunset',
'assistant.card.tour.latestStartShort': 'Start by {time}',
'assistant.card.tour.climate': 'Typical weather for the date, no forecast yet',
'assistant.card.tour.weatherNear': 'Weather near the highest point',
'assistant.card.tour.warn.thunder': 'Thunderstorms possible from {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Atardecer {time}',
'assistant.card.tour.nextDay': 'al día siguiente',
'assistant.card.tour.latestStart': 'Sal antes de las {time} para terminar antes del atardecer',
'assistant.card.tour.latestStartShort': 'Salida antes de las {time}',
'assistant.card.tour.climate': 'Tiempo típico para la fecha, aún sin previsión',
'assistant.card.tour.weatherNear': 'Tiempo cerca del punto más alto',
'assistant.card.tour.warn.thunder': 'Posibles tormentas desde las {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Päikeseloojang {time}',
'assistant.card.tour.nextDay': 'järgmisel päeval',
'assistant.card.tour.latestStart': 'Alusta hiljemalt {time}, et jõuda enne päikeseloojangut',
'assistant.card.tour.latestStartShort': 'Start hiljemalt {time}',
'assistant.card.tour.climate': 'Kuupäevale tüüpiline ilm, prognoosi veel pole',
'assistant.card.tour.weatherNear': 'Ilm kõrgeima punkti lähedal',
'assistant.card.tour.warn.thunder': 'Äike võimalik alates {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Coucher du soleil {time}',
'assistant.card.tour.nextDay': 'le lendemain',
'assistant.card.tour.latestStart': 'Partir avant {time} pour finir avant le coucher du soleil',
'assistant.card.tour.latestStartShort': 'Départ avant {time}',
'assistant.card.tour.climate': 'Météo habituelle pour cette date, pas encore de prévision',
'assistant.card.tour.weatherNear': 'Météo près du point culminant',
'assistant.card.tour.warn.thunder': 'Orages possibles dès {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Δύση {time}',
'assistant.card.tour.nextDay': 'την επόμενη μέρα',
'assistant.card.tour.latestStart': 'Ξεκίνα έως τις {time} για να τελειώσεις πριν τη δύση',
'assistant.card.tour.latestStartShort': 'Αναχώρηση έως {time}',
'assistant.card.tour.climate': 'Τυπικός καιρός για την ημερομηνία, χωρίς πρόγνωση ακόμη',
'assistant.card.tour.weatherNear': 'Καιρός κοντά στο υψηλότερο σημείο',
'assistant.card.tour.warn.thunder': 'Πιθανές καταιγίδες από τις {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Napnyugta {time}',
'assistant.card.tour.nextDay': 'másnap',
'assistant.card.tour.latestStart': 'Indulj legkésőbb {time}-kor, hogy napnyugta előtt végezz',
'assistant.card.tour.latestStartShort': 'Indulás legkésőbb {time}',
'assistant.card.tour.climate': 'A dátumra jellemző időjárás, még nincs előrejelzés',
'assistant.card.tour.weatherNear': 'Időjárás a legmagasabb pont közelében',
'assistant.card.tour.warn.thunder': 'Zivatar lehetséges {time}-tól',
+1
View File
@@ -57,6 +57,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Matahari terbenam {time}',
'assistant.card.tour.nextDay': 'hari berikutnya',
'assistant.card.tour.latestStart': 'Mulai paling lambat {time} agar selesai sebelum matahari terbenam',
'assistant.card.tour.latestStartShort': 'Berangkat paling lambat {time}',
'assistant.card.tour.climate': 'Cuaca khas untuk tanggal ini, belum ada prakiraan',
'assistant.card.tour.weatherNear': 'Cuaca di dekat titik tertinggi',
'assistant.card.tour.warn.thunder': 'Kemungkinan badai petir mulai {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Tramonto {time}',
'assistant.card.tour.nextDay': 'il giorno dopo',
'assistant.card.tour.latestStart': 'Parti entro le {time} per finire prima del tramonto',
'assistant.card.tour.latestStartShort': 'Partenza entro le {time}',
'assistant.card.tour.climate': 'Meteo tipico per la data, ancora nessuna previsione',
'assistant.card.tour.weatherNear': 'Meteo vicino al punto più alto',
'assistant.card.tour.warn.thunder': 'Possibili temporali dalle {time}',
+1
View File
@@ -57,6 +57,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': '日の入り{time}',
'assistant.card.tour.nextDay': '翌日',
'assistant.card.tour.latestStart': '日没前に終えるには{time}までに出発',
'assistant.card.tour.latestStartShort': '{time}までに出発',
'assistant.card.tour.climate': 'この日付の平年の天気、予報はまだありません',
'assistant.card.tour.weatherNear': '最高地点付近の天気',
'assistant.card.tour.warn.thunder': '{time}から雷雨の恐れ',
+1
View File
@@ -57,6 +57,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': '일몰 {time}',
'assistant.card.tour.nextDay': '다음 날',
'assistant.card.tour.latestStart': '일몰 전에 끝내려면 {time}까지 출발하세요',
'assistant.card.tour.latestStartShort': '{time}까지 출발',
'assistant.card.tour.climate': '이 날짜의 평년 날씨, 아직 예보 없음',
'assistant.card.tour.weatherNear': '최고 지점 근처 날씨',
'assistant.card.tour.warn.thunder': '{time}부터 뇌우 가능성',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Zonsondergang {time}',
'assistant.card.tour.nextDay': 'volgende dag',
'assistant.card.tour.latestStart': 'Vertrek uiterlijk om {time} om voor zonsondergang klaar te zijn',
'assistant.card.tour.latestStartShort': 'Vertrek uiterlijk {time}',
'assistant.card.tour.climate': 'Gebruikelijk weer voor deze datum, nog geen verwachting',
'assistant.card.tour.weatherNear': 'Weer bij het hoogste punt',
'assistant.card.tour.warn.thunder': 'Kans op onweer vanaf {time}',
+1
View File
@@ -63,6 +63,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Zachód słońca {time}',
'assistant.card.tour.nextDay': 'następnego dnia',
'assistant.card.tour.latestStart': 'Wyrusz najpóźniej o {time}, aby skończyć przed zachodem słońca',
'assistant.card.tour.latestStartShort': 'Start najpóźniej {time}',
'assistant.card.tour.climate': 'Typowa pogoda na tę datę, jeszcze bez prognozy',
'assistant.card.tour.weatherNear': 'Pogoda przy najwyższym punkcie',
'assistant.card.tour.warn.thunder': 'Możliwe burze od {time}',
+1
View File
@@ -63,6 +63,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Закат {time}',
'assistant.card.tour.nextDay': 'на следующий день',
'assistant.card.tour.latestStart': 'Выйдите не позже {time}, чтобы закончить до заката',
'assistant.card.tour.latestStartShort': 'Выход до {time}',
'assistant.card.tour.climate': 'Обычная погода для этой даты, прогноза пока нет',
'assistant.card.tour.weatherNear': 'Погода у высшей точки',
'assistant.card.tour.warn.thunder': 'Возможны грозы с {time}',
+1
View File
@@ -61,6 +61,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Západ slnka {time}',
'assistant.card.tour.nextDay': 'ďalší deň',
'assistant.card.tour.latestStart': 'Vyraz najneskôr o {time}, aby si skončil pred západom slnka',
'assistant.card.tour.latestStartShort': 'Vyraziť do {time}',
'assistant.card.tour.climate': 'Typické počasie pre dátum, zatiaľ bez predpovede',
'assistant.card.tour.weatherNear': 'Počasie pri najvyššom bode',
'assistant.card.tour.warn.thunder': 'Možné búrky od {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Solnedgång {time}',
'assistant.card.tour.nextDay': 'nästa dag',
'assistant.card.tour.latestStart': 'Gå senast {time} för att vara klar före solnedgången',
'assistant.card.tour.latestStartShort': 'Start senast {time}',
'assistant.card.tour.climate': 'Typiskt väder för datumet, ingen prognos än',
'assistant.card.tour.weatherNear': 'Väder nära högsta punkten',
'assistant.card.tour.warn.thunder': 'Åska möjlig från {time}',
+1
View File
@@ -57,6 +57,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'พระอาทิตย์ตก {time}',
'assistant.card.tour.nextDay': 'วันถัดไป',
'assistant.card.tour.latestStart': 'ออกเดินภายใน {time} เพื่อให้เสร็จก่อนพระอาทิตย์ตก',
'assistant.card.tour.latestStartShort': 'ออกเดินก่อน {time}',
'assistant.card.tour.climate': 'สภาพอากาศตามปกติของวันนี้ ยังไม่มีพยากรณ์',
'assistant.card.tour.weatherNear': 'สภาพอากาศใกล้จุดสูงสุด',
'assistant.card.tour.warn.thunder': 'อาจมีพายุฝนฟ้าคะนองตั้งแต่ {time}',
+1
View File
@@ -59,6 +59,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Gün batımı {time}',
'assistant.card.tour.nextDay': 'ertesi gün',
'assistant.card.tour.latestStart': 'Gün batımından önce bitirmek için en geç {time} yola çık',
'assistant.card.tour.latestStartShort': 'En geç {time} başla',
'assistant.card.tour.climate': 'Bu tarih için tipik hava, henüz tahmin yok',
'assistant.card.tour.weatherNear': 'En yüksek nokta yakınındaki hava',
'assistant.card.tour.warn.thunder': '{time} itibarıyla gök gürültülü fırtına olası',
+1
View File
@@ -63,6 +63,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Захід сонця {time}',
'assistant.card.tour.nextDay': 'наступного дня',
'assistant.card.tour.latestStart': 'Вирушайте не пізніше {time}, щоб закінчити до заходу сонця',
'assistant.card.tour.latestStartShort': 'Вийти до {time}',
'assistant.card.tour.climate': 'Звичайна погода для цієї дати, прогнозу ще немає',
'assistant.card.tour.weatherNear': 'Погода біля найвищої точки',
'assistant.card.tour.warn.thunder': 'Можливі грози з {time}',
+1
View File
@@ -57,6 +57,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': 'Mặt trời lặn {time}',
'assistant.card.tour.nextDay': 'ngày hôm sau',
'assistant.card.tour.latestStart': 'Xuất phát muộn nhất lúc {time} để xong trước khi mặt trời lặn',
'assistant.card.tour.latestStartShort': 'Xuất phát trước {time}',
'assistant.card.tour.climate': 'Thời tiết thường gặp vào ngày này, chưa có dự báo',
'assistant.card.tour.weatherNear': 'Thời tiết gần điểm cao nhất',
'assistant.card.tour.warn.thunder': 'Có thể có giông từ {time}',
+1
View File
@@ -57,6 +57,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': '日落{time}',
'assistant.card.tour.nextDay': '隔天',
'assistant.card.tour.latestStart': '最晚{time}出發,才能在日落前走完',
'assistant.card.tour.latestStartShort': '最晚 {time} 出發',
'assistant.card.tour.climate': '該日期的典型天氣,暫無預報',
'assistant.card.tour.weatherNear': '最高點附近的天氣',
'assistant.card.tour.warn.thunder': '{time}起可能有雷雨',
+1
View File
@@ -57,6 +57,7 @@ const assistantTour: TranslationStrings = {
'assistant.card.tour.sunset': '日落{time}',
'assistant.card.tour.nextDay': '次日',
'assistant.card.tour.latestStart': '最晚{time}出发,才能在日落前走完',
'assistant.card.tour.latestStartShort': '最晚 {time} 出发',
'assistant.card.tour.climate': '该日期的典型天气,暂无预报',
'assistant.card.tour.weatherNear': '最高点附近的天气',
'assistant.card.tour.warn.thunder': '{time}起可能有雷暴',