From f19a25385258985b4c30d513456b9ce2bf927af3 Mon Sep 17 00:00:00 2001 From: Alternate001 Date: Tue, 7 Jul 2026 12:56:31 +0200 Subject: [PATCH 1/3] solution --- README.md | 2 +- src/App.tsx | 62 ++- src/api.ts | 2 +- src/components/PeopleFilters.tsx | 116 +++-- src/components/PeoplePage.tsx | 54 ++- src/components/PeopleTable.tsx | 810 +++++++------------------------ src/index.tsx | 6 +- 7 files changed, 365 insertions(+), 687 deletions(-) diff --git a/README.md b/README.md index 064a39440..5fa18d26d 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://Alternate001.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..0beeb3cdf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,20 +1,66 @@ +import './App.scss'; +import { Link, Navigate, Route, Routes, useLocation } from 'react-router-dom'; import { PeoplePage } from './components/PeoplePage'; -import { Navbar } from './components/Navbar'; -import './App.scss'; +const People = () => { + return ( + <> + + + ); +}; export const App = () => { + const location = useLocation(); + return (
- + -
+
-

Home Page

-

Page not found

- + + Home Page} /> + } /> + } /> + } /> + Page not found} + /> +
-
+
); }; diff --git a/src/api.ts b/src/api.ts index dafca7c8d..0c0a91cf8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -8,7 +8,7 @@ function wait(delay: number) { return new Promise(resolve => setTimeout(resolve, delay)); } -export async function getPeople(): Promise { +export function getPeople(): Promise { // keep this delay for testing purpose return wait(500) .then(() => fetch(API_URL)) diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index c9c819cd3..ff9832ab0 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -1,18 +1,43 @@ +import { SearchLink } from './SearchLink'; +import { useSearchParams } from 'react-router-dom'; + export const PeopleFilters = () => { + const [searchParams] = useSearchParams(); + const selectedCenturies = searchParams.getAll('centuries'); + + const getCenturyParams = (century: string) => { + const newCenturies = selectedCenturies.includes(century) + ? selectedCenturies.filter(item => item !== century) + : [...selectedCenturies, century]; + + return { + centuries: newCenturies.length > 0 ? newCenturies : null, + }; + }; + return ( ); diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index b682bad9b..021b3ff2c 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -1,32 +1,50 @@ import { PeopleFilters } from './PeopleFilters'; import { Loader } from './Loader'; import { PeopleTable } from './PeopleTable'; +import { useState } from 'react'; +import { getPeople } from '../api'; +import { useEffect } from 'react'; +import { Person } from '../types'; export const PeoplePage = () => { + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + const [list, setList] = useState([]); + + useEffect(() => { + getPeople() + .then(people => { + setList(people); + setIsLoading(false); + }) + .catch(() => { + setHasError(true); + setIsLoading(false); + }); + }, []); + return ( <>

People Page

-
-
-
- -
- -
-
- - -

Something went wrong

- -

There are no people on the server

- -

There are no people matching the current search criteria

- - + {isLoading ? ( + + ) : hasError ? ( +

+ Something went wrong +

+ ) : list.length === 0 ? ( +

There are no people on the server

+ ) : ( +
+
+ +
+
+
-
+ )}
); diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx index fdd814b4a..5c65ba21c 100644 --- a/src/components/PeopleTable.tsx +++ b/src/components/PeopleTable.tsx @@ -1,645 +1,207 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ -export const PeopleTable = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +import { Person } from '../types'; +import { Link, useLocation, useSearchParams } from 'react-router-dom'; +import { SearchLink } from './SearchLink'; +import { SearchParams } from '../utils/searchHelper'; - - - - - - - - +const PersonLink = ({ person }: { person: Person }) => { + return ( + + ); +}; - - - - - - - - +export const PeopleTable = ({ list }: { list: Person[] }) => { + const path = useLocation().pathname; + const [searchParams] = useSearchParams(); + const sex = searchParams.get('sex') || null; + const centuries = searchParams.getAll('centuries'); + const currentOrder = searchParams.get('order'); - - - - - - - - + type SortField = 'name' | 'sex' | 'born' | 'died'; + const sortFields: SortField[] = ['name', 'sex', 'born', 'died']; + const sortParam = searchParams.get('sort'); - - - - - - - - + const currentSort: SortField | null = + sortParam && sortFields.includes(sortParam as SortField) + ? (sortParam as SortField) + : null; - - - - - - - - + const getSortParams = (param: string): SearchParams => { + if (currentSort !== param) { + return { sort: param, order: null }; + } - - - - - - - - + if (currentOrder === null) { + return { sort: param, order: 'desc' }; + } - - - - - - - - + return { sort: null, order: null }; + }; - - - - - - - - + const getSortIcon = (param: string) => { + if (currentSort !== param) { + return 'fas fa-sort'; + } - - - - - - - - + if (currentOrder === 'desc') { + return 'fas fa-sort-down'; + } - - - - - - - - + return 'fas fa-sort-up'; + }; - - - - - - - - + const sortPeople = (a: Person, b: Person): number => { + if (currentSort === null) { + return 0; + } - - - - - - - - + if (currentSort === 'sex' || currentSort === 'name') { + const valueA = a[currentSort]; + const valueB = b[currentSort]; - - - - - - - - + if (currentOrder === 'desc') { + return valueB.localeCompare(valueA); + } - - - - - - - - + return valueA.localeCompare(valueB); + } - - - - - - - - + const valueA = a[currentSort]; + const valueB = b[currentSort]; - - - - - - - - - - - - - - - - - - - - - - - - - - + if (currentOrder === 'desc') { + return valueB - valueA; + } - - - - - - - - - - - - - - - - - + return valueA - valueB; + }; - - - - - - - - - - - - - - - - - - -
- - Name - - - - - - - - - Sex - - - - - - - - - Born - - - - - - - - - Died - - - - - - - MotherFather
- 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 -
+ + {person.name} + +
- - 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 -
+ return ( +
+
+
+
+ + + + + + + + + + + + + + + + + + {list + .filter(person => !sex || person.sex === sex) + .filter(person => { + const personCentury = Math.ceil(person.born / 100); + + return ( + centuries.length === 0 || + centuries.includes(String(personCentury)) + ); + }) + .sort(sortPeople) + .map(person => { + const mother = list.find(p => p.name === person.motherName); + const father = list.find(p => p.name === person.fatherName); + + return ( + + + + + + + {mother ? ( + + ) : ( + + )} + {father ? ( + + ) : ( + + )} + + ); + })} + +
+ + Name + + + + + + + + + Sex + + + + + + + + + Born + + + + + + + + + Died + + + + + + + MotherFather
+ + {person.name} + + {person.sex}{person.born}{person.died}{person.motherName || '-'}{person.fatherName || '-'}
+
+
+
+
); }; diff --git a/src/index.tsx b/src/index.tsx index d72ba5730..5c0e95b7c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,5 @@ import { createRoot } from 'react-dom/client'; -import { HashRouter as Router } from 'react-router-dom'; +import { HashRouter } from 'react-router-dom'; import 'bulma/css/bulma.css'; import '@fortawesome/fontawesome-free/css/all.css'; @@ -7,7 +7,7 @@ import '@fortawesome/fontawesome-free/css/all.css'; import { App } from './App'; createRoot(document.getElementById('root') as HTMLDivElement).render( - + - , + , ); From c0876034746f1e1a8abf48baa1c968e6ef8a2cd5 Mon Sep 17 00:00:00 2001 From: Alternate001 Date: Tue, 7 Jul 2026 15:36:18 +0200 Subject: [PATCH 2/3] solution --- src/components/PeopleFilters.tsx | 16 +++++++++- src/components/PeopleTable.tsx | 55 ++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index ff9832ab0..594199d36 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -2,7 +2,7 @@ import { SearchLink } from './SearchLink'; import { useSearchParams } from 'react-router-dom'; export const PeopleFilters = () => { - const [searchParams] = useSearchParams(); + const [searchParams, setSearchParams] = useSearchParams(); const selectedCenturies = searchParams.getAll('centuries'); const getCenturyParams = (century: string) => { @@ -15,6 +15,17 @@ export const PeopleFilters = () => { }; }; + const InputHandler = (value: string) => { + const copy = new URLSearchParams(searchParams); + + copy.set('query', value); + if (value === '') { + copy.delete('query'); + } + + setSearchParams(copy); + }; + return (