Skip to content

Commit

Permalink
solution
Browse files Browse the repository at this point in the history
  • Loading branch information
yaros-dev committed Aug 20, 2024
1 parent 3e9aa73 commit 27ded53
Show file tree
Hide file tree
Showing 12 changed files with 14,209 additions and 7,489 deletions.
21,077 changes: 13,728 additions & 7,349 deletions package-lock.json

Large diffs are not rendered by default.

243 changes: 108 additions & 135 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,156 +1,129 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';
import React, { useCallback, useEffect, useRef } from 'react';
import { useGlobalDispatch, useGlobalState } from './context/store';
import { TodoItem } from './components/TodoItem';
import { TodoFilter } from './components/TodoFilter';
import { Todo } from './types/Todo';
import classNames from 'classnames';
import { handleInputChange, setInputValue } from './helpers/InputHelpers/Input';

import {
loadTodosFromLocalStorage,
filterTodos,
toggleAllTodos,
deleteCompletedTodos,
} from './services/TodoService';

export const App: React.FC = () => {
const { todos, inputValue, filterStatus } = useGlobalState();
const dispatch = useGlobalDispatch();
const inputElementRef = useRef<HTMLInputElement>(null);

const filteredTodos = filterTodos(todos, filterStatus);
const completedTodos = todos.filter(todo => todo.completed);
const activeTodos = todos.filter(todo => !todo.completed);
const allCompleted = completedTodos.length === todos.length;

const setTodos = useCallback(
(value: Todo[]) => {
dispatch({ type: 'setTodos', payload: value });
},
[dispatch],
);

const handlePostTodo = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const preparedInputValue = inputValue.trim();

if (!preparedInputValue.length) {
return;
}

const newTodo: Todo = {
id: +new Date(),
title: preparedInputValue,
completed: false,
};

dispatch({ type: 'setTodo', payload: newTodo });
setInputValue(dispatch, '');
};

const handleDeleteAllCompleted = () => {
const remainingTodos = deleteCompletedTodos(todos);

setTodos(remainingTodos);
inputElementRef.current?.focus();
};

const handleToggleAll = () => {
const toggledTodos = toggleAllTodos(todos, allCompleted);

setTodos(toggledTodos);
inputElementRef.current?.focus();
};

useEffect(() => {
const todosFromStorage = loadTodosFromLocalStorage();

setTodos(todosFromStorage);
inputElementRef.current?.focus();
}, [setTodos]);

return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>

<div className="todoapp__content">
<header className="todoapp__header">
{/* this button should have `active` class only if all todos are completed */}
<button
type="button"
className="todoapp__toggle-all active"
data-cy="ToggleAllButton"
/>

{/* Add a todo on form submit */}
<form>
{!!todos.length && (
<button
onClick={handleToggleAll}
type="button"
className={classNames('todoapp__toggle-all', {
active: allCompleted,
})}
data-cy="ToggleAllButton"
/>
)}
<form onSubmit={handlePostTodo}>
<input
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
value={inputValue}
onChange={e =>
handleInputChange(e, value => setInputValue(dispatch, value))
}
ref={inputElementRef}
/>
</form>
</header>

<section className="todoapp__main" data-cy="TodoList">
{/* This is a completed todo */}
<div data-cy="Todo" className="todo completed">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
checked
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Completed Todo
</span>

{/* Remove button appears only on hover */}
<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>

{/* This todo is an active todo */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Not Completed Todo
</span>

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>

{/* This todo is being edited */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>

{/* This form is shown instead of the title and remove button */}
<form>
<input
data-cy="TodoTitleField"
type="text"
className="todo__title-field"
placeholder="Empty todo will be deleted"
value="Todo is being edited now"
/>
</form>
</div>

{/* This todo is in loadind state */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Todo is being saved now
</span>

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
{filteredTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
inputElement={inputElementRef}
/>
))}
</section>

{/* Hide the footer if there are no todos */}
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
3 items left
</span>

{/* Active link should have the 'selected' class */}
<nav className="filter" data-cy="Filter">
<a
href="#/"
className="filter__link selected"
data-cy="FilterLinkAll"
>
All
</a>

<a
href="#/active"
className="filter__link"
data-cy="FilterLinkActive"
>
Active
</a>

<a
href="#/completed"
className="filter__link"
data-cy="FilterLinkCompleted"
{!!todos.length && (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{`${activeTodos.length} items left`}
</span>
<TodoFilter />
<button
disabled={!completedTodos.length}
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
onClick={handleDeleteAllCompleted}
>
Completed
</a>
</nav>

{/* this button should be disabled if there are no completed todos */}
<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
>
Clear completed
</button>
</footer>
Clear completed
</button>
</footer>
)}
</div>
</div>
);
Expand Down
41 changes: 41 additions & 0 deletions src/components/TodoFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useGlobalDispatch, useGlobalState } from '../context/store';
import { Filter } from '../types/Filter';
import classNames from 'classnames';
import React from 'react';

export const TodoFilter: React.FC = () => {
const { filterStatus } = useGlobalState();
const dispatch = useGlobalDispatch();

const handleFilter = (event: React.MouseEvent<HTMLAnchorElement>) => {
const filterParam = event.currentTarget.textContent;

if (filterParam === filterStatus) {
return;
}

dispatch({ type: 'setFilterStatus', payload: filterParam as Filter });
};

const filterParams = Object.values(Filter);

return (
<nav className="filter" data-cy="Filter">
{filterParams.map(param => {
return (
<a
key={param}
href={`#/${param.toLocaleLowerCase()}`}
className={classNames('filter__link', {
selected: filterStatus === param,
})}
data-cy={`FilterLink${param}`}
onClick={handleFilter}
>
{param}
</a>
);
})}
</nav>
);
};
Loading

0 comments on commit 27ded53

Please sign in to comment.