diff --git a/package.json b/package.json index 2b097dc1..1cc7b2bd 100755 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@cypress/webpack-dev-server": "^1.8.4", "@mate-academy/cypress-tools": "^1.0.4", "@mate-academy/eslint-config-react-typescript": "^1.0.11", - "@mate-academy/scripts": "^1.2.3", + "@mate-academy/scripts": "^1.2.8", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^17.0.45", diff --git a/src/App.scss b/src/App.scss index 93f0af3a..4d26146b 100644 --- a/src/App.scss +++ b/src/App.scss @@ -2,3 +2,11 @@ iframe { display: none; } + +button { + cursor: pointer; +} + +tr { + transition: 0.3s background-color; +} diff --git a/src/App.tsx b/src/App.tsx index f6361547..7cd1f6e3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,39 +1,28 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import '@fortawesome/fontawesome-free/css/all.css'; import 'bulma/css/bulma.css'; import './App.scss'; -import peopleFromServer from './people.json'; +import { Loader } from './components/Loader'; +import { PeopleList } from './components/PeopleList'; -export class App extends React.Component { - state = {}; +export const App: React.FC = () => { + const [isLoaded, setIsLoaded] = useState(false); - render() { - return ( -
-

People table

+ useEffect(() => { + setTimeout(() => { + setIsLoaded(true); + }, 1000); + }, []); - - - - - - - - + return ( +
+

People table

-
- {peopleFromServer.map(person => ( - - - - - - ))} - -
namesexborn
{person.name}{person.sex}{person.born}
-
- ); - } -} + {isLoaded + ? + : } + + ); +}; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx new file mode 100644 index 00000000..33fd3cf8 --- /dev/null +++ b/src/components/Button/Button.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +type Props = React.ButtonHTMLAttributes & { + children: React.ReactNode; +}; + +export const Button: React.FC = ({ children, className, ...props }) => ( + +); diff --git a/src/components/Button/index.ts b/src/components/Button/index.ts new file mode 100644 index 00000000..8b166a86 --- /dev/null +++ b/src/components/Button/index.ts @@ -0,0 +1 @@ +export * from './Button'; diff --git a/src/components/PeopleList/PeopleList.tsx b/src/components/PeopleList/PeopleList.tsx new file mode 100644 index 00000000..13663868 --- /dev/null +++ b/src/components/PeopleList/PeopleList.tsx @@ -0,0 +1,118 @@ +import React, { useState } from 'react'; +import { Person } from '../../types/Person'; +import peopleFromServer from '../../people.json'; +import { SelectedPeople } from '../SelectedPeople'; +import { PeopleTable } from '../PeopleTable'; +import { SortField } from '../../types/SortFields'; + +function sortByField(items: Person[], field: SortField) { + return [...items].sort((personA, personB) => { + switch (field) { + case SortField.Born: + case SortField.Died: + return personA[field] - personB[field]; + + case SortField.Name: + case SortField.Slug: + return personA[field].localeCompare(personB[field]); + + default: + return 0; + } + }); +} + +export const PeopleList: React.FC<{}> = () => { + const [people, setPeople] = useState(peopleFromServer); + + const moveDown = (person: Person) => { + setPeople(currentPeople => { + const peopleCopy = [...currentPeople]; + + const personIndex = peopleCopy.findIndex( + personA => personA.slug === person.slug, + ); + + if (personIndex === peopleCopy.length - 1) { + return currentPeople; + } + + peopleCopy[personIndex + 1] = person; + peopleCopy[personIndex] = currentPeople[personIndex + 1]; + + return peopleCopy; + }); + }; + + const moveUp = (person: Person) => { + setPeople(currentPeople => { + const peopleCopy = [...currentPeople]; + + const personIndex = peopleCopy.findIndex( + personA => personA.slug === person.slug, + ); + + if (personIndex === 0) { + return currentPeople; + } + + peopleCopy[personIndex - 1] = person; + peopleCopy[personIndex] = currentPeople[personIndex - 1]; + + return peopleCopy; + }); + }; + + const selectionToggler = (personId: string) => { + setPeople(currentPeople => ( + currentPeople.map(person => { + if (person.id === personId) { + return { + ...person, + selected: !person.selected, + }; + } + + return person; + }) + )); + }; + + const clearSelectedPeople = () => { + setPeople(currentPeople => ( + currentPeople.map(person => ({ + ...person, + selected: false, + })) + )); + }; + + const sortPeople = (field: SortField) => { + setPeople(currentPeople => ( + sortByField(currentPeople, field) + )); + }; + + if (people.length === 0) { + return ( +

No people data yet

+ ); + } + + return ( + <> + + + + + ); +}; diff --git a/src/components/PeopleList/index.ts b/src/components/PeopleList/index.ts new file mode 100644 index 00000000..487eb57c --- /dev/null +++ b/src/components/PeopleList/index.ts @@ -0,0 +1 @@ +export * from './PeopleList'; diff --git a/src/components/PeopleTable/PeopleTable.tsx b/src/components/PeopleTable/PeopleTable.tsx new file mode 100644 index 00000000..e3f059ac --- /dev/null +++ b/src/components/PeopleTable/PeopleTable.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import cn from 'classnames'; +import { Button } from '../Button'; +import { SortField } from '../../types/SortFields'; +import { Person } from '../../types/Person'; + +type Props = { + people: Person[]; + moveUp: (person: Person) => void; + moveDown: (person: Person) => void; + selectionToggler: (personId: string) => void; + sortPeople: (field: SortField) => void; +}; + +export const PeopleTable: React.FC = ({ + people, + moveUp, + moveDown, + selectionToggler, + sortPeople, +}) => ( + + + + + + + + + + + + {people.map(person => ( + + + + + + + + + + ))} + +
select + name + + sortPeople(SortField.Name)} + > + + + + + sexborn
+ + + {person.name} + {person.sex}{person.born} + + + +
+); diff --git a/src/components/PeopleTable/index.ts b/src/components/PeopleTable/index.ts new file mode 100644 index 00000000..45984e7c --- /dev/null +++ b/src/components/PeopleTable/index.ts @@ -0,0 +1 @@ +export * from './PeopleTable'; diff --git a/src/components/SelectedPeople/SelectedPeople.tsx b/src/components/SelectedPeople/SelectedPeople.tsx new file mode 100644 index 00000000..728ed03f --- /dev/null +++ b/src/components/SelectedPeople/SelectedPeople.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Person } from '../../types/Person'; + +type Props = { + people: Person[]; + clearSelectedPeople: () => void; +}; + +export const SelectedPeople: React.FC = ({ + people, + clearSelectedPeople, +}) => { + const selectedPeople = people.filter(person => person.selected); + + if (selectedPeople.length === 0) { + return ( +

+ No selected people +

+ ); + } + + return ( +
+
+ ); +}; diff --git a/src/components/SelectedPeople/index.ts b/src/components/SelectedPeople/index.ts new file mode 100644 index 00000000..006681ba --- /dev/null +++ b/src/components/SelectedPeople/index.ts @@ -0,0 +1 @@ +export * from './SelectedPeople'; diff --git a/src/people.json b/src/people.json index c9a921dd..20e75582 100644 --- a/src/people.json +++ b/src/people.json @@ -1,353 +1,431 @@ [ { + "selected": false, "name": "Carolus Haverbeke", "sex": "m", "born": 1832, "died": 1905, "fatherName": "Carel Haverbeke", "motherName": "Maria van Brussel", - "slug": "carolus-haverbeke-1832" + "slug": "carolus-haverbeke-1832", + "id": "80e2e424-ecbc-4b43-98fc-80912106bd3e" }, { + "selected": false, "name": "Emma de Milliano", "sex": "f", "born": 1876, "died": 1956, "fatherName": "Petrus de Milliano", "motherName": "Sophia van Damme", - "slug": "emma-de-milliano-1876" + "slug": "emma-de-milliano-1876", + "id": "245e79e8-16f2-46f2-acda-03d224832211" }, { + "selected": false, "name": "Maria de Rycke", "sex": "f", "born": 1683, "died": 1724, "fatherName": "Frederik de Rycke", "motherName": "Laurentia van Vlaenderen", - "slug": "maria-de-rycke-1683" + "slug": "maria-de-rycke-1683", + "id": "d7460ec8-530e-4a8e-82dd-447f8a9cd5ae" }, { + "selected": false, "name": "Jan van Brussel", "sex": "m", "born": 1714, "died": 1748, "fatherName": "Jacobus van Brussel", "motherName": "Joanna van Rooten", - "slug": "jan-van-brussel-1714" + "slug": "jan-van-brussel-1714", + "id": "ac9aadf0-122d-4733-b99e-f8299902875e" }, { + "selected": false, "name": "Philibert Haverbeke", "sex": "m", "born": 1907, "died": 1997, "fatherName": "Emile Haverbeke", "motherName": "Emma de Milliano", - "slug": "philibert-haverbeke-1907" + "slug": "philibert-haverbeke-1907", + "id": "4bbe4da6-acc0-46d4-be93-fbe7ec3415cd" }, { + "selected": false, "name": "Jan Frans van Brussel", "sex": "m", "born": 1761, "died": 1833, "fatherName": "Jacobus Bernardus van Brussel", "motherName": null, - "slug": "jan-frans-van-brussel-1761" + "slug": "jan-frans-van-brussel-1761", + "id": "ff033b7e-83a1-4471-961a-a846c0964b0c" }, { + "selected": false, "name": "Pauwels van Haverbeke", "sex": "m", "born": 1535, "died": 1582, "fatherName": "N. van Haverbeke", "motherName": null, - "slug": "pauwels-van-haverbeke-1535" + "slug": "pauwels-van-haverbeke-1535", + "id": "79dbd0be-c254-4e76-992e-cdb48bab4477" }, { + "selected": false, "name": "Clara Aernoudts", "sex": "f", "born": 1918, "died": 2012, "fatherName": "Henry Aernoudts", "motherName": "Sidonie Coene", - "slug": "clara-aernoudts-1918" + "slug": "clara-aernoudts-1918", + "id": "196d4d1b-19f5-4630-af34-1791767b602f" }, { + "selected": false, "name": "Emile Haverbeke", "sex": "m", "born": 1877, "died": 1968, "fatherName": "Carolus Haverbeke", "motherName": "Maria Sturm", - "slug": "emile-haverbeke-1877" + "slug": "emile-haverbeke-1877", + "id": "29ef4f9e-5cb2-4765-bb91-b216bb53c645" }, { + "selected": false, "name": "Lieven de Causmaecker", "sex": "m", "born": 1696, "died": 1724, "fatherName": "Carel de Causmaecker", "motherName": "Joanna Claes", - "slug": "lieven-de-causmaecker-1696" + "slug": "lieven-de-causmaecker-1696", + "id": "5c0e79ef-9d9c-484e-8367-9f9c90ccbcfb" }, { + "selected": false, "name": "Pieter Haverbeke", "sex": "m", "born": 1602, "died": 1642, "fatherName": "Lieven van Haverbeke", "motherName": null, - "slug": "pieter-haverbeke-1602" + "slug": "pieter-haverbeke-1602", + "id": "af16931d-21f1-4f0a-ae7c-fec6f9dfc4e5" }, { + "selected": false, "name": "Livina Haverbeke", "sex": "f", "born": 1692, "died": 1743, "fatherName": "Daniel Haverbeke", "motherName": "Joanna de Pape", - "slug": "livina-haverbeke-1692" + "slug": "livina-haverbeke-1692", + "id": "bfa53e2c-3c1b-4349-b396-a2a051274550" }, { + "selected": false, "name": "Pieter Bernard Haverbeke", "sex": "m", "born": 1695, "died": 1762, "fatherName": "Willem Haverbeke", "motherName": "Petronella Wauters", - "slug": "pieter-bernard-haverbeke-1695" + "slug": "pieter-bernard-haverbeke-1695", + "id": "2638569b-1f3a-4aac-9308-89bf0e9f6825" }, { + "selected": false, "name": "Lieven van Haverbeke", "sex": "m", "born": 1570, "died": 1636, "fatherName": "Pauwels van Haverbeke", "motherName": "Lievijne Jans", - "slug": "lieven-van-haverbeke-1570" + "slug": "lieven-van-haverbeke-1570", + "id": "f545659f-0c55-4ecf-9546-97b66cd0332b" }, { + "selected": false, "name": "Joanna de Causmaecker", "sex": "f", "born": 1762, "died": 1807, "fatherName": "Bernardus de Causmaecker", "motherName": null, - "slug": "joanna-de-causmaecker-1762" + "slug": "joanna-de-causmaecker-1762", + "id": "e0a22678-4b36-433b-89d7-4d5e9cd27d43" }, { + "selected": false, "name": "Willem Haverbeke", "sex": "m", "born": 1668, "died": 1731, "fatherName": "Lieven Haverbeke", "motherName": "Elisabeth Hercke", - "slug": "willem-haverbeke-1668" + "slug": "willem-haverbeke-1668", + "id": "7ca2c432-274d-430b-8b84-91ba8bb04651" }, { + "selected": false, "name": "Pieter Antone Haverbeke", "sex": "m", "born": 1753, "died": 1798, "fatherName": "Jan Francies Haverbeke", "motherName": "Petronella de Decker", - "slug": "pieter-antone-haverbeke-1753" + "slug": "pieter-antone-haverbeke-1753", + "id": "beac3fd7-a67d-47e5-9314-a9fe35b7f822" }, { + "selected": false, "name": "Maria van Brussel", "sex": "f", "born": 1801, "died": 1834, "fatherName": "Jan Frans van Brussel", "motherName": "Joanna de Causmaecker", - "slug": "maria-van-brussel-1801" + "slug": "maria-van-brussel-1801", + "id": "b7469884-00ad-4eac-aa8e-b5873765d5f7" }, { + "selected": false, "name": "Angela Haverbeke", "sex": "f", "born": 1728, "died": 1734, "fatherName": "Pieter Bernard Haverbeke", "motherName": "Livina de Vrieze", - "slug": "angela-haverbeke-1728" + "slug": "angela-haverbeke-1728", + "id": "658b4e03-f620-43b2-8642-982f153ca483" }, { + "selected": false, "name": "Elisabeth Haverbeke", "sex": "f", "born": 1711, "died": 1754, "fatherName": "Jan Haverbeke", "motherName": "Maria de Rycke", - "slug": "elisabeth-haverbeke-1711" + "slug": "elisabeth-haverbeke-1711", + "id": "64d72b93-fdab-4702-8851-af88918a034c" }, { + "selected": false, "name": "Lievijne Jans", "sex": "f", "born": 1542, "died": 1582, "fatherName": null, "motherName": null, - "slug": "lievijne-jans-1542" + "slug": "lievijne-jans-1542", + "id": "0c9e7d61-4681-4b13-ac1a-d106806924e1" }, { + "selected": false, "name": "Bernardus de Causmaecker", "sex": "m", "born": 1721, "died": 1789, "fatherName": "Lieven de Causmaecker", "motherName": "Livina Haverbeke", - "slug": "bernardus-de-causmaecker-1721" + "slug": "bernardus-de-causmaecker-1721", + "id": "410ebb42-b987-47a4-8a0f-ebc6d06cb798" }, { + "selected": false, "name": "Jacoba Lammens", "sex": "f", "born": 1699, "died": 1740, "fatherName": "Lieven Lammens", "motherName": "Livina de Vrieze", - "slug": "jacoba-lammens-1699" + "slug": "jacoba-lammens-1699", + "id": "186318d9-fd1e-46ba-bf4b-8f1d52530f9a" }, { + "selected": false, "name": "Pieter de Decker", "sex": "m", "born": 1705, "died": 1780, "fatherName": "Joos de Decker", "motherName": "Petronella van de Steene", - "slug": "pieter-de-decker-1705" + "slug": "pieter-de-decker-1705", + "id": "371ad187-976f-4d06-9640-67bf82c85adb" }, { + "selected": false, "name": "Joanna de Pape", "sex": "f", "born": 1654, "died": 1723, "fatherName": "Vincent de Pape", "motherName": "Petronella Wauters", - "slug": "joanna-de-pape-1654" + "slug": "joanna-de-pape-1654", + "id": "5b74f55e-67f0-4805-ab03-cedd9571eb27" }, { + "selected": false, "name": "Daniel Haverbeke", "sex": "m", "born": 1652, "died": 1723, "fatherName": "Lieven Haverbeke", "motherName": "Elisabeth Hercke", - "slug": "daniel-haverbeke-1652" + "slug": "daniel-haverbeke-1652", + "id": "cd17b587-efc8-4708-a568-0a150d931eb8" }, { + "selected": false, "name": "Lieven Haverbeke", "sex": "m", "born": 1631, "died": 1676, "fatherName": "Pieter Haverbeke", "motherName": "Anna van Hecke", - "slug": "lieven-haverbeke-1631" + "slug": "lieven-haverbeke-1631", + "id": "b5d91ab5-0955-40a3-bd17-7b19dfa7efe6" }, { + "selected": false, "name": "Martina de Pape", "sex": "f", "born": 1666, "died": 1727, "fatherName": "Vincent de Pape", "motherName": "Petronella Wauters", - "slug": "martina-de-pape-1666" + "slug": "martina-de-pape-1666", + "id": "c7dd2ac1-8f99-4171-80b3-e748720cbe9e" }, { + "selected": false, "name": "Jan Francies Haverbeke", "sex": "m", "born": 1725, "died": 1779, "fatherName": "Pieter Bernard Haverbeke", "motherName": "Livina de Vrieze", - "slug": "jan-francies-haverbeke-1725" + "slug": "jan-francies-haverbeke-1725", + "id": "4558bad9-533f-4fc6-aa82-14af28a98519" }, { + "selected": false, "name": "Maria Haverbeke", "sex": "m", "born": 1905, "died": 1997, "fatherName": "Emile Haverbeke", "motherName": "Emma de Milliano", - "slug": "maria-haverbeke-1905" + "slug": "maria-haverbeke-1905", + "id": "31f8dcb0-1875-4b5f-855b-bdfa62d8549d" }, { + "selected": false, "name": "Petronella de Decker", "sex": "f", "born": 1731, "died": 1781, "fatherName": "Pieter de Decker", "motherName": "Livina Haverbeke", - "slug": "petronella-de-decker-1731" + "slug": "petronella-de-decker-1731", + "id": "d090e67f-6671-434b-b1fd-243d6d50ee71" }, { + "selected": false, "name": "Livina Sierens", "sex": "f", "born": 1761, "died": 1826, "fatherName": "Jan Sierens", "motherName": "Maria van Waes", - "slug": "livina-sierens-1761" + "slug": "livina-sierens-1761", + "id": "05c1bdbb-dc7e-4bd8-98e8-c4db91052439" }, { + "selected": false, "name": "Laurentia Haverbeke", "sex": "f", "born": 1710, "died": 1786, "fatherName": "Jan Haverbeke", "motherName": "Maria de Rycke", - "slug": "laurentia-haverbeke-1710" + "slug": "laurentia-haverbeke-1710", + "id": "2742212c-2e66-494e-858a-d34649693e80" }, { + "selected": false, "name": "Carel Haverbeke", "sex": "m", "born": 1796, "died": 1837, "fatherName": "Pieter Antone Haverbeke", "motherName": "Livina Sierens", - "slug": "carel-haverbeke-1796" + "slug": "carel-haverbeke-1796", + "id": "71b6e760-c32b-4bfd-8373-2a706370d6c4" }, { + "selected": false, "name": "Elisabeth Hercke", "sex": "f", "born": 1632, "died": 1674, "fatherName": "Willem Hercke", "motherName": "Margriet de Brabander", - "slug": "elisabeth-hercke-1632" + "slug": "elisabeth-hercke-1632", + "id": "5639bb8d-fb69-4b99-bab5-65b139ac5e27" }, { + "selected": false, "name": "Jan Haverbeke", "sex": "m", "born": 1671, "died": 1731, "fatherName": "Lieven Haverbeke", "motherName": "Elisabeth Hercke", - "slug": "jan-haverbeke-1671" + "slug": "jan-haverbeke-1671", + "id": "ab40cc90-5cc2-41da-b0a1-2aa2163c36cf" }, { + "selected": false, "name": "Anna van Hecke", "sex": "f", "born": 1607, "died": 1670, "fatherName": "Paschasius van Hecke", "motherName": "Martijntken Beelaert", - "slug": "anna-van-hecke-1607" + "slug": "anna-van-hecke-1607", + "id": "5fb52e13-020f-4254-bdf3-13559aa7fd89" }, { + "selected": false, "name": "Maria Sturm", "sex": "f", "born": 1835, "died": 1917, "fatherName": "Charles Sturm", "motherName": "Seraphina Spelier", - "slug": "maria-sturm-1835" + "slug": "maria-sturm-1835", + "id": "c0ebb75a-90e6-4f7c-aae2-d73e02aaee53" }, { + "selected": false, "name": "Jacobus Bernardus van Brussel", "sex": "m", "born": 1736, "died": 1809, "fatherName": "Jan van Brussel", "motherName": "Elisabeth Haverbeke", - "slug": "jacobus-bernardus-van-brussel-1736" + "slug": "jacobus-bernardus-van-brussel-1736", + "id": "72df7d4e-12d8-4739-bf1e-8325e31d3cc9" } ] diff --git a/src/types/Person.ts b/src/types/Person.ts index 5e65e546..721e66dc 100644 --- a/src/types/Person.ts +++ b/src/types/Person.ts @@ -1,4 +1,5 @@ export interface Person { + id: string; born: number; died: number; fatherName: string | null; @@ -6,4 +7,5 @@ export interface Person { name: string; sex: string; slug: string; + selected: boolean; } diff --git a/src/types/SortFields.ts b/src/types/SortFields.ts new file mode 100644 index 00000000..708e655b --- /dev/null +++ b/src/types/SortFields.ts @@ -0,0 +1,7 @@ +export enum SortField { + Name = 'name', + Slug = 'slug', + Born = 'born', + Died = 'died', + Null = 'null', +}