From 1e8fbd1c916bf23a14588b215f0b84fe5056269a Mon Sep 17 00:00:00 2001 From: annatkachenko Date: Tue, 31 Jan 2023 17:48:36 +0200 Subject: [PATCH 1/4] Add people table component --- src/App.tsx | 45 ++++---- src/components/Button/Button.tsx | 17 +++ src/components/Button/index.tsx | 1 + src/components/PeopleTable/PeopleTable.tsx | 115 +++++++++++++++++++++ src/components/PeopleTable/index.tsx | 1 + 5 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 src/components/Button/Button.tsx create mode 100644 src/components/Button/index.tsx create mode 100644 src/components/PeopleTable/PeopleTable.tsx create mode 100644 src/components/PeopleTable/index.tsx diff --git a/src/App.tsx b/src/App.tsx index f6361547..a3cb0745 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,35 +4,36 @@ 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 { PeopleTable } from './components/PeopleTable'; -export class App extends React.Component { - state = {}; +type State = { + isLoaded: boolean; +}; + +export class App extends React.Component<{}, State> { + state: Readonly = { + isLoaded: false, + }; + + componentDidMount() { + setTimeout(() => { + this.setState({ + isLoaded: true, + }); + }, 1000); + } render() { + const { isLoaded } = this.state; + 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..c3631fe3 --- /dev/null +++ b/src/components/Button/Button.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +type Props = React.HtmlHTMLAttributes & { + children: React.ReactNode, +}; + +export const Button: React.FC = ({ children, className, ...props }) => { + return ( + + ); +}; diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx new file mode 100644 index 00000000..8b166a86 --- /dev/null +++ b/src/components/Button/index.tsx @@ -0,0 +1 @@ +export * from './Button'; diff --git a/src/components/PeopleTable/PeopleTable.tsx b/src/components/PeopleTable/PeopleTable.tsx new file mode 100644 index 00000000..bee95a56 --- /dev/null +++ b/src/components/PeopleTable/PeopleTable.tsx @@ -0,0 +1,115 @@ +import React from 'react'; + +import '@fortawesome/fontawesome-free/css/all.css'; +import 'bulma/css/bulma.css'; + +import cn from 'classnames'; + +import peopleFromServer from '../../people.json'; +import { Person } from '../../types/Person'; +import { Button } from '../Button'; + +type State = { + people: Person[], + selectedPerson: Person | null, +}; + +export class PeopleTable extends React.Component<{}, State> { + state: Readonly = { + people: [], + selectedPerson: null, + }; + + componentDidMount() { + this.setState({ + people: peopleFromServer, + }); + } + + selectPerson = (person: Person | null) => { + this.setState({ + selectedPerson: person, + }); + } + + render() { + const { people, selectedPerson } = this.state; + + if (people.length === 0) { + return ( +

+ No people on server +

+ ); + } + + return ( + + + + + + + + + + + + + + {people.map(person => { + const isPersonSelected = person.slug === selectedPerson?.slug; + + return ( + + + + + + + ); + })} + +
+ {selectedPerson + ? `${selectedPerson.name}` + : 'No selected person'} +
-namesexborn
+ {isPersonSelected + ? ( + + ) + : ( + + )} + + {person.name} + {person.sex}{person.born}
+ ); + } +} diff --git a/src/components/PeopleTable/index.tsx b/src/components/PeopleTable/index.tsx new file mode 100644 index 00000000..45984e7c --- /dev/null +++ b/src/components/PeopleTable/index.tsx @@ -0,0 +1 @@ +export * from './PeopleTable'; From 42812be659e04af0d5f295e1f4a16bc8d7a25600 Mon Sep 17 00:00:00 2001 From: annatkachenko Date: Thu, 2 Feb 2023 12:56:28 +0200 Subject: [PATCH 2/4] Add ability to select several people and sort the array --- src/components/PeopleTable/PeopleTable.tsx | 115 ++++++++++++++++++--- src/types/SortField.ts | 5 + 2 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 src/types/SortField.ts diff --git a/src/components/PeopleTable/PeopleTable.tsx b/src/components/PeopleTable/PeopleTable.tsx index bee95a56..ef186294 100644 --- a/src/components/PeopleTable/PeopleTable.tsx +++ b/src/components/PeopleTable/PeopleTable.tsx @@ -8,16 +8,39 @@ import cn from 'classnames'; import peopleFromServer from '../../people.json'; import { Person } from '../../types/Person'; import { Button } from '../Button'; +import { SortField } from '../../types/SortField'; type State = { people: Person[], - selectedPerson: Person | null, + selectedPeople: Person[], + sortBy: SortField; }; +function getReorderedPeople(people: Person[], sortBy: SortField) { + const peopleCopy = [...people]; + + peopleCopy.sort((personA, personB) => { + switch (sortBy) { + case SortField.Name: + return personA.name.localeCompare(personB.name); + + case SortField.Born: + return personA.born - personB.born; + + case SortField.None: + default: + return 0; + } + }); + + return peopleCopy; +} + export class PeopleTable extends React.Component<{}, State> { state: Readonly = { people: [], - selectedPerson: null, + selectedPeople: [], + sortBy: SortField.None, }; componentDidMount() { @@ -26,16 +49,48 @@ export class PeopleTable extends React.Component<{}, State> { }); } - selectPerson = (person: Person | null) => { + selectPerson = (person: Person) => { + this.setState((state) => ({ + selectedPeople: [...state.selectedPeople, person], + })); + }; + + unselectPerson = (person: Person) => { + this.setState((state) => ({ + selectedPeople: state.selectedPeople.filter( + (selectedPerson) => selectedPerson.slug !== person.slug, + ), + })); + } + + isPersonSelected = (person: Person) => { + const { + selectedPeople, + } = this.state; + + return selectedPeople.some( + selectedPerson => selectedPerson.slug === person.slug, + ); + } + + clearSelectedPeople = () => { this.setState({ - selectedPerson: person, + selectedPeople: [], + }); + } + + changeSortBy = (sortField: SortField) => { + this.setState({ + sortBy: sortField, }); } render() { - const { people, selectedPerson } = this.state; + const { people, selectedPeople, sortBy } = this.state; + + const visiblePeople = getReorderedPeople(people, sortBy); - if (people.length === 0) { + if (visiblePeople.length === 0) { return (

No people on server @@ -46,23 +101,44 @@ export class PeopleTable extends React.Component<{}, State> { return ( - + + - + + - {people.map(person => { - const isPersonSelected = person.slug === selectedPerson?.slug; + {visiblePeople.map(person => { + const isPersonSelected = this.isPersonSelected(person); return ( { ? ( ) : ( @@ -90,8 +168,11 @@ export class PeopleTable extends React.Component<{}, State> { onClick={() => { this.selectPerson(person); }} + className="is-small is-rounded is-success" > - + + + + )} diff --git a/src/types/SortField.ts b/src/types/SortField.ts new file mode 100644 index 00000000..d10c18b2 --- /dev/null +++ b/src/types/SortField.ts @@ -0,0 +1,5 @@ +export enum SortField { + None = 'none', + Name = 'name', + Born = 'born', +} From 8356e6085477e23f9c0b8c06a0eb9f0ff05216a5 Mon Sep 17 00:00:00 2001 From: annatkachenko Date: Fri, 3 Feb 2023 12:51:36 +0200 Subject: [PATCH 3/4] Replace Class components with Functional ones --- src/App.tsx | 42 ++-- src/components/PeopleTable/PeopleTable.tsx | 268 +++++++++------------ 2 files changed, 134 insertions(+), 176 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a3cb0745..b6630aaf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import '@fortawesome/fontawesome-free/css/all.css'; import 'bulma/css/bulma.css'; @@ -7,34 +7,22 @@ import './App.scss'; import { Loader } from './components/Loader'; import { PeopleTable } from './components/PeopleTable'; -type State = { - isLoaded: boolean; -}; - -export class App extends React.Component<{}, State> { - state: Readonly = { - isLoaded: false, - }; +export const App: React.FC = () => { + const [isLoaded, setIsLoaded] = useState(false); - componentDidMount() { + useEffect(() => { setTimeout(() => { - this.setState({ - isLoaded: true, - }); + setIsLoaded(true); }, 1000); - } + }, []); - render() { - const { isLoaded } = this.state; + return ( +
+

People table

- return ( -
-

People table

- - {isLoaded - ? - : } -
- ); - } -} + {isLoaded + ? + : } +
+ ); +}; diff --git a/src/components/PeopleTable/PeopleTable.tsx b/src/components/PeopleTable/PeopleTable.tsx index ef186294..1c6a6d44 100644 --- a/src/components/PeopleTable/PeopleTable.tsx +++ b/src/components/PeopleTable/PeopleTable.tsx @@ -1,21 +1,14 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import '@fortawesome/fontawesome-free/css/all.css'; import 'bulma/css/bulma.css'; import cn from 'classnames'; - import peopleFromServer from '../../people.json'; import { Person } from '../../types/Person'; import { Button } from '../Button'; import { SortField } from '../../types/SortField'; -type State = { - people: Person[], - selectedPeople: Person[], - sortBy: SortField; -}; - function getReorderedPeople(people: Person[], sortBy: SortField) { const peopleCopy = [...people]; @@ -36,161 +29,138 @@ function getReorderedPeople(people: Person[], sortBy: SortField) { return peopleCopy; } -export class PeopleTable extends React.Component<{}, State> { - state: Readonly = { - people: [], - selectedPeople: [], - sortBy: SortField.None, - }; +export const PeopleTable: React.FC = () => { + const [people, setPeople] = useState([]); + const [selectedPeople, setSelectedPeople] = useState([]); + const [sortBy, setSortBy] = useState(SortField.None); - componentDidMount() { - this.setState({ - people: peopleFromServer, - }); - } + useEffect(() => { + setPeople(peopleFromServer); + }, []); - selectPerson = (person: Person) => { - this.setState((state) => ({ - selectedPeople: [...state.selectedPeople, person], - })); + const selectPerson = (person: Person) => { + setSelectedPeople((currentSelectedPeople) => ( + [...currentSelectedPeople, person] + )); }; - unselectPerson = (person: Person) => { - this.setState((state) => ({ - selectedPeople: state.selectedPeople.filter( + const unselectPerson = (person: Person) => { + setSelectedPeople((currentSelectedPeople) => ( + currentSelectedPeople.filter( (selectedPerson) => selectedPerson.slug !== person.slug, - ), - })); - } - - isPersonSelected = (person: Person) => { - const { - selectedPeople, - } = this.state; + ))); + }; + const isSelected = (person: Person) => { return selectedPeople.some( selectedPerson => selectedPerson.slug === person.slug, ); - } - - clearSelectedPeople = () => { - this.setState({ - selectedPeople: [], - }); - } - - changeSortBy = (sortField: SortField) => { - this.setState({ - sortBy: sortField, - }); - } - - render() { - const { people, selectedPeople, sortBy } = this.state; - - const visiblePeople = getReorderedPeople(people, sortBy); + }; - if (visiblePeople.length === 0) { - return ( -

- No people on server -

- ); - } + const visiblePeople = getReorderedPeople(people, sortBy); + if (visiblePeople.length === 0) { return ( -
- {selectedPerson - ? `${selectedPerson.name}` - : 'No selected person'} + {selectedPeople.length > 0 && ( +
-name { + this.changeSortBy(SortField.Name); + }} + > + name + sexborn { + this.changeSortBy(SortField.Born); + }} + > + born +
- - - - - - - - +

+ No people on server +

+ ); + } -
+ + + ); + })} + +
- {selectedPeople.length > 0 && ( -
- { - this.changeSortBy(SortField.Name); - }} - > - name - sex { - this.changeSortBy(SortField.Born); - }} + return ( + + + + + + + + + + + + + + + + {visiblePeople.map(person => { + const isPersonSelected = isSelected(person); + + return ( + - born - - - - - - {visiblePeople.map(person => { - const isPersonSelected = this.isPersonSelected(person); - - return ( - + {isPersonSelected + ? ( + + ) + : ( + + )} + + - - - - - ); - })} - -
+ {selectedPeople.length > 0 && ( +
- { + setSortBy(SortField.Name); + }} + > + name + sex { + setSortBy(SortField.Born); + }} + > + born +
- - {isPersonSelected - ? ( - - ) - : ( - - )} - - {person.name} - {person.sex}{person.born}
- ); - } -} + {person.name} + +
{person.sex}{person.born}
+ ); +}; From bbe214dcf26b48cb97c8bb72361b8cddbbf0d1f7 Mon Sep 17 00:00:00 2001 From: annatkachenko Date: Fri, 3 Feb 2023 14:53:13 +0200 Subject: [PATCH 4/4] Process isReversed field for the people array --- src/components/PeopleTable/PeopleTable.tsx | 70 +++++++++++++++++----- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/src/components/PeopleTable/PeopleTable.tsx b/src/components/PeopleTable/PeopleTable.tsx index 1c6a6d44..2e51ce3a 100644 --- a/src/components/PeopleTable/PeopleTable.tsx +++ b/src/components/PeopleTable/PeopleTable.tsx @@ -9,7 +9,11 @@ import { Person } from '../../types/Person'; import { Button } from '../Button'; import { SortField } from '../../types/SortField'; -function getReorderedPeople(people: Person[], sortBy: SortField) { +function getReorderedPeople( + people: Person[], + sortBy: SortField, + isReversed: boolean, +) { const peopleCopy = [...people]; peopleCopy.sort((personA, personB) => { @@ -26,6 +30,10 @@ function getReorderedPeople(people: Person[], sortBy: SortField) { } }); + if (isReversed) { + peopleCopy.reverse(); + } + return peopleCopy; } @@ -33,6 +41,7 @@ export const PeopleTable: React.FC = () => { const [people, setPeople] = useState([]); const [selectedPeople, setSelectedPeople] = useState([]); const [sortBy, setSortBy] = useState(SortField.None); + const [isReversed, setIsReversed] = useState(false); useEffect(() => { setPeople(peopleFromServer); @@ -57,7 +66,18 @@ export const PeopleTable: React.FC = () => { ); }; - const visiblePeople = getReorderedPeople(people, sortBy); + const changeSortType = (newSortBy: SortField) => { + const isAlreadySorted = sortBy === newSortBy; + + if (isAlreadySorted) { + setIsReversed((currentIsReversed) => !currentIsReversed); + } else { + setIsReversed(false); + setSortBy(newSortBy); + } + }; + + const visiblePeople = getReorderedPeople(people, sortBy, isReversed); if (visiblePeople.length === 0) { return ( @@ -87,22 +107,44 @@ export const PeopleTable: React.FC = () => { - - { - setSortBy(SortField.Name); - }} - > - name + + name + { + changeSortType(SortField.Name); + }} + > + + sex - { - setSortBy(SortField.Born); - }} - > - born + + born + { + changeSortType(SortField.Born); + }} + > + +