From 1ffa8b4b3743854a424f129fce908e9e2f3551a9 Mon Sep 17 00:00:00 2001 From: Anna Pastushenko Date: Thu, 30 Nov 2023 18:33:03 +0100 Subject: [PATCH] add task solution --- src/App.tsx | 7 +- src/PeopleRow.tsx | 35 ++ src/Root.tsx | 29 ++ src/components/HomePage.tsx | 5 + src/components/Navbar.tsx | 22 +- src/components/NotFoundPage.tsx | 5 + src/components/PeopleFilters.tsx | 107 ++--- src/components/PeoplePage.tsx | 60 ++- src/components/PeopleTable.tsx | 708 ++---------------------------- src/components/PersonLink.tsx | 25 ++ src/helpers/getPreparedPeople.tsx | 60 +++ src/helpers/getSortingParams.tsx | 27 ++ src/helpers/preparePeople.ts | 13 + src/index.tsx | 7 +- src/types/SexFilter.ts | 5 + src/types/Sort.ts | 6 + 16 files changed, 379 insertions(+), 742 deletions(-) create mode 100644 src/PeopleRow.tsx create mode 100644 src/Root.tsx create mode 100644 src/components/HomePage.tsx create mode 100644 src/components/NotFoundPage.tsx create mode 100644 src/components/PersonLink.tsx create mode 100644 src/helpers/getPreparedPeople.tsx create mode 100644 src/helpers/getSortingParams.tsx create mode 100644 src/helpers/preparePeople.ts create mode 100644 src/types/SexFilter.ts create mode 100644 src/types/Sort.ts diff --git a/src/App.tsx b/src/App.tsx index adcb8594e..1e6e96031 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,5 @@ -import { PeoplePage } from './components/PeoplePage'; +import { Outlet } from 'react-router-dom'; import { Navbar } from './components/Navbar'; - import './App.scss'; export const App = () => { @@ -10,9 +9,7 @@ export const App = () => {
-

Home Page

-

Page not found

- +
diff --git a/src/PeopleRow.tsx b/src/PeopleRow.tsx new file mode 100644 index 000000000..624d20e3d --- /dev/null +++ b/src/PeopleRow.tsx @@ -0,0 +1,35 @@ +import { useParams } from 'react-router-dom'; +import classNames from 'classnames'; +import { PersonLink } from './components/PersonLink'; +import { Person } from './types'; + +type Props = { + person: Person; +}; + +export const PeopleRow: React.FC = ({ person }) => { + const { + sex, born, died, fatherName, motherName, father, mother, + } = person; + const { slug } = useParams(); + + return ( + + + + + + + {sex} + {born} + {died} + {mother ? () : (motherName || '-')} + {father ? () : (fatherName || '-')} + + ); +}; diff --git a/src/Root.tsx b/src/Root.tsx new file mode 100644 index 000000000..7a6a76343 --- /dev/null +++ b/src/Root.tsx @@ -0,0 +1,29 @@ +import { + Navigate, + Route, + HashRouter as Router, + Routes, +} from 'react-router-dom'; +import { HomePage } from './components/HomePage'; +import { PeoplePage } from './components/PeoplePage'; +import { NotFoundPage } from './components/NotFoundPage'; +import { App } from './App'; + +export const Root = () => { + return ( + + + + }> + } /> + } /> + + } /> + + } /> + + + + + ); +}; diff --git a/src/components/HomePage.tsx b/src/components/HomePage.tsx new file mode 100644 index 000000000..0b41a51df --- /dev/null +++ b/src/components/HomePage.tsx @@ -0,0 +1,5 @@ +export const HomePage = () => { + return ( +

Home Page

+ ); +}; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index c2aa20f1c..3b8d738ee 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,4 +1,13 @@ +import classNames from 'classnames'; +import { NavLink } from 'react-router-dom'; + export const Navbar = () => { + const activeClassName = ({ isActive }: { isActive: boolean }) => { + return classNames('navbar-item', { + 'has-background-grey-lighter': isActive, + }); + }; + return ( diff --git a/src/components/NotFoundPage.tsx b/src/components/NotFoundPage.tsx new file mode 100644 index 000000000..5a1e070ff --- /dev/null +++ b/src/components/NotFoundPage.tsx @@ -0,0 +1,5 @@ +export const NotFoundPage: React.FC = () => { + return ( +

Page not found

+ ); +}; diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index 2f1608bef..fa56f2c28 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -1,12 +1,38 @@ +import React from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; +import classNames from 'classnames'; +import { getSearchWith } from '../utils/searchHelper'; +import { SexFilter } from '../types/SexFilter'; +import { SearchLink } from './SearchLink'; + +const CENTURIES = ['16', '17', '18', '19', '20']; + export const PeopleFilters = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const sex = searchParams.get('sex') || ''; + const query = searchParams.get('query') || ''; + const centuries = searchParams.getAll('centuries') || []; + + const handleSearh = (event: React.ChangeEvent) => { + setSearchParams(getSearchWith(searchParams, { + query: event.target.value || null, + })); + }; + return ( ); diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index 4c8713b16..5e0e3e467 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -1,32 +1,70 @@ -import { PeopleFilters } from './PeopleFilters'; +import { useEffect, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; import { Loader } from './Loader'; +import { Person } from '../types'; +import { getPeople } from '../api'; +import { PeopleFilters } from './PeopleFilters'; +import { getPreparedPeople } from '../helpers/getPreparedPeople'; +import { preparePeople } from '../helpers/preparePeople'; import { PeopleTable } from './PeopleTable'; -export const PeoplePage = () => { +export const PeoplePage: React.FC = () => { + const [people, setPeople] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(false); + + const [searchParams] = useSearchParams(); + const sort = searchParams.get('sort') || ''; + const order = searchParams.get('order') || ''; + const sex = searchParams.get('sex') || ''; + const query = searchParams.get('query') || ''; + const centuries = searchParams.getAll('centuries') || []; + + const visiblePeople = getPreparedPeople(people, { + sort, order, sex, query, centuries: centuries.join(','), + }); + + useEffect(() => { + setIsLoading(true); + setError(false); + + getPeople() + .then(peopleFromServer => setPeople(preparePeople(peopleFromServer))) + .catch(() => setError(true)) + .finally(() => setIsLoading(false)); + }, []); + return ( <>

People Page

-
+ + {!isLoading && (
+ )} -
-
- +
+
+ {isLoading && ()} -

Something went wrong

+ {error && ( +

+ Something went wrong +

+ )} + {!isLoading && !error && !people.length && (

There are no people on the server

+ )} -

There are no people matching the current search criteria

- - -
+ {!isLoading && !error && !!people.length && ( + + )}
diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx index b8275e8e0..a58fdce1a 100644 --- a/src/components/PeopleTable.tsx +++ b/src/components/PeopleTable.tsx @@ -1,5 +1,20 @@ -/* eslint-disable jsx-a11y/control-has-associated-label */ -export const PeopleTable = () => { +import { useSearchParams } from 'react-router-dom'; +import classNames from 'classnames'; +import { SearchLink } from './SearchLink'; +import { Person } from '../types'; +import { getSortingParams } from '../helpers/getSortingParams'; +import { PeopleRow } from '../PeopleRow'; +import { Sort } from '../types/Sort'; + +type Props = { + people: Person[], +}; + +export const PeopleTable: React.FC = ({ people }) => { + const [searchParams] = useSearchParams(); + const sort = searchParams.get('sort') || ''; + const order = searchParams.get('order') || ''; + return ( { > - - - - - - - + {Object.entries(Sort).map(([key, value]) => ( + + ))} @@ -57,627 +48,12 @@ export const PeopleTable = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {people.map(person => ( + + ))}
- - Name - - - - - - - - - Sex - - - - - - - - - Born - - - - - - - - - Died - - - - - - - + + {key} + + + + + + + Mother Father
- Pieter Haverbeke - m16021642- - - Lieven van Haverbeke - -
- - Anna van Hecke - - f16071670Martijntken BeelaertPaschasius van Hecke
- Lieven Haverbeke - m16311676 - - Anna van Hecke - - - Pieter Haverbeke -
- - Elisabeth Hercke - - f16321674Margriet de BrabanderWillem Hercke
- Daniel Haverbeke - m16521723 - - Elisabeth Hercke - - - Lieven Haverbeke -
- - Joanna de Pape - - f16541723Petronella WautersVincent de Pape
- - Martina de Pape - - f16661727Petronella WautersVincent de Pape
- Willem Haverbeke - m16681731 - - Elisabeth Hercke - - - Lieven Haverbeke -
- Jan Haverbeke - m16711731 - - Elisabeth Hercke - - - Lieven Haverbeke -
- - Maria de Rycke - - f16831724Laurentia van VlaenderenFrederik de Rycke
- - Livina Haverbeke - - f16921743 - - Joanna de Pape - - - Daniel Haverbeke -
- - Pieter Bernard Haverbeke - - m16951762Petronella Wauters - Willem Haverbeke -
- - Lieven de Causmaecker - - m16961724Joanna ClaesCarel de Causmaecker
- - Jacoba Lammens - - f16991740Livina de VriezeLieven Lammens
- Pieter de Decker - m17051780Petronella van de SteeneJoos de Decker
- - Laurentia Haverbeke - - f17101786 - - Maria de Rycke - - - Jan Haverbeke -
- - Elisabeth Haverbeke - - f17111754 - - Maria de Rycke - - - Jan Haverbeke -
- Jan van Brussel - m17141748Joanna van RootenJacobus van Brussel
- - Bernardus de Causmaecker - - m17211789 - - Livina Haverbeke - - - - Lieven de Causmaecker - -
- - Jan Francies Haverbeke - - m17251779Livina de Vrieze - - Pieter Bernard Haverbeke - -
- - Angela Haverbeke - - f17281734Livina de Vrieze - - Pieter Bernard Haverbeke - -
- - Petronella de Decker - - f17311781 - - Livina Haverbeke - - - Pieter de Decker -
- - Jacobus Bernardus van Brussel - - m17361809 - - Elisabeth Haverbeke - - - Jan van Brussel -
- - Pieter Antone Haverbeke - - m17531798 - - Petronella de Decker - - - - Jan Francies Haverbeke - -
- - Jan Frans van Brussel - - m17611833- - - Jacobus Bernardus van Brussel - -
- - Livina Sierens - - f17611826Maria van WaesJan Sierens
- - Joanna de Causmaecker - - f17621807- - - Bernardus de Causmaecker - -
- Carel Haverbeke - m17961837 - - Livina Sierens - - - - Pieter Antone Haverbeke - -
- - Maria van Brussel - - f18011834 - - Joanna de Causmaecker - - - - Jan Frans van Brussel - -
- Carolus Haverbeke - m18321905 - - Maria van Brussel - - - Carel Haverbeke -
- - Maria Sturm - - f18351917Seraphina SpelierCharles Sturm
- - Emma de Milliano - - f18761956Sophia van DammePetrus de Milliano
- Emile Haverbeke - m18771968 - - Maria Sturm - - - Carolus Haverbeke -
); diff --git a/src/components/PersonLink.tsx b/src/components/PersonLink.tsx new file mode 100644 index 000000000..a7dae5663 --- /dev/null +++ b/src/components/PersonLink.tsx @@ -0,0 +1,25 @@ +import classNames from 'classnames'; +import { Link, useSearchParams } from 'react-router-dom'; +import { Person } from '../types'; + +type Props = { + person: Person, +}; + +export const PersonLink: React.FC = ({ person }) => { + const [searchParams] = useSearchParams(); + const { name, slug, sex } = person; + const SEX_TYPE_FEMALE = 'f'; + + return ( + + {name} + + ); +}; diff --git a/src/helpers/getPreparedPeople.tsx b/src/helpers/getPreparedPeople.tsx new file mode 100644 index 000000000..d071df7cd --- /dev/null +++ b/src/helpers/getPreparedPeople.tsx @@ -0,0 +1,60 @@ +import { Person } from '../types'; +import { Sort } from '../types/Sort'; + +type FilterParam = { + sort: string, + order: string, + sex: string, + query: string, + centuries: string, +}; + +export function getPreparedPeople(people: Person[], filterParam: FilterParam) { + let preparedPeople = [...people]; + + if (filterParam.sex) { + preparedPeople = preparedPeople.filter( + person => person.sex === filterParam.sex, + ); + } + + if (filterParam.centuries.length) { + preparedPeople = preparedPeople.filter( + person => ( + filterParam.centuries.includes(Math.ceil(person.born / 100).toString()) + ), + ); + } + + if (filterParam.query) { + const normalizedQuery = filterParam.query.toLowerCase(); + + preparedPeople = preparedPeople.filter(person => { + return person.name.toLowerCase().includes(normalizedQuery) + || person.motherName?.toLowerCase().includes(normalizedQuery) + || person.fatherName?.toLowerCase().includes(normalizedQuery); + }); + } + + if (filterParam.sort) { + const reverse = filterParam.order ? -1 : 1; + + preparedPeople = [...preparedPeople].sort((a, b) => { + switch (filterParam.sort) { + case Sort.Name: + case Sort.Sex: + return a[filterParam.sort] + .localeCompare(b[filterParam.sort]) * reverse; + + case Sort.Born: + case Sort.Died: + return (a[filterParam.sort] - b[filterParam.sort]) * reverse; + + default: + return 1; + } + }); + } + + return preparedPeople; +} diff --git a/src/helpers/getSortingParams.tsx b/src/helpers/getSortingParams.tsx new file mode 100644 index 000000000..a6be9bf83 --- /dev/null +++ b/src/helpers/getSortingParams.tsx @@ -0,0 +1,27 @@ +import { SearchParams } from '../utils/searchHelper'; + +export function getSortingParams( + sort: string | null, + order: string | null, + value: string, +) { + const sortingParams: SearchParams = { + sort: null, + order: null, + }; + + if (!sort) { + sortingParams.sort = value; + } + + if (sort && sort !== value) { + sortingParams.sort = value; + } + + if (sort === value && !order) { + sortingParams.sort = value; + sortingParams.order = 'desc'; + } + + return sortingParams; +} diff --git a/src/helpers/preparePeople.ts b/src/helpers/preparePeople.ts new file mode 100644 index 000000000..b137c3af0 --- /dev/null +++ b/src/helpers/preparePeople.ts @@ -0,0 +1,13 @@ +import { Person } from '../types'; + +export const preparePeople = (peopleFromServer: Person[]): Person[] => { + return peopleFromServer.map((person) => ({ + ...person, + mother: peopleFromServer.find( + (mother) => mother.name === person.motherName, + ), + father: peopleFromServer.find( + (father) => father.name === person.fatherName, + ), + })); +}; diff --git a/src/index.tsx b/src/index.tsx index 8cb63aeff..e629c386e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,14 +1,11 @@ import { createRoot } from 'react-dom/client'; -import { HashRouter as Router } from 'react-router-dom'; import 'bulma/css/bulma.css'; import '@fortawesome/fontawesome-free/css/all.css'; -import { App } from './App'; +import { Root } from './Root'; createRoot(document.getElementById('root') as HTMLDivElement) .render( - - - , + , ); diff --git a/src/types/SexFilter.ts b/src/types/SexFilter.ts new file mode 100644 index 000000000..22a50a566 --- /dev/null +++ b/src/types/SexFilter.ts @@ -0,0 +1,5 @@ +export enum SexFilter { + All = '', + Male = 'm', + Female = 'f', +} diff --git a/src/types/Sort.ts b/src/types/Sort.ts new file mode 100644 index 000000000..8e609425d --- /dev/null +++ b/src/types/Sort.ts @@ -0,0 +1,6 @@ +export enum Sort { + Name = 'name', + Sex = 'sex', + Born = 'born', + Died = 'died', +}