Skip to content

Commit

Permalink
feat:implement solution
Browse files Browse the repository at this point in the history
  • Loading branch information
ab3MN committed Nov 15, 2024
1 parent f1536f7 commit af5d1bc
Show file tree
Hide file tree
Showing 31 changed files with 806 additions and 173 deletions.
165 changes: 8 additions & 157 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,157 +1,8 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';

export const App: React.FC = () => {
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>
<input
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
/>
</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>
</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"
>
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>
</div>
</div>
);
};
import { Todos } from './components/Todo/Todos';
import { TodosProvider } from './context/TodoContext';

export const App = () => (
<TodosProvider>
<Todos />
</TodosProvider>
);
20 changes: 20 additions & 0 deletions src/components/ErrorNotification/ErrorNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FC } from 'react';
import { getTodoErrorsMessage } from '../../utils/todos/getTodoErrorsMessage';
import cn from 'classnames';
import { TodoErrors } from '../../utils/enums/TodoErrors';

interface ErrorNotificationProps {
error: TodoErrors | null;
}

export const ErrorNotification: FC<ErrorNotificationProps> = ({ error }) => (
<div
data-cy="ErrorNotification"
className={cn('notification is-danger is-light has-text-weight-normal', {
hidden: !error,
})}
>
<button data-cy="HideErrorButton" type="button" className="delete" />
{error && getTodoErrorsMessage(error)}
</div>
);
61 changes: 61 additions & 0 deletions src/components/Todo/TodoFooter/TodoFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Dispatch, FC, SetStateAction } from 'react';
import cn from 'classnames';

import { Todo } from '../../../types/Todo';
import { TODO_FILTER_OPTIONS } from '../../../constants/TodoFilter';

import { FilterStatuses } from '../../../utils/enums/FilterStatuses';
import {
getInCompletedTodos,
hasCompletedTodos,
} from '../../../utils/todos/getTodos';
import { useDeleteTodo } from '../../../hooks/useDeleteTodo';

interface TodoFooterProps {
todos: Todo[];
setStatus: Dispatch<SetStateAction<FilterStatuses>>;
status: FilterStatuses;
}

export const TodoFooter: FC<TodoFooterProps> = ({
todos,
setStatus,
status,
}) => {
const isCompletedTodoCounter = getInCompletedTodos(todos).length;
const { handleDeleteCompletedTodos } = useDeleteTodo();

return (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{isCompletedTodoCounter}
{isCompletedTodoCounter === 1 ? ' item ' : ' items '}
left
</span>

<nav className="filter" data-cy="Filter">
{TODO_FILTER_OPTIONS.map(({ value, title, href, id }) => (
<a
href={href}
className={cn('filter__link', { selected: status === value })}
data-cy={`FilterLink${title}`}
key={id}
onClick={() => setStatus(value)}
>
{title}
</a>
))}
</nav>

<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
disabled={!hasCompletedTodos(todos)}
onClick={handleDeleteCompletedTodos}
>
Clear completed
</button>
</footer>
);
};
25 changes: 25 additions & 0 deletions src/components/Todo/TodoForm/TodoForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useContext } from 'react';

import { TodosContext } from '../../../context/TodoContext';
import { useTodoFormManager } from '../../../hooks/useTodoFormManager';

export const TodoForm = () => {
const { inputRef } = useContext(TodosContext);
const { title, handleSubmit, handleChangeTitle, isInputDisabled } =
useTodoFormManager();

return (
<form onSubmit={handleSubmit}>
<input
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
value={title}
ref={inputRef}
disabled={isInputDisabled}
onChange={handleChangeTitle}
/>
</form>
);
};
32 changes: 32 additions & 0 deletions src/components/Todo/TodoHeader/TodoHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FC } from 'react';
import cn from 'classnames';

import { Todo } from '../../../types/Todo';
import { isAllTodosCompleted } from '../../../utils/todos/getTodos';
import { TodoForm } from '../TodoForm/TodoForm';
import { useTodoFormManager } from '../../../hooks/useTodoFormManager';

interface TodoHeaderProps {
todos: Todo[];
}

export const TodoHeader: FC<TodoHeaderProps> = ({ todos }) => {
const { handleToogleAllTodoStatus } = useTodoFormManager();

return (
<header className="todoapp__header">
{!!todos.length && (
<button
type="button"
className={cn('todoapp__toggle-all', {
active: isAllTodosCompleted(todos),
})}
data-cy="ToggleAllButton"
onClick={handleToogleAllTodoStatus}
/>
)}

<TodoForm />
</header>
);
};
Loading

0 comments on commit af5d1bc

Please sign in to comment.