From 8facbbd25835406b7d175f0ebc6ea7d5135a0645 Mon Sep 17 00:00:00 2001 From: AlenaLoik Date: Thu, 7 May 2020 16:08:48 +0300 Subject: [PATCH 1/3] solution --- README.md | 2 +- src/App.js | 197 +++++++++++++-------- src/components/NewTodo/NewTodo.css | 0 src/components/NewTodo/NewTodo.js | 49 +++++ src/components/TodoList/TodoList.css | 0 src/components/TodoList/TodoList.js | 43 +++++ src/components/TodosFilter/TodosFilter.css | 0 src/components/TodosFilter/TodosFilter.js | 36 ++++ src/index.css | 2 +- src/index.js | 2 +- 10 files changed, 258 insertions(+), 73 deletions(-) create mode 100644 src/components/NewTodo/NewTodo.css create mode 100644 src/components/NewTodo/NewTodo.js create mode 100644 src/components/TodoList/TodoList.css create mode 100644 src/components/TodoList/TodoList.js create mode 100644 src/components/TodosFilter/TodosFilter.css create mode 100644 src/components/TodosFilter/TodosFilter.js diff --git a/README.md b/README.md index dbb87e138..8c49628b1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # React ToDo App -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_todo-app/) +- Replace `` with your Github username in the [DEMO LINK](https://alenaloik.github.io/react_todo-app/) - Follow the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline) ## Description diff --git a/src/App.js b/src/App.js index 13ea9e061..4017274b7 100644 --- a/src/App.js +++ b/src/App.js @@ -1,85 +1,142 @@ -import React from 'react'; +import React, { Component } from 'react'; +import { TodoList } from './components/TodoList/TodoList'; +import NewTodo from './components/NewTodo/NewTodo'; +import { TodosFilter } from './components/TodosFilter/TodosFilter'; -function App() { - return ( -
-
-

todos

+class App extends Component { + state = { + todos: [], + showParam: 'all', + }; - -
+ addTodo = (todo) => { + this.setState(prevState => ({ + todos: [...prevState.todos, todo], + })); + } -
- - + updateTodosToShow = (todoToShow) => { + this.setState({ showParam: todoToShow }); + } -
    -
  • -
    - - -
    - -
  • + handleRemuve = (id) => { + this.setState(prevState => ({ + todos: prevState.todos.filter(todo => ( + todo.id !== id + )), + })); + } -
  • -
    - - -
    - -
  • + handleRemuveCompleted = () => { + this.setState(prevState => ({ + todos: prevState.todos.filter(todo => ( + !todo.completed + )), + })); + } -
  • -
    - - -
    - -
  • + toggleComplete = (id) => { + this.setState(prevState => ({ + todos: prevState.todos.map((todo) => { + if (todo.id === id) { + return { + ...todo, + completed: !todo.completed, + }; + } -
  • -
    - - -
    - -
  • -
-
+ return todo; + }), + })); + } -
- - 3 items left - + toggleCompleteAll = () => { + if (this.state.todos.every(todo => (todo.completed))) { + this.setState(prevState => ({ + todos: prevState.todos.map(todo => ({ + ...todo, + completed: false, + })), + })); + } else { + this.setState(prevState => ({ + todos: prevState.todos.map(todo => ({ + ...todo, + completed: true, + })), + })); + } + } -
    -
  • - All -
  • + render() { + let todoView = []; -
  • - Active -
  • + switch (this.state.showParam) { + case 'active': + todoView = [...this.state.todos].filter(todo => ( + !todo.completed + )); + break; + case 'completed': + todoView = [...this.state.todos].filter(todo => ( + todo.completed + )); + break; + default: + todoView = [...this.state.todos]; + } -
  • - Completed -
  • -
+ return ( +
+
+

todos

+ +
+ {(this.state.todos.length) + ? ( + <> +
+ + + +
- -
-
- ); +
+ + {this.state.todos.filter(todo => ( + !todo.completed)).length} + item left + + + { + (this.state.todos.filter(todo => ( + todo.completed)).length) + ? ( + + ) + : ('') + } +
+ + ) : ''} + + ); + } } export default App; diff --git a/src/components/NewTodo/NewTodo.css b/src/components/NewTodo/NewTodo.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/NewTodo/NewTodo.js b/src/components/NewTodo/NewTodo.js new file mode 100644 index 000000000..32777a399 --- /dev/null +++ b/src/components/NewTodo/NewTodo.js @@ -0,0 +1,49 @@ +import React, { Component } from 'react'; +import './NewTodo.css'; +import PropTypes from 'prop-types'; + +class NewTodo extends Component { + state = { + content: '', + id: 1, + completed: false, + } + + handleChange = (event) => { + this.setState({ + content: event.target.value.trim(), + }); + } + + handleSubmit = (e) => { + e.preventDefault(); + + if (this.state.content) { + this.props.addTodo(this.state); + + this.setState(prevState => ({ + content: '', + id: prevState.id + 1, + })); + } + } + + render() { + return ( +
+ +
+ ); + } +} + +export default NewTodo; + +NewTodo.propTypes = { + addTodo: PropTypes.func.isRequired, +}; diff --git a/src/components/TodoList/TodoList.css b/src/components/TodoList/TodoList.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/TodoList/TodoList.js b/src/components/TodoList/TodoList.js new file mode 100644 index 000000000..e00392016 --- /dev/null +++ b/src/components/TodoList/TodoList.js @@ -0,0 +1,43 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const TodoList = ({ todos, remuve, toggleComplete }) => ( +
    + {todos.map(todo => ( +
  • +
    + { + toggleComplete(todo.id); + }} + type="checkbox" + className="toggle" + id={`todo-${todo.id}`} + /> + +
    + +
  • + ))} +
