feat: add weekly forecast to weather widget (#1932)

This commit is contained in:
Yossi Hillali
2024-03-09 17:57:16 +02:00
committed by GitHub
parent 0906420584
commit 918585c3e2
3 changed files with 122 additions and 60 deletions

View File

@@ -10,6 +10,12 @@
"displayCityName":{
"label":"Display City Name"
},
"displayWeekly":{
"label": "Display Weekly Forecast"
},
"forecastDays":{
"label": "Days To Display"
},
"location": {
"label": "Weather location"
}

View File

@@ -18,6 +18,8 @@ const weatherSchema = z.object({
temperature: z.number(),
}),
daily: z.object({
time: z.array(z.string()),
weathercode: z.array(z.number()),
temperature_2m_max: z.array(z.number()),
temperature_2m_min: z.array(z.number()),
}),

View File

@@ -1,4 +1,4 @@
import { Center, Flex, Group, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Card, Center, Flex, Group, Stack, Text, Title } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import {
IconArrowDownRight,
@@ -7,9 +7,11 @@ import {
IconMapPin,
} from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { Weather } from '~/server/api/routers/weather';
import { api } from '~/utils/api';
import { defineWidget } from '../helper';
import { WidgetLoading } from '../loading';
import { IWidget } from '../widgets';
import { WeatherIcon } from './WeatherIcon';
@@ -25,6 +27,17 @@ const definition = defineWidget({
type: 'switch',
defaultValue: false,
},
displayWeekly: {
type: 'switch',
defaultValue: false,
},
forecastDays: {
type: 'slider',
defaultValue: 5,
min: 1,
max: 7,
step: 1,
},
location: {
type: 'location',
defaultValue: {
@@ -55,24 +68,7 @@ function WeatherTile({ widget }: WeatherTileProps) {
const { t } = useTranslation('modules/weather');
if (isLoading) {
return (
<Stack
ref={ref}
spacing="xs"
justify="space-around"
align="center"
style={{ height: '100%', width: '100%' }}
>
<Skeleton height={40} width={100} mb="xl" />
<Group noWrap>
<Skeleton height={50} circle />
<Group>
<Skeleton height={25} width={70} mr="lg" />
<Skeleton height={25} width={70} />
</Group>
</Group>
</Stack>
);
return <WidgetLoading />;
}
if (isError) {
@@ -83,56 +79,114 @@ function WeatherTile({ widget }: WeatherTileProps) {
);
}
// TODO: add widgetWrapper that is generic and uses the definition
return (
<Stack
style={{ height: '100%', width: '100%' }}
justify="space-around"
ref={ref}
spacing={0}
align="center"
>
<Flex
align="center"
gap={width < 120 ? '0.25rem' : 'xs'}
justify={'center'}
direction={width < 200 ? 'column' : 'row'}
>
<WeatherIcon size={width < 300 ? 30 : 50} code={weather.current_weather.weathercode} />
<Title size={'h2'}>
{getPerferedUnit(
weather.current_weather.temperature,
widget.properties.displayInFahrenheit
)}
</Title>
</Flex>
<Stack w="100%" h="100%" justify="space-around" ref={ref} spacing={0} align="center">
{(widget?.properties.displayWeekly && (
<>
<Flex
align="center"
gap={width < 120 ? '0.25rem' : 'xs'}
justify={'center'}
direction={'row'}
>
{widget.properties.displayCityName && (
<Group noWrap spacing={5} align="center">
<IconMapPin color="blue" size={30} />
<Text size={25} style={{ whiteSpace: 'nowrap' }}>
{widget.properties.location.name}
</Text>
</Group>
)}
<WeatherIcon size={width < 300 ? 30 : 50} code={weather.current_weather.weathercode} />
<Title size={'h2'} color={weather.current_weather.temperature > 20 ? 'red' : 'blue'}>
{getPreferredUnit(
weather.current_weather.temperature,
widget.properties.displayInFahrenheit
)}
</Title>
</Flex>
<Forecast weather={weather} widget={widget} />
</>
)) || (
<>
<Flex
align="center"
gap={width < 120 ? '0.25rem' : 'xs'}
justify={'center'}
direction={width < 200 ? 'column' : 'row'}
>
<WeatherIcon size={width < 300 ? 30 : 50} code={weather.current_weather.weathercode} />
<Title size={'h2'}>
{getPreferredUnit(
weather.current_weather.temperature,
widget.properties.displayInFahrenheit
)}
</Title>
</Flex>
{width > 200 && (
<Group noWrap spacing="xs">
<IconArrowUpRight />
{getPerferedUnit(
weather.daily.temperature_2m_max[0],
widget.properties.displayInFahrenheit
{width > 200 && (
<Group noWrap spacing="xs">
<IconArrowUpRight />
{getPreferredUnit(
weather.daily.temperature_2m_max[0],
widget.properties.displayInFahrenheit
)}
<IconArrowDownRight />
{getPreferredUnit(
weather.daily.temperature_2m_min[0],
widget.properties.displayInFahrenheit
)}
</Group>
)}
<IconArrowDownRight />
{getPerferedUnit(
weather.daily.temperature_2m_min[0],
widget.properties.displayInFahrenheit
)}
</Group>
)}
{widget.properties.displayCityName && (
<Group noWrap spacing={5} align="center">
<IconMapPin height={15} width={15} />
<Text style={{ whiteSpace: 'nowrap' }}>{widget.properties.location.name}</Text>
</Group>
{widget.properties.displayCityName && (
<Group noWrap spacing={5} align="center">
<IconMapPin height={15} width={15} />
<Text style={{ whiteSpace: 'nowrap' }}>{widget.properties.location.name}</Text>
</Group>
)}
</>
)}
</Stack>
);
}
const getPerferedUnit = (value: number, isFahrenheit = false): string =>
const getPreferredUnit = (value: number, isFahrenheit = false): string =>
isFahrenheit ? `${(value * (9 / 5) + 32).toFixed(1)}°F` : `${value.toFixed(1)}°C`;
interface ForecastProps {
weather: Weather;
widget: IWeatherWidget;
}
function Forecast({ weather: { daily }, widget }: ForecastProps) {
const { width } = useElementSize();
return (
<Flex align="center" direction="row" justify="space-between" w="100%" px="sm">
{daily.time.slice(0, widget.properties.forecastDays).map((time: any, index: number) => (
<Card key={index} padding="0.25rem">
<Flex direction="column" align="center">
<Text fw={700} lh="1.25rem">
{time.split('-')[2]}
</Text>
<WeatherIcon size={width < 300 ? 30 : 50} code={daily.weathercode[index]} />
<Text fz="sm" lh="1rem">
{getPreferredUnit(
daily.temperature_2m_max[index],
widget.properties.displayInFahrenheit
)}
</Text>
<Text fz="sm" lh="1rem" color="grey">
{getPreferredUnit(
daily.temperature_2m_min[index],
widget.properties.displayInFahrenheit
)}
</Text>
</Flex>
</Card>
))}
</Flex>
);
}
export default definition;