This commit is contained in:
632
package-lock.json
generated
632
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gfazioli/mantine-text-animate": "^4.0.6",
|
"@gfazioli/mantine-text-animate": "^4.0.7",
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@mantine/core": "^9.4.1",
|
"@mantine/core": "^9.4.1",
|
||||||
"@mantine/dates": "^9.4.1",
|
"@mantine/dates": "^9.4.1",
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"idb-keyval": "^6.3.0",
|
"idb-keyval": "^6.3.0",
|
||||||
"jotai": "^2.20.1",
|
"jotai": "^2.20.1",
|
||||||
|
"openmeteo": "^1.2.3",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
"react-fontpicker-ts": "^1.3.0",
|
"react-fontpicker-ts": "^1.3.0",
|
||||||
|
|||||||
@@ -1,27 +1,63 @@
|
|||||||
import { Box, Card, Group, Stack, Text } from '@mantine/core';
|
import { Box, Card, Group, Stack, Text } from '@mantine/core';
|
||||||
import { useCommonHooks, useSunStateAPI } from 'hooks';
|
import { useCommonHooks } from 'hooks';
|
||||||
import { FaLongArrowAltDown, FaLongArrowAltUp, FaWind } from 'react-icons/fa';
|
import { FaLongArrowAltDown, FaLongArrowAltUp, FaWind } from 'react-icons/fa';
|
||||||
import { WiBarometer, WiHumidity, WiSunrise, WiSunset } from 'react-icons/wi';
|
import { WiBarometer, WiHumidity, WiSunrise, WiSunset } from 'react-icons/wi';
|
||||||
import { textClasses } from 'styles';
|
import { textClasses } from 'styles';
|
||||||
import type { CurrentWeather as CW, SunState } from 'types';
|
import type { MeteoWeather } from 'types';
|
||||||
import WeatherFeaturePill from './WeatherFeaturePill';
|
import WeatherFeaturePill from './WeatherFeaturePill';
|
||||||
import WeatherIcon from './WeatherIcon';
|
import WeatherIcon from './WeatherIcon';
|
||||||
|
|
||||||
export default function CurrentWeather({ current }: { current: CW }) {
|
const WeatherCodes: { [key: number]: string } = {
|
||||||
const { currentLocation, isMobile, theme, textShadowStyle } = useCommonHooks();
|
0: 'Clear sky',
|
||||||
const { data: sunStateData } = useSunStateAPI({
|
|
||||||
lat: currentLocation.lat,
|
|
||||||
lon: currentLocation.lon,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sunStates: SunState[] =
|
1: 'Mainly clear',
|
||||||
sunStateData?.results.map((item: SunState) => {
|
2: 'Partly cloudy',
|
||||||
return {
|
3: 'Overcast',
|
||||||
date: item.date,
|
|
||||||
sunrise: item.sunrise,
|
45: 'Fog',
|
||||||
sunset: item.sunset,
|
48: 'Depositing rime fog',
|
||||||
};
|
|
||||||
}) || [];
|
51: 'Drizzle: Light',
|
||||||
|
53: 'Drizzle: Moderate',
|
||||||
|
55: 'Drizzle: Dense',
|
||||||
|
|
||||||
|
56: 'Freezing Drizzle: Light',
|
||||||
|
57: 'Freezing Drizzle: Dense',
|
||||||
|
|
||||||
|
61: 'Rain: Slight',
|
||||||
|
63: 'Rain: Moderate',
|
||||||
|
65: 'Rain: Heavy',
|
||||||
|
|
||||||
|
66: 'Freezing Rain: Light',
|
||||||
|
67: 'Freezing Rain: Heavy',
|
||||||
|
|
||||||
|
71: 'Snowfall: Slight',
|
||||||
|
73: 'Snowfall: Moderate',
|
||||||
|
75: 'Snowfall: Heavy',
|
||||||
|
|
||||||
|
77: 'Snow grains',
|
||||||
|
|
||||||
|
80: 'Rain showers: Slight',
|
||||||
|
81: 'Rain showers: Moderate',
|
||||||
|
82: 'Rain showers: Violent',
|
||||||
|
|
||||||
|
85: 'Snow showers: Slight',
|
||||||
|
86: 'Snow showers: Heavy',
|
||||||
|
|
||||||
|
95: 'Thunderstorm: Slight or moderate',
|
||||||
|
96: 'Thunderstorm with slight hail',
|
||||||
|
99: 'Thunderstorm with heavy hail',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export default function CurrentWeather({
|
||||||
|
current,
|
||||||
|
}: {
|
||||||
|
current: MeteoWeather | undefined;
|
||||||
|
}) {
|
||||||
|
const { isMobile, theme, textShadowStyle } = useCommonHooks();
|
||||||
|
|
||||||
|
const sunrise = new Date(current?.daily.sunrise[0] || 0).toLocaleTimeString();
|
||||||
|
const sunset = new Date(current?.daily.sunset[0] || 0).toLocaleTimeString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -71,25 +107,11 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WeatherIcon
|
<WeatherIcon size={'lg'} code={current.current.weather_code} />
|
||||||
size={'lg'}
|
|
||||||
textDescription={`${
|
|
||||||
current.textDescription.includes('and')
|
|
||||||
? current.textDescription.substring(
|
|
||||||
0,
|
|
||||||
current.textDescription.indexOf('and'),
|
|
||||||
)
|
|
||||||
: current.textDescription.includes('then')
|
|
||||||
? current.textDescription.substring(
|
|
||||||
0,
|
|
||||||
current.textDescription.indexOf('then'),
|
|
||||||
)
|
|
||||||
: current.textDescription
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Text fz={isMobile ? '5rem' : '7rem'} fw={400}>
|
<Text fz={isMobile ? '5rem' : '7rem'} fw={400}>
|
||||||
{current.temperature}°<sup style={{ fontSize: '3rem' }}>F</sup>
|
{current.current.temperature_2m?.toFixed(0)}°
|
||||||
|
<sup style={{ fontSize: '3rem' }}>F</sup>
|
||||||
</Text>
|
</Text>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
@@ -108,7 +130,11 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
justify={current.textDescription.length > 25 ? 'flex-start' : 'center'}
|
justify={
|
||||||
|
WeatherCodes[Number(current.current.weather_code)].length > 25
|
||||||
|
? 'flex-start'
|
||||||
|
: 'center'
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: '180px',
|
maxWidth: '180px',
|
||||||
@@ -120,13 +146,15 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
className={
|
className={
|
||||||
current.textDescription.length > 25
|
WeatherCodes[Number(current.current.weather_code)].length > 25
|
||||||
? textClasses.autoScrollText
|
? textClasses.autoScrollText
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
classNames={textClasses}
|
classNames={textClasses}
|
||||||
>
|
>
|
||||||
{current.temperature === 69 ? 'Nice' : current.textDescription}
|
{current.current.temperature_2m?.toFixed(0) === '69'
|
||||||
|
? 'Nice'
|
||||||
|
: WeatherCodes[Number(current.current.weather_code)]}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -134,8 +162,9 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
leftSide={<Text>Feels like </Text>}
|
leftSide={<Text>Feels like </Text>}
|
||||||
rightSide={
|
rightSide={
|
||||||
<Text>
|
<Text>
|
||||||
{current.heatIndex ?? current.windChill ?? current.temperature}°
|
{current.current.apparent_temperature?.toFixed(0) ??
|
||||||
<sup>F</sup>
|
current.current.temperature_2m?.toFixed(0)}
|
||||||
|
°<sup>F</sup>
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
@@ -164,7 +193,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
}
|
}
|
||||||
rightSide={
|
rightSide={
|
||||||
<Text>
|
<Text>
|
||||||
{current.highTemp}°<sup>F</sup>
|
{current.daily.temperature_2m_max?.[0].toFixed(0)}°<sup>F</sup>
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
@@ -178,7 +207,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
}
|
}
|
||||||
rightSide={
|
rightSide={
|
||||||
<Text>
|
<Text>
|
||||||
{current.lowTemp}°<sup>F</sup>
|
{current.daily.temperature_2m_min?.[0].toFixed(0)}°<sup>F</sup>
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
@@ -205,7 +234,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
Humidity
|
Humidity
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{current.humidity}%</Text>}
|
rightSide={<Text>{current.current.relative_humidity_2m}%</Text>}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
/>
|
/>
|
||||||
<WeatherFeaturePill
|
<WeatherFeaturePill
|
||||||
@@ -215,7 +244,9 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
Wind
|
Wind
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{current.windSpeed}mph</Text>}
|
rightSide={
|
||||||
|
<Text>{current.current.wind_speed_10m?.toFixed(0)}mph</Text>
|
||||||
|
}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
/>
|
/>
|
||||||
<WeatherFeaturePill
|
<WeatherFeaturePill
|
||||||
@@ -225,7 +256,9 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
Pressure
|
Pressure
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{current.pressure} in</Text>}
|
rightSide={
|
||||||
|
<Text>{current.current.surface_pressure?.toFixed(0)} mbar</Text>
|
||||||
|
}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -257,7 +290,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{sunStates[0]?.sunrise}</Text>}
|
rightSide={<Text>{sunrise}</Text>}
|
||||||
highlight='left'
|
highlight='left'
|
||||||
/>
|
/>
|
||||||
<WeatherFeaturePill
|
<WeatherFeaturePill
|
||||||
@@ -274,7 +307,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{sunStates[0]?.sunset}</Text>}
|
rightSide={<Text>{sunset}</Text>}
|
||||||
highlight='left'
|
highlight='left'
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,17 +1,78 @@
|
|||||||
import { Card, Group, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Card, Group, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||||
import { useCommonHooks } from 'hooks';
|
import { useCommonHooks } from 'hooks';
|
||||||
import { textClasses } from 'styles';
|
import { textClasses } from 'styles';
|
||||||
import type { ExtendedForecast } from 'types';
|
import type { Daily } from 'types';
|
||||||
import WeatherIcon from './WeatherIcon';
|
import WeatherIcon from './WeatherIcon';
|
||||||
|
|
||||||
export default function ExtendedForecastCard({
|
const WeatherCodes: { [key: number]: string } = {
|
||||||
forecast,
|
0: 'Clear sky',
|
||||||
}: {
|
|
||||||
forecast: ExtendedForecast[];
|
1: 'Mainly clear',
|
||||||
}) {
|
2: 'Partly cloudy',
|
||||||
|
3: 'Overcast',
|
||||||
|
|
||||||
|
45: 'Fog',
|
||||||
|
48: 'Depositing rime fog',
|
||||||
|
|
||||||
|
51: 'Drizzle: Light',
|
||||||
|
53: 'Drizzle: Moderate',
|
||||||
|
55: 'Drizzle: Dense',
|
||||||
|
|
||||||
|
56: 'Freezing Drizzle: Light',
|
||||||
|
57: 'Freezing Drizzle: Dense',
|
||||||
|
|
||||||
|
61: 'Rain: Slight',
|
||||||
|
63: 'Rain: Moderate',
|
||||||
|
65: 'Rain: Heavy',
|
||||||
|
|
||||||
|
66: 'Freezing Rain: Light',
|
||||||
|
67: 'Freezing Rain: Heavy',
|
||||||
|
|
||||||
|
71: 'Snowfall: Slight',
|
||||||
|
73: 'Snowfall: Moderate',
|
||||||
|
75: 'Snowfall: Heavy',
|
||||||
|
|
||||||
|
77: 'Snow grains',
|
||||||
|
|
||||||
|
80: 'Rain showers: Slight',
|
||||||
|
81: 'Rain showers: Moderate',
|
||||||
|
82: 'Rain showers: Violent',
|
||||||
|
|
||||||
|
85: 'Snow showers: Slight',
|
||||||
|
86: 'Snow showers: Heavy',
|
||||||
|
|
||||||
|
95: 'Thunderstorm: Slight or moderate',
|
||||||
|
96: 'Thunderstorm with slight hail',
|
||||||
|
99: 'Thunderstorm with heavy hail',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export default function ExtendedForecastCard({ daily }: { daily: Daily | undefined }) {
|
||||||
const { isMobile, theme } = useCommonHooks();
|
const { isMobile, theme } = useCommonHooks();
|
||||||
const day = forecast.slice(2, 14).filter((item: ExtendedForecast) => item.isDaytime);
|
const forecast = daily?.time.map((_: any, index: number) => {
|
||||||
const night = forecast.slice(2, 14).filter((item: ExtendedForecast) => !item.isDaytime);
|
const time = new Date(daily.time[index]);
|
||||||
|
time.setMinutes(daily.time[index].getTimezoneOffset() + time.getMinutes());
|
||||||
|
|
||||||
|
return {
|
||||||
|
apparentTemperatureMax: daily.apparent_temperature_max?.[index],
|
||||||
|
apparentTemperatureMin: daily.apparent_temperature_min?.[index],
|
||||||
|
daylightDuration: daily.daylight_duration?.[index],
|
||||||
|
precipitationHours: daily.precipitation_hours?.[index],
|
||||||
|
precipitationProbability: daily.precipitation_probability_max?.[index],
|
||||||
|
precipitationSum: daily.precipitation_sum?.[index],
|
||||||
|
sunrise: daily.sunrise[index],
|
||||||
|
sunset: daily.sunset[index],
|
||||||
|
sunshineDuration: daily.sunshine_duration?.[index],
|
||||||
|
temperatureMax: daily.temperature_2m_max?.[index],
|
||||||
|
temperatureMin: daily.temperature_2m_min?.[index],
|
||||||
|
time: time,
|
||||||
|
unIndexClearSkyMax: daily.uv_index_clear_sky_max?.[index],
|
||||||
|
uvIndexMax: daily.uv_index_max?.[index],
|
||||||
|
weatherCode: daily.weather_code?.[index],
|
||||||
|
windDirection: daily.wind_direction_10m_dominant?.[index],
|
||||||
|
windGusts: daily.wind_gusts_10m_max?.[index],
|
||||||
|
windSpeed: daily.wind_speed_10m_max?.[index],
|
||||||
|
};
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
w={'100%'}
|
w={'100%'}
|
||||||
@@ -37,13 +98,7 @@ export default function ExtendedForecastCard({
|
|||||||
Extended Forecast
|
Extended Forecast
|
||||||
</Text>
|
</Text>
|
||||||
<SimpleGrid w={'100%'} pt={'.5rem'} cols={isMobile ? 3 : 6}>
|
<SimpleGrid w={'100%'} pt={'.5rem'} cols={isMobile ? 3 : 6}>
|
||||||
{day.map((item: ExtendedForecast, index: number) => {
|
{forecast?.slice(1, 7).map((item: any) => {
|
||||||
const short = item.shortForecast.includes('And')
|
|
||||||
? item.shortForecast.substring(0, item.shortForecast.indexOf('And'))
|
|
||||||
: item.shortForecast.includes('then')
|
|
||||||
? item.shortForecast.substring(0, item.shortForecast.indexOf('then'))
|
|
||||||
: item.shortForecast;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
key={item.name}
|
key={item.name}
|
||||||
@@ -55,7 +110,11 @@ export default function ExtendedForecastCard({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Group
|
<Group
|
||||||
justify={item.name.length > 15 ? 'flex-start' : 'center'}
|
justify={
|
||||||
|
item.time.toLocaleDateString('en-US', { weekday: 'long' }).length > 15
|
||||||
|
? 'flex-start'
|
||||||
|
: 'center'
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
@@ -66,16 +125,20 @@ export default function ExtendedForecastCard({
|
|||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
className={
|
className={
|
||||||
item.name.length > 15 ? textClasses.autoScrollText : undefined
|
item.time.toLocaleDateString('en-US', { weekday: 'long' }).length > 15
|
||||||
|
? textClasses.autoScrollText
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
classNames={textClasses}
|
classNames={textClasses}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.time.toLocaleDateString('en-US', { weekday: 'long' })}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<WeatherIcon size='md' textDescription={short} />
|
<WeatherIcon size='md' code={item.weatherCode} />
|
||||||
<Group
|
<Group
|
||||||
justify={short.length > 15 ? 'flex-start' : 'center'}
|
justify={
|
||||||
|
WeatherCodes[item.weatherCode].length > 15 ? 'flex-start' : 'center'
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
@@ -85,15 +148,18 @@ export default function ExtendedForecastCard({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
className={short.length > 15 ? textClasses.autoScrollText : undefined}
|
className={
|
||||||
|
WeatherCodes[item.weatherCode].length > 15
|
||||||
|
? textClasses.autoScrollText
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
classNames={textClasses}
|
classNames={textClasses}
|
||||||
>
|
>
|
||||||
{short}
|
{WeatherCodes[item.weatherCode]}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Text>
|
<Text>
|
||||||
{item.temperature}°{item.temperatureUnit}/{night[index].temperature}°
|
{item.temperatureMax.toFixed(0)}°F/{item.temperatureMin.toFixed(0)}°F
|
||||||
{item.temperatureUnit}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,16 +11,31 @@ import {
|
|||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import type { HourlyForecast } from 'types';
|
import type { Hourly } from 'types';
|
||||||
import WeatherTooltip from './WeatherTooltip';
|
import WeatherTooltip from './WeatherTooltip';
|
||||||
|
|
||||||
export default function WeatherGraphs({
|
export default function WeatherGraphs({ hourly }: { hourly: Hourly | undefined }) {
|
||||||
hourlyForecast,
|
|
||||||
}: {
|
|
||||||
hourlyForecast: HourlyForecast[];
|
|
||||||
}) {
|
|
||||||
const colorPrecipitation = 'colorPrecipitation';
|
const colorPrecipitation = 'colorPrecipitation';
|
||||||
const colorHumidity = 'colorHumidity';
|
const colorHumidity = 'colorHumidity';
|
||||||
|
|
||||||
|
const later = new Date();
|
||||||
|
const now = new Date();
|
||||||
|
later.setDate(now.getDate() + 1);
|
||||||
|
const hourlyForecast = hourly?.time
|
||||||
|
?.map((_: any, index: number) => {
|
||||||
|
return {
|
||||||
|
dewpoint: hourly.dew_point_2m?.[index].toFixed(0),
|
||||||
|
isDaytime: hourly.is_day?.[index],
|
||||||
|
name: hourly.time[index],
|
||||||
|
startTime: hourly.time[index],
|
||||||
|
endTime: hourly.time[index + 1],
|
||||||
|
probabilityOfPrecipitation: hourly.precipitation_probability?.[index],
|
||||||
|
temperature: hourly.temperature_2m?.[index].toFixed(0),
|
||||||
|
relativeHumidity: hourly.relative_humidity_2m?.[index].toFixed(0),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((hour: any) => hour.startTime > now && hour.startTime < later);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
style={{
|
style={{
|
||||||
@@ -34,7 +49,7 @@ export default function WeatherGraphs({
|
|||||||
<XAxis
|
<XAxis
|
||||||
type='category'
|
type='category'
|
||||||
stroke={'var(--mantine-color-primary-0)'}
|
stroke={'var(--mantine-color-primary-0)'}
|
||||||
dataKey={(data: HourlyForecast) => {
|
dataKey={(data) => {
|
||||||
return new Date(data.startTime).toLocaleTimeString('en-US', {
|
return new Date(data.startTime).toLocaleTimeString('en-US', {
|
||||||
hour: 'numeric',
|
hour: 'numeric',
|
||||||
minute: 'numeric',
|
minute: 'numeric',
|
||||||
@@ -49,7 +64,7 @@ export default function WeatherGraphs({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CartesianGrid stroke='#f5f5f522' />
|
<CartesianGrid stroke='#f5f5f522' />
|
||||||
<Tooltip content={<WeatherTooltip />} />
|
<Tooltip wrapperStyle={{ zIndex: 100 }} content={<WeatherTooltip />} />
|
||||||
<Line
|
<Line
|
||||||
unit={'°F'}
|
unit={'°F'}
|
||||||
type='monotone'
|
type='monotone'
|
||||||
@@ -67,9 +82,7 @@ export default function WeatherGraphs({
|
|||||||
unit={'°F'}
|
unit={'°F'}
|
||||||
type='monotone'
|
type='monotone'
|
||||||
name='Dew Point'
|
name='Dew Point'
|
||||||
dataKey={(data: HourlyForecast) => {
|
dataKey='dewpoint'
|
||||||
return (data.dewpoint.value * 9) / 5 + 32;
|
|
||||||
}}
|
|
||||||
stroke={'var(--mantine-color-accent-0)'}
|
stroke={'var(--mantine-color-accent-0)'}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={{
|
dot={{
|
||||||
@@ -78,7 +91,7 @@ export default function WeatherGraphs({
|
|||||||
fill: 'var(--mantine-color-accent-0)',
|
fill: 'var(--mantine-color-accent-0)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend style={{ background: 'var(--mantine-color-search-0)' }} />
|
<Legend wrapperStyle={{ zIndex: 0, position: 'absolute' }} />
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
<ResponsiveContainer width={'100%'} height={150}>
|
<ResponsiveContainer width={'100%'} height={150}>
|
||||||
@@ -112,7 +125,7 @@ export default function WeatherGraphs({
|
|||||||
<XAxis
|
<XAxis
|
||||||
type='category'
|
type='category'
|
||||||
stroke={'var(--mantine-color-primary-0)'}
|
stroke={'var(--mantine-color-primary-0)'}
|
||||||
dataKey={(data: HourlyForecast) => {
|
dataKey={(data) => {
|
||||||
return new Date(data.startTime).toLocaleTimeString('en-US', {
|
return new Date(data.startTime).toLocaleTimeString('en-US', {
|
||||||
hour: 'numeric',
|
hour: 'numeric',
|
||||||
minute: 'numeric',
|
minute: 'numeric',
|
||||||
@@ -121,14 +134,12 @@ export default function WeatherGraphs({
|
|||||||
/>
|
/>
|
||||||
<YAxis stroke={'var(--mantine-color-primary-0)'} unit={'%'} domain={[0, 100]} />
|
<YAxis stroke={'var(--mantine-color-primary-0)'} unit={'%'} domain={[0, 100]} />
|
||||||
<CartesianGrid stroke='#f5f5f522' />
|
<CartesianGrid stroke='#f5f5f522' />
|
||||||
<Tooltip content={<WeatherTooltip />} />
|
<Tooltip wrapperStyle={{ zIndex: 100 }} content={<WeatherTooltip />} />
|
||||||
<Area
|
<Area
|
||||||
unit={'%'}
|
unit={'%'}
|
||||||
type='monotone'
|
type='monotone'
|
||||||
name='Humidity'
|
name='Humidity'
|
||||||
dataKey={(data: HourlyForecast) => {
|
dataKey={'relativeHumidity'}
|
||||||
return data.relativeHumidity.value;
|
|
||||||
}}
|
|
||||||
stroke={'var(--mantine-color-primary-0)'}
|
stroke={'var(--mantine-color-primary-0)'}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill={`url(#${colorPrecipitation})`}
|
fill={`url(#${colorPrecipitation})`}
|
||||||
@@ -142,9 +153,7 @@ export default function WeatherGraphs({
|
|||||||
unit={'%'}
|
unit={'%'}
|
||||||
type='monotone'
|
type='monotone'
|
||||||
name='Precipitation'
|
name='Precipitation'
|
||||||
dataKey={(data: HourlyForecast) => {
|
dataKey={'probabilityOfPrecipitation'}
|
||||||
return data.probabilityOfPrecipitation.value;
|
|
||||||
}}
|
|
||||||
stroke={'var(--mantine-color-accent-0)'}
|
stroke={'var(--mantine-color-accent-0)'}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill={`url(#${colorHumidity})`}
|
fill={`url(#${colorHumidity})`}
|
||||||
@@ -154,7 +163,7 @@ export default function WeatherGraphs({
|
|||||||
fill: 'var(--mantine-color-accent-0)',
|
fill: 'var(--mantine-color-accent-0)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend style={{ background: 'var(--mantine-color-search-0)' }} />
|
<Legend wrapperStyle={{ zIndex: 0, position: 'absolute' }} />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Group, Image } from '@mantine/core';
|
import { Image } from '@mantine/core';
|
||||||
import { FaArrowRight } from 'react-icons/fa';
|
|
||||||
import CloudyIcon from '../../assets/weather/cloudy.svg';
|
import CloudyIcon from '../../assets/weather/cloudy.svg';
|
||||||
import HazeIcon from '../../assets/weather/haze.svg';
|
import HazeIcon from '../../assets/weather/haze.svg';
|
||||||
import HeavyRainIcon from '../../assets/weather/heavy-rain.svg';
|
import HeavyRainIcon from '../../assets/weather/heavy-rain.svg';
|
||||||
@@ -9,168 +8,166 @@ import SleetIcon from '../../assets/weather/sleet.svg';
|
|||||||
import SnowIcon from '../../assets/weather/snow.svg';
|
import SnowIcon from '../../assets/weather/snow.svg';
|
||||||
import SunnyIcon from '../../assets/weather/sunny.svg';
|
import SunnyIcon from '../../assets/weather/sunny.svg';
|
||||||
import ThunderstormIcon from '../../assets/weather/thunderstorm.svg';
|
import ThunderstormIcon from '../../assets/weather/thunderstorm.svg';
|
||||||
import WindyIcon from '../../assets/weather/windy.svg';
|
|
||||||
|
|
||||||
export default function WeatherIcon({
|
export default function WeatherIcon({
|
||||||
size,
|
size,
|
||||||
textDescription,
|
code,
|
||||||
}: {
|
}: {
|
||||||
size: string;
|
size: string;
|
||||||
textDescription: string;
|
code: number | undefined;
|
||||||
}) {
|
}) {
|
||||||
const icon = textDescription.includes('then')
|
const lookupSource = (code: any) => {
|
||||||
? [
|
switch (code) {
|
||||||
textDescription.substring(0, textDescription.trim().indexOf('then')),
|
case 0:
|
||||||
textDescription.substring(textDescription.trim().indexOf('then') + 4).trim(),
|
|
||||||
]
|
|
||||||
: [textDescription.trim()];
|
|
||||||
|
|
||||||
const lookupSource = (icon: any) => {
|
|
||||||
switch (icon.trim()) {
|
|
||||||
case 'Ash':
|
|
||||||
return {
|
return {
|
||||||
src: HazeIcon,
|
src: SunnyIcon,
|
||||||
alt: 'HazeIcon',
|
alt: 'SunnyIcon',
|
||||||
};
|
};
|
||||||
case 'Broken Clouds':
|
|
||||||
|
case 1:
|
||||||
return {
|
return {
|
||||||
src: PartlyCloudyIcon,
|
src: PartlyCloudyIcon,
|
||||||
alt: 'PartlyCloudyIcon',
|
alt: 'PartlyCloudyIcon',
|
||||||
};
|
};
|
||||||
case 'Chance Rain Showers':
|
case 2:
|
||||||
return {
|
return {
|
||||||
src: RainIcon,
|
src: PartlyCloudyIcon,
|
||||||
alt: 'RainIcon',
|
alt: 'PartlyCloudyIcon',
|
||||||
};
|
};
|
||||||
case 'Chance Showers':
|
case 3:
|
||||||
return {
|
|
||||||
src: RainIcon,
|
|
||||||
alt: 'RainIcon',
|
|
||||||
};
|
|
||||||
case 'Slight Chance Showers':
|
|
||||||
return {
|
|
||||||
src: RainIcon,
|
|
||||||
alt: 'RainIcon',
|
|
||||||
};
|
|
||||||
case 'Cloudy':
|
|
||||||
return {
|
return {
|
||||||
src: CloudyIcon,
|
src: CloudyIcon,
|
||||||
alt: 'CloudyIcon',
|
alt: 'CloudyIcon',
|
||||||
};
|
};
|
||||||
case 'Dust':
|
|
||||||
|
case 45:
|
||||||
return {
|
return {
|
||||||
src: HazeIcon,
|
src: HazeIcon,
|
||||||
alt: 'HazeIcon',
|
alt: 'HazeIcon',
|
||||||
};
|
};
|
||||||
case 'Drizzle':
|
case 48:
|
||||||
|
return {
|
||||||
|
src: HazeIcon,
|
||||||
|
alt: 'HazeIcon',
|
||||||
|
};
|
||||||
|
|
||||||
|
case 51:
|
||||||
return {
|
return {
|
||||||
src: RainIcon,
|
src: RainIcon,
|
||||||
alt: 'RainIcon',
|
alt: 'RainIcon',
|
||||||
};
|
};
|
||||||
case 'Fog':
|
case 53:
|
||||||
return {
|
return {
|
||||||
src: HazeIcon,
|
src: RainIcon,
|
||||||
alt: 'HazeIcon',
|
alt: 'RainIcon',
|
||||||
};
|
};
|
||||||
case 'Freezing Rain':
|
case 55:
|
||||||
|
return {
|
||||||
|
src: RainIcon,
|
||||||
|
alt: 'RainIcon',
|
||||||
|
};
|
||||||
|
|
||||||
|
case 56:
|
||||||
return {
|
return {
|
||||||
src: SleetIcon,
|
src: SleetIcon,
|
||||||
alt: 'SleetIcon',
|
alt: 'SleetIcon',
|
||||||
};
|
};
|
||||||
case 'Haze':
|
case 57:
|
||||||
return {
|
return {
|
||||||
src: HazeIcon,
|
src: SleetIcon,
|
||||||
alt: 'HazeIcon',
|
alt: 'SleetIcon',
|
||||||
};
|
};
|
||||||
case 'Heavy Rain':
|
|
||||||
|
case 61:
|
||||||
|
return {
|
||||||
|
src: RainIcon,
|
||||||
|
alt: 'RainIcon',
|
||||||
|
};
|
||||||
|
case 63:
|
||||||
|
return {
|
||||||
|
src: RainIcon,
|
||||||
|
alt: 'RainIcon',
|
||||||
|
};
|
||||||
|
case 65:
|
||||||
|
return {
|
||||||
|
src: RainIcon,
|
||||||
|
alt: 'RainIcon',
|
||||||
|
};
|
||||||
|
|
||||||
|
case 66:
|
||||||
|
return {
|
||||||
|
src: SleetIcon,
|
||||||
|
alt: 'SleetIcon',
|
||||||
|
};
|
||||||
|
case 67:
|
||||||
|
return {
|
||||||
|
src: SleetIcon,
|
||||||
|
alt: 'SleetIcon',
|
||||||
|
};
|
||||||
|
|
||||||
|
case 71:
|
||||||
|
return {
|
||||||
|
src: SnowIcon,
|
||||||
|
alt: 'SnowIcon',
|
||||||
|
};
|
||||||
|
case 73:
|
||||||
|
return {
|
||||||
|
src: SnowIcon,
|
||||||
|
alt: 'SnowIcon',
|
||||||
|
};
|
||||||
|
case 75:
|
||||||
|
return {
|
||||||
|
src: SnowIcon,
|
||||||
|
alt: 'SnowIcon',
|
||||||
|
};
|
||||||
|
|
||||||
|
case 77:
|
||||||
|
return {
|
||||||
|
src: SnowIcon,
|
||||||
|
alt: 'SnowIcon',
|
||||||
|
};
|
||||||
|
|
||||||
|
case 80:
|
||||||
return {
|
return {
|
||||||
src: HeavyRainIcon,
|
src: HeavyRainIcon,
|
||||||
alt: 'HeavyRainIcon',
|
alt: 'HeavyRainIcon',
|
||||||
};
|
};
|
||||||
case 'Heavy Snow':
|
case 81:
|
||||||
|
return {
|
||||||
|
src: HeavyRainIcon,
|
||||||
|
alt: 'HeavyRainIcon',
|
||||||
|
};
|
||||||
|
case 82:
|
||||||
|
return {
|
||||||
|
src: HeavyRainIcon,
|
||||||
|
alt: 'HeavyRainIcon',
|
||||||
|
};
|
||||||
|
|
||||||
|
case 85:
|
||||||
return {
|
return {
|
||||||
src: SnowIcon,
|
src: SnowIcon,
|
||||||
alt: 'SnowIcon',
|
alt: 'SnowIcon',
|
||||||
};
|
};
|
||||||
case 'Light Rain':
|
case 86:
|
||||||
return {
|
|
||||||
src: RainIcon,
|
|
||||||
alt: 'RainIcon',
|
|
||||||
};
|
|
||||||
case 'Mist':
|
|
||||||
return {
|
|
||||||
src: HazeIcon,
|
|
||||||
alt: 'HazeIcon',
|
|
||||||
};
|
|
||||||
case 'Moderate Rain':
|
|
||||||
return {
|
|
||||||
src: RainIcon,
|
|
||||||
alt: 'RainIcon',
|
|
||||||
};
|
|
||||||
case 'Mostly Cloudy':
|
|
||||||
return {
|
|
||||||
src: CloudyIcon,
|
|
||||||
alt: 'CloudyIcon',
|
|
||||||
};
|
|
||||||
case 'Overcast Clouds':
|
|
||||||
return {
|
|
||||||
src: PartlyCloudyIcon,
|
|
||||||
alt: 'PartlyCloudyIcon',
|
|
||||||
};
|
|
||||||
case 'Patchy Fog':
|
|
||||||
return {
|
|
||||||
src: HazeIcon,
|
|
||||||
alt: 'HazeIcon',
|
|
||||||
};
|
|
||||||
case 'Partly Sunny':
|
|
||||||
return {
|
|
||||||
src: PartlyCloudyIcon,
|
|
||||||
alt: 'PartlyCloudyIcon',
|
|
||||||
};
|
|
||||||
case 'Rain':
|
|
||||||
return {
|
|
||||||
src: RainIcon,
|
|
||||||
alt: 'RainIcon',
|
|
||||||
};
|
|
||||||
case 'Scattered Clouds':
|
|
||||||
return {
|
|
||||||
src: PartlyCloudyIcon,
|
|
||||||
alt: 'PartlyCloudyIcon',
|
|
||||||
};
|
|
||||||
case 'Showers':
|
|
||||||
return {
|
|
||||||
src: RainIcon,
|
|
||||||
alt: 'RainIcon',
|
|
||||||
};
|
|
||||||
case 'Sleet':
|
|
||||||
return {
|
|
||||||
src: SleetIcon,
|
|
||||||
alt: 'SleetIcon',
|
|
||||||
};
|
|
||||||
case 'Smoke':
|
|
||||||
return {
|
|
||||||
src: HazeIcon,
|
|
||||||
alt: 'HazeIcon',
|
|
||||||
};
|
|
||||||
case 'Snow':
|
|
||||||
return {
|
return {
|
||||||
src: SnowIcon,
|
src: SnowIcon,
|
||||||
alt: 'SnowIcon',
|
alt: 'SnowIcon',
|
||||||
};
|
};
|
||||||
case 'Squall':
|
|
||||||
return {
|
case 95:
|
||||||
src: WindyIcon,
|
|
||||||
alt: 'WindyIcon',
|
|
||||||
};
|
|
||||||
case 'Thunderstorm':
|
|
||||||
return {
|
return {
|
||||||
src: ThunderstormIcon,
|
src: ThunderstormIcon,
|
||||||
alt: 'ThunderstormIcon',
|
alt: 'ThunderstormIcon',
|
||||||
};
|
};
|
||||||
case 'Tornado':
|
case 96:
|
||||||
return {
|
return {
|
||||||
src: WindyIcon,
|
src: ThunderstormIcon,
|
||||||
alt: 'WindyIcon',
|
alt: 'ThunderstormIcon',
|
||||||
|
};
|
||||||
|
case 99:
|
||||||
|
return {
|
||||||
|
src: ThunderstormIcon,
|
||||||
|
alt: 'ThunderstormIcon',
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -182,61 +179,14 @@ export default function WeatherIcon({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
{icon.length === 1 ? (
|
|
||||||
<Image
|
<Image
|
||||||
style={
|
style={
|
||||||
size === 'lg'
|
size === 'lg'
|
||||||
? { width: '150px', height: '150px' }
|
? { width: '150px', height: '150px' }
|
||||||
: { width: '75px', height: '75px' }
|
: { width: '75px', height: '75px' }
|
||||||
}
|
}
|
||||||
src={lookupSource(icon[0]).src}
|
src={lookupSource(code).src}
|
||||||
alt={lookupSource(icon[0]).alt}
|
alt={lookupSource(code).alt}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<Group
|
|
||||||
pos={'relative'}
|
|
||||||
style={
|
|
||||||
size === 'lg'
|
|
||||||
? { width: '150px', height: '150px' }
|
|
||||||
: { width: '75px', height: '75px' }
|
|
||||||
}
|
|
||||||
align='center'
|
|
||||||
justify='center'
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
style={{
|
|
||||||
mask: 'linear-gradient(105deg, rgba(0, 0, 0, 1) 52%, rgba(0, 0, 0, 0) 56%)',
|
|
||||||
}}
|
|
||||||
pos={'absolute'}
|
|
||||||
left={-8}
|
|
||||||
src={lookupSource(icon[0]).src}
|
|
||||||
alt={lookupSource(icon[0]).alt}
|
|
||||||
/>
|
|
||||||
<Image
|
|
||||||
style={{
|
|
||||||
mask: 'linear-gradient(105deg, rgba(0, 0, 0, 0) 44%, rgba(0, 0, 0, 1) 48%)',
|
|
||||||
}}
|
|
||||||
pos={'absolute'}
|
|
||||||
right={-8}
|
|
||||||
src={lookupSource(icon[1]).src}
|
|
||||||
alt={lookupSource(icon[1]).alt}
|
|
||||||
/>
|
|
||||||
<FaArrowRight
|
|
||||||
size={28}
|
|
||||||
style={{
|
|
||||||
color: 'var(--mantine-color-background-0)',
|
|
||||||
position: 'absolute',
|
|
||||||
stroke: 'var(--mantine-color-primary-0)',
|
|
||||||
strokeWidth: 16,
|
|
||||||
backgroundColor:
|
|
||||||
'color-mix(in srgb, var(--mantine-color-accent-0), transparent 20%)',
|
|
||||||
borderRadius: '50%',
|
|
||||||
padding: '5px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,23 @@
|
|||||||
import { Center, Group, Modal, Stack, Text } from '@mantine/core';
|
import { Center, Group, Modal, Stack, Text } from '@mantine/core';
|
||||||
import { useCommonHooks, useWeatherAPI } from 'hooks';
|
import { useCommonHooks } from 'hooks';
|
||||||
import { modalClasses } from 'styles';
|
import { modalClasses } from 'styles';
|
||||||
|
import type { MeteoWeather } from 'types';
|
||||||
import type { CurrentWeather as CW, ExtendedForecast, HourlyForecast } from 'types';
|
|
||||||
import CurrentWeather from './CurrentWeather';
|
import CurrentWeather from './CurrentWeather';
|
||||||
import ExtendedForecastCard from './ExtendedForecastCard';
|
import ExtendedForecastCard from './ExtendedForecastCard';
|
||||||
import WeatherGraphs from './WeatherGraphs';
|
import WeatherGraphs from './WeatherGraphs';
|
||||||
|
|
||||||
export function WeatherModal({
|
export function WeatherModal({
|
||||||
current,
|
current,
|
||||||
extendedForecast,
|
|
||||||
weatherModalOpened,
|
weatherModalOpened,
|
||||||
handleResetSearch,
|
handleResetSearch,
|
||||||
closeWeatherModal,
|
closeWeatherModal,
|
||||||
}: {
|
}: {
|
||||||
current: CW;
|
current: MeteoWeather | undefined;
|
||||||
extendedForecast: ExtendedForecast[];
|
|
||||||
weatherModalOpened: boolean;
|
weatherModalOpened: boolean;
|
||||||
handleResetSearch: () => void;
|
handleResetSearch: () => void;
|
||||||
closeWeatherModal: () => void;
|
closeWeatherModal: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { isMobile, currentUser, currentLocation } = useCommonHooks();
|
const { isMobile, currentUser } = useCommonHooks();
|
||||||
const { data: hourly } = useWeatherAPI({
|
|
||||||
endpoint: 'hourly',
|
|
||||||
office: currentLocation.office,
|
|
||||||
x: currentLocation.x,
|
|
||||||
y: currentLocation.y,
|
|
||||||
});
|
|
||||||
const hourlyForecast: HourlyForecast[] =
|
|
||||||
hourly?.properties?.periods?.slice(1, 25) || [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal.Root
|
<Modal.Root
|
||||||
@@ -68,8 +57,8 @@ export function WeatherModal({
|
|||||||
<Center w={'100%'}>
|
<Center w={'100%'}>
|
||||||
<Stack w={'100%'}>
|
<Stack w={'100%'}>
|
||||||
<CurrentWeather current={current} />
|
<CurrentWeather current={current} />
|
||||||
<WeatherGraphs hourlyForecast={hourlyForecast} />
|
<WeatherGraphs hourly={current?.hourly} />
|
||||||
<ExtendedForecastCard forecast={extendedForecast} />
|
<ExtendedForecastCard daily={current?.daily} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ function WeatherTooltip({ payload, active }: any) {
|
|||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Text size='md' c='var(--mantine-color-primary-0)'>
|
||||||
|
{payload[0]?.payload.startTime?.toLocaleString()}
|
||||||
|
</Text>
|
||||||
{payload.map((item: any) => (
|
{payload.map((item: any) => (
|
||||||
<Text
|
<Text
|
||||||
key={item.name}
|
key={item.name}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
export {
|
export {
|
||||||
useMutationAPI,
|
useMutationAPI,
|
||||||
|
useOpenMeteoAPI,
|
||||||
useOSMAPI,
|
useOSMAPI,
|
||||||
useQueryAPI,
|
useQueryAPI,
|
||||||
useSunStateAPI,
|
|
||||||
useWeatherAPI,
|
|
||||||
} from './useAPI';
|
} from './useAPI';
|
||||||
export { useCommonHooks } from './useCommonHooks';
|
export { useCommonHooks } from './useCommonHooks';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Text } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { fetchWeatherApi } from 'openmeteo';
|
||||||
import { FaCheck } from 'react-icons/fa';
|
import { FaCheck } from 'react-icons/fa';
|
||||||
import { VscLoading } from 'react-icons/vsc';
|
import { VscLoading } from 'react-icons/vsc';
|
||||||
|
|
||||||
@@ -14,12 +15,195 @@ const headers: HeadersInit = {
|
|||||||
'Sec-Fetch-Mode': 'no-cors',
|
'Sec-Fetch-Mode': 'no-cors',
|
||||||
'Sec-Fetch-Site': 'same-site',
|
'Sec-Fetch-Site': 'same-site',
|
||||||
};
|
};
|
||||||
const callEndpoint = async (url: string) => {
|
|
||||||
const response = await fetch(url);
|
const callWeatherEndpoint = async (lat: number, lon: number) => {
|
||||||
if (!response.ok) {
|
const weatherParams = {
|
||||||
throw new Error(`${response.status} ${response.statusText}`);
|
latitude: lat,
|
||||||
}
|
longitude: lon,
|
||||||
return await response.json();
|
daily: [
|
||||||
|
'sunrise',
|
||||||
|
'sunset',
|
||||||
|
'daylight_duration',
|
||||||
|
'sunshine_duration',
|
||||||
|
'uv_index_max',
|
||||||
|
'uv_index_clear_sky_max',
|
||||||
|
'temperature_2m_max',
|
||||||
|
'temperature_2m_min',
|
||||||
|
'apparent_temperature_max',
|
||||||
|
'apparent_temperature_min',
|
||||||
|
'wind_speed_10m_max',
|
||||||
|
'wind_gusts_10m_max',
|
||||||
|
'wind_direction_10m_dominant',
|
||||||
|
'precipitation_sum',
|
||||||
|
'precipitation_hours',
|
||||||
|
'precipitation_probability_max',
|
||||||
|
'weather_code',
|
||||||
|
],
|
||||||
|
hourly: [
|
||||||
|
'temperature_2m',
|
||||||
|
'relative_humidity_2m',
|
||||||
|
'dew_point_2m',
|
||||||
|
'apparent_temperature',
|
||||||
|
'precipitation_probability',
|
||||||
|
'precipitation',
|
||||||
|
'rain',
|
||||||
|
'showers',
|
||||||
|
'snowfall',
|
||||||
|
'snow_depth',
|
||||||
|
'weather_code',
|
||||||
|
'pressure_msl',
|
||||||
|
'surface_pressure',
|
||||||
|
'cloud_cover',
|
||||||
|
'cloud_cover_low',
|
||||||
|
'cloud_cover_mid',
|
||||||
|
'cloud_cover_high',
|
||||||
|
'visibility',
|
||||||
|
'evapotranspiration',
|
||||||
|
'et0_fao_evapotranspiration',
|
||||||
|
'vapour_pressure_deficit',
|
||||||
|
'wind_speed_10m',
|
||||||
|
'wind_direction_10m',
|
||||||
|
'wind_gusts_10m',
|
||||||
|
'uv_index',
|
||||||
|
'uv_index_clear_sky',
|
||||||
|
'is_day',
|
||||||
|
'sunshine_duration',
|
||||||
|
],
|
||||||
|
current: [
|
||||||
|
'temperature_2m',
|
||||||
|
'relative_humidity_2m',
|
||||||
|
'apparent_temperature',
|
||||||
|
'is_day',
|
||||||
|
'wind_speed_10m',
|
||||||
|
'wind_direction_10m',
|
||||||
|
'wind_gusts_10m',
|
||||||
|
'precipitation',
|
||||||
|
'rain',
|
||||||
|
'showers',
|
||||||
|
'snowfall',
|
||||||
|
'surface_pressure',
|
||||||
|
'pressure_msl',
|
||||||
|
'cloud_cover',
|
||||||
|
'weather_code',
|
||||||
|
],
|
||||||
|
wind_speed_unit: 'mph',
|
||||||
|
temperature_unit: 'fahrenheit',
|
||||||
|
precipitation_unit: 'inch',
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = 'https://api.open-meteo.com/v1/forecast';
|
||||||
|
const responses = await fetchWeatherApi(url, weatherParams);
|
||||||
|
|
||||||
|
const response = responses[0];
|
||||||
|
|
||||||
|
const current = response ? response.current() : null;
|
||||||
|
const hourly = response ? response.hourly() : null;
|
||||||
|
const daily = response ? response.daily() : null;
|
||||||
|
|
||||||
|
const sunrise = daily?.variables(0);
|
||||||
|
const sunset = daily?.variables(1);
|
||||||
|
const utcOffsetSeconds = response.utcOffsetSeconds();
|
||||||
|
const weatherData = {
|
||||||
|
current: {
|
||||||
|
time: new Date((Number(current?.time()) + utcOffsetSeconds) * 1000),
|
||||||
|
temperature_2m: current?.variables(0)?.value(),
|
||||||
|
relative_humidity_2m: current?.variables(1)?.value(),
|
||||||
|
apparent_temperature: current?.variables(2)?.value(),
|
||||||
|
is_day: current?.variables(3)?.value(),
|
||||||
|
wind_speed_10m: current?.variables(4)?.value(),
|
||||||
|
wind_direction_10m: current?.variables(5)?.value(),
|
||||||
|
wind_gusts_10m: current?.variables(6)?.value(),
|
||||||
|
precipitation: current?.variables(7)?.value(),
|
||||||
|
rain: current?.variables(8)?.value(),
|
||||||
|
showers: current?.variables(9)?.value(),
|
||||||
|
snowfall: current?.variables(10)?.value(),
|
||||||
|
surface_pressure: current?.variables(11)?.value(),
|
||||||
|
pressure_msl: current?.variables(12)?.value(),
|
||||||
|
cloud_cover: current?.variables(13)?.value(),
|
||||||
|
weather_code: current?.variables(14)?.value(),
|
||||||
|
},
|
||||||
|
hourly: {
|
||||||
|
time: Array.from(
|
||||||
|
{
|
||||||
|
length:
|
||||||
|
(Number(hourly?.timeEnd()) - Number(hourly?.time())) /
|
||||||
|
Number(hourly?.interval()),
|
||||||
|
},
|
||||||
|
(_, i) =>
|
||||||
|
new Date(
|
||||||
|
(Number(hourly?.time()) + i * Number(hourly?.interval()) + utcOffsetSeconds) *
|
||||||
|
1000,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
temperature_2m: hourly?.variables(0)?.valuesArray(),
|
||||||
|
relative_humidity_2m: hourly?.variables(1)?.valuesArray(),
|
||||||
|
dew_point_2m: hourly?.variables(2)?.valuesArray(),
|
||||||
|
apparent_temperature: hourly?.variables(3)?.valuesArray(),
|
||||||
|
precipitation_probability: hourly?.variables(4)?.valuesArray(),
|
||||||
|
precipitation: hourly?.variables(5)?.valuesArray(),
|
||||||
|
rain: hourly?.variables(6)?.valuesArray(),
|
||||||
|
showers: hourly?.variables(7)?.valuesArray(),
|
||||||
|
snowfall: hourly?.variables(8)?.valuesArray(),
|
||||||
|
snow_depth: hourly?.variables(9)?.valuesArray(),
|
||||||
|
weather_code: hourly?.variables(10)?.valuesArray(),
|
||||||
|
pressure_msl: hourly?.variables(11)?.valuesArray(),
|
||||||
|
surface_pressure: hourly?.variables(12)?.valuesArray(),
|
||||||
|
cloud_cover: hourly?.variables(13)?.valuesArray(),
|
||||||
|
cloud_cover_low: hourly?.variables(14)?.valuesArray(),
|
||||||
|
cloud_cover_mid: hourly?.variables(15)?.valuesArray(),
|
||||||
|
cloud_cover_high: hourly?.variables(16)?.valuesArray(),
|
||||||
|
visibility: hourly?.variables(17)?.valuesArray(),
|
||||||
|
evapotranspiration: hourly?.variables(18)?.valuesArray(),
|
||||||
|
et0_fao_evapotranspiration: hourly?.variables(19)?.valuesArray(),
|
||||||
|
vapour_pressure_deficit: hourly?.variables(20)?.valuesArray(),
|
||||||
|
wind_speed_10m: hourly?.variables(21)?.valuesArray(),
|
||||||
|
wind_direction_10m: hourly?.variables(22)?.valuesArray(),
|
||||||
|
wind_gusts_10m: hourly?.variables(23)?.valuesArray(),
|
||||||
|
uv_index: hourly?.variables(24)?.valuesArray(),
|
||||||
|
uv_index_clear_sky: hourly?.variables(25)?.valuesArray(),
|
||||||
|
is_day: hourly?.variables(26)?.valuesArray(),
|
||||||
|
sunshine_duration: hourly?.variables(27)?.valuesArray(),
|
||||||
|
},
|
||||||
|
daily: {
|
||||||
|
time: Array.from(
|
||||||
|
{
|
||||||
|
length:
|
||||||
|
(Number(daily?.timeEnd()) - Number(daily?.time())) /
|
||||||
|
Number(daily?.interval()),
|
||||||
|
},
|
||||||
|
(_, i) =>
|
||||||
|
new Date(
|
||||||
|
(Number(daily?.time()) + i * Number(daily?.interval()) + utcOffsetSeconds) *
|
||||||
|
1000,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Map Int64 values to according structure
|
||||||
|
sunrise: [new Array(sunrise?.valuesInt64Length())].map(
|
||||||
|
(_, i) => new Date((Number(sunrise?.valuesInt64(i)) + utcOffsetSeconds) * 1000),
|
||||||
|
),
|
||||||
|
// Map Int64 values to according structure
|
||||||
|
sunset: [new Array(sunset?.valuesInt64Length())].map(
|
||||||
|
(_, i) => new Date((Number(sunset?.valuesInt64(i)) + utcOffsetSeconds) * 1000),
|
||||||
|
),
|
||||||
|
daylight_duration: daily?.variables(2)?.valuesArray(),
|
||||||
|
sunshine_duration: daily?.variables(3)?.valuesArray(),
|
||||||
|
uv_index_max: daily?.variables(4)?.valuesArray(),
|
||||||
|
uv_index_clear_sky_max: daily?.variables(5)?.valuesArray(),
|
||||||
|
temperature_2m_max: daily?.variables(6)?.valuesArray(),
|
||||||
|
temperature_2m_min: daily?.variables(7)?.valuesArray(),
|
||||||
|
apparent_temperature_max: daily?.variables(8)?.valuesArray(),
|
||||||
|
apparent_temperature_min: daily?.variables(9)?.valuesArray(),
|
||||||
|
wind_speed_10m_max: daily?.variables(10)?.valuesArray(),
|
||||||
|
wind_gusts_10m_max: daily?.variables(11)?.valuesArray(),
|
||||||
|
wind_direction_10m_dominant: daily?.variables(12)?.valuesArray(),
|
||||||
|
precipitation_sum: daily?.variables(13)?.valuesArray(),
|
||||||
|
precipitation_hours: daily?.variables(14)?.valuesArray(),
|
||||||
|
precipitation_probability_max: daily?.variables(15)?.valuesArray(),
|
||||||
|
weather_code: daily?.variables(16)?.valuesArray(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return weatherData;
|
||||||
};
|
};
|
||||||
export function useQueryAPI({ endpoint }: { endpoint: string }) {
|
export function useQueryAPI({ endpoint }: { endpoint: string }) {
|
||||||
const callEndpoint = async (endpoint: string) => {
|
const callEndpoint = async (endpoint: string) => {
|
||||||
@@ -143,54 +327,10 @@ export function useMutationAPI() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWeatherAPI({
|
export function useOpenMeteoAPI({ lat, lon }: { lat: number; lon: number }) {
|
||||||
endpoint,
|
|
||||||
lat,
|
|
||||||
lon,
|
|
||||||
office,
|
|
||||||
station,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
}: {
|
|
||||||
endpoint: string;
|
|
||||||
lat?: number;
|
|
||||||
lon?: number;
|
|
||||||
office?: string;
|
|
||||||
station?: string;
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
}) {
|
|
||||||
let url = '';
|
|
||||||
if (endpoint === 'baseline') {
|
|
||||||
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}`;
|
|
||||||
} else if (endpoint === 'current') {
|
|
||||||
url = `https://api.weather.gov/stations/${station}/observations`;
|
|
||||||
} else if (endpoint === 'forecast') {
|
|
||||||
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/forecast`;
|
|
||||||
} else if (endpoint === 'hourly') {
|
|
||||||
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/forecast/hourly`;
|
|
||||||
} else if (endpoint === 'points') {
|
|
||||||
url = `https://api.weather.gov/points/${lat},${lon}`;
|
|
||||||
} else if (endpoint === 'stations') {
|
|
||||||
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/stations`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['GET', url],
|
queryKey: ['GET', 'weather'],
|
||||||
queryFn: () => callEndpoint(url),
|
queryFn: () => callWeatherEndpoint(lat, lon),
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSunStateAPI({ lat, lon }: { lat: number; lon: number }) {
|
|
||||||
const date = new Date();
|
|
||||||
const today = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
|
||||||
date.setDate(date.getDate() + 1);
|
|
||||||
const tomorrow = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
|
||||||
const url = `https://api.sunrisesunset.io/json?lat=${lat}&lng=${lon}&date_start=${today}&date_end=${tomorrow}`;
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['GET', url],
|
|
||||||
queryFn: () => callEndpoint(url),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,10 +108,6 @@ export function useCommonHooks() {
|
|||||||
const [currentLocation, setCurrentLocation] = useLocalStorage<Location>({
|
const [currentLocation, setCurrentLocation] = useLocalStorage<Location>({
|
||||||
key: 'location',
|
key: 'location',
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
office: 'HGX',
|
|
||||||
station: 'KHOU',
|
|
||||||
x: 65,
|
|
||||||
y: 96,
|
|
||||||
lat: 29.750950134386066,
|
lat: 29.750950134386066,
|
||||||
lon: -95.36220654860034,
|
lon: -95.36220654860034,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { Box, Grid, Stack } from '@mantine/core';
|
import { Box, Grid, Stack } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import CurrentWeather from 'components/WeatherModal/CurrentWeather';
|
import { useCommonHooks, useOpenMeteoAPI, useOSMAPI, useQueryAPI } from 'hooks';
|
||||||
import { useCommonHooks, useOSMAPI, useQueryAPI, useWeatherAPI } from 'hooks';
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { stackClasses } from 'styles';
|
import { stackClasses } from 'styles';
|
||||||
import type {
|
import type { Message, MeteoWeather, PersonalData } from 'types';
|
||||||
CurrentWeather as CW,
|
import CurrentWeather from '../../components/WeatherModal/CurrentWeather';
|
||||||
ExtendedForecast,
|
|
||||||
Message,
|
|
||||||
PersonalData,
|
|
||||||
} from 'types';
|
|
||||||
import {
|
import {
|
||||||
Applications,
|
Applications,
|
||||||
Bookmarks,
|
Bookmarks,
|
||||||
@@ -48,73 +43,19 @@ export function Homepage() {
|
|||||||
const { data: weatherMetadata } = useOSMAPI({
|
const { data: weatherMetadata } = useOSMAPI({
|
||||||
address: personalData?.[0]?.physicalAddress,
|
address: personalData?.[0]?.physicalAddress,
|
||||||
});
|
});
|
||||||
const { data: points } = useWeatherAPI({
|
const { data: meteoWeather } = useOpenMeteoAPI({
|
||||||
endpoint: 'points',
|
|
||||||
lat: weatherMetadata?.[0].lat ?? currentLocation.lat,
|
lat: weatherMetadata?.[0].lat ?? currentLocation.lat,
|
||||||
lon: weatherMetadata?.[0].lon ?? currentLocation.lon,
|
lon: weatherMetadata?.[0].lon ?? currentLocation.lon,
|
||||||
});
|
});
|
||||||
const { data: basicWeather } = useWeatherAPI({
|
|
||||||
endpoint: 'baseline',
|
|
||||||
office: points?.properties.gridId ?? currentLocation.office,
|
|
||||||
x: points?.properties.gridX ?? currentLocation.x,
|
|
||||||
y: points?.properties.gridY ?? currentLocation.y,
|
|
||||||
});
|
|
||||||
const { data: stations } = useWeatherAPI({
|
|
||||||
endpoint: 'stations',
|
|
||||||
office: points?.properties.gridId ?? currentLocation.office,
|
|
||||||
x: points?.properties.gridX ?? currentLocation.x,
|
|
||||||
y: points?.properties.gridY ?? currentLocation.y,
|
|
||||||
});
|
|
||||||
const { data: weather } = useWeatherAPI({
|
|
||||||
endpoint: 'current',
|
|
||||||
station:
|
|
||||||
stations?.features[0].properties.stationIdentifier ?? currentLocation.station,
|
|
||||||
});
|
|
||||||
const { data: forecast } = useWeatherAPI({
|
|
||||||
endpoint: 'forecast',
|
|
||||||
office: points?.properties.gridId ?? currentLocation.office,
|
|
||||||
x: points?.properties.gridX ?? currentLocation.x,
|
|
||||||
y: points?.properties.gridY ?? currentLocation.y,
|
|
||||||
});
|
|
||||||
|
|
||||||
const personal: PersonalData = personalData?.[0];
|
const personal: PersonalData = personalData?.[0];
|
||||||
const message: Message = messageData?.[0] || {};
|
const message: Message = messageData?.[0] || {};
|
||||||
const [displayedGreeting, setDisplayedGreeting] = useState<string | undefined>('');
|
const [displayedGreeting, setDisplayedGreeting] = useState<string | undefined>('');
|
||||||
const [current, setCurrent] = useState<CW>({
|
|
||||||
icon: '',
|
const [meteoCurrent, setMeteoCurrent] = useState<MeteoWeather>();
|
||||||
textDescription: '',
|
|
||||||
temperature: 0,
|
|
||||||
highTemp: 0,
|
|
||||||
lowTemp: 0,
|
|
||||||
precipitation: 'N/A',
|
|
||||||
humidity: 0,
|
|
||||||
heatIndex: null,
|
|
||||||
windChill: null,
|
|
||||||
windSpeed: 0,
|
|
||||||
windDirection: 0,
|
|
||||||
pressure: 0,
|
|
||||||
});
|
|
||||||
const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] =
|
const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] =
|
||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
const extendedForecast = forecast?.properties?.periods.map(
|
|
||||||
(item: ExtendedForecast) => ({
|
|
||||||
detailedForecast: item.detailedForecast,
|
|
||||||
isDaytime: item.isDaytime,
|
|
||||||
name: item.name,
|
|
||||||
shortForecast: item.shortForecast,
|
|
||||||
temperature: item.temperature,
|
|
||||||
temperatureUnit: item.temperatureUnit,
|
|
||||||
}),
|
|
||||||
) || [
|
|
||||||
{
|
|
||||||
detailedForecast: '',
|
|
||||||
isDaytime: true,
|
|
||||||
name: '',
|
|
||||||
shortForecast: '',
|
|
||||||
temperature: 0,
|
|
||||||
temperatureUnit: 'F',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
|
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
|
||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
|
|
||||||
@@ -142,59 +83,16 @@ export function Homepage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [message, personal]);
|
}, [message, personal]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (weather !== undefined && basicWeather !== undefined) {
|
if (meteoWeather) {
|
||||||
setCurrent({
|
setMeteoCurrent(meteoWeather);
|
||||||
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '',
|
|
||||||
textDescription: weather.features[0]?.properties.textDescription,
|
|
||||||
temperature: Math.round(
|
|
||||||
(weather.features?.find((feature: any) => feature.properties.temperature.value)
|
|
||||||
.properties.temperature.value *
|
|
||||||
9) /
|
|
||||||
5 +
|
|
||||||
32,
|
|
||||||
),
|
|
||||||
highTemp: Math.round(
|
|
||||||
(basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32,
|
|
||||||
),
|
|
||||||
lowTemp: Math.round(
|
|
||||||
(basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
|
|
||||||
),
|
|
||||||
precipitation:
|
|
||||||
weather.features[0]?.properties?.precipitationLastHour?.value ?? 'N/A',
|
|
||||||
humidity: Math.round(weather.features[0]?.properties?.relativeHumidity?.value),
|
|
||||||
heatIndex:
|
|
||||||
basicWeather.properties.heatIndex?.values[0].value === null
|
|
||||||
? null
|
|
||||||
: Math.round(
|
|
||||||
(basicWeather.properties.heatIndex.values[0].value * 9) / 5 + 32,
|
|
||||||
),
|
|
||||||
windChill:
|
|
||||||
basicWeather.properties.windChill?.values[0].value === null
|
|
||||||
? null
|
|
||||||
: Math.round(
|
|
||||||
(basicWeather.properties.windChill.values[0].value * 9) / 5 + 32,
|
|
||||||
),
|
|
||||||
windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934),
|
|
||||||
windDirection: weather.features[0]?.properties.windDirection.value,
|
|
||||||
pressure: Math.round(
|
|
||||||
weather.features[0]?.properties.barometricPressure.value / 3386.38,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [weather, basicWeather]);
|
|
||||||
useEffect(() => {
|
|
||||||
if (forecast) {
|
|
||||||
setCurrentLocation({
|
setCurrentLocation({
|
||||||
office: points?.properties.gridId,
|
|
||||||
station: stations?.features[0].properties.stationIdentifier,
|
|
||||||
x: points?.properties.gridX,
|
|
||||||
y: points?.properties.gridY,
|
|
||||||
lat: weatherMetadata?.[0].lat,
|
lat: weatherMetadata?.[0].lat,
|
||||||
lon: weatherMetadata?.[0].lon,
|
lon: weatherMetadata?.[0].lon,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [forecast]);
|
}, [meteoWeather]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
@@ -206,8 +104,7 @@ export function Homepage() {
|
|||||||
{appFilter.length > 0 || bookmarkFilter.length > 0 ? (
|
{appFilter.length > 0 || bookmarkFilter.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<Search
|
<Search
|
||||||
current={current}
|
current={meteoCurrent}
|
||||||
extendedForecast={extendedForecast}
|
|
||||||
weatherModalOpened={weatherModalOpened}
|
weatherModalOpened={weatherModalOpened}
|
||||||
closeWeatherModal={closeWeatherModal}
|
closeWeatherModal={closeWeatherModal}
|
||||||
openWeatherModal={openWeatherModal}
|
openWeatherModal={openWeatherModal}
|
||||||
@@ -220,7 +117,7 @@ export function Homepage() {
|
|||||||
<Header
|
<Header
|
||||||
greet={displayedGreeting}
|
greet={displayedGreeting}
|
||||||
date={date}
|
date={date}
|
||||||
current={current}
|
current={meteoCurrent}
|
||||||
openWeatherModal={openWeatherModal}
|
openWeatherModal={openWeatherModal}
|
||||||
/>
|
/>
|
||||||
<Applications contentType={appFilter} />
|
<Applications contentType={appFilter} />
|
||||||
@@ -231,12 +128,11 @@ export function Homepage() {
|
|||||||
<Stack align='center'>
|
<Stack align='center'>
|
||||||
<LargeHeader greet={displayedGreeting} date={date} />
|
<LargeHeader greet={displayedGreeting} date={date} />
|
||||||
<Grid grow columns={24} pos={'absolute'} bottom={'1rem'}>
|
<Grid grow columns={24} pos={'absolute'} bottom={'1rem'}>
|
||||||
<CurrentWeather current={current} />
|
{meteoCurrent && <CurrentWeather current={meteoCurrent} />}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Search
|
<Search
|
||||||
current={current}
|
current={meteoCurrent}
|
||||||
extendedForecast={extendedForecast}
|
|
||||||
weatherModalOpened={weatherModalOpened}
|
weatherModalOpened={weatherModalOpened}
|
||||||
closeWeatherModal={closeWeatherModal}
|
closeWeatherModal={closeWeatherModal}
|
||||||
openWeatherModal={openWeatherModal}
|
openWeatherModal={openWeatherModal}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Group, Stack, Text } from '@mantine/core';
|
|||||||
import { useCommonHooks } from 'hooks';
|
import { useCommonHooks } from 'hooks';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { textClasses } from 'styles';
|
import { textClasses } from 'styles';
|
||||||
import type { CurrentWeather } from 'types';
|
import type { MeteoWeather } from 'types';
|
||||||
|
|
||||||
export function Header({
|
export function Header({
|
||||||
greet,
|
greet,
|
||||||
@@ -12,7 +12,7 @@ export function Header({
|
|||||||
}: {
|
}: {
|
||||||
greet: string | undefined;
|
greet: string | undefined;
|
||||||
date: string;
|
date: string;
|
||||||
current: CurrentWeather;
|
current: MeteoWeather | undefined;
|
||||||
openWeatherModal: () => void;
|
openWeatherModal: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [time, setTime] = useState(new Date());
|
const [time, setTime] = useState(new Date());
|
||||||
@@ -101,7 +101,7 @@ export function Header({
|
|||||||
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
|
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{current.temperature}°F
|
{current?.current.temperature_2m?.toFixed(0)}°F
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
className={textClasses.dateTime}
|
className={textClasses.dateTime}
|
||||||
@@ -112,7 +112,8 @@ export function Header({
|
|||||||
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
|
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{current.highTemp}°F/{current.lowTemp}°F
|
{current?.daily.temperature_2m_max?.[0].toFixed(0)}°F/
|
||||||
|
{current?.daily.temperature_2m_min?.[0].toFixed(0)}°F
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ import { selectClasses } from 'styles';
|
|||||||
import type {
|
import type {
|
||||||
Application,
|
Application,
|
||||||
Bookmark,
|
Bookmark,
|
||||||
CurrentWeather,
|
|
||||||
ExtendedForecast,
|
|
||||||
GuacamoleClient,
|
GuacamoleClient,
|
||||||
|
MeteoWeather,
|
||||||
PersonalData,
|
PersonalData,
|
||||||
Provider,
|
Provider,
|
||||||
SearchHistoryItem,
|
SearchHistoryItem,
|
||||||
@@ -32,7 +31,6 @@ import RenderItem from './components/RenderItem';
|
|||||||
|
|
||||||
export function Search({
|
export function Search({
|
||||||
current,
|
current,
|
||||||
extendedForecast,
|
|
||||||
weatherModalOpened,
|
weatherModalOpened,
|
||||||
closeWeatherModal,
|
closeWeatherModal,
|
||||||
openWeatherModal,
|
openWeatherModal,
|
||||||
@@ -42,8 +40,7 @@ export function Search({
|
|||||||
openProviderModal,
|
openProviderModal,
|
||||||
closeProviderModal,
|
closeProviderModal,
|
||||||
}: {
|
}: {
|
||||||
current: CurrentWeather;
|
current: MeteoWeather | undefined;
|
||||||
extendedForecast: ExtendedForecast[];
|
|
||||||
weatherModalOpened: boolean;
|
weatherModalOpened: boolean;
|
||||||
closeWeatherModal: () => void;
|
closeWeatherModal: () => void;
|
||||||
openWeatherModal: () => void;
|
openWeatherModal: () => void;
|
||||||
@@ -647,7 +644,6 @@ export function Search({
|
|||||||
{weatherModalOpened && (
|
{weatherModalOpened && (
|
||||||
<WeatherModal
|
<WeatherModal
|
||||||
current={current}
|
current={current}
|
||||||
extendedForecast={extendedForecast}
|
|
||||||
weatherModalOpened={weatherModalOpened}
|
weatherModalOpened={weatherModalOpened}
|
||||||
handleResetSearch={handleResetSearch}
|
handleResetSearch={handleResetSearch}
|
||||||
closeWeatherModal={closeWeatherModal}
|
closeWeatherModal={closeWeatherModal}
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ export type { Theme, ThemePlaylist } from './theme';
|
|||||||
export { ThemeProperties } from './theme';
|
export { ThemeProperties } from './theme';
|
||||||
export type { User } from './user';
|
export type { User } from './user';
|
||||||
export type {
|
export type {
|
||||||
CurrentWeather,
|
Daily,
|
||||||
ExtendedForecast,
|
Hourly,
|
||||||
HourlyForecast,
|
MeteoWeather,
|
||||||
OutlookWeather,
|
|
||||||
SunState,
|
SunState,
|
||||||
} from './weather';
|
} from './weather';
|
||||||
export type { WolClient } from './wolClient';
|
export type { WolClient } from './wolClient';
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
export interface Location {
|
export interface Location {
|
||||||
office: string;
|
|
||||||
station: string;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
lat: number;
|
lat: number;
|
||||||
lon: number;
|
lon: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,83 @@
|
|||||||
export interface CurrentWeather {
|
|
||||||
icon: string;
|
|
||||||
textDescription: string;
|
|
||||||
temperature: number;
|
|
||||||
highTemp: number;
|
|
||||||
lowTemp: number;
|
|
||||||
precipitation: string;
|
|
||||||
humidity: number;
|
|
||||||
heatIndex: number | null;
|
|
||||||
windChill: number | null;
|
|
||||||
windSpeed: number;
|
|
||||||
windDirection: number;
|
|
||||||
pressure: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OutlookWeather {
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
isDayTime: boolean;
|
|
||||||
temperature: number;
|
|
||||||
precipitation: number;
|
|
||||||
windSpeed: string;
|
|
||||||
windDirection: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExtendedForecast {
|
|
||||||
detailedForecast: string;
|
|
||||||
isDaytime: boolean;
|
|
||||||
name: string;
|
|
||||||
shortForecast: string;
|
|
||||||
temperature: number;
|
|
||||||
temperatureUnit: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HourlyForecast {
|
|
||||||
detailedForecast: string;
|
|
||||||
dewpoint: DewPoint;
|
|
||||||
endTime: string;
|
|
||||||
icon: string;
|
|
||||||
isDaytime: boolean;
|
|
||||||
name: string;
|
|
||||||
number: number;
|
|
||||||
probabilityOfPrecipitation: ProbabilityOfPrecipitation;
|
|
||||||
relativeHumidity: RelativeHumidity;
|
|
||||||
shortForecast: string;
|
|
||||||
startTime: string;
|
|
||||||
temperature: number;
|
|
||||||
temperatureTrend: string;
|
|
||||||
temperatureUnit: string;
|
|
||||||
windDirection: string;
|
|
||||||
windSpeed: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProbabilityOfPrecipitation {
|
|
||||||
unitCode: string;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RelativeHumidity {
|
|
||||||
unitCode: string;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DewPoint {
|
|
||||||
unitCode: string;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SunState {
|
export interface SunState {
|
||||||
date: string;
|
date: string;
|
||||||
sunrise: string;
|
sunrise: string;
|
||||||
sunset: string;
|
sunset: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MeteoWeather {
|
||||||
|
current: Current;
|
||||||
|
hourly: Hourly;
|
||||||
|
daily: Daily;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Current {
|
||||||
|
time: Date;
|
||||||
|
temperature_2m: number | undefined;
|
||||||
|
relative_humidity_2m: number | undefined;
|
||||||
|
apparent_temperature: number | undefined;
|
||||||
|
is_day: number | undefined;
|
||||||
|
wind_speed_10m: number | undefined;
|
||||||
|
wind_direction_10m: number | undefined;
|
||||||
|
wind_gusts_10m: number | undefined;
|
||||||
|
precipitation: number | undefined;
|
||||||
|
rain: number | undefined;
|
||||||
|
showers: number | undefined;
|
||||||
|
snowfall: number | undefined;
|
||||||
|
surface_pressure: number | undefined;
|
||||||
|
pressure_msl: number | undefined;
|
||||||
|
cloud_cover: number | undefined;
|
||||||
|
weather_code: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Hourly {
|
||||||
|
apparent_temperature: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
cloud_cover: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
cloud_cover_high: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
cloud_cover_mid: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
cloud_cover_low: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
dew_point_2m: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
et0_fao_evapotranspiration: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
evapotranspiration: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
is_day: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
precipitation: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
precipitation_probability: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
pressure_msl: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
rain: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
relative_humidity_2m: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
showers: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
snow_depth: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
snowfall: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
sunshine_duration: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
surface_pressure: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
temperature_2m: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
time: Date[];
|
||||||
|
uv_index_clear_sky: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
uv_index: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
vapour_pressure_deficit: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
visibility: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
weather_code: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
wind_direction_10m: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
wind_gusts_10m: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
wind_speed_10m: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Daily {
|
||||||
|
apparent_temperature_max: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
apparent_temperature_min: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
daylight_duration: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
precipitation_hours: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
precipitation_probability_max: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
precipitation_sum: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
sunrise: Date[];
|
||||||
|
sunset: Date[];
|
||||||
|
sunshine_duration: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
temperature_2m_max: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
temperature_2m_min: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
time: Date[];
|
||||||
|
uv_index_clear_sky_max: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
uv_index_max: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
weather_code: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
wind_direction_10m_dominant: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
wind_gusts_10m_max: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
wind_speed_10m_max: Float32Array<ArrayBufferLike> | null | undefined;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
"target": "es2023",
|
|
||||||
"lib": ["ES2023", "DOM"],
|
|
||||||
"module": "esnext",
|
|
||||||
"types": ["vite/client"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"composite": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": false,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": false,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"erasableSyntaxOnly": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2022",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "nodenext",
|
"module": "ES2022",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"rootDir": "./src",
|
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"],
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"types": ["vite/client", "vitest"],
|
"types": ["vite/client", "vitest"],
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
@@ -25,17 +24,13 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"components": ["./components/index.ts"],
|
"components": ["./src/components/index.ts"],
|
||||||
"contexts": ["./contexts/index.ts"],
|
"contexts": ["./src/contexts/index.ts"],
|
||||||
"hooks": ["./hooks/index.ts"],
|
"hooks": ["./src/hooks/index.ts"],
|
||||||
"pages": ["./pages/index.ts"],
|
"pages": ["./src/pages/index.ts"],
|
||||||
"styles": ["./styles/index.ts"],
|
"styles": ["./src/styles/index.ts"],
|
||||||
"types": ["./types/index.ts"]
|
"types": ["./src/types/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"references": [
|
|
||||||
{ "path": "./tsconfig.node.json" },
|
|
||||||
{ "path": "./tsconfig.app.json" }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
||||||
"target": "es2023",
|
|
||||||
"lib": ["ES2023"],
|
|
||||||
"module": "esnext",
|
|
||||||
"types": ["node"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"composite": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": false,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": false,
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"erasableSyntaxOnly": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user