From a55e75d29efc4061a5fa3a7c76e7b71aa69c925a Mon Sep 17 00:00:00 2001 From: Denys Cheporniuk Date: Sun, 23 Oct 2022 22:39:58 +0300 Subject: [PATCH 1/2] initial commit --- package.json | 2 +- src/App.scss | 8 + src/App.tsx | 45 ++-- src/components/Button/Button.tsx | 15 ++ src/components/Button/index.ts | 1 + src/components/PeopleTable/PeopleTable.tsx | 237 +++++++++++++++++++++ src/components/PeopleTable/index.ts | 1 + 7 files changed, 286 insertions(+), 23 deletions(-) create mode 100644 src/components/Button/Button.tsx create mode 100644 src/components/Button/index.ts create mode 100644 src/components/PeopleTable/PeopleTable.tsx create mode 100644 src/components/PeopleTable/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.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..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..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/PeopleTable/PeopleTable.tsx b/src/components/PeopleTable/PeopleTable.tsx new file mode 100644 index 00000000..5c50f603 --- /dev/null +++ b/src/components/PeopleTable/PeopleTable.tsx @@ -0,0 +1,237 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Person } from '../../types/Person'; +import { Button } from '../Button'; +import peopleFromServer from '../../people.json'; + +enum SortField { + Name = 'name', + Slug = 'slug', + Born = 'born', + Died = 'died', + Null = 'null', +} + +type State = { + selectedPeople: Person[]; + people: Person[]; + sortField: SortField; +}; + +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 class PeopleTable extends React.Component<{}, State> { + state: Readonly = { + selectedPeople: [], + people: peopleFromServer, + sortField: SortField.Null, + }; + + moveDown = (person: Person) => { + this.setState((state: State) => { + const peopleCopy = [...state.people]; + + const personIndex = peopleCopy.findIndex( + personA => personA.slug === person.slug, + ); + + if (personIndex === peopleCopy.length - 1) { + return null; + } + + peopleCopy[personIndex + 1] = person; + peopleCopy[personIndex] = state.people[personIndex + 1]; + + return ({ + people: peopleCopy, + }); + }); + }; + + moveUp = (person: Person) => { + this.setState((state: State) => { + const peopleCopy = [...state.people]; + + const personIndex = peopleCopy.findIndex( + personA => personA.slug === person.slug, + ); + + if (personIndex === 0) { + return null; + } + + peopleCopy[personIndex - 1] = person; + peopleCopy[personIndex] = state.people[personIndex - 1]; + + return ({ + people: peopleCopy, + }); + }); + }; + + selectPerson = (person: Person) => { + this.setState(({ selectedPeople }) => { + const newSelectedPeople = [...selectedPeople, person]; + + return ({ + selectedPeople: newSelectedPeople, + }); + }); + }; + + unselectPerson = (person: Person) => { + this.setState(({ selectedPeople }) => ({ + selectedPeople: selectedPeople.filter( + selectedPerson => selectedPerson.slug !== person.slug, + ), + })); + }; + + clearSelectedPeople = () => { + this.setState({ + selectedPeople: [], + }); + }; + + setSortField = (sortField: SortField) => { + this.setState({ + sortField, + }); + } + + render() { + const { + selectedPeople, + people, + sortField, + } = this.state; + + if (people.length === 0) { + return ( +

No people data yet

+ ); + } + + function isPersonSelected(person: Person) { + return selectedPeople.some( + selectedPerson => selectedPerson.slug === person.slug, + ); + } + + const visiblePeople = sortByField(people, sortField); + + return ( + + + + + + + + + + + + + + {visiblePeople.map(person => ( + + + + + + + + + + ))} + +
+ {selectedPeople.length > 0 && ( +
select + name + + this.setSortField(SortField.Name)} + > + + + + + sexborn
+ {isPersonSelected(person) + ? ( + + ) + : ( + + )} + + {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'; From 945fe49c84520808c47cfd896fd3d82442f4ee2d Mon Sep 17 00:00:00 2001 From: Denys Cheporniuk Date: Sun, 23 Oct 2022 23:45:42 +0300 Subject: [PATCH 2/2] implement react people table --- src/App.tsx | 42 ++- src/components/PeopleTable/PeopleTable.tsx | 296 +++++++++------------ 2 files changed, 146 insertions(+), 192 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a3cb0745..e9cb3635 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } 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, setLoading] = useState(false); - componentDidMount() { + useEffect(() => { setTimeout(() => { - this.setState({ - isLoaded: true, - }); + setLoading(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 5c50f603..c5dfbbb9 100644 --- a/src/components/PeopleTable/PeopleTable.tsx +++ b/src/components/PeopleTable/PeopleTable.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import classNames from 'classnames'; import { Person } from '../../types/Person'; import { Button } from '../Button'; @@ -12,12 +12,6 @@ enum SortField { Null = 'null', } -type State = { - selectedPeople: Person[]; - people: Person[]; - sortField: SortField; -}; - function sortByField(items: Person[], field: SortField) { return [...items].sort((personA, personB) => { switch (field) { @@ -35,203 +29,175 @@ function sortByField(items: Person[], field: SortField) { }); } -export class PeopleTable extends React.Component<{}, State> { - state: Readonly = { - selectedPeople: [], - people: peopleFromServer, - sortField: SortField.Null, - }; +export const PeopleTable: React.FC<{}> = () => { + const [people, setPeople] = useState(peopleFromServer); + const [selectedPeople, setSelectedPeople] = useState([]); + const [sortField, setSortField] = useState(SortField.Null); - moveDown = (person: Person) => { - this.setState((state: State) => { - const peopleCopy = [...state.people]; + const moveDown = (person: Person) => { + setPeople(currentPeople => { + const peopleCopy = [...currentPeople]; const personIndex = peopleCopy.findIndex( personA => personA.slug === person.slug, ); if (personIndex === peopleCopy.length - 1) { - return null; + return currentPeople; } peopleCopy[personIndex + 1] = person; - peopleCopy[personIndex] = state.people[personIndex + 1]; + peopleCopy[personIndex] = currentPeople[personIndex + 1]; - return ({ - people: peopleCopy, - }); + return peopleCopy; }); }; - moveUp = (person: Person) => { - this.setState((state: State) => { - const peopleCopy = [...state.people]; + const moveUp = (person: Person) => { + setPeople(currentPeople => { + const peopleCopy = [...currentPeople]; const personIndex = peopleCopy.findIndex( personA => personA.slug === person.slug, ); if (personIndex === 0) { - return null; + return currentPeople; } peopleCopy[personIndex - 1] = person; - peopleCopy[personIndex] = state.people[personIndex - 1]; + peopleCopy[personIndex] = currentPeople[personIndex - 1]; - return ({ - people: peopleCopy, - }); + return peopleCopy; }); }; - selectPerson = (person: Person) => { - this.setState(({ selectedPeople }) => { - const newSelectedPeople = [...selectedPeople, person]; - - return ({ - selectedPeople: newSelectedPeople, - }); - }); + const selectPerson = (person: Person) => { + setSelectedPeople(currentPeople => [...currentPeople, person]); }; - unselectPerson = (person: Person) => { - this.setState(({ selectedPeople }) => ({ - selectedPeople: selectedPeople.filter( + const unselectPerson = (person: Person) => { + setSelectedPeople((currentPeople) => ( + currentPeople.filter( selectedPerson => selectedPerson.slug !== person.slug, - ), - })); + ) + )); }; - clearSelectedPeople = () => { - this.setState({ - selectedPeople: [], - }); + const clearSelectedPeople = () => { + setSelectedPeople([]); }; - setSortField = (sortField: SortField) => { - this.setState({ - sortField, - }); - } - - render() { - const { - selectedPeople, - people, - sortField, - } = this.state; - - if (people.length === 0) { - return ( -

No people data yet

- ); - } - - function isPersonSelected(person: Person) { - return selectedPeople.some( - selectedPerson => selectedPerson.slug === person.slug, - ); - } + const isPersonSelected = (person: Person) => { + return selectedPeople.some( + selectedPerson => selectedPerson.slug === person.slug, + ); + }; - const visiblePeople = sortByField(people, sortField); + const visiblePeople = sortByField(people, sortField); + if (people.length === 0) { return ( - - - - - - - - - - - +

No people data yet

+ ); + } - - {visiblePeople.map(person => ( - + + + + + + + + + + + + + {visiblePeople.map(person => ( + + + - + + - - - - - - - ))} - -
- {selectedPeople.length > 0 && ( -
select - name - - this.setSortField(SortField.Name)} - > - - - - - sexborn
+ {selectedPeople.length > 0 && ( +
select + name + + setSortField(SortField.Name)} + > + + + + + sexborn
+ {isPersonSelected(person) + ? ( + + ) + : ( + + )} + - - {isPersonSelected(person) - ? ( - - ) - : ( - - )} - + {person.sex}{person.born} + {person.sex}{person.born} - - - -
- ); - } -} + ↓ + + + + + + + + ))} + + + ); +};