From aba0efb0c6ae17e0c79bab7369fa0e6490cd5a02 Mon Sep 17 00:00:00 2001 From: NazarHaida <nazar.haida.knm.2020@lpnu.ua> Date: Mon, 9 Dec 2024 06:16:06 +0200 Subject: [PATCH] add task solution --- src/App.tsx | 17 +- src/components/Home.tsx | 7 + src/components/Navbar.tsx | 20 +- src/components/PageNotFound.tsx | 9 + src/components/PeopleFilters.tsx | 108 +++-- src/components/PeoplePage.tsx | 9 - src/components/PeopleTable.tsx | 810 +++++++------------------------ src/components/PersonLink.tsx | 20 + 8 files changed, 287 insertions(+), 713 deletions(-) create mode 100644 src/components/Home.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..3309e9836 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,19 +2,22 @@ import { PeoplePage } from './components/PeoplePage'; import { Navbar } from './components/Navbar'; import './App.scss'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import { PageNotFound } from './components/PageNotFound'; +import { Home } from './components/Home'; export const App = () => { return ( <div data-cy="app"> <Navbar /> - <div className="section"> - <div className="container"> - <h1 className="title">Home Page</h1> - <h1 className="title">Page not found</h1> - <PeoplePage /> - </div> - </div> + <Routes> + <Route path="/" element={<Home />} /> + <Route path="/home" element={<Navigate to={'/'} replace={true} />} /> + <Route path="/people" element={<PeoplePage />} /> + <Route path="/people/:slug" element={<PeoplePage />} /> + <Route path="*" element={<PageNotFound />} /> + </Routes> </div> ); }; diff --git a/src/components/Home.tsx b/src/components/Home.tsx new file mode 100644 index 000000000..134153e5d --- /dev/null +++ b/src/components/Home.tsx @@ -0,0 +1,7 @@ +export const Home = () => ( + <main className="section"> + <div className="container"> + <h1 className="title">Home Page</h1> + </div> + </main> +); diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 3f63898b2..e00857b10 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,3 +1,11 @@ +import { NavLink } from 'react-router-dom'; +import classNames from 'classnames'; + +const getLinkClass = ({ isActive }: { isActive: boolean }) => + classNames('navbar-item', { + 'has-background-grey-lighter': isActive, + }); + export const Navbar = () => { return ( <nav @@ -8,17 +16,13 @@ export const Navbar = () => { > <div className="container"> <div className="navbar-brand"> - <a className="navbar-item" href="#/"> + <NavLink className={getLinkClass} to="/"> Home - </a> + </NavLink> - <a - aria-current="page" - className="navbar-item has-background-grey-lighter" - href="#/people" - > + <NavLink className={getLinkClass} to="/people"> People - </a> + </NavLink> </div> </div> </nav> diff --git a/src/components/PageNotFound.tsx b/src/components/PageNotFound.tsx new file mode 100644 index 000000000..7a7c3571c --- /dev/null +++ b/src/components/PageNotFound.tsx @@ -0,0 +1,9 @@ +export const PageNotFound = () => ( + <> + <div className="section"> + <div className="container"> + <h1 className="title">Page not found</h1> + </div> + </div> + </> +); diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx index c9c819cd3..e8e315f5a 100644 --- a/src/components/PeopleFilters.tsx +++ b/src/components/PeopleFilters.tsx @@ -1,18 +1,47 @@ +import { useSearchParams } from 'react-router-dom'; +import { ChangeEvent } from 'react'; +import { getSearchWith, SearchParams } from '../utils/searchHelper'; +import { SearchLink } from './SearchLink'; +import classNames from 'classnames'; + export const PeopleFilters = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const centuries = searchParams.getAll('centuries') || []; + const sex = searchParams.get('sex') || null; + + function setSearchWith(params: SearchParams) { + const search = getSearchWith(searchParams, params); + + setSearchParams(search); + } + + const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => { + setSearchWith({ search: event.target.value || null }); + }; + return ( <nav className="panel"> <p className="panel-heading">Filters</p> <p className="panel-tabs" data-cy="SexFilter"> - <a className="is-active" href="#/people"> + <SearchLink + params={{ sex: null }} + className={classNames({ 'is-active': !sex })} + > All - </a> - <a className="" href="#/people?sex=m"> + </SearchLink> + <SearchLink + params={{ sex: 'm' }} + className={classNames({ 'is-active': sex === 'm' })} + > Male - </a> - <a className="" href="#/people?sex=f"> + </SearchLink> + <SearchLink + params={{ sex: 'f' }} + className={classNames({ 'is-active': sex === 'f' })} + > Female - </a> + </SearchLink> </p> <div className="panel-block"> @@ -22,6 +51,7 @@ export const PeopleFilters = () => { type="search" className="input" placeholder="Search" + onChange={event => handleSearchChange(event)} /> <span className="icon is-left"> @@ -33,55 +63,33 @@ export const PeopleFilters = () => { <div className="panel-block"> <div className="level is-flex-grow-1 is-mobile" data-cy="CenturyFilter"> <div className="level-left"> - <a - data-cy="century" - className="button mr-1" - href="#/people?centuries=16" - > - 16 - </a> - - <a - data-cy="century" - className="button mr-1 is-info" - href="#/people?centuries=17" - > - 17 - </a> - - <a - data-cy="century" - className="button mr-1 is-info" - href="#/people?centuries=18" - > - 18 - </a> - - <a - data-cy="century" - className="button mr-1 is-info" - href="#/people?centuries=19" - > - 19 - </a> - - <a - data-cy="century" - className="button mr-1" - href="#/people?centuries=20" - > - 20 - </a> + {[16, 17, 18, 19, 20].map(century => ( + <SearchLink + key={century} + params={{ + centuries: centuries.includes(century.toString()) + ? centuries.filter(c => c !== century.toString()) + : [...centuries, century.toString()], + }} + className={classNames('button mr-1', { + 'is-info': centuries.includes(century.toString()), + })} + data-cy="century" + > + {century} + </SearchLink> + ))} </div> - <div className="level-right ml-4"> - <a + <SearchLink + params={{ centuries: null }} data-cy="centuryALL" - className="button is-success is-outlined" - href="#/people" + className={classNames('button is-success', { + 'is-outlined': centuries.length > 0, + })} > All - </a> + </SearchLink> </div> </div> </div> diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx index b682bad9b..616ee9ee6 100644 --- a/src/components/PeoplePage.tsx +++ b/src/components/PeoplePage.tsx @@ -1,5 +1,4 @@ import { PeopleFilters } from './PeopleFilters'; -import { Loader } from './Loader'; import { PeopleTable } from './PeopleTable'; export const PeoplePage = () => { @@ -15,14 +14,6 @@ export const PeoplePage = () => { <div className="column"> <div className="box table-container"> - <Loader /> - - <p data-cy="peopleLoadingError">Something went wrong</p> - - <p data-cy="noPeopleMessage">There are no people on the server</p> - - <p>There are no people matching the current search criteria</p> - <PeopleTable /> </div> </div> diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx index fdd814b4a..6d4114cca 100644 --- a/src/components/PeopleTable.tsx +++ b/src/components/PeopleTable.tsx @@ -1,645 +1,177 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ -export const PeopleTable = () => { - return ( - <table - data-cy="peopleTable" - className="table is-striped is-hoverable is-narrow is-fullwidth" - > - <thead> - <tr> - <th> - <span className="is-flex is-flex-wrap-nowrap"> - Name - <a href="#/people?sort=name"> - <span className="icon"> - <i className="fas fa-sort" /> - </span> - </a> - </span> - </th> - - <th> - <span className="is-flex is-flex-wrap-nowrap"> - Sex - <a href="#/people?sort=sex"> - <span className="icon"> - <i className="fas fa-sort" /> - </span> - </a> - </span> - </th> - - <th> - <span className="is-flex is-flex-wrap-nowrap"> - Born - <a href="#/people?sort=born&order=desc"> - <span className="icon"> - <i className="fas fa-sort-up" /> - </span> - </a> - </span> - </th> - - <th> - <span className="is-flex is-flex-wrap-nowrap"> - Died - <a href="#/people?sort=died"> - <span className="icon"> - <i className="fas fa-sort" /> - </span> - </a> - </span> - </th> - - <th>Mother</th> - <th>Father</th> - </tr> - </thead> - - <tbody> - <tr data-cy="person"> - <td> - <a href="#/people/pieter-haverbeke-1602">Pieter Haverbeke</a> - </td> - <td>m</td> - <td>1602</td> - <td>1642</td> - <td>-</td> - <td> - <a href="#/people/lieven-van-haverbeke-1570"> - Lieven van Haverbeke - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a className="has-text-danger" href="#/people/anna-van-hecke-1607"> - Anna van Hecke - </a> - </td> - <td>f</td> - <td>1607</td> - <td>1670</td> - <td>Martijntken Beelaert</td> - <td>Paschasius van Hecke</td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/lieven-haverbeke-1631">Lieven Haverbeke</a> - </td> - <td>m</td> - <td>1631</td> - <td>1676</td> - <td> - <a className="has-text-danger" href="#/people/anna-van-hecke-1607"> - Anna van Hecke - </a> - </td> - <td> - <a href="#/people/pieter-haverbeke-1602">Pieter Haverbeke</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/elisabeth-hercke-1632" - > - Elisabeth Hercke - </a> - </td> - <td>f</td> - <td>1632</td> - <td>1674</td> - <td>Margriet de Brabander</td> - <td>Willem Hercke</td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/daniel-haverbeke-1652">Daniel Haverbeke</a> - </td> - <td>m</td> - <td>1652</td> - <td>1723</td> - <td> - <a - className="has-text-danger" - href="#/people/elisabeth-hercke-1632" - > - Elisabeth Hercke - </a> - </td> - <td> - <a href="#/people/lieven-haverbeke-1631">Lieven Haverbeke</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a className="has-text-danger" href="#/people/joanna-de-pape-1654"> - Joanna de Pape - </a> - </td> - <td>f</td> - <td>1654</td> - <td>1723</td> - <td>Petronella Wauters</td> - <td>Vincent de Pape</td> - </tr> - - <tr data-cy="person"> - <td> - <a className="has-text-danger" href="#/people/martina-de-pape-1666"> - Martina de Pape - </a> - </td> - <td>f</td> - <td>1666</td> - <td>1727</td> - <td>Petronella Wauters</td> - <td>Vincent de Pape</td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/willem-haverbeke-1668">Willem Haverbeke</a> - </td> - <td>m</td> - <td>1668</td> - <td>1731</td> - <td> - <a - className="has-text-danger" - href="#/people/elisabeth-hercke-1632" - > - Elisabeth Hercke - </a> - </td> - <td> - <a href="#/people/lieven-haverbeke-1631">Lieven Haverbeke</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/jan-haverbeke-1671">Jan Haverbeke</a> - </td> - <td>m</td> - <td>1671</td> - <td>1731</td> - <td> - <a - className="has-text-danger" - href="#/people/elisabeth-hercke-1632" - > - Elisabeth Hercke - </a> - </td> - <td> - <a href="#/people/lieven-haverbeke-1631">Lieven Haverbeke</a> - </td> - </tr> - - <tr data-cy="person" className="has-background-warning"> - <td> - <a className="has-text-danger" href="#/people/maria-de-rycke-1683"> - Maria de Rycke - </a> - </td> - <td>f</td> - <td>1683</td> - <td>1724</td> - <td>Laurentia van Vlaenderen</td> - <td>Frederik de Rycke</td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/livina-haverbeke-1692" - > - Livina Haverbeke - </a> - </td> - <td>f</td> - <td>1692</td> - <td>1743</td> - <td> - <a className="has-text-danger" href="#/people/joanna-de-pape-1654"> - Joanna de Pape - </a> - </td> - <td> - <a href="#/people/daniel-haverbeke-1652">Daniel Haverbeke</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/pieter-bernard-haverbeke-1695"> - Pieter Bernard Haverbeke - </a> - </td> - <td>m</td> - <td>1695</td> - <td>1762</td> - <td>Petronella Wauters</td> - <td> - <a href="#/people/willem-haverbeke-1668">Willem Haverbeke</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/lieven-de-causmaecker-1696"> - Lieven de Causmaecker - </a> - </td> - <td>m</td> - <td>1696</td> - <td>1724</td> - <td>Joanna Claes</td> - <td>Carel de Causmaecker</td> - </tr> - <tr data-cy="person"> - <td> - <a className="has-text-danger" href="#/people/jacoba-lammens-1699"> - Jacoba Lammens - </a> - </td> - <td>f</td> - <td>1699</td> - <td>1740</td> - <td>Livina de Vrieze</td> - <td>Lieven Lammens</td> - </tr> +import { useEffect, useState } from 'react'; +import { useParams, useSearchParams } from 'react-router-dom'; +import { Person } from '../types'; +import { getPeople } from '../api'; +import { PersonLink } from './PersonLink'; +import { Loader } from './Loader'; - <tr data-cy="person"> - <td> - <a href="#/people/pieter-de-decker-1705">Pieter de Decker</a> - </td> - <td>m</td> - <td>1705</td> - <td>1780</td> - <td>Petronella van de Steene</td> - <td>Joos de Decker</td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/laurentia-haverbeke-1710" - > - Laurentia Haverbeke - </a> - </td> - <td>f</td> - <td>1710</td> - <td>1786</td> - <td> - <a className="has-text-danger" href="#/people/maria-de-rycke-1683"> - Maria de Rycke - </a> - </td> - <td> - <a href="#/people/jan-haverbeke-1671">Jan Haverbeke</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/elisabeth-haverbeke-1711" - > - Elisabeth Haverbeke - </a> - </td> - <td>f</td> - <td>1711</td> - <td>1754</td> - <td> - <a className="has-text-danger" href="#/people/maria-de-rycke-1683"> - Maria de Rycke - </a> - </td> - <td> - <a href="#/people/jan-haverbeke-1671">Jan Haverbeke</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/jan-van-brussel-1714">Jan van Brussel</a> - </td> - <td>m</td> - <td>1714</td> - <td>1748</td> - <td>Joanna van Rooten</td> - <td>Jacobus van Brussel</td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/bernardus-de-causmaecker-1721"> - Bernardus de Causmaecker - </a> - </td> - <td>m</td> - <td>1721</td> - <td>1789</td> - <td> - <a - className="has-text-danger" - href="#/people/livina-haverbeke-1692" - > - Livina Haverbeke - </a> - </td> - <td> - <a href="#/people/lieven-de-causmaecker-1696"> - Lieven de Causmaecker - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/jan-francies-haverbeke-1725"> - Jan Francies Haverbeke - </a> - </td> - <td>m</td> - <td>1725</td> - <td>1779</td> - <td>Livina de Vrieze</td> - <td> - <a href="#/people/pieter-bernard-haverbeke-1695"> - Pieter Bernard Haverbeke - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/angela-haverbeke-1728" - > - Angela Haverbeke - </a> - </td> - <td>f</td> - <td>1728</td> - <td>1734</td> - <td>Livina de Vrieze</td> - <td> - <a href="#/people/pieter-bernard-haverbeke-1695"> - Pieter Bernard Haverbeke - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/petronella-de-decker-1731" - > - Petronella de Decker - </a> - </td> - <td>f</td> - <td>1731</td> - <td>1781</td> - <td> - <a - className="has-text-danger" - href="#/people/livina-haverbeke-1692" - > - Livina Haverbeke - </a> - </td> - <td> - <a href="#/people/pieter-de-decker-1705">Pieter de Decker</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/jacobus-bernardus-van-brussel-1736"> - Jacobus Bernardus van Brussel - </a> - </td> - <td>m</td> - <td>1736</td> - <td>1809</td> - <td> - <a - className="has-text-danger" - href="#/people/elisabeth-haverbeke-1711" - > - Elisabeth Haverbeke - </a> - </td> - <td> - <a href="#/people/jan-van-brussel-1714">Jan van Brussel</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/pieter-antone-haverbeke-1753"> - Pieter Antone Haverbeke - </a> - </td> - <td>m</td> - <td>1753</td> - <td>1798</td> - <td> - <a - className="has-text-danger" - href="#/people/petronella-de-decker-1731" - > - Petronella de Decker - </a> - </td> - <td> - <a href="#/people/jan-francies-haverbeke-1725"> - Jan Francies Haverbeke - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/jan-frans-van-brussel-1761"> - Jan Frans van Brussel - </a> - </td> - <td>m</td> - <td>1761</td> - <td>1833</td> - <td>-</td> - <td> - <a href="#/people/jacobus-bernardus-van-brussel-1736"> - Jacobus Bernardus van Brussel - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a className="has-text-danger" href="#/people/livina-sierens-1761"> - Livina Sierens - </a> - </td> - <td>f</td> - <td>1761</td> - <td>1826</td> - <td>Maria van Waes</td> - <td>Jan Sierens</td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/joanna-de-causmaecker-1762" - > - Joanna de Causmaecker - </a> - </td> - <td>f</td> - <td>1762</td> - <td>1807</td> - <td>-</td> - <td> - <a href="#/people/bernardus-de-causmaecker-1721"> - Bernardus de Causmaecker - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/carel-haverbeke-1796">Carel Haverbeke</a> - </td> - <td>m</td> - <td>1796</td> - <td>1837</td> - <td> - <a className="has-text-danger" href="#/people/livina-sierens-1761"> - Livina Sierens - </a> - </td> - <td> - <a href="#/people/pieter-antone-haverbeke-1753"> - Pieter Antone Haverbeke - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/maria-van-brussel-1801" - > - Maria van Brussel - </a> - </td> - <td>f</td> - <td>1801</td> - <td>1834</td> - <td> - <a - className="has-text-danger" - href="#/people/joanna-de-causmaecker-1762" - > - Joanna de Causmaecker - </a> - </td> - <td> - <a href="#/people/jan-frans-van-brussel-1761"> - Jan Frans van Brussel - </a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a href="#/people/carolus-haverbeke-1832">Carolus Haverbeke</a> - </td> - <td>m</td> - <td>1832</td> - <td>1905</td> - <td> - <a - className="has-text-danger" - href="#/people/maria-van-brussel-1801" - > - Maria van Brussel - </a> - </td> - <td> - <a href="#/people/carel-haverbeke-1796">Carel Haverbeke</a> - </td> - </tr> - - <tr data-cy="person"> - <td> - <a className="has-text-danger" href="#/people/maria-sturm-1835"> - Maria Sturm - </a> - </td> - <td>f</td> - <td>1835</td> - <td>1917</td> - <td>Seraphina Spelier</td> - <td>Charles Sturm</td> - </tr> - - <tr data-cy="person"> - <td> - <a - className="has-text-danger" - href="#/people/emma-de-milliano-1876" - > - Emma de Milliano - </a> - </td> - <td>f</td> - <td>1876</td> - <td>1956</td> - <td>Sophia van Damme</td> - <td>Petrus de Milliano</td> - </tr> +export const PeopleTable = () => { + const [searchParams] = useSearchParams(); + const [people, setPeople] = useState<Person[]>([]); + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(false); + const [errorMessageNoMatch, setErrorMessageNoMatch] = useState(false); + const centuries = searchParams.getAll('centuries'); + const search = searchParams.get('search') || ''; + const sex = searchParams.getAll('sex'); + const { slug } = useParams(); + + const filteredPeople = people.filter(person => { + const matchesSex = !sex.length || sex.includes(person.sex); + + const matchesCentury = + !centuries.length || + centuries.includes(Math.floor(person.born / 100 + 1).toString()); + + const matchesSearch = + !search || + person.name.toLowerCase().includes(search.toLowerCase()) || + person.motherName?.toLowerCase().includes(search.toLowerCase()) || + person.fatherName?.toLowerCase().includes(search.toLowerCase()); + + return matchesSex && matchesCentury && matchesSearch; + }); + + const chosenPerson = people.find(p => slug === p.slug); + + useEffect(() => { + if (!isLoading && people.length > 0 && filteredPeople.length === 0) { + setErrorMessageNoMatch(true); + } else { + setErrorMessageNoMatch(false); + } + }, [people, filteredPeople, isLoading]); + + useEffect(() => { + setIsLoading(true); + getPeople() + .then(setPeople) + .catch(() => { + setErrorMessage(true); + }) + .finally(() => { + setIsLoading(false); + }); + }, []); - <tr data-cy="person"> - <td> - <a href="#/people/emile-haverbeke-1877">Emile Haverbeke</a> - </td> - <td>m</td> - <td>1877</td> - <td>1968</td> - <td> - <a className="has-text-danger" href="#/people/maria-sturm-1835"> - Maria Sturm - </a> - </td> - <td> - <a href="#/people/carolus-haverbeke-1832">Carolus Haverbeke</a> - </td> - </tr> - </tbody> - </table> + return ( + <div> + {isLoading && <Loader />} + + {errorMessage && ( + <p data-cy="peopleLoadingError" className="has-text-danger"> + Something went wrong + </p> + )} + + {errorMessageNoMatch && ( + <p data-cy="noMatchingPeople"> + There are no people matching the current search criteria + </p> + )} + + {people.length === 0 && !isLoading ? ( + <p data-cy="noPeopleMessage">There are no people on the server</p> + ) : ( + !isLoading && + !errorMessageNoMatch && ( + <table + data-cy="peopleTable" + className="table is-striped is-hoverable is-narrow is-fullwidth" + > + <thead> + <tr> + <th> + <span className="is-flex is-flex-wrap-nowrap"> + Name + <a href="#/people?sort=name"> + <span className="icon"> + <i className="fas fa-sort" /> + </span> + </a> + </span> + </th> + + <th> + <span className="is-flex is-flex-wrap-nowrap"> + Sex + <a href="#/people?sort=sex"> + <span className="icon"> + <i className="fas fa-sort" /> + </span> + </a> + </span> + </th> + + <th> + <span className="is-flex is-flex-wrap-nowrap"> + Born + <a href="#/people?sort=born&order=desc"> + <span className="icon"> + <i className="fas fa-sort-up" /> + </span> + </a> + </span> + </th> + + <th> + <span className="is-flex is-flex-wrap-nowrap"> + Died + <a href="#/people?sort=died"> + <span className="icon"> + <i className="fas fa-sort" /> + </span> + </a> + </span> + </th> + + <th>Mother</th> + <th>Father</th> + </tr> + </thead> + + <tbody> + {filteredPeople.map(person => ( + <tr + data-cy="person" + key={person.slug} + className={ + chosenPerson === person ? `has-background-warning` : '' + } + > + <td> + <PersonLink + person={person} + people={people} + name={person.name} + /> + </td> + + <td>{person.sex}</td> + <td>{person.born}</td> + <td>{person.died}</td> + <td> + <PersonLink + person={people.find(p => p.name === person.motherName)} + people={people} + name={person.motherName} + /> + </td> + <td> + <PersonLink + person={people.find(p => p.name === person.fatherName)} + people={people} + name={person.fatherName} + /> + </td> + </tr> + ))} + </tbody> + </table> + ) + )} + </div> ); }; diff --git a/src/components/PersonLink.tsx b/src/components/PersonLink.tsx new file mode 100644 index 000000000..41512fcca --- /dev/null +++ b/src/components/PersonLink.tsx @@ -0,0 +1,20 @@ +import { NavLink } from 'react-router-dom'; + +export const PersonLink = ({ person, people, name }) => { + if (!name) { + return '-'; + } + + const foundPerson = people.find(p => p.name === name); + + return foundPerson ? ( + <NavLink + to={`/people/${person.slug}`} + className={`has-text-link ${person.sex === 'f' ? 'has-text-danger' : ''}`} + > + {name} + </NavLink> + ) : ( + name + ); +};