From a38d9f85cc3b7e9c5ad1acc48308a2985e821c6e Mon Sep 17 00:00:00 2001 From: Nazar Kolomiets Date: Thu, 30 Nov 2023 17:24:00 +0300 Subject: [PATCH 1/3] add task solution --- src/App.tsx | 16 +++-- src/components/HomePage.tsx | 5 ++ src/components/Navbar.tsx | 21 ++++-- src/components/PageNotFound.tsx | 5 ++ src/components/PeopleFilters.tsx | 116 +++++++++++++++++------------- src/components/PeoplePage.tsx | 118 ++++++++++++++++++++++++++++--- src/components/PersonLink.tsx | 20 ++++++ 7 files changed, 235 insertions(+), 66 deletions(-) create mode 100644 src/components/HomePage.tsx create mode 100644 src/components/PageNotFound.tsx create mode 100644 src/components/PersonLink.tsx diff --git a/src/App.tsx b/src/App.tsx index adcb8594e..61d653ed7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,9 @@ +import { Navigate, Route, Routes } from 'react-router-dom'; import { PeoplePage } from './components/PeoplePage'; import { Navbar } from './components/Navbar'; - import './App.scss'; +import { HomePage } from './components/HomePage'; +import { PageNotFound } from './components/PageNotFound'; export const App = () => { return ( @@ -10,9 +12,15 @@ export const App = () => {
-

Home Page

-

Page not found

- + + } /> + } /> + + } /> + } /> + + } /> +
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..4e94a70a1 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 isActiveClass = ({ isActive }: { isActive: boolean }) => classNames( + 'navbar-item', { + 'has-background-grey-lighter': isActive, + }, + ); + return ( diff --git a/src/components/PageNotFound.tsx b/src/components/PageNotFound.tsx new file mode 100644 index 000000000..0eaf93c08 --- /dev/null +++ b/src/components/PageNotFound.tsx @@ -0,0 +1,5 @@ +export const PageNotFound = () => { + return ( +

Page not found

+ ); +}; diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index 2f1608bef..f2fb299e0 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -1,12 +1,49 @@ +import { ChangeEvent } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; +import { getSearchWith } from '../utils/searchHelper'; +import { SearchLink } from './SearchLink'; + export const PeopleFilters = () => { + const [searchParams, setSearchParams] = useSearchParams(); + + const centuriesList = ['16', '17', '18', '19', '20']; + + const sex = searchParams.get('sex'); + const centuries = searchParams.getAll('centuries'); + const query = searchParams.get('query') || ''; + + function handleSearch(e: ChangeEvent): void { + const newParam = (e.target.value === '') + ? getSearchWith(searchParams, { query: null }) + : getSearchWith(searchParams, { query: e.target.value }); + + setSearchParams(newParam); + } + return ( ); diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index 4c8713b16..283fb64a2 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -1,8 +1,102 @@ +import { useEffect, useState } from 'react'; +import { useParams, useSearchParams } from 'react-router-dom'; import { PeopleFilters } from './PeopleFilters'; import { Loader } from './Loader'; import { PeopleTable } from './PeopleTable'; +import { Person } from '../types'; +import { getPeople } from '../api'; export const PeoplePage = () => { + const [people, setPeople] = useState([]); + const [isLoader, setIsLoader] = useState(false); + const [errorMessage, setErrorMessage] = useState(false); + const [searchParams] = useSearchParams(); + + const { slug } = useParams(); + + useEffect(() => { + setIsLoader(true); + getPeople() + .then(response => { + const peopleServer: Person[] = response.map(person => { + const mother = response + .find(item => item.name === person.motherName); + const father = response + .find(item => item.name === person.fatherName); + + return ({ + ...person, + mother, + father, + }); + }); + + return peopleServer; + }) + .then(preparedPeople => setPeople(preparedPeople)) + .catch(() => setErrorMessage(true)) + .finally(() => setIsLoader(false)); + }, []); + + const preparedPeople = () => { + const sort = searchParams.get('sort'); + const order = searchParams.get('order'); + const filterSex = searchParams.get('sex'); + const query = searchParams.get('query')?.trim().toLocaleLowerCase(); + const centuries = searchParams.getAll('centuries'); + let prepared = [...people]; + + if (query) { + prepared = prepared.filter(elem => elem.name.toLowerCase() + .includes(query) + || elem.motherName?.toLowerCase().includes(query) + || elem.fatherName?.toLowerCase().includes(query)); + } + + if (filterSex) { + prepared = prepared.filter(elem => elem.sex === filterSex); + } + + if (centuries && centuries.length > 0) { + prepared = prepared.filter(elem => ( + centuries?.includes(Math.ceil(elem.born / 100).toString()))); + } + + if (sort) { + switch (sort) { + case 'name': + { + prepared.sort((a, b) => a.name.localeCompare(b.name)); + break; + } + + case 'sex': + { + prepared.sort((a, b) => a.sex.localeCompare(b.sex)); + break; + } + + case 'born': { + prepared.sort((a, b) => a.born - b.born); + break; + } + + case 'died': { + prepared.sort((a, b) => a.died - b.died); + break; + } + + default: + } + } + + if (order) { + prepared.reverse(); + } + + return prepared; + }; + return ( <>

People Page

@@ -10,22 +104,30 @@ export const PeoplePage = () => {
- + {!isLoader && }
- + {isLoader && } -

Something went wrong

+ {errorMessage && ( +

Something went wrong

+ )} -

- There are no people on the server -

+ {(!isLoader && !errorMessage && people.length === 0) && ( +

+ There are no people on the server +

+ )} -

There are no people matching the current search criteria

+ {(people.length === 0 && !isLoader) && ( +

There are no people matching the current search criteria

+ )} - + {(people.length > 0) && ( + + )}
diff --git a/src/components/PersonLink.tsx b/src/components/PersonLink.tsx new file mode 100644 index 000000000..2e8f3a0b1 --- /dev/null +++ b/src/components/PersonLink.tsx @@ -0,0 +1,20 @@ +import { Link } from 'react-router-dom'; +import cn from 'classnames'; +import { Person } from '../types'; + +type Props = { + person: Person; +}; + +export const PersonLink: React.FC = ({ person }) => { + const { name, sex, slug } = person; + + return ( + + {name} + + ); +}; From b320cf17886877091c17062fccddd64a609ad43c Mon Sep 17 00:00:00 2001 From: Nazar Kolomiets Date: Wed, 6 Dec 2023 09:57:40 +0300 Subject: [PATCH 2/3] table-advansed --- src/App.tsx | 16 +- src/components/HomePage.tsx | 5 + src/components/Navbar.tsx | 21 +- src/components/PageNotFound.tsx | 5 + src/components/PeopleFilters.tsx | 116 ++--- src/components/PeoplePage.tsx | 118 ++++- src/components/PeopleTable.tsx | 729 ++++--------------------------- src/components/PersonLink.tsx | 22 + src/components/SearchLink.tsx | 3 +- 9 files changed, 334 insertions(+), 701 deletions(-) create mode 100644 src/components/HomePage.tsx create mode 100644 src/components/PageNotFound.tsx create mode 100644 src/components/PersonLink.tsx diff --git a/src/App.tsx b/src/App.tsx index adcb8594e..61d653ed7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,9 @@ +import { Navigate, Route, Routes } from 'react-router-dom'; import { PeoplePage } from './components/PeoplePage'; import { Navbar } from './components/Navbar'; - import './App.scss'; +import { HomePage } from './components/HomePage'; +import { PageNotFound } from './components/PageNotFound'; export const App = () => { return ( @@ -10,9 +12,15 @@ export const App = () => {
-

Home Page

-

Page not found

- + + } /> + } /> + + } /> + } /> + + } /> +
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..4e94a70a1 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 isActiveClass = ({ isActive }: { isActive: boolean }) => classNames( + 'navbar-item', { + 'has-background-grey-lighter': isActive, + }, + ); + return ( diff --git a/src/components/PageNotFound.tsx b/src/components/PageNotFound.tsx new file mode 100644 index 000000000..0eaf93c08 --- /dev/null +++ b/src/components/PageNotFound.tsx @@ -0,0 +1,5 @@ +export const PageNotFound = () => { + return ( +

Page not found

+ ); +}; diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index 2f1608bef..f2fb299e0 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -1,12 +1,49 @@ +import { ChangeEvent } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; +import { getSearchWith } from '../utils/searchHelper'; +import { SearchLink } from './SearchLink'; + export const PeopleFilters = () => { + const [searchParams, setSearchParams] = useSearchParams(); + + const centuriesList = ['16', '17', '18', '19', '20']; + + const sex = searchParams.get('sex'); + const centuries = searchParams.getAll('centuries'); + const query = searchParams.get('query') || ''; + + function handleSearch(e: ChangeEvent): void { + const newParam = (e.target.value === '') + ? getSearchWith(searchParams, { query: null }) + : getSearchWith(searchParams, { query: e.target.value }); + + setSearchParams(newParam); + } + return ( ); diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index 4c8713b16..e4f994d91 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -1,8 +1,102 @@ +import { useEffect, useState } from 'react'; +import { useParams, useSearchParams } from 'react-router-dom'; import { PeopleFilters } from './PeopleFilters'; import { Loader } from './Loader'; import { PeopleTable } from './PeopleTable'; +import { Person } from '../types'; +import { getPeople } from '../api'; export const PeoplePage = () => { + const [people, setPeople] = useState([]); + const [isLoader, setIsLoader] = useState(false); + const [errorMessage, setErrorMessage] = useState(false); + const [searchParams] = useSearchParams(); + + const { slug } = useParams(); + + useEffect(() => { + setIsLoader(true); + getPeople() + .then(response => { + const peopleServer: Person[] = response.map(person => { + const mother = response + .find(item => item.name === person.motherName); + const father = response + .find(item => item.name === person.fatherName); + + return ({ + ...person, + mother, + father, + }); + }); + + return peopleServer; + }) + .then(preparedPeople => setPeople(preparedPeople)) + .catch(() => setErrorMessage(true)) + .finally(() => setIsLoader(false)); + }, []); + + const preparedPeople = () => { + const sort = searchParams.get('sort'); + const order = searchParams.get('order'); + const filterSex = searchParams.get('sex'); + const query = searchParams.get('query')?.trim().toLocaleLowerCase(); + const centuries = searchParams.getAll('centuries'); + let prepared = [...people]; + + if (query) { + prepared = prepared.filter(elem => elem.name.toLowerCase() + .includes(query) + || elem.motherName?.toLowerCase().includes(query) + || elem.fatherName?.toLowerCase().includes(query)); + } + + if (filterSex) { + prepared = prepared.filter(elem => elem.sex === filterSex); + } + + if (centuries && centuries.length > 0) { + prepared = prepared.filter(elem => ( + centuries?.includes(Math.ceil(elem.born / 100).toString()))); + } + + if (sort) { + switch (sort) { + case 'name': + { + prepared.sort((a, b) => a.name.localeCompare(b.name)); + break; + } + + case 'sex': + { + prepared.sort((a, b) => a.sex.localeCompare(b.sex)); + break; + } + + case 'born': { + prepared.sort((a, b) => a.born - b.born); + break; + } + + case 'died': { + prepared.sort((a, b) => a.died - b.died); + break; + } + + default: + } + } + + if (order) { + prepared.reverse(); + } + + return prepared; + }; + return ( <>

People Page

@@ -10,22 +104,30 @@ export const PeoplePage = () => {
- + {!isLoader && }
- + {isLoader && } -

Something went wrong

+ {errorMessage && ( +

Something went wrong

+ )} -

- There are no people on the server -

+ {(!isLoader && !errorMessage && !people.length) && ( +

+ There are no people on the server +

+ )} -

There are no people matching the current search criteria

+ {(!people.length && !isLoader) && ( +

There are no people matching the current search criteria

+ )} - + {!!people.length && ( + + )}
diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx index b8275e8e0..8603047b4 100644 --- a/src/components/PeopleTable.tsx +++ b/src/components/PeopleTable.tsx @@ -1,5 +1,42 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ -export const PeopleTable = () => { +import React from 'react'; + +import { useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; +import { Person } from '../types'; +import { SearchLink } from './SearchLink'; +import { SearchParams } from '../utils/searchHelper'; +import { PersonLink } from './PersonLink'; + +type Props = { + people: Person[]; + slugSelected?: string; +}; + +export const PeopleTable: React.FC = ({ people, slugSelected }) => { + const [searchParams] = useSearchParams(); + + const sort = searchParams.get('sort'); + const order = searchParams.get('order'); + + const sortParams = (param: string) => { + if (sort !== param) { + return { + sort: param, + order: null, + }; + } + + if (sort === param && order !== 'desc') { + return { order: 'desc' }; + } + + return { + sort: null, + order: null, + }; + }; + return ( { @@ -57,627 +110,35 @@ export const PeopleTable = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {people.map(personList => { + return ( + + + + + + + + + + ); + })}
Name - + - + - + Sex - + - + - + Born - + - + - + Died - + - + - +
- 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 -
+ + {personList.sex}{personList.born}{personList.died} + {personList.mother + ? () + : personList.motherName || '-'} + + {personList.father + ? () + : personList.fatherName || '-'} +
); diff --git a/src/components/PersonLink.tsx b/src/components/PersonLink.tsx new file mode 100644 index 000000000..54c67f0fc --- /dev/null +++ b/src/components/PersonLink.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { Link } from 'react-router-dom'; +import cn from 'classnames'; +import { Person } from '../types'; + +type Props = { + person: Person; +}; + +export const PersonLink: React.FC = ({ person }) => { + const { name, sex, slug } = person; + + return ( + + {name} + + ); +}; diff --git a/src/components/SearchLink.tsx b/src/components/SearchLink.tsx index 9b409b2f6..4a1e21dab 100644 --- a/src/components/SearchLink.tsx +++ b/src/components/SearchLink.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + import { Link, LinkProps, useSearchParams } from 'react-router-dom'; import { getSearchWith, SearchParams } from '../utils/searchHelper'; @@ -8,7 +10,6 @@ import { getSearchWith, SearchParams } from '../utils/searchHelper'; type Props = Omit & { params: SearchParams, }; - /** * SearchLink updates the given `params` in the search keeping the `pathname` * and the other existing search params (see `getSearchWith`) From c90191bbe4ef03afe763528b6cf490b72beda629 Mon Sep 17 00:00:00 2001 From: Nazar Kolomiets Date: Thu, 7 Dec 2023 11:45:08 +0300 Subject: [PATCH 3/3] add task solution 2.0 --- src/components/PeoplePage.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index e4f994d91..b360b8cae 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -65,24 +65,15 @@ export const PeoplePage = () => { if (sort) { switch (sort) { case 'name': - { - prepared.sort((a, b) => a.name.localeCompare(b.name)); - break; - } - case 'sex': { - prepared.sort((a, b) => a.sex.localeCompare(b.sex)); - break; - } - - case 'born': { - prepared.sort((a, b) => a.born - b.born); + prepared.sort((a, b) => a[sort].localeCompare(b[sort])); break; } + case 'born': case 'died': { - prepared.sort((a, b) => a.died - b.died); + prepared.sort((a, b) => a[sort] - b[sort]); break; }