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
-
-
-
-
- name |
- sex |
- born |
-
-
-
-
-
- Carolus Haverbeke |
- m |
- 1832 |
-
-
-
- Emma de Milliano |
- f |
- 1842 |
-
-
-
-
- );
- }
-}
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,
+ })}
+ >
+ {person.name} |
+
+ {person.sex}
+ |
+ {person.born} |
+ {isSelected ? (
+
+ ) : (
+
+ )}
+
+ );
+ })}
+
+
+);
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();