mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
refactor(weather): share the day weather anchor and icon map between desktop and phone
The day plan, the day panel, the phone timeline and the phone day sheet read the day local weather anchor from dayWeatherAnchor, and every weather icon comes from one map in weatherIcons.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef, useId, type ReactNode } from 'react'
|
||||
import { X, Sun, Cloud, CloudRain, CloudSnow, CloudDrizzle, CloudLightning, Wind, Droplets, Sunrise, Sunset, Hotel, Calendar, MapPin, LogIn, LogOut, Pencil, ChevronsDown, ChevronsUp, ArrowRight, Check, Moon, type LucideIcon } from 'lucide-react'
|
||||
import { X, CloudRain, Wind, Droplets, Sunrise, Sunset, Hotel, Calendar, MapPin, LogIn, LogOut, Pencil, ChevronsDown, ChevronsUp, ArrowRight, Check, Moon, type LucideIcon } from 'lucide-react'
|
||||
import type { WeatherResult } from '@trek/shared'
|
||||
import { usePluginViewContributions, PluginCardFooter } from '../Plugins/PluginContributions'
|
||||
import { usePluginStore } from '../../store/pluginStore'
|
||||
@@ -24,14 +24,10 @@ import { INPUT, PANEL, EditorField, AddRowButton, PillSelect } from '../shared/d
|
||||
import { BOX, Eyebrow, Field, RoundAction, TypeTile, toneOf, toneColor, toneTint } from './bookings/bookingParts'
|
||||
import { SoftPill, TimePill, tintOf } from './planParts'
|
||||
import { typeInfo } from './bookings/bookingsModel'
|
||||
|
||||
const WEATHER_ICON_MAP: Record<string, LucideIcon> = {
|
||||
Clear: Sun, Clouds: Cloud, Rain: CloudRain, Drizzle: CloudDrizzle,
|
||||
Thunderstorm: CloudLightning, Snow: CloudSnow, Mist: Wind, Fog: Wind, Haze: Wind,
|
||||
}
|
||||
import { weatherIconFor } from '../Weather/weatherIcons'
|
||||
|
||||
function WIcon({ main, size = 14 }: { main: string; size?: number }) {
|
||||
const Icon = WEATHER_ICON_MAP[main] || Cloud
|
||||
const Icon = weatherIconFor(main)
|
||||
return <Icon size={size} strokeWidth={1.8} />
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import { withinDriveRange } from '@trek/shared/roadtrip'
|
||||
import { formatDate, formatTime, formatMoneySum, splitReservationDateTime } from '../../utils/formatters'
|
||||
import { dayHeadingParts } from '../../utils/dayLabel'
|
||||
import { pendingStayIds } from '../../utils/pendingStays'
|
||||
import { dayWeatherAnchor } from '../../utils/dayWeather'
|
||||
import { planCosts } from './planCosts'
|
||||
import { useDayNotes } from '../../hooks/useDayNotes'
|
||||
import { useExchangeRates } from '../../hooks/useExchangeRates'
|
||||
@@ -1806,10 +1807,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
|
||||
// Day-local anchor only (#2167): the day's first located stop, else the
|
||||
// hotel you wake up in. No trip-wide fallback — on a roadtrip that
|
||||
// silently showed another city's weather with nothing naming the place.
|
||||
const weatherHotel = loc == null ? dayBookends.morning : undefined
|
||||
const wLat = loc?.place?.lat ?? weatherHotel?.place_lat
|
||||
const wLng = loc?.place?.lng ?? weatherHotel?.place_lng
|
||||
const weatherName = loc?.place?.name ?? weatherHotel?.place_name ?? null
|
||||
const { lat: wLat, lng: wLng, name: weatherName } = dayWeatherAnchor(da, () => dayBookends.morning)
|
||||
const hasWeather = !!(day.date && wLat != null && wLng != null)
|
||||
const dayAccs = accommodations.filter(a => isDayInAccommodationRange(day, a.start_day_id, a.end_day_id, days))
|
||||
// Sort: check-out first, then ongoing stays, then check-in last
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Sun, Cloud, CloudRain, CloudSnow, CloudDrizzle, CloudLightning, Wind } from 'lucide-react'
|
||||
import { fetchWeather } from '../../services/weatherQueue'
|
||||
import { useSettingsStore } from '../../store/settingsStore'
|
||||
import { Tooltip } from '../shared/Tooltip'
|
||||
|
||||
const WEATHER_ICON_MAP = {
|
||||
Clear: Sun,
|
||||
Clouds: Cloud,
|
||||
Rain: CloudRain,
|
||||
Drizzle: CloudDrizzle,
|
||||
Thunderstorm: CloudLightning,
|
||||
Snow: CloudSnow,
|
||||
Mist: Wind,
|
||||
Fog: Wind,
|
||||
Haze: Wind,
|
||||
}
|
||||
import { weatherIconFor } from './weatherIcons'
|
||||
|
||||
interface WeatherIconProps {
|
||||
main: string
|
||||
@@ -22,7 +10,7 @@ interface WeatherIconProps {
|
||||
}
|
||||
|
||||
function WeatherIcon({ main, size = 13 }: WeatherIconProps) {
|
||||
const Icon = WEATHER_ICON_MAP[main] || Cloud
|
||||
const Icon = weatherIconFor(main)
|
||||
return <Icon size={size} strokeWidth={1.8} />
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// FE-WEATHER-ICONS-001 to -002: the one condition-to-icon map behind the day plan's
|
||||
// weather badge, the desktop day panel and the phone's timeline and day sheet.
|
||||
// FE-WEATHER-ICONS-001 is the case FE-MOB-PTLM-041 held while the map lived in the
|
||||
// phone's timeline model.
|
||||
import { Cloud, CloudDrizzle, CloudLightning, CloudRain, CloudSnow, Sun, Wind } from 'lucide-react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { WEATHER_ICON_MAP, weatherIconFor } from './weatherIcons';
|
||||
|
||||
describe('weatherIcons', () => {
|
||||
it('FE-WEATHER-ICONS-001: maps the known conditions and defaults to a cloud', () => {
|
||||
expect(weatherIconFor('Clear')).toBe(Sun);
|
||||
expect(weatherIconFor('Rain')).toBe(CloudRain);
|
||||
expect(weatherIconFor('Thunderstorm')).toBe(CloudLightning);
|
||||
expect(weatherIconFor('Snow')).toBe(CloudSnow);
|
||||
expect(weatherIconFor('Haze')).toBe(Wind);
|
||||
expect(weatherIconFor('Tornado')).toBe(Cloud);
|
||||
expect(weatherIconFor(undefined)).toBe(Cloud);
|
||||
expect(weatherIconFor('')).toBe(Cloud);
|
||||
});
|
||||
|
||||
it('FE-WEATHER-ICONS-002: covers every condition the forecast names', () => {
|
||||
expect(WEATHER_ICON_MAP).toEqual({
|
||||
Clear: Sun,
|
||||
Clouds: Cloud,
|
||||
Rain: CloudRain,
|
||||
Drizzle: CloudDrizzle,
|
||||
Thunderstorm: CloudLightning,
|
||||
Snow: CloudSnow,
|
||||
Mist: Wind,
|
||||
Fog: Wind,
|
||||
Haze: Wind,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Cloud, CloudDrizzle, CloudLightning, CloudRain, CloudSnow, Sun, Wind, type LucideIcon } from 'lucide-react';
|
||||
|
||||
/** The icon for each weather condition the forecast names. */
|
||||
export const WEATHER_ICON_MAP: Record<string, LucideIcon> = {
|
||||
Clear: Sun,
|
||||
Clouds: Cloud,
|
||||
Rain: CloudRain,
|
||||
Drizzle: CloudDrizzle,
|
||||
Thunderstorm: CloudLightning,
|
||||
Snow: CloudSnow,
|
||||
Mist: Wind,
|
||||
Fog: Wind,
|
||||
Haze: Wind,
|
||||
};
|
||||
|
||||
/** The icon for a forecast's condition; an unknown or missing one shows a cloud. */
|
||||
export function weatherIconFor(main: string | undefined): LucideIcon {
|
||||
return (main && WEATHER_ICON_MAP[main]) || Cloud;
|
||||
}
|
||||
Reference in New Issue
Block a user