+); + +TodoList.propTypes = { + todos: PropTypes.arrayOf( + PropTypes.shape({ + content: PropTypes.string.isRequired, + id: PropTypes.number.isRequired, + completed: PropTypes.bool.isRequired, + }), + ).isRequired, + remuve: PropTypes.func.isRequired, + toggleComplete: PropTypes.func.isRequired, +}; diff --git a/src/components/TodosFilter/TodosFilter.css b/src/components/TodosFilter/TodosFilter.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/TodosFilter/TodosFilter.js b/src/components/TodosFilter/TodosFilter.js new file mode 100644 index 000000000..a75393d27 --- /dev/null +++ b/src/components/TodosFilter/TodosFilter.js @@ -0,0 +1,36 @@ +import React from 'react'; +import './TodosFilter.css'; +import PropTypes from 'prop-types'; + +export const TodosFilter = ({ updateTodosToShow }) => ( + +); + +TodosFilter.propTypes = { + updateTodosToShow: PropTypes.func.isRequired, +}; diff --git a/src/index.css b/src/index.css index 5e01b4b2e..282a067eb 100644 --- a/src/index.css +++ b/src/index.css @@ -217,7 +217,7 @@ body { transition: color 0.4s; } -.todo-list li.completed label { +.todo-list li .completed label { color: #d9d9d9; text-decoration: line-through; } diff --git a/src/index.js b/src/index.js index ebde5cecc..a7c2f1e16 100644 --- a/src/index.js +++ b/src/index.js @@ -6,5 +6,5 @@ import App from './App'; ReactDOM.render( , - document.getElementById('root') + document.getElementById('root'), ); From e747ece0258fd1a15515a10fb90a290933b291e4 Mon Sep 17 00:00:00 2001 From: AlenaLoik Date: Sun, 10 May 2020 16:31:53 +0300 Subject: [PATCH 2/3] edit input + esc was add --- src/App.js | 33 +++++++- src/components/TodoList/TodoList.js | 121 +++++++++++++++++++++------- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/src/App.js b/src/App.js index 4017274b7..5b5e37d3e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { TodoList } from './components/TodoList/TodoList'; +import TodoList from './components/TodoList/TodoList'; import NewTodo from './components/NewTodo/NewTodo'; import { TodosFilter } from './components/TodosFilter/TodosFilter'; @@ -27,6 +27,35 @@ class App extends Component { })); } + handleDobleClick = ({ id }) => { + this.setState(prevState => ({ + todos: prevState.todos.map(todo => ( + (todo.id !== id) ? ({ + ...todo, editing: false, + }) : ({ + ...todo, editing: true, + }) + )), + })); + } + + editTodo = (content) => { + if (content) { + this.setState(prevState => ({ + todos: prevState.todos.map(todo => ( + (todo.editing) ? ({ + ...todo, content, + }) : todo)), + })); + } else { + this.setState(prevState => ({ + todos: prevState.todos.filter(todo => ( + !todo.editing + )), + })); + } + } + handleRemuveCompleted = () => { this.setState(prevState => ({ todos: prevState.todos.filter(todo => ( @@ -107,6 +136,8 @@ class App extends Component { todos={todoView} remuve={this.handleRemuve} toggleComplete={this.toggleComplete} + handleDobleClick={this.handleDobleClick} + editTodo={this.editTodo} /> diff --git a/src/components/TodoList/TodoList.js b/src/components/TodoList/TodoList.js index e00392016..e7e16d01c 100644 --- a/src/components/TodoList/TodoList.js +++ b/src/components/TodoList/TodoList.js @@ -1,34 +1,95 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -export const TodoList = ({ todos, remuve, toggleComplete }) => ( -
    - {todos.map(todo => ( -
  • -
    - { - toggleComplete(todo.id); - }} - type="checkbox" - className="toggle" - id={`todo-${todo.id}`} - /> - -
    - -
  • - ))} -
-); +class TodoList extends Component { + state = { + content: '', + submitEditing: true, + } + + handleChange = (event) => { + this.setState({ + content: event.target.value.trim(), + }); + } + + handleSubmit = (e) => { + e.preventDefault(); + this.props.editTodo(this.state.content); + this.setState({ + submitEditing: false, + }); + } + + render() { + return ( +
    + {this.props.todos.map(todo => ( +
  • +
    +
    + { + this.props.toggleComplete(todo.id); + }} + checked={todo.completed} + type="checkbox" + className="toggle" + id={`todo-${todo.id}`} + /> + +
    + + { + if (e.key === 'Escape') { + this.setState({ submitEditing: false }); + } + } + } + /> +
    +
  • + )) + } +
+ ); + } +} + +export default TodoList; TodoList.propTypes = { todos: PropTypes.arrayOf( @@ -40,4 +101,6 @@ TodoList.propTypes = { ).isRequired, remuve: PropTypes.func.isRequired, toggleComplete: PropTypes.func.isRequired, + handleDobleClick: PropTypes.func.isRequired, + editTodo: PropTypes.func.isRequired, }; From 93fd528ce070854fdb799a791e12e1d660105457 Mon Sep 17 00:00:00 2001 From: AlenaLoik Date: Tue, 12 May 2020 16:38:17 +0300 Subject: [PATCH 3/3] fixed --- src/App.js | 67 ++++++++++------------- src/components/Footer/Footer.css | 0 src/components/Footer/Footer.js | 46 ++++++++++++++++ src/components/Header/Header.css | 0 src/components/Header/Header.js | 15 +++++ src/components/NewTodo/NewTodo.js | 13 +++-- src/components/TodoList/TodoList.js | 28 +++++----- src/components/TodosFilter/TodosFilter.js | 46 ++++++---------- 8 files changed, 129 insertions(+), 86 deletions(-) create mode 100644 src/components/Footer/Footer.css create mode 100644 src/components/Footer/Footer.js create mode 100644 src/components/Header/Header.css create mode 100644 src/components/Header/Header.js diff --git a/src/App.js b/src/App.js index 5b5e37d3e..aa472ca50 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import TodoList from './components/TodoList/TodoList'; -import NewTodo from './components/NewTodo/NewTodo'; -import { TodosFilter } from './components/TodosFilter/TodosFilter'; +import { Footer } from './components/Footer/Footer'; +import { Header } from './components/Header/Header'; class App extends Component { state = { @@ -19,7 +19,7 @@ class App extends Component { this.setState({ showParam: todoToShow }); } - handleRemuve = (id) => { + handleRemove = (id) => { this.setState(prevState => ({ todos: prevState.todos.filter(todo => ( todo.id !== id @@ -56,7 +56,7 @@ class App extends Component { } } - handleRemuveCompleted = () => { + handleRemoveCompleted = () => { this.setState(prevState => ({ todos: prevState.todos.filter(todo => ( !todo.completed @@ -97,36 +97,44 @@ class App extends Component { } } - render() { + getTodos = (status) => { let todoView = []; + const { todos } = this.state; - switch (this.state.showParam) { + switch (status) { case 'active': - todoView = [...this.state.todos].filter(todo => ( + todoView = todos.filter(todo => ( !todo.completed )); break; case 'completed': - todoView = [...this.state.todos].filter(todo => ( + todoView = todos.filter(todo => ( todo.completed )); break; default: - todoView = [...this.state.todos]; + todoView = todos; } + return todoView; + } + + render() { + const { todos, showParam } = this.state; + const todoView = this.getTodos(showParam); + const itemLeft = todos.filter(todo => ( + !todo.completed)).length; + return (
-
-

todos

- -
- {(this.state.todos.length) +
+ {(todos.length) ? ( <>
Mark all as complete
- -
- - {this.state.todos.filter(todo => ( - !todo.completed)).length} - item left - - - { - (this.state.todos.filter(todo => ( - todo.completed)).length) - ? ( - - ) - : ('') - } -
+
) : ''}
diff --git a/src/components/Footer/Footer.css b/src/components/Footer/Footer.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/Footer/Footer.js b/src/components/Footer/Footer.js new file mode 100644 index 000000000..fba79705b --- /dev/null +++ b/src/components/Footer/Footer.js @@ -0,0 +1,46 @@ +import React from 'react'; +import './Footer.css'; +import PropTypes from 'prop-types'; +import { TodosFilter } from '../TodosFilter/TodosFilter'; + +export const Footer = ({ + todos, + updateTodosToShow, + handleRemoveCompleted, + itemLeft, +}) => ( +
+ + {itemLeft} + {' '} + item left + + + { + (todos.filter(todo => (todo.completed)).length) + ? ( + + ) + : ('') + } +
+); + +Footer.propTypes = { + todos: PropTypes.arrayOf( + PropTypes.shape({ + content: PropTypes.string.isRequired, + id: PropTypes.number.isRequired, + completed: PropTypes.bool.isRequired, + }), + ).isRequired, + updateTodosToShow: PropTypes.func.isRequired, + handleRemoveCompleted: PropTypes.func.isRequired, + itemLeft: PropTypes.number.isRequired, +}; diff --git a/src/components/Header/Header.css b/src/components/Header/Header.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js new file mode 100644 index 000000000..1a79fa25a --- /dev/null +++ b/src/components/Header/Header.js @@ -0,0 +1,15 @@ +import React from 'react'; +import './Header.css'; +import PropTypes from 'prop-types'; +import NewTodo from '../NewTodo/NewTodo'; + +export const Header = ({ addTodo }) => ( +
+

todos

+ +
+); + +Header.propTypes = { + addTodo: PropTypes.func.isRequired, +}; diff --git a/src/components/NewTodo/NewTodo.js b/src/components/NewTodo/NewTodo.js index 32777a399..4cc7dd0ba 100644 --- a/src/components/NewTodo/NewTodo.js +++ b/src/components/NewTodo/NewTodo.js @@ -5,8 +5,6 @@ import PropTypes from 'prop-types'; class NewTodo extends Component { state = { content: '', - id: 1, - completed: false, } handleChange = (event) => { @@ -17,13 +15,18 @@ class NewTodo extends Component { handleSubmit = (e) => { e.preventDefault(); + const { content } = this.state; + const id = +Date.now(); + const completed = false; + const todo = { + content, id, completed, + }; - if (this.state.content) { - this.props.addTodo(this.state); + if (content) { + this.props.addTodo(todo); this.setState(prevState => ({ content: '', - id: prevState.id + 1, })); } } diff --git a/src/components/TodoList/TodoList.js b/src/components/TodoList/TodoList.js index e7e16d01c..2f00ac632 100644 --- a/src/components/TodoList/TodoList.js +++ b/src/components/TodoList/TodoList.js @@ -21,6 +21,14 @@ class TodoList extends Component { }); } + handleEdit = (todo) => { + this.props.handleDobleClick(todo); + this.setState(() => ({ + content: todo.content, + submitEditing: true, + })); + } + render() { return (
    @@ -30,13 +38,8 @@ class TodoList extends Component { className={(todo.editing && this.state.submitEditing) ? 'editing' : ''} > -
    -
    + +
    { this.props.toggleComplete(todo.id); @@ -49,24 +52,19 @@ class TodoList extends Component {
    - ( - -); +export const TodosFilter = ({ updateTodosToShow }) => { + const statuses = ['all', 'active', 'completed']; + + return ( + + ); +}; TodosFilter.propTypes = { updateTodosToShow: PropTypes.func.isRequired,