From f28259ef059c4bbdf2a1ac22335dae13ff0f5b1b Mon Sep 17 00:00:00 2001 From: Yadurani Lopez Date: Tue, 1 Mar 2022 16:52:05 -0500 Subject: [PATCH 1/8] feat: add loadings and errors --- src/app/ListData/selectors.js | 10 ++ src/app/ListData/slice.js | 12 ++ src/components/Filter/Filter.jsx | 215 +++++++++++++++++-------------- 3 files changed, 142 insertions(+), 95 deletions(-) diff --git a/src/app/ListData/selectors.js b/src/app/ListData/selectors.js index b22bfe5..0d365c9 100644 --- a/src/app/ListData/selectors.js +++ b/src/app/ListData/selectors.js @@ -29,3 +29,13 @@ export const selectAllList = createSelector( (state) => state.ListData.list, (all) => all, ); + +export const selectLoading = createSelector( + (state) => state.ListData.loading, + (load) => load, +); + +export const selectError = createSelector( + (state) => state.ListData.error, + (error) => error, +); diff --git a/src/app/ListData/slice.js b/src/app/ListData/slice.js index 7f8a58f..f490a0b 100644 --- a/src/app/ListData/slice.js +++ b/src/app/ListData/slice.js @@ -29,10 +29,22 @@ const dataSlice = createSlice({ TypeWork: [], Locations: [], }, + loading: false, + error: null, }, extraReducers: { [fetchListData.fulfilled]: (state, action) => { state.list = action.payload; + state.loading = false; + state.error = null; + }, + [fetchListData.pending]: (state) => { + state.loading = true; + state.error = null; + }, + [fetchListData.rejected]: (state) => { + state.loading = false; + state.error = 'Ups! There is an error'; }, }, }); diff --git a/src/components/Filter/Filter.jsx b/src/components/Filter/Filter.jsx index 2823d4c..99bb59f 100644 --- a/src/components/Filter/Filter.jsx +++ b/src/components/Filter/Filter.jsx @@ -12,8 +12,9 @@ import TextField from '@mui/material/TextField'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import FormControl from '@mui/material/FormControl'; +import Skeleton from '@mui/material/Skeleton'; -import { selectAllList } from 'App/ListData/selectors'; +import { selectAllList, selectLoading, selectError } from 'App/ListData/selectors'; import { changeFilter, resetFilters } from 'App/Filters/slice'; const defaultValues = { @@ -24,7 +25,7 @@ const defaultValues = { company: null, }; -const Filter = ({ list, setFilters, resetFilter }) => { +const Filter = ({ list, setFilters, resetFilter, loading, error }) => { const { register, handleSubmit, formState, control, getValues, reset } = useForm({ defaultValues, }); @@ -50,103 +51,119 @@ const Filter = ({ list, setFilters, resetFilter }) => { return ( - - - Filters Offers + {error && ( + + Not found filters 😒 - ( - - )} - /> - data} - control={control} - defaultValue={{ id: '', name: '' }} - render={({ field: { onChange, ...field } }) => ( - onChange(data)} - disableClearable - options={list.Companies} - getOptionLabel={(option) => option?.name} - renderInput={(params) => } + )} + {loading && !error && ( + + + + + + )} + {!loading && ( + + + Filters Offers + + ( + + )} + /> + data} + control={control} + defaultValue={{ id: '', name: '' }} + render={({ field: { onChange, ...field } }) => ( + onChange(data)} + disableClearable + options={list.Companies} + getOptionLabel={(option) => option?.name} + renderInput={(params) => } + /> + )} + /> + data} + defaultValue="" + render={({ field: { value, onChange, ...field } }) => ( + onChange(data)} + freeSolo + sx={{ mt: 1 }} + disableClearable + options={list.Locations?.map((location) => location.job_location)} + renderInput={(params) => ( + + )} + /> + )} + /> + + + Number(getValues('max_salary')) + ? Number(getValues('max_salary')) >= Number(getValues('min_salary')) + : true, + })} /> - )} - /> - data} - defaultValue="" - render={({ field: { value, onChange, ...field } }) => ( - onChange(data)} - freeSolo - sx={{ mt: 1 }} - disableClearable - options={list.Locations?.map((location) => location.job_location)} - renderInput={(params) => } + + !Number(getValues('min_salary')) + ? Number(getValues('min_salary')) <= Number(getValues('max_salary')) + : true, + })} /> - )} - /> - - + - - + type="submit" + > + Filter + + + + )} ); }; @@ -173,10 +190,18 @@ Filter.propTypes = { }).isRequired, setFilters: PropTypes.func.isRequired, resetFilter: PropTypes.func.isRequired, + loading: PropTypes.bool.isRequired, + error: PropTypes.string, +}; + +Filter.defaultProps = { + error: null, }; const mapStateToProps = (state) => ({ list: selectAllList(state), + loading: selectLoading(state), + error: selectError(state), }); const mapDispatchToProps = (dispatch) => ({ From ace63f21f0554ee344af2c3f5018da538a17ea8b Mon Sep 17 00:00:00 2001 From: Yadurani Lopez Date: Tue, 1 Mar 2022 18:13:11 -0500 Subject: [PATCH 2/8] fix: clear fields with clear filters button --- src/components/Filter/Filter.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Filter/Filter.jsx b/src/components/Filter/Filter.jsx index 99bb59f..fad6548 100644 --- a/src/components/Filter/Filter.jsx +++ b/src/components/Filter/Filter.jsx @@ -21,8 +21,6 @@ const defaultValues = { typeWork: null ?? '', min_salary: null, max_salary: null, - job_location: null, - company: null, }; const Filter = ({ list, setFilters, resetFilter, loading, error }) => { @@ -103,7 +101,7 @@ const Filter = ({ list, setFilters, resetFilter, loading, error }) => { control={control} onChange={([, data]) => data} defaultValue="" - render={({ field: { value, onChange, ...field } }) => ( + render={({ field: { onChange, ...field } }) => ( onChange(data)} From c07c0064ca8fc446469b915e984407048ebf7483 Mon Sep 17 00:00:00 2001 From: Yadurani Lopez Date: Tue, 1 Mar 2022 22:22:16 -0500 Subject: [PATCH 3/8] feat: add loaders and errors --- src/app/CalculateSalary/slice.js | 3 + src/components/Currencies/Currencies.jsx | 41 +++-- src/components/FormCard/FormCard.jsx | 184 +++++++++++-------- src/components/FormCard/FormCardSkeleton.jsx | 18 ++ src/components/JobOffers/JobCard.jsx | 21 ++- src/components/TabCalculate/TabCalculate.jsx | 4 +- src/helpers/index.js | 11 ++ 7 files changed, 188 insertions(+), 94 deletions(-) create mode 100644 src/components/FormCard/FormCardSkeleton.jsx diff --git a/src/app/CalculateSalary/slice.js b/src/app/CalculateSalary/slice.js index eb45567..a9ffdb7 100644 --- a/src/app/CalculateSalary/slice.js +++ b/src/app/CalculateSalary/slice.js @@ -67,6 +67,9 @@ const calculateSalary = createSlice({ }, clearFormMain(state) { state.formMain = initialState.formMain; + state.formComparison = initialState.formComparison; + state.chartData = initialState.chartData; + state.comparisonChartData = initialState.comparisonChartData; }, deleteChip: (state, action) => { state.formMain.technologies = state.formMain.technologies.filter((chip) => chip !== action.payload); diff --git a/src/components/Currencies/Currencies.jsx b/src/components/Currencies/Currencies.jsx index 9ef5b01..c76a5a5 100644 --- a/src/components/Currencies/Currencies.jsx +++ b/src/components/Currencies/Currencies.jsx @@ -1,38 +1,53 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { Autocomplete, TextField } from '@mui/material'; +import Skeleton from '@mui/material/Skeleton'; +import Autocomplete from '@mui/material/Autocomplete'; +import TextField from '@mui/material/TextField'; import { changeCurrency } from 'App/CalculateSalary/slice'; import { selectCurrency } from 'App/CalculateSalary/selectors'; -import { selectListCurrencies } from 'App/ListData/selectors'; +import { selectListCurrencies, selectLoading, selectError } from 'App/ListData/selectors'; -const Currencies = ({ handleCurrency, currency, listCurrencies }) => { +const Currencies = ({ handleCurrency, currency, listCurrencies, error, loading }) => { const handleCurrencies = (_, values) => handleCurrency(values); - + if (error) return null; return ( - option === value} - renderInput={(params) => } - /> + + {loading && !error && } + {!loading && ( + option === value} + renderInput={(params) => } + /> + )} + ); }; +Currencies.defaultProps = { + error: null, +}; + Currencies.propTypes = { handleCurrency: PropTypes.func.isRequired, currency: PropTypes.string.isRequired, listCurrencies: PropTypes.arrayOf(PropTypes.string).isRequired, + error: PropTypes.string, + loading: PropTypes.bool.isRequired, }; const mapStateToProps = (state) => ({ currency: selectCurrency(state), listCurrencies: selectListCurrencies(state), + loading: selectLoading(state), + error: selectError(state), }); const mapDispatchToProps = (dispatch) => ({ diff --git a/src/components/FormCard/FormCard.jsx b/src/components/FormCard/FormCard.jsx index f788d22..567f523 100644 --- a/src/components/FormCard/FormCard.jsx +++ b/src/components/FormCard/FormCard.jsx @@ -1,14 +1,27 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, Fragment } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { Typography, Card, Autocomplete, TextField, Grid, MenuItem } from '@mui/material'; +import Typography from '@mui/material/Typography'; +import Card from '@mui/material/Card'; +import Autocomplete from '@mui/material/Autocomplete'; +import TextField from '@mui/material/TextField'; +import Grid from '@mui/material/Grid'; +import MenuItem from '@mui/material/MenuItem'; import Select from 'Components/Commons/Select'; import { InfoTooltip } from 'Components/Commons/InfoTooltip/InfoTooltip'; -import { selectTechnologies, selectJobs, selectSeniority, selectEnglish } from 'App/ListData/selectors'; +import { + selectTechnologies, + selectJobs, + selectSeniority, + selectEnglish, + selectLoading, + selectError, +} from 'App/ListData/selectors'; import { fetchListData } from 'App/ListData/slice'; +import FormCardSkeleton from './FormCardSkeleton'; const FormCard = ({ onChange, @@ -20,6 +33,8 @@ const FormCard = ({ listEnglish, children, addListData, + loading, + error, }) => { const { title_name, technologies, seniority, english_level } = values; @@ -32,86 +47,99 @@ const FormCard = ({ return ( - - - {!!title && {title}} - - - option === value} - onChange={handleTitle} - value={title_name} - renderInput={(params) => } - /> - - - option === value} - onChange={handleTechnologies} - ChipProps={{ - color: 'primary', - variant: 'outlined', - size: 'small', - }} - defaultValue={[]} - value={technologies} - renderInput={(params) => } - /> - - - - + {error && ( + + Not found data, try again 😒 + + )} + {loading && !error && } + {!loading && ( + + + + {!!title && {title}} + + + option === value} + onChange={handleTitle} + value={title_name} + renderInput={(params) => } + /> + + + option === value} + onChange={handleTechnologies} + ChipProps={{ + color: 'primary', + variant: 'outlined', + size: 'small', + }} + defaultValue={[]} + value={technologies} + renderInput={(params) => ( + + )} + /> + + + + + + + + + + + + + + + level)} - > - {listSeniority.texts?.map(({ level }, key) => ( - - {level} - - ))} - + + + {children} + - - - - - - -