diff --git a/README.md b/README.md index f1183f65..a8d546d6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # React - People table - Replace `` with your Github username in the - [DEMO LINK](https://.github.io/react_people-table/) + [DEMO LINK](https://naveed-shoukat.github.io/react_people-table/) - Follow the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline) ## If you don't use **Typescript** 1. Rename `.tsx` files to `.jsx` -1. use `eslint-config-react` in `.eslintrs.js` +1. use `eslint-config-react` in `.eslintrs.js` ## Basic tasks 1. Install all the NPM packages you need and types for them. @@ -91,3 +91,5 @@ - make `mother` and `father` field optional - update the list of `mothers` and `fathers` according to the entered `born` year (they must be alive) (selects should be empty and disabled before the born year was entered) + +Here is [the working example](https://mate-academy.github.io/react_people-table-advanced/) diff --git a/src/App.tsx b/src/App.tsx index f6361547..cd900119 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,39 +1,19 @@ import React from 'react'; +import { Outlet } from 'react-router-dom'; +import { Navigation } from './components/Navigation'; import '@fortawesome/fontawesome-free/css/all.css'; import 'bulma/css/bulma.css'; import './App.scss'; -import peopleFromServer from './people.json'; +export const App: React.FC = () => ( +
+ -export class App extends React.Component { - state = {}; - - render() { - return ( -
-

People table

- - - - - - - - - - - - {peopleFromServer.map(person => ( - - - - - - ))} - -
namesexborn
{person.name}{person.sex}{person.born}
+
+
+
- ); - } -} +
+
+); diff --git a/src/AppContext.tsx b/src/AppContext.tsx new file mode 100644 index 00000000..82efad04 --- /dev/null +++ b/src/AppContext.tsx @@ -0,0 +1,76 @@ +import React, { ReactNode, Reducer, useReducer } from 'react'; +import { Person } from './types/Person'; + +export enum ReducerActions { + setPeople = 'setPeople', + setIsPeopleLoading = 'setIsPeopleLoading', + setHasPeopleLoadingError = 'setHasPeopleLoadingError', +} + +type Props = { + children: ReactNode; +}; + +type DispatchContextType = (action: DispatchActions) => void; + +interface State { + people: Person[] | null; + isPeopleLoading: boolean; + hasPeopleLoadingError: boolean; +} +const initialState: State = { + people: null, + isPeopleLoading: false, + hasPeopleLoadingError: false, +}; + +interface DispatchActions { + type: ReducerActions, + // eslint-disable-next-line + payload: any, +} + +const reducerCallBack: Reducer = (state, action) => { + const { type, payload } = action; + + switch (type) { + case ReducerActions.setPeople: + return { + ...state, + people: payload, + }; + + case ReducerActions.setIsPeopleLoading: + return { + ...state, + isPeopleLoading: payload, + }; + + case ReducerActions.setHasPeopleLoadingError: + return { + ...state, + hasPeopleLoadingError: payload, + }; + + default: + return state; + } +}; + +export const DispatchContext = React.createContext< +DispatchContextType +>(() => {}); + +export const StateContext = React.createContext(initialState); + +export const StateProvider: React.FC = ({ children }) => { + const [state, dispatch] = useReducer(reducerCallBack, initialState); + + return ( + + + {children} + + + ); +}; diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 00000000..03bd2e42 --- /dev/null +++ b/src/api.ts @@ -0,0 +1,15 @@ +import { Person } from './types/Person'; + +// eslint-disable-next-line max-len +const API_URL = 'https://mate-academy.github.io/react_people-table/api/people.json'; + +function wait(delay: number) { + return new Promise(resolve => setTimeout(resolve, delay)); +} + +export function getPeople(): Promise { + // keep this delay for testing purpose + return wait(500) + .then(() => fetch(API_URL)) + .then(response => response.json()); +} diff --git a/src/components/CenturyFilterLink/CenturyFilterLink.tsx b/src/components/CenturyFilterLink/CenturyFilterLink.tsx new file mode 100644 index 00000000..a232774c --- /dev/null +++ b/src/components/CenturyFilterLink/CenturyFilterLink.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import cn from 'classnames'; +import { Link, useLocation } from 'react-router-dom'; + +type Props = { + century: string, + handelCentury: (querytype: string, value: string) => void; +}; + +export const CenturyFilterLink: React.FC = (props) => { + const { century, handelCentury } = props; + const { search } = useLocation(); + const isActive = search.includes(`century=${century}`); + + return ( + { + event.preventDefault(); + handelCentury('century', century); + }} + > + {century} + + ); +}; diff --git a/src/components/CenturyFilterLink/index.ts b/src/components/CenturyFilterLink/index.ts new file mode 100644 index 00000000..5a0c1c12 --- /dev/null +++ b/src/components/CenturyFilterLink/index.ts @@ -0,0 +1 @@ +export * from './CenturyFilterLink'; diff --git a/src/components/FilteringForm/FilteringForm.tsx b/src/components/FilteringForm/FilteringForm.tsx new file mode 100644 index 00000000..bbeda059 --- /dev/null +++ b/src/components/FilteringForm/FilteringForm.tsx @@ -0,0 +1,188 @@ +import React from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; +import { CenturyFilterLink } from '../CenturyFilterLink'; + +export const FilteringForm: React.FC = () => { + const [searchParams, setSearchParams] = useSearchParams(); + + const handelFilterSearchURL = (filterType: string, value: string) => { + if (filterType === 'sex' || filterType === 'query') { + const prevValue = searchParams.get(filterType); + + if (!prevValue) { + searchParams.append(filterType, value); + setSearchParams(searchParams); + + return; + } + + if (!value) { + searchParams.delete(filterType); + setSearchParams(searchParams); + + return; + } + + searchParams.set(filterType, value); + setSearchParams(searchParams); + } + + if (filterType === 'century') { + const prevValue = searchParams.getAll(filterType); + + if (prevValue.length === 0) { + searchParams.append(filterType, value); + setSearchParams(searchParams); + + return; + } + + const newCenturyValue = prevValue.includes(value) + ? prevValue.filter((century: string) => century !== value) + : [...prevValue, value]; + + searchParams.delete(filterType); + + if (!newCenturyValue.length) { + setSearchParams(searchParams); + + return; + } + + newCenturyValue.forEach( + (century: string) => searchParams.append(filterType, century), + ); + + setSearchParams(searchParams); + } + }; + + const resetAllCenturies = (event: React.SyntheticEvent) => { + event.preventDefault(); + searchParams.delete('century'); + setSearchParams(searchParams); + }; + + const resetAllFilter = (event: React.SyntheticEvent) => { + event.preventDefault(); + searchParams.delete('sex'); + searchParams.delete('century'); + searchParams.delete('query'); + setSearchParams(searchParams); + }; + + return ( +
+