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..aa472ca50 100644 --- a/src/App.js +++ b/src/App.js @@ -1,85 +1,164 @@ -import React from 'react'; - -function App() { - return ( -
-
-

todos

- - -
- -
- - - -
    -
  • -
    - - -
    - -
  • - -
  • -
    - - -
    - -
  • - -
  • -
    - - -
    - -
  • - -
  • -
    - - -
    - -
  • -
-
+import React, { Component } from 'react'; +import TodoList from './components/TodoList/TodoList'; +import { Footer } from './components/Footer/Footer'; +import { Header } from './components/Header/Header'; + +class App extends Component { + state = { + todos: [], + showParam: 'all', + }; + + addTodo = (todo) => { + this.setState(prevState => ({ + todos: [...prevState.todos, todo], + })); + } + + updateTodosToShow = (todoToShow) => { + this.setState({ showParam: todoToShow }); + } + + handleRemove = (id) => { + this.setState(prevState => ({ + todos: prevState.todos.filter(todo => ( + todo.id !== id + )), + })); + } + + 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 + )), + })); + } + } + + handleRemoveCompleted = () => { + this.setState(prevState => ({ + todos: prevState.todos.filter(todo => ( + !todo.completed + )), + })); + } -
- - 3 items left - - - - - -
-
- ); + toggleComplete = (id) => { + this.setState(prevState => ({ + todos: prevState.todos.map((todo) => { + if (todo.id === id) { + return { + ...todo, + completed: !todo.completed, + }; + } + + return todo; + }), + })); + } + + 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, + })), + })); + } + } + + getTodos = (status) => { + let todoView = []; + const { todos } = this.state; + + switch (status) { + case 'active': + todoView = todos.filter(todo => ( + !todo.completed + )); + break; + case 'completed': + todoView = todos.filter(todo => ( + todo.completed + )); + break; + default: + 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.length) + ? ( + <> +
+ + + +
+
+ + ) : ''} +
+ ); + } } export default App; 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.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..4cc7dd0ba --- /dev/null +++ b/src/components/NewTodo/NewTodo.js @@ -0,0 +1,52 @@ +import React, { Component } from 'react'; +import './NewTodo.css'; +import PropTypes from 'prop-types'; + +class NewTodo extends Component { + state = { + content: '', + } + + handleChange = (event) => { + this.setState({ + content: event.target.value.trim(), + }); + } + + handleSubmit = (e) => { + e.preventDefault(); + const { content } = this.state; + const id = +Date.now(); + const completed = false; + const todo = { + content, id, completed, + }; + + if (content) { + this.props.addTodo(todo); + + this.setState(prevState => ({ + content: '', + })); + } + } + + 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..2f00ac632 --- /dev/null +++ b/src/components/TodoList/TodoList.js @@ -0,0 +1,104 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +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, + }); + } + + handleEdit = (todo) => { + this.props.handleDobleClick(todo); + this.setState(() => ({ + content: todo.content, + submitEditing: true, + })); + } + + 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( + PropTypes.shape({ + content: PropTypes.string.isRequired, + id: PropTypes.number.isRequired, + completed: PropTypes.bool.isRequired, + }), + ).isRequired, + remove: PropTypes.func.isRequired, + toggleComplete: PropTypes.func.isRequired, + handleDobleClick: PropTypes.func.isRequired, + editTodo: 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..d6038f946 --- /dev/null +++ b/src/components/TodosFilter/TodosFilter.js @@ -0,0 +1,26 @@ +import React from 'react'; +import './TodosFilter.css'; +import PropTypes from 'prop-types'; + +export const TodosFilter = ({ updateTodosToShow }) => { + const statuses = ['all', 'active', 'completed']; + + return ( + + ); +}; + +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'), );