diff --git a/src/App.tsx b/src/App.tsx index adcb8594e..1069f401e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,9 @@ -import { PeoplePage } from './components/PeoplePage'; +import { Navigate, Route, Routes } from 'react-router-dom'; + import { Navbar } from './components/Navbar'; +import { PeoplePage } from './components/PeoplePage'; +import { HomePage } from './pages/HomePage'; +import { NotFoundPage } from './pages/NotFoundPage'; import './App.scss'; @@ -10,9 +14,18 @@ export const App = () => {
-

Home Page

-

Page not found

- + + } /> + + } /> + + + } /> + } /> + + + } /> +
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 3f63898b2..aaa3334fd 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,4 +1,8 @@ +import { NavLink, useSearchParams } from 'react-router-dom'; + export const Navbar = () => { + const [searchParams] = useSearchParams(); + return ( diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index c9c819cd3..b11266264 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -1,18 +1,51 @@ +import { ChangeEvent } from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; + +import { SearchLink } from './SearchLink'; + export const PeopleFilters = () => { + const [searchParams, setSearchParams] = useSearchParams(); + + const query = searchParams.get('query') || ''; + const selectedCenturies = searchParams.getAll('centuries'); + const centuries = ['16', '17', '18', '19', '20']; + const sex = searchParams.get('sex') || ''; + + const handleQueryChange = (inputEvent: ChangeEvent) => { + const newParams = new URLSearchParams(searchParams); + const newQuery = inputEvent.target.value; + + if (newQuery) { + newParams.set('query', newQuery); + } else { + newParams.delete('query'); + } + + setSearchParams(newParams); + }; + return ( ); diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index b682bad9b..09e97be3e 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -1,29 +1,153 @@ -import { PeopleFilters } from './PeopleFilters'; +import { useEffect, useState } from 'react'; +import { useParams, useSearchParams } from 'react-router-dom'; + +import { getPeople } from '../api'; +import { Person } from '../types'; + import { Loader } from './Loader'; +import { PeopleFilters } from './PeopleFilters'; import { PeopleTable } from './PeopleTable'; +function getVisiblePeople( + people: Person[], + query: string, + centuries: string[], + sort: string, + order: string, + sex: string, +): Person[] { + let visiblePeople = [...people]; + + const normalizedQuery = query.trim().toLowerCase(); + + if (normalizedQuery) { + visiblePeople = visiblePeople.filter(person => { + const name = person.name.toLowerCase(); + const motherName = person.motherName?.toLowerCase() || ''; + const fatherName = person.fatherName?.toLowerCase() || ''; + + return ( + name.includes(normalizedQuery) || + motherName.includes(normalizedQuery) || + fatherName.includes(normalizedQuery) + ); + }); + } + + if (sex) { + visiblePeople = visiblePeople.filter(person => person.sex === sex); + } + + if (centuries.length > 0) { + visiblePeople = visiblePeople.filter(person => { + const century = Math.ceil(person.born / 100).toString(); + + return centuries.includes(century); + }); + } + + if (sort) { + visiblePeople.sort((person1, person2) => { + switch (sort) { + case 'name': + case 'sex': + return person1[sort].localeCompare(person2[sort]); + + case 'born': + case 'died': + return person1[sort] - person2[sort]; + + default: + return 0; + } + }); + + if (order === 'desc') { + visiblePeople.reverse(); + } + } + + return visiblePeople; +} + export const PeoplePage = () => { + const { slug } = useParams(); + const [searchParams] = useSearchParams(); + + const [people, setPeople] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [hasLoadingError, setHasLoadingError] = useState(false); + + const query = searchParams.get('query') || ''; + const centuries = searchParams.getAll('centuries'); + const sort = searchParams.get('sort') || ''; + const order = searchParams.get('order') || ''; + const sex = searchParams.get('sex') || ''; + + const visiblePeople = getVisiblePeople( + people, + query, + centuries, + sort, + order, + sex, + ); + + useEffect(() => { + getPeople() + .then(setPeople) + .catch(() => { + setHasLoadingError(true); + }) + .finally(() => { + setIsLoading(false); + }); + }, []); + + const shouldShowNoMatchingPeople = + !isLoading && + !hasLoadingError && + people.length > 0 && + visiblePeople.length === 0; + return ( <>

People Page

-
- -
+ {!isLoading && !hasLoadingError && people.length > 0 && ( +
+ +
+ )}
- + {isLoading && } -

Something went wrong

+ {!isLoading && hasLoadingError && ( +

Something went wrong

+ )} -

There are no people on the server

+ {!isLoading && !hasLoadingError && people.length === 0 && ( +

+ There are no people on the server +

+ )} -

There are no people matching the current search criteria

+ {shouldShowNoMatchingPeople && ( +

There are no people matching the current search criteria

+ )} - + {!isLoading && !hasLoadingError && visiblePeople.length > 0 && ( + + )}
diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx index fdd814b4a..f2c9bdd2f 100644 --- a/src/components/PeopleTable.tsx +++ b/src/components/PeopleTable.tsx @@ -1,5 +1,45 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ -export const PeopleTable = () => { +import { Person } from '../types/Person'; +import { PersonLink } from './PersonLink'; +import { SearchLink } from './SearchLink'; + +type Props = { + people: Person[]; + selectedSlug?: string; + sort: string; + order: string; +}; + +export const PeopleTable = ({ people, selectedSlug, sort, order }: Props) => { + const getSortParams = (field: string) => { + if (sort !== field) { + return { + sort: field, + order: null, + }; + } + + if (!order) { + return { + sort: field, + order: 'desc', + }; + } + + return { + sort: null, + order: null, + }; + }; + + const getSortIcon = (field: string) => { + if (sort !== field) { + return 'fas fa-sort'; + } + + return order === 'desc' ? 'fas fa-sort-down' : 'fas fa-sort-up'; + }; + return ( { @@ -57,588 +97,44 @@ export const PeopleTable = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {people.map(person => { + const mother = people.find(p => p.name === person.motherName); + const father = people.find(p => p.name === person.fatherName); + + 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 -
+ + {person.sex}{person.born}{person.died} + {mother ? ( + + ) : ( + person.motherName || '-' + )} + + {father ? ( + + ) : ( + person.fatherName || '-' + )} +
); diff --git a/src/components/PersonLink.tsx b/src/components/PersonLink.tsx new file mode 100644 index 000000000..a2c4c2711 --- /dev/null +++ b/src/components/PersonLink.tsx @@ -0,0 +1,23 @@ +import { Link, useSearchParams } from 'react-router-dom'; + +import { Person } from '../types/Person'; + +type Props = { + person: Person; +}; + +export const PersonLink = ({ person }: Props) => { + const [searchParams] = useSearchParams(); + + return ( + + {person.name} + + ); +}; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx new file mode 100644 index 000000000..153c06306 --- /dev/null +++ b/src/pages/HomePage.tsx @@ -0,0 +1 @@ +export const HomePage = () =>

Home Page

; diff --git a/src/pages/NotFoundPage.tsx b/src/pages/NotFoundPage.tsx new file mode 100644 index 000000000..a7c487626 --- /dev/null +++ b/src/pages/NotFoundPage.tsx @@ -0,0 +1 @@ +export const NotFoundPage = () =>

Page not found

;