Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add task solution #691

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 1 addition & 56 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,56 +1 @@
# React ToDo App
Implement a simple [TODO app](http://todomvc.com/examples/vanillajs/) working as described below.

> If you are not sure about how a feature should work just open the real TodoApp and look how it works there

![todoapp](./description/todoapp.gif)

1. Use `TodosContext` to store and update the todos.
1. Implement `TodoApp` component with an input field to create new todos on submit (Enter). Each item should have:
- `id` - unique identifier (`+new Date()` is good enough)
- `title` - the text of a todo
- `completed` - current status (`false` by default)
1. Show the number of not completed todos in `TodoApp`;
1. Implement `TodoList` component to display a list of todos;
```jsx harmony
<TodoList items={todos} />
```
1. Implement `TodoItem` component with ability to toggle the `completed` status using a checkbox.
- move a `li` tag inside the `TodoItem`;
- add class `completed` if todo is completed;
1. Add the ability to toggle the completed status of all the todos with the `toggleAll` checkbox.
- `toggleAll` checkbox is active only if all the todos are completed;
- if you click the checkbox all the items should be marked as `completed`/`not completed` depending on `toggleAll` checked;
1. Create `TodosFilter` component to switch between `All`/`Active`/`Completed` todos (add it to the `App`)
- add the `Status` enum with the required values;
- href should be `#/`, `#/active` or `#/completed`)
1. Add ability to remove a todo using the `destroy` button (`X`).
1. Add ability to clear completed todos - remove all completed items from the list. The button should contain text `Clear completed` in it.
- It should be visible if there is at least 1 completed item in the list.
1. Hide everything except the input to add new todo if there are no todos. But not if todos are just filtered out.
1. Make inline editing for the TODO item
- double click on the TODO title makes it editable (just add a class `editing` to a `li`)
- DON'T add `htmlFor` to the label!!!
- `Enter` saves changes
- `Ecs` cancels editing (use `onKeyup` and `event.key === 'Escape'`)
- Todo title can't be empty! If a user presses `Enter` when the title is empty, this todo should be removed.
- (*) save changes `onBlur`
1. Save state of the APP to the `localStorage` using the name `todos` for the key (Watch Custom Hooks lesson)
- use `JSON.stringify` before saving and `JSON.parse` on reading

![todoedit](./description/edittodo.gif)

## If you want to implement styles yourself
- Font: 'helvetica neue'
- Font sizes to use: 100px, 24px, 14px
- implement arrow by rotating '❯' symbol
- Use '✕' symbol to remove TODO item on hover
- [checked](./public/icons/checked.svg)
- [unchecked](./public/icons/unchecked.svg)

## Instructions

- Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline).
- Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript).
- Open one more terminal and run tests with `npm test` to ensure your solution is correct.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://<your_account>.github.io/react_todo-app/) and add it to the PR description.
The project presents a "Todo" application where we can add tasks to complete, mark them as done, filter them, and delete them. The project utilizes the REACT library, and data is stored in Local Storage, ensuring that the data is preserved even after the page is reloaded.
171 changes: 95 additions & 76 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,93 +1,112 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';
import React, { useEffect, useState } from 'react';
import { TodosFilter } from './components/TodosFilter';
import { Header } from './components/Header';
import { TodoList } from './components/TodoList';
import { SortBy, Todo } from './types';

const todosFromLocalStorage = localStorage.getItem('todos');
const parsedTodos = todosFromLocalStorage
? JSON.parse(todosFromLocalStorage) : [];
const todosArray = Array.isArray(parsedTodos) ? parsedTodos : [];

export const App: React.FC = () => {
return (
<div className="todoapp">
<header className="header">
<h1>todos</h1>
const [completedTodosId, setCompletedTodosId] = useState<number[]>(
todosArray.filter(todo => todo.completed)
.map(todoId => todoId.id),
);
const [todosList, setTodos] = useState<Todo[]>(todosArray);
const [sortBy, setSortBy] = useState<SortBy>(SortBy.all);

<form>
<input
type="text"
data-cy="createTodo"
className="new-todo"
placeholder="What needs to be done?"
/>
</form>
</header>
const updateCheckTodo = (todoId: number) => {
setCompletedTodosId((prevCompletedTodo) => {
if (prevCompletedTodo.includes(todoId)) {
return prevCompletedTodo.filter((number) => number !== todoId);
}

<section className="main">
<input
type="checkbox"
id="toggle-all"
className="toggle-all"
data-cy="toggleAll"
/>
<label htmlFor="toggle-all">Mark all as complete</label>
return [...prevCompletedTodo, todoId];
});
};

<ul className="todo-list" data-cy="todoList">
<li>
<div className="view">
<input type="checkbox" className="toggle" id="toggle-view" />
<label htmlFor="toggle-view">asdfghj</label>
<button type="button" className="destroy" data-cy="deleteTodo" />
</div>
<input type="text" className="edit" />
</li>
useEffect(() => {
setTodos(todosList.map(todo => (completedTodosId.includes(todo.id)
? { ...todo, completed: true }
: { ...todo, completed: false })));
}, [completedTodosId]);

<li className="completed">
<div className="view">
<input type="checkbox" className="toggle" id="toggle-completed" />
<label htmlFor="toggle-completed">qwertyuio</label>
<button type="button" className="destroy" data-cy="deleteTodo" />
</div>
<input type="text" className="edit" />
</li>
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todosList));
}, [todosList]);

<li className="editing">
<div className="view">
<input type="checkbox" className="toggle" id="toggle-editing" />
<label htmlFor="toggle-editing">zxcvbnm</label>
<button type="button" className="destroy" data-cy="deleteTodo" />
</div>
<input type="text" className="edit" />
</li>
const handleUpdateCheckTodo = (value: number) => updateCheckTodo(value);

<li>
<div className="view">
<input type="checkbox" className="toggle" id="toggle-view2" />
<label htmlFor="toggle-view2">1234567890</label>
<button type="button" className="destroy" data-cy="deleteTodo" />
</div>
<input type="text" className="edit" />
</li>
</ul>
</section>
const handleAddTodo = (newTodo: Todo) => setTodos([...todosList, newTodo]);

const handleDeleteTodo = (todoId: number) => {
setTodos(todosList.filter(todo => todo.id !== todoId));
setCompletedTodosId(completedTodosId.filter(number => number !== todoId));
};

<footer className="footer">
<span className="todo-count" data-cy="todosCounter">
3 items left
</span>
const deleteCompletedTodos = () => {
setCompletedTodosId([]);
setTodos(todosList.filter(todo => !todo.completed));
};

<ul className="filters">
<li>
<a href="#/" className="selected">All</a>
</li>
const handleSetSortBy = (value: SortBy) => {
setSortBy(value);
};

<li>
<a href="#/active">Active</a>
</li>
const handleSetTodos = (newTodos: Todo[]) => {
setTodos(newTodos);
};

<li>
<a href="#/completed">Completed</a>
</li>
</ul>
const updateCheckAllTodo = () => {
if (todosList.length - completedTodosId.length === 0) {
setTodos(todosList.map(todo => ({ ...todo, completed: false })));
setCompletedTodosId([]);
} else {
setTodos(todosList.map(todo => ({ ...todo, completed: true })));
setCompletedTodosId(todosList.map(todo => todo.id));
}
};

<button type="button" className="clear-completed">
Clear completed
</button>
</footer>
return (
<div className="todoapp">
<Header handleAddTodo={handleAddTodo} />

<section className="main">
<input
type="checkbox"
checked={todosList.length - completedTodosId.length === 0}
id="toggle-all"
className="toggle-all"
data-cy="toggleAll"
onClick={updateCheckAllTodo}
/>
{todosList.length > 0 && (
<label htmlFor="toggle-all">Mark all as complete</label>
)}
{todosList.length !== 0 && (
<ul className="todo-list" data-cy="todosList">
<TodoList
todos={todosList}
handleUpdateCheckTodo={handleUpdateCheckTodo}
handleDeleteTodo={handleDeleteTodo}
sortBy={sortBy}
handleSetTodos={handleSetTodos}
/>
</ul>
)}
</section>
{todosList.length !== 0 && (
<TodosFilter
handleSetSortBy={handleSetSortBy}
sortBy={sortBy}
completedTodosId={completedTodosId}
deleteCompletedTodos={deleteCompletedTodos}
todosList={todosList}
/>
)}
Comment on lines +89 to +109

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can combine it in one condition

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can not do it, becouse TodoList is in "section" element and TodosFilter is not in "section".

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, yeah, sorry

</div>
);
};
15 changes: 15 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Todo } from '../types';
import { TodoApp } from './TodoApp';

type Props = {
handleAddTodo: (value: Todo) => void;
};

export const Header: React.FC<Props> = ({ handleAddTodo }) => {
return (
<header className="header">
<h1>todos</h1>
<TodoApp handleAddTodo={handleAddTodo} />
</header>
);
};
42 changes: 42 additions & 0 deletions src/components/TodoApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable consistent-return */
import { FormEvent, useState } from 'react';
import { Todo } from '../types';

type Props = {
handleAddTodo: (value: Todo) => void;
};

export const TodoApp: React.FC<Props> = ({ handleAddTodo }) => {
const [inputValue, setInputValue] = useState<string>('');

const AddTodo = (
event: FormEvent<HTMLFormElement>,
) => {
event.preventDefault();

if (inputValue.trim() !== '') {
const newTodo = {
id: +new Date(),
title: inputValue.trim(),
completed: false,
};

handleAddTodo(newTodo);
}

setInputValue('');
};

return (
<form onSubmit={AddTodo}>
<input
type="text"
data-cy="createTodo"
className="new-todo"
placeholder="What needs to be done?"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
</form>
);
};
Loading