From 891bc2138b98581d3c2689a61a10357255ad85f9 Mon Sep 17 00:00:00 2001 From: Vladyslav Atanov Date: Sun, 19 Nov 2023 18:06:03 +0200 Subject: [PATCH 1/2] made people-table-advanced --- README.md | 2 +- src/App.tsx | 15 +- src/components/Navbar.tsx | 21 +- src/components/PeopleFilters.tsx | 141 ++++-- src/components/PeoplePage.tsx | 109 ++++- src/components/PeopleTable.tsx | 743 +++-------------------------- src/components/PersonComponent.tsx | 59 +++ 7 files changed, 356 insertions(+), 734 deletions(-) create mode 100644 src/components/PersonComponent.tsx diff --git a/README.md b/README.md index 24519caab..acb350d18 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,4 @@ implement the ability to filter and sort people in the table. - Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline). - Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript). - Open one more terminal and run tests with `npm test` to ensure your solution is correct. -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_people-table-advanced/) and add it to the PR description. +- Replace `` with your Github username in the [DEMO LINK](https://sintax1s.github.io/react_people-table-advanced/) and add it to the PR description. diff --git a/src/App.tsx b/src/App.tsx index adcb8594e..aa5c79ea8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,4 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; import { PeoplePage } from './components/PeoplePage'; import { Navbar } from './components/Navbar'; @@ -10,9 +11,17 @@ export const App = () => {
-

Home Page

-

Page not found

- + + Home Page} /> + } /> + }> + } /> + + Page not found} + /> +
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index c2aa20f1c..7288a8039 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,4 +1,15 @@ +import { NavLink } from 'react-router-dom'; +import cn from 'classnames'; + +interface NavLinkProps { + isActive?: boolean; +} + export const Navbar = () => { + const getClass = ({ isActive }: NavLinkProps) => { + return cn('navbar-item', { 'has-background-grey-lighter': isActive }); + }; + return ( diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index 2f1608bef..894de7721 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -1,12 +1,74 @@ -export const PeopleFilters = () => { +import { Link, useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; +import { getSearchWith } from '../utils/searchHelper'; + +const centuriesArray = [ + '16', '17', '18', '19', '20', +]; + +export const PeopleFilters: React.FC = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const centuries = searchParams.getAll('centuries') || []; + const sex = searchParams.get('sex') || 'All'; + const query = searchParams.get('query') || ''; + + const toggleCenturies = (century: string) => { + if (!century) { + return getSearchWith(searchParams, { + centuries: [], + }); + } + + return getSearchWith(searchParams, { + centuries: centuries.includes(century) + ? centuries.filter(ch => ch !== century) + : [...centuries, century], + }); + }; + + const toggleSex = (newSex: string | null) => { + if (newSex === 'All') { + return getSearchWith(searchParams, { sex: null }); + } + + return getSearchWith(searchParams, { sex: newSex }); + }; + + const toggleQuery = (newQuery: string) => { + const search = getSearchWith(searchParams, { query: newQuery }); + + setSearchParams(search); + }; + + const toggleAllReset = () => { + return getSearchWith( + searchParams, { query: null, centuries: [], sex: null }, + ); + }; + return ( ); diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index 4c8713b16..fb5bef658 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -1,8 +1,84 @@ +import { useEffect, useState } from 'react'; +import { 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 [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); + + const [searchParams] = useSearchParams(); + const centuries = searchParams.getAll('centuries') || []; + const sex = searchParams.get('sex') || 'All'; + const query = searchParams.get('query') || ''; + const sort = searchParams.get('sort') || ''; + const order = searchParams.get('order') || ''; + + useEffect(() => { + setIsLoading(true); + + getPeople() + .then((peopleFromServer) => setPeople(peopleFromServer)) + .catch(() => setIsError(true)) + .finally(() => setIsLoading(false)); + }, []); + + const getFilteredPeople = () => { + let filteredPeople = [...people]; + + if (centuries.length) { + filteredPeople = filteredPeople.filter( + person => centuries.includes( + Math.ceil(person.born / 100).toString(), + ), + ); + } + + if (sex !== 'All') { + filteredPeople = filteredPeople.filter(person => person.sex === sex); + } + + if (query) { + const validQuery = query.toLocaleLowerCase().trim(); + + filteredPeople = filteredPeople.filter( + (person) => person.name.toLocaleLowerCase().includes(validQuery) + || person.fatherName?.toLocaleLowerCase().includes(validQuery) + || person.motherName?.toLocaleLowerCase().includes(validQuery), + ); + } + + if (sort) { + switch (sort) { + case 'name': + case 'sex': + filteredPeople = filteredPeople.sort((person1, person2) => { + return (order === 'desc') + ? person2[sort].localeCompare(person1[sort]) + : person1[sort].localeCompare(person2[sort]); + }); + break; + case 'born': + case 'died': + filteredPeople = filteredPeople.sort((person1, person2) => { + return (order === 'desc') + ? person2[sort] - person1[sort] + : person1[sort] - person2[sort]; + }); + break; + default: + } + } + + return filteredPeople; + }; + + const visiblePeople = getFilteredPeople(); + return ( <>

People Page

@@ -10,22 +86,35 @@ export const PeoplePage = () => {
- + {!isLoading && ( + + )}
- - -

Something went wrong

- -

- There are no people on the server -

+ {isLoading && ( + + )} -

There are no people matching the current search criteria

+ {isError && ( +

Something went wrong

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

+ There are no people on the server +

+ )} + {!visiblePeople.length && !isLoading && ( +

There are no people matching the current search criteria

+ )} + {!isLoading && !isError && !!visiblePeople.length && ( + + )} -
diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx index 8ab5b90f7..8f2f78203 100644 --- a/src/components/PeopleTable.tsx +++ b/src/components/PeopleTable.tsx @@ -1,4 +1,42 @@ -export const PeopleTable = () => { +import { Link, useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; +import { Person } from '../types'; +import { PersonComponent } from './PersonComponent'; +import { getSearchWith } from '../utils/searchHelper'; + +type Props = { + people: Person[]; + visiblePeople: Person[]; +}; + +const sortCategories = [ + { title: 'Name', urlParam: 'name' }, + { title: 'Sex', urlParam: 'sex' }, + { title: 'Born', urlParam: 'born' }, + { title: 'Died', urlParam: 'died' }, +]; + +export const PeopleTable: React.FC = ({ + people, + visiblePeople, +}) => { + const [searchParams] = useSearchParams(); + + const sort = searchParams.get('sort') || ''; + const order = searchParams.get('order') || ''; + + const sortToggle = (sortType: string) => { + if (sort !== sortType) { + return getSearchWith(searchParams, { sort: sortType, order: null }); + } + + if (sort === sortType && order === 'desc') { + return getSearchWith(searchParams, { sort: null, order: null }); + } + + return getSearchWith(searchParams, { sort: sortType, order: 'desc' }); + }; + return ( { > - - - - - - - + {sortCategories.map((sortCategory) => ( + + ))} @@ -56,627 +71,25 @@ export const PeopleTable = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {visiblePeople.map((person) => { + const mother = people.find( + p => p.name === person.motherName, + ); + + const father = people.find( + p => p.name === person.fatherName, + ); + + const personWithParents = { + ...person, + mother, + father, + }; + + return ( + + ); + })}
- - Name - - - - - - - - - Sex - - - - - - - - - Born - - - - - - - - - Died - - - - - - - + + {sortCategory.title} + + + + + + + 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/PersonComponent.tsx b/src/components/PersonComponent.tsx new file mode 100644 index 000000000..aed098885 --- /dev/null +++ b/src/components/PersonComponent.tsx @@ -0,0 +1,59 @@ +import cn from 'classnames'; +import { Link, useLocation, useParams } from 'react-router-dom'; +import { Person } from '../types'; + +type Props = { + person: Person +}; + +export const PersonComponent: React.FC = ({ person }) => { + const { + name, + sex, + born, + died, + fatherName, + motherName, + slug, + mother, + father, + } = person; + + const { personSlug } = useParams(); + const { search } = useLocation(); + + return ( + + + + {name} + + + + {sex} + {born} + {died} + {mother + ? ( + + + {motherName} + + + ) + : {motherName || '-'}} + {father + ? {fatherName} + : {fatherName || '-'}} + + ); +}; From db5ab185a9e1298c62e00c53ac806a944ff7defd Mon Sep 17 00:00:00 2001 From: Vladyslav Atanov Date: Wed, 22 Nov 2023 17:54:52 +0200 Subject: [PATCH 2/2] made edits --- src/components/PeopleFilters.tsx | 73 ++++++++++++++------------------ src/components/PeoplePage.tsx | 65 +++------------------------- src/components/PeopleTable.tsx | 15 +++---- src/utils/getFilteredPeople.ts | 58 +++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 108 deletions(-) create mode 100644 src/utils/getFilteredPeople.ts diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index 894de7721..1cad1e01a 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -1,11 +1,18 @@ -import { Link, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import cn from 'classnames'; import { getSearchWith } from '../utils/searchHelper'; +import { SearchLink } from './SearchLink'; const centuriesArray = [ '16', '17', '18', '19', '20', ]; +const sexArray = [ + { title: 'All', urlParam: 'All' }, + { title: 'Male', urlParam: 'm' }, + { title: 'Female', urlParam: 'f' }, +]; + export const PeopleFilters: React.FC = () => { const [searchParams, setSearchParams] = useSearchParams(); const centuries = searchParams.getAll('centuries') || []; @@ -14,36 +21,32 @@ export const PeopleFilters: React.FC = () => { const toggleCenturies = (century: string) => { if (!century) { - return getSearchWith(searchParams, { - centuries: [], - }); + return { centuries: [] }; } - return getSearchWith(searchParams, { + return { centuries: centuries.includes(century) ? centuries.filter(ch => ch !== century) : [...centuries, century], - }); + }; }; const toggleSex = (newSex: string | null) => { if (newSex === 'All') { - return getSearchWith(searchParams, { sex: null }); + return { sex: null }; } - return getSearchWith(searchParams, { sex: newSex }); + return { sex: newSex }; }; const toggleQuery = (newQuery: string) => { - const search = getSearchWith(searchParams, { query: newQuery }); + const search = getSearchWith(searchParams, { query: newQuery || null }); setSearchParams(search); }; const toggleAllReset = () => { - return getSearchWith( - searchParams, { query: null, centuries: [], sex: null }, - ); + return { query: null, centuries: [], sex: null }; }; return ( @@ -51,24 +54,14 @@ export const PeopleFilters: React.FC = () => {

Filters

- - All - - - Male - - - Female - + {sexArray.map((currSex) => ( + + {currSex.title} + + ))}

@@ -92,42 +85,40 @@ export const PeopleFilters: React.FC = () => {
{centuriesArray.map((century) => ( - {century} - + ))}
- All - +
- Reset all filters - +
); diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index fb5bef658..46914b96d 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -1,84 +1,31 @@ -import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; +import { useEffect, useState } from 'react'; import { PeopleFilters } from './PeopleFilters'; import { Loader } from './Loader'; import { PeopleTable } from './PeopleTable'; import { Person } from '../types'; import { getPeople } from '../api'; +import { getFilteredPeople } from '../utils/getFilteredPeople'; + export const PeoplePage = () => { const [people, setPeople] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); const [searchParams] = useSearchParams(); - const centuries = searchParams.getAll('centuries') || []; - const sex = searchParams.get('sex') || 'All'; - const query = searchParams.get('query') || ''; - const sort = searchParams.get('sort') || ''; - const order = searchParams.get('order') || ''; + + const visiblePeople = getFilteredPeople(people, searchParams); useEffect(() => { setIsLoading(true); getPeople() - .then((peopleFromServer) => setPeople(peopleFromServer)) + .then(setPeople) .catch(() => setIsError(true)) .finally(() => setIsLoading(false)); }, []); - const getFilteredPeople = () => { - let filteredPeople = [...people]; - - if (centuries.length) { - filteredPeople = filteredPeople.filter( - person => centuries.includes( - Math.ceil(person.born / 100).toString(), - ), - ); - } - - if (sex !== 'All') { - filteredPeople = filteredPeople.filter(person => person.sex === sex); - } - - if (query) { - const validQuery = query.toLocaleLowerCase().trim(); - - filteredPeople = filteredPeople.filter( - (person) => person.name.toLocaleLowerCase().includes(validQuery) - || person.fatherName?.toLocaleLowerCase().includes(validQuery) - || person.motherName?.toLocaleLowerCase().includes(validQuery), - ); - } - - if (sort) { - switch (sort) { - case 'name': - case 'sex': - filteredPeople = filteredPeople.sort((person1, person2) => { - return (order === 'desc') - ? person2[sort].localeCompare(person1[sort]) - : person1[sort].localeCompare(person2[sort]); - }); - break; - case 'born': - case 'died': - filteredPeople = filteredPeople.sort((person1, person2) => { - return (order === 'desc') - ? person2[sort] - person1[sort] - : person1[sort] - person2[sort]; - }); - break; - default: - } - } - - return filteredPeople; - }; - - const visiblePeople = getFilteredPeople(); - return ( <>

People Page

diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx index 8f2f78203..9907baa30 100644 --- a/src/components/PeopleTable.tsx +++ b/src/components/PeopleTable.tsx @@ -1,8 +1,8 @@ -import { Link, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import cn from 'classnames'; import { Person } from '../types'; import { PersonComponent } from './PersonComponent'; -import { getSearchWith } from '../utils/searchHelper'; +import { SearchLink } from './SearchLink'; type Props = { people: Person[]; @@ -21,20 +21,19 @@ export const PeopleTable: React.FC = ({ visiblePeople, }) => { const [searchParams] = useSearchParams(); - const sort = searchParams.get('sort') || ''; const order = searchParams.get('order') || ''; const sortToggle = (sortType: string) => { if (sort !== sortType) { - return getSearchWith(searchParams, { sort: sortType, order: null }); + return { sort: sortType, order: null }; } if (sort === sortType && order === 'desc') { - return getSearchWith(searchParams, { sort: null, order: null }); + return { sort: null, order: null }; } - return getSearchWith(searchParams, { sort: sortType, order: 'desc' }); + return { sort: sortType, order: 'desc' }; }; return ( @@ -48,7 +47,7 @@ export const PeopleTable: React.FC = ({ {sortCategory.title} - + = ({ })} /> - + ))} diff --git a/src/utils/getFilteredPeople.ts b/src/utils/getFilteredPeople.ts new file mode 100644 index 000000000..9a6387fc0 --- /dev/null +++ b/src/utils/getFilteredPeople.ts @@ -0,0 +1,58 @@ +import { Person } from '../types'; + +export const getFilteredPeople = ( + people: Person[], filters: URLSearchParams, +) => { + let filteredPeople = [...people]; + const query = filters.get('query') || ''; + const sort = filters.get('sort') || ''; + const order = filters.get('order') || ''; + const centuries = filters.getAll('centuries') || []; + const sex = filters.get('sex') || 'All'; + + if (centuries.length) { + filteredPeople = filteredPeople.filter( + person => centuries.includes( + Math.ceil(person.born / 100).toString(), + ), + ); + } + + if (sex !== 'All') { + filteredPeople = filteredPeople.filter(person => person.sex === sex); + } + + if (query) { + const validQuery = query.toLocaleLowerCase().trim(); + + filteredPeople = filteredPeople.filter( + (person) => person.name.toLocaleLowerCase().includes(validQuery) + || person.fatherName?.toLocaleLowerCase().includes(validQuery) + || person.motherName?.toLocaleLowerCase().includes(validQuery), + ); + } + + if (sort) { + switch (sort) { + case 'name': + case 'sex': + filteredPeople = filteredPeople.sort((person1, person2) => { + return (order === 'desc') + ? person2[sort].localeCompare(person1[sort]) + : person1[sort].localeCompare(person2[sort]); + }); + break; + case 'born': + case 'died': + filteredPeople = filteredPeople.sort((person1, person2) => { + return (order === 'desc') + ? person2[sort] - person1[sort] + : person1[sort] - person2[sort]; + }); + break; + default: + } + } + + return filteredPeople; +};