From 2332e2454bfc29c5922f17e484dfe38d5fe55f9b Mon Sep 17 00:00:00 2001 From: Denys Cheporniuk Date: Sun, 5 Feb 2023 20:03:39 +0200 Subject: [PATCH 1/2] initial commit --- package.json | 2 +- src/App.tsx | 258 +++++++++++++++++++++++++++---- src/components/Button/Button.tsx | 15 ++ src/components/Button/index.ts | 1 + 4 files changed, 244 insertions(+), 32 deletions(-) create mode 100644 src/components/Button/Button.tsx create mode 100644 src/components/Button/index.ts 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.tsx b/src/App.tsx index f6361547..2f16615a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,39 +1,235 @@ -import React from 'react'; +import { useState } from 'react'; +import classNames from 'classnames'; import '@fortawesome/fontawesome-free/css/all.css'; import 'bulma/css/bulma.css'; -import './App.scss'; import peopleFromServer from './people.json'; +import { Person } from './types/Person'; +import { Button } from './components/Button'; -export class App extends React.Component { - state = {}; - - render() { - return ( -
-

People table

- - - - - - - - - - - - {peopleFromServer.map(person => ( - - - - - - ))} - -
namesexborn
{person.name}{person.sex}{person.born}
-
+export const App = () => { + const [people, setPeople] = useState(peopleFromServer); + const [selectedPeople, setSelectedPeople] = useState([]); + const [sortField, setSortField] = useState(''); + const [isReversed, setReversed] = useState(false); + + if (people.length === 0) { + return

No people yet

; + } + + function isSelected({ slug }: Person) { + return selectedPeople.some(person => person.slug === slug); + } + + const sortBy = (field: string) => { + const isFirstClick = sortField !== field; + const isSecondClick = sortField === field && !isReversed; + + setSortField(isFirstClick || isSecondClick ? field : ''); + setReversed(isSecondClick); + }; + + const selectPerson = (personToAdd: Person) => { + setSelectedPeople(current => [...current, personToAdd]); + }; + + const unselectPerson = (personToDelete: Person) => { + setSelectedPeople(current => current.filter( + person => person.slug !== personToDelete.slug, + )); + }; + + const clearSelection = () => { + setSelectedPeople([]); + }; + + const moveUp = (personToMove: Person) => { + setPeople(currentPeople => { + const position = currentPeople.findIndex( + person => person.slug === personToMove.slug, + ); + + if (position === 0) { + return currentPeople; + } + + return [ + ...currentPeople.slice(0, position - 1), + currentPeople[position], + currentPeople[position - 1], + ...currentPeople.slice(position + 1), + ]; + }); + }; + + const moveDown = (personToMove: Person) => { + setPeople(curentPeople => { + const position = curentPeople.findIndex( + person => person.slug === personToMove.slug, + ); + + if (position === curentPeople.length - 1) { + return curentPeople; + } + + const updatedPeople = [...curentPeople]; + + updatedPeople[position] = curentPeople[position + 1]; + updatedPeople[position + 1] = curentPeople[position]; + + return updatedPeople; + }); + }; + + const visiblePeople = [...people]; + + if (sortField) { + visiblePeople.sort( + (a, b) => { + switch (sortField) { + case 'name': + case 'sex': + return a[sortField].localeCompare(b[sortField]); + + case 'born': + return a.born - b.born; + + default: + return 0; + } + }, ); } -} + + if (isReversed) { + visiblePeople.reverse(); + } + + return ( + + + + + + + + + + + + + + + + + + {visiblePeople.map(person => ( + + + + + + + + + + + ))} + +
+

+ {selectedPeople.length === 0 ? '-' : ( + <> + {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} +

+ name + sortBy('name')}> + + + + + + sex + sortBy('sex')}> + + + + + + born + sortBy('born')}> + + + + +
+ {isSelected(person) ? ( + + ) : ( + + )} + + {person.name} + {person.sex}{person.born} + + + +
+ ); +}; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx new file mode 100644 index 00000000..655f09a1 --- /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'; From 0eed0fc5125c1dc028c8ec539c49e6527fd5bb16 Mon Sep 17 00:00:00 2001 From: Denys Cheporniuk Date: Sun, 5 Feb 2023 20:27:49 +0200 Subject: [PATCH 2/2] add people filtering --- src/App.tsx | 130 +++++++++++++++++++-------- src/components/SortLink/SortLink.tsx | 24 +++++ src/components/SortLink/index.ts | 1 + 3 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 src/components/SortLink/SortLink.tsx create mode 100644 src/components/SortLink/index.ts diff --git a/src/App.tsx b/src/App.tsx index 2f16615a..eeac0549 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,12 +7,15 @@ import 'bulma/css/bulma.css'; import peopleFromServer from './people.json'; import { Person } from './types/Person'; import { Button } from './components/Button'; +import { SortLink } from './components/SortLink'; export const App = () => { const [people, setPeople] = useState(peopleFromServer); const [selectedPeople, setSelectedPeople] = useState([]); - const [sortField, setSortField] = useState(''); + const [sortField, setSortField] = useState(''); const [isReversed, setReversed] = useState(false); + const [query, setQuery] = useState(''); + const [sex, setSex] = useState(''); if (people.length === 0) { return

No people yet

; @@ -22,7 +25,7 @@ export const App = () => { return selectedPeople.some(person => person.slug === slug); } - const sortBy = (field: string) => { + const sortBy = (field: keyof Person) => { const isFirstClick = sortField !== field; const isSecondClick = sortField === field && !isReversed; @@ -82,7 +85,27 @@ export const App = () => { }); }; - const visiblePeople = [...people]; + let visiblePeople = [...people]; + + if (sex) { + visiblePeople = visiblePeople.filter(person => person.sex === sex); + } + + if (query) { + const lowerQuery = query.toLocaleLowerCase(); + + visiblePeople = visiblePeople.filter(person => { + const stringToCheck = ` + ${person.name} + ${person.motherName || ''} + ${person.fatherName || ''} + `; + + return stringToCheck + .toLocaleLowerCase() + .includes(lowerQuery); + }); + } if (sortField) { visiblePeople.sort( @@ -90,7 +113,13 @@ export const App = () => { switch (sortField) { case 'name': case 'sex': - return a[sortField].localeCompare(b[sortField]); + case 'motherName': + case 'fatherName': { + const aValue = a[sortField] || ''; + const bValue = a[sortField] || ''; + + return aValue.localeCompare(bValue); + } case 'born': return a.born - b.born; @@ -122,6 +151,31 @@ export const App = () => { )}

+ +
+
+ setQuery(e.target.value)} + /> + + + + +
+
+ +
+
+ +
+
@@ -129,47 +183,47 @@ export const App = () => { name - sortBy('name')}> - - - - + sortBy('name')} + /> sex - sortBy('sex')}> - - - - + sortBy('sex')} + /> born - sortBy('born')}> - - - - + sortBy('born')} + /> + + + + Mother + sortBy('motherName')} + /> + + + + Father + sortBy('fatherName')} + /> @@ -217,6 +271,8 @@ export const App = () => { {person.sex} {person.born} + {person.motherName || ''} + {person.fatherName || ''}