-
Notifications
You must be signed in to change notification settings - Fork 36
Stephen Swaringin - Redux Eval #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a8b0f90
4646662
b9ba67d
d6a59d1
2a61423
8b37cec
428663c
a890d1a
1174d8e
a864956
f3d2702
9a516da
4ed8a97
fb25412
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { Sparklines, SparklinesLine, SparklinesReferenceLine, SparklinesSpots } from 'react-sparklines'; | ||
|
|
||
| export function NewSparkline({ data, color = 'grey', lineType = 'avg' }) { | ||
| return ( | ||
| <Sparklines data={data} width={100} height={30}> | ||
| <SparklinesLine style={{stroke: color, strokeWidth: ".75", fill: color, fillOpacity: ".25"}}/> | ||
| <SparklinesReferenceLine type={lineType}/> | ||
| {/* <SparklinesSpots size={.75} style={{stroke: color, fill: color}}/> */} | ||
| </Sparklines> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import React, { useState } from 'react'; | ||
| import { useDispatch } from 'react-redux'; | ||
| import { fetchForecast } from '../store/slices/cities'; | ||
| import { useForm } from 'react-hook-form'; | ||
| import { yupResolver } from '@hookform/resolvers/yup'; | ||
| import * as yup from 'yup'; | ||
|
|
||
| export function WeatherSearch() { | ||
| const schema = yup.object({ // using yup to help build a schema for react-hook-form | ||
| search: yup | ||
| .string() | ||
| .required('A city name is required to search.') | ||
| .test('already-searched', 'This city has already been searched.', (value) => { | ||
| return !searches.includes(value?.toLowerCase()); | ||
| }) | ||
| }); | ||
| const dispatch = useDispatch(); | ||
| const [search, setSearch] = useState(''); // Keeps track of the current search | ||
| const [searches, setSearches] = useState([]); // Keeps track of previous search values | ||
|
|
||
| const { // using react-hook-form for form validation | ||
| register, | ||
| handleSubmit, | ||
| formState: { errors }, | ||
| } = useForm({ resolver: yupResolver(schema), }); | ||
|
|
||
| const handleFormSubmit = async () => { | ||
| setSearches([...searches, search.toLowerCase()]); // Adds search to previous searches | ||
|
|
||
| try { | ||
| dispatch(fetchForecast(search)); // Dispatch asyncThunk in cities slice | ||
| } catch (error) { | ||
| console.error(error); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesn't help in terms of UX, alert or showing errors is better |
||
| } | ||
| setSearch(''); // Clear the input field after each search | ||
| }; | ||
|
|
||
| return ( | ||
| <div> | ||
| <form onSubmit={handleSubmit(handleFormSubmit)} className='d-flex justify-content-center'> | ||
| <div className='form-group col-8 d-flex'> | ||
| <input | ||
| {...register('search')} | ||
| value={search} | ||
| className='form-control' | ||
| placeholder='Get a five-day forecast in your favorite cities' | ||
| onChange={event => setSearch(event.target.value)} | ||
| /> | ||
| <button | ||
| className='btn btn-secondary' | ||
| type='submit' | ||
| > | ||
| Submit | ||
| </button> | ||
| </div> | ||
| </form> | ||
| <br/> | ||
| <div className='d-flex justify-content-center'> | ||
| {errors.search?.message && ( | ||
| <div className='text-danger'>{errors.search?.message}</div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| 'use client' | ||
| import { WeatherTableRow } from './WeatherTableRow'; | ||
| import { useSelector } from 'react-redux'; | ||
|
|
||
| /** | ||
| * This compenent renders a table for weather data. | ||
| * Includes columns for city name, temp, pressure, and humidity. | ||
| * @returns {ReactNode} A React element that renders a table to display the weather. | ||
| */ | ||
| export function WeatherTable() { | ||
| const cities = useSelector((state) => state.cities.cities); | ||
|
|
||
| return ( | ||
| <div className='container'> | ||
| <div className='col-12 text-center'> | ||
| <table className='table'> | ||
| <thead className='table-dark'> | ||
| <tr> | ||
| <th>City</th> | ||
| <th>Temperature (F)</th> | ||
| <th>Pressure (hPa)</th> | ||
| <th>Humidity (%)</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {cities.map(cityObj => (<WeatherTableRow city={cityObj} key={cityObj.city.id}/>))} | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { getItemsPerDay, getAvgItemsPerDay, avg, convertKToF, getHighsAndLows } from './functions'; | ||
| import { NewSparkline } from './NewSparkline'; | ||
|
|
||
| /** | ||
| * This component renders a table row with the specific weather data for a searched city. | ||
| * @param {Object} city | ||
| * @returns {ReactElement} | ||
| */ | ||
| export function WeatherTableRow({ city }) { | ||
| /** Gets an array of only the temp data */ | ||
| const temps = city.list.map(weather => weather.main.temp); | ||
| /** Groups temps by day */ | ||
| const allTempsPerDay = getItemsPerDay(temps); | ||
| /** Gets the highs and lows per day and converts them to Fahrenheit*/ | ||
| const highAndLowTemps = getHighsAndLows(allTempsPerDay).map(day => convertKToF(day)); | ||
| /** Gets the average temp per day and converts them to Fahrenheit*/ | ||
| const avgTempsPerDay = getAvgItemsPerDay(allTempsPerDay).map(day => convertKToF(day)); | ||
|
|
||
| /** Gets an array of only pressure data */ | ||
| const pressures = city.list.map(weather => weather.main.pressure); | ||
| /** Groups pressures by day */ | ||
| const allPressuresPerDay = getItemsPerDay(pressures); | ||
| /** Gets the high and low pressures for each day */ | ||
| const highAndLowPressures = getHighsAndLows(allPressuresPerDay); | ||
| /** Gets the average pressure per day */ | ||
| const avgPressuresPerDay = getAvgItemsPerDay(allPressuresPerDay) | ||
|
|
||
| /** Gets an array of only humidity data */ | ||
| const humidities = city.list.map(weather => weather.main.humidity); | ||
| /** Groups humidity by day */ | ||
| const allHumiditiesPerDay = getItemsPerDay(humidities); | ||
| /** Gets the high and low humidity for each day */ | ||
| const highAndLowHumidities = getHighsAndLows(allHumiditiesPerDay); | ||
| /** Gets the average humidity per day */ | ||
| const avgHumiditiesPerDay = getAvgItemsPerDay(allHumiditiesPerDay); | ||
|
|
||
| return ( | ||
| <tr> | ||
| <td>{city.city.name}, {city.city.country}</td> | ||
| <td> | ||
| <NewSparkline data={highAndLowTemps} color='orange'/> | ||
| <div>Avg: {avg(avgTempsPerDay)} F</div> | ||
| </td> | ||
| <td> | ||
| <NewSparkline data={highAndLowPressures} color='green'/> | ||
| <div>Avg: {avg(avgPressuresPerDay)} hPa</div> | ||
| </td> | ||
| <td> | ||
| <NewSparkline data={highAndLowHumidities} color='blue'/> | ||
| <div>Avg: {avg(avgHumiditiesPerDay)} %</div> | ||
|
Comment on lines
+42
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you are doing all the average, why here calling avg again? |
||
| </td> | ||
| </tr> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /** | ||
| * Takes an array of 40 data points and groups them into 5 sub-arrays | ||
| * Each sub-array represents a day. | ||
| * @param {Array} arr | ||
| * @returns {Array} | ||
| */ | ||
| export const getItemsPerDay = (arr) => { | ||
| const chunk = 8; | ||
|
|
||
| const groups = arr.reduce((result, item, index) => { | ||
| if (index % chunk === 0) { | ||
| result.push(arr.slice(index, index + chunk)); | ||
| } | ||
| return result; | ||
| }, []); | ||
| return groups; | ||
| }; | ||
|
|
||
| /** | ||
| * Iterates through the data points grouped by day and returns the average of each group | ||
| * @param {Array} arr | ||
| * @returns {Array} | ||
| */ | ||
| export const getAvgItemsPerDay = (arr) => { | ||
| return arr.reduce((result, item) => { | ||
| result.push(avg(item)); | ||
| return result; | ||
| }, []); | ||
| }; | ||
|
|
||
| /** | ||
| * Returns an average of a supplied array of numbers | ||
| * @param {Array} arr | ||
| * @returns {Number} | ||
| */ | ||
| export const avg = (arr) => { | ||
| return Math.round(arr.reduce((result, item) => result + item, 0 ) / arr.length); | ||
| }; | ||
|
|
||
| /** | ||
| * Assuming that the passed value is in Kelvin, this function will convert it to Fahrenheit. | ||
| * @param {Number} temp | ||
| * @returns {Number} | ||
| */ | ||
| export const convertKToF = (temp) => { | ||
| return Math.round(1.8 * (temp - 273) + 32); | ||
| }; | ||
|
|
||
| /** | ||
| * Iterates through the data points grouped by day and returns the high and low for each day | ||
| * @param {Array} arr | ||
| * @returns {Array} | ||
| */ | ||
| export const getHighsAndLows = (arr) => { | ||
| const newArr = [] | ||
|
|
||
| arr.forEach(subarr => { | ||
|
|
||
| const high = subarr.reduce((acc, cur) => { | ||
| return acc > cur ? acc : cur; | ||
| }) | ||
|
|
||
| const low = subarr.reduce((acc, cur) => { | ||
| return acc <= cur ? acc : cur; | ||
| }) | ||
|
|
||
| newArr.push(high, low); | ||
| }) | ||
|
|
||
| return newArr; | ||
| }; |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,14 @@ | ||
| import './globals.css' | ||
| import { Inter } from 'next/font/google' | ||
|
|
||
| const inter = Inter({ subsets: ['latin'] }) | ||
|
|
||
| export const metadata = { | ||
| title: 'Create Next App', | ||
| description: 'Generated by create next app', | ||
| } | ||
| 'use client' | ||
| import 'bootstrap/dist/css/bootstrap.min.css'; | ||
| import { Provider } from 'react-redux'; | ||
| import store from './store/configureStore'; | ||
|
|
||
| export default function RootLayout({ children }) { | ||
| return ( | ||
| <html lang="en"> | ||
| <body className={inter.className}>{children}</body> | ||
| <body> | ||
| <Provider store={store}>{children}</Provider> | ||
| </body> | ||
| </html> | ||
| ) | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove commented code