diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..718b1932 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,103 @@ +/* eslint-disable import/order */ +import React, { useState } from 'react'; + +import '@fortawesome/fontawesome-free/css/all.css'; +import 'bulma/css/bulma.css'; +import './App.scss'; + +import peopleFromServer from './people.json'; +import { PeopleHeader } from './components/PeopleHeader'; +import { PeopleFilters } from './components/PeopleFilters'; +import { PeopleTable } from './components/PeopleTable'; +import { FILTERBYSEX, SORTBY } from './constants'; + +function getFilteredPeople(people, sex, query) { + let filteredPeople = [...people]; + + if (query) { + const normalizedQuery = query.toLowerCase(); + + filteredPeople = filteredPeople.filter(person => ( + person.name.toLowerCase().startsWith(normalizedQuery) + )); + } + + if (sex !== 'all') { + filteredPeople = filteredPeople.filter(person => person.sex === sex); + } + + return filteredPeople; +} + +function sortPeople(people, sortBy) { + const sortedPeople = [...people]; + + if (!Object.keys(sortBy).length) { + return sortedPeople; + } + + const [property] = Object.entries(sortBy); + + const [key, value] = property; + + switch (key) { + case SORTBY.BORN: + sortedPeople.sort((a, b) => { + return value === 'ASC' + ? a.born - b.born + : b.born - a.born; + }); + break; + + case SORTBY.NAME: + case SORTBY.SEX: + sortedPeople.sort((a, b) => { + return value === 'ASC' + ? a[key].localeCompare(b[key]) + : b[key].localeCompare(a[key]); + }); + break; + + default: + return sortedPeople; + } + + return sortedPeople; +} + +export const App = () => { + const [selectedPeople, setSelectedPeople] = useState([]); + const [filterBy, setFilterBy] = useState(FILTERBYSEX.ALL); + const [query, setQuery] = useState(''); + const [sortBy, setSortBy] = useState({}); + + const peopleForRendering = getFilteredPeople( + peopleFromServer, filterBy, query, + ); + const visiblePeople = sortPeople(peopleForRendering, sortBy); + + const selectedPeopleNames = selectedPeople.map(person => person.name) + .join(', '); + + return ( +
+ + + + + +
+ ); +}; diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index 5d253930..00000000 --- a/src/App.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; - -import '@fortawesome/fontawesome-free/css/all.css'; -import 'bulma/css/bulma.css'; -import './App.scss'; - -// import peopleFromServer from './people.json'; - -export class App extends React.Component { - state = {}; - - render() { - return ( -
-

People table

- - - - - - - - - - - - - - - - - - - - - - - -
namesexborn
Carolus Haverbekem1832
Emma de Millianof1842
-
- ); - } -} diff --git a/src/components/Button.jsx b/src/components/Button.jsx new file mode 100644 index 00000000..796e69a2 --- /dev/null +++ b/src/components/Button.jsx @@ -0,0 +1,19 @@ +export const Button = ({ + type = 'button', + onClick, + className = 'button', + children, + ...props +}) => { + return ( + + ); +}; diff --git a/src/components/PeopleFilters.jsx b/src/components/PeopleFilters.jsx new file mode 100644 index 00000000..aa204835 --- /dev/null +++ b/src/components/PeopleFilters.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import cn from 'classnames'; +import { Button } from './Button'; +import { FILTERBYSEX } from '../constants'; + +export const PeopleFilters = ({ + setFilterBy, + filterBy, + setQuery, +}) => { + return ( + <> + + + + + + + { + setQuery(event.target.value); + }} + /> + + ); +}; diff --git a/src/components/PeopleHeader.jsx b/src/components/PeopleHeader.jsx new file mode 100644 index 00000000..8b545d07 --- /dev/null +++ b/src/components/PeopleHeader.jsx @@ -0,0 +1,11 @@ +export const PeopleHeader = ({ selectedPeopleNames }) => { + return ( +

+ {selectedPeopleNames.length === 0 ? ( + 'People table' + ) : ( + `Selected person is ${selectedPeopleNames}` + )} +

+ ); +}; diff --git a/src/components/PeopleTable.jsx b/src/components/PeopleTable.jsx new file mode 100644 index 00000000..e84d01fa --- /dev/null +++ b/src/components/PeopleTable.jsx @@ -0,0 +1,115 @@ +import React from 'react'; +import cn from 'classnames'; +import { SORTBY } from '../constants'; +import { Button } from './Button'; + +function isSelected(selected, current) { + return selected === current; +} + +export const PeopleTable = ({ + setSortBy, + sortBy, + peopleToRender, + selectedPeople, + setSelectedPeople, +}) => ( + + + + + + + + + {peopleToRender.map((person) => { + const isPersonSelected = selectedPeople.includes(person); + + return ( + { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + isPersonSelected + ? setSelectedPeople(selectedPeople.filter(p => ( + p.slug !== person.slug + ))) + : setSelectedPeople([...selectedPeople, person]); + }} + className={cn({ + 'has-background-warning': isPersonSelected, + })} + > + + + + {isSelected ? ( + + ) : ( + + )} + + ); + })} + +
+ + + + + +
{person.name} + {person.sex} + {person.born}
+); diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 00000000..092c0938 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,11 @@ +export const FILTERBYSEX = { + ALL: 'all', + M: 'm', + F: 'f', +}; + +export const SORTBY = { + NAME: 'name', + SEX: 'sex', + BORN: 'born', +}; diff --git a/src/index.tsx b/src/index.jsx similarity index 70% rename from src/index.tsx rename to src/index.jsx index b69de798..22a3129e 100644 --- a/src/index.tsx +++ b/src/index.jsx @@ -2,7 +2,7 @@ import { createRoot } from 'react-dom/client'; import { App } from './App'; const root = createRoot( - document.getElementById('root') as HTMLDivElement, + document.getElementById('root'), ); root.render();