Skip to content

Commit

Permalink
add task solution
Browse files Browse the repository at this point in the history
  • Loading branch information
mpohorenyi committed Oct 27, 2023
1 parent 3c2abd6 commit c91cc46
Show file tree
Hide file tree
Showing 12 changed files with 439 additions and 91 deletions.
99 changes: 8 additions & 91 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,93 +1,10 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';

export const App: React.FC = () => {
return (
<div className="todoapp">
<header className="header">
<h1>todos</h1>

<form>
<input
type="text"
data-cy="createTodo"
className="new-todo"
placeholder="What needs to be done?"
/>
</form>
</header>

<section className="main">
<input
type="checkbox"
id="toggle-all"
className="toggle-all"
data-cy="toggleAll"
/>
<label htmlFor="toggle-all">Mark all as complete</label>

<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>

<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>

<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>

<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>

<footer className="footer">
<span className="todo-count" data-cy="todosCounter">
3 items left
</span>

<ul className="filters">
<li>
<a href="#/" className="selected">All</a>
</li>

<li>
<a href="#/active">Active</a>
</li>

<li>
<a href="#/completed">Completed</a>
</li>
</ul>

<button type="button" className="clear-completed">
Clear completed
</button>
</footer>
</div>
);
};
import { TodosProvider } from './TodosContext';
import { TodoApp } from './components/TodoApp';

export const App: React.FC = () => (
<TodosProvider>
<TodoApp />
</TodosProvider>
);
58 changes: 58 additions & 0 deletions src/TodosContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react';
import { Option } from './types/Option';
import { useLocalStorage } from './hooks/useLocalStorage';
import { Todo } from './types/Todo';
import { Context } from './types/Context';

const initialValue: Context = {
todos: [],
setTodos: () => { },
filter: Option.All,
setFilter: () => { },
visibleTodos: [],
isToggleCheckedAll: false,
setIsToggleCheckedAll: () => {},
};

export const TodosContext = React.createContext(initialValue);

type Props = {
children: React.ReactNode;
};

export const TodosProvider: React.FC<Props> = ({ children }) => {
const [todos, setTodos] = useLocalStorage<Todo[]>('todos', []);
const [filter, setFilter] = useState(Option.All);
const [isToggleCheckedAll, setIsToggleCheckedAll]
= useState(todos.every(todo => todo.completed));

const visibleTodos = todos.filter(todo => {
switch (filter) {
case Option.Active:
return !todo.completed;

case Option.Completed:
return todo.completed;

case Option.All:
default:
return true;
}
});

const context: Context = {
todos,
setTodos,
filter,
setFilter,
visibleTodos,
isToggleCheckedAll,
setIsToggleCheckedAll,
};

return (
<TodosContext.Provider value={context}>
{children}
</TodosContext.Provider>
);
};
76 changes: 76 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useContext } from 'react';
import cn from 'classnames';
import { TodosContext } from '../TodosContext';
import { Option } from '../types/Option';

export const Footer: React.FC = () => {
const {
todos,
setTodos,
filter,
setFilter,
} = useContext(TodosContext);

const TotalUncompletedTodos = todos.filter(todo => !todo.completed);

const isCompletedTodos = todos.some(todo => todo.completed);

const cleanCompletedTodos = () => {
setTodos(todos.filter(todo => !todo.completed));
};

return (
<footer className="footer" data-cy="todosFilter">
<span className="todo-count" data-cy="todosCounter">
{`${TotalUncompletedTodos.length} ${TotalUncompletedTodos.length === 1 ? 'item' : 'items'} left`}
</span>

<ul className="filters">
<li>
<a
href="#/"
className={cn({
selected: filter === Option.All,
})}
onClick={() => setFilter(Option.All)}
>
All
</a>
</li>

<li>
<a
href="#/active"
className={cn({
selected: filter === Option.Active,
})}
onClick={() => setFilter(Option.Active)}
>
Active
</a>
</li>

<li>
<a
href="#/completed"
className={cn({
selected: filter === Option.Completed,
})}
onClick={() => setFilter(Option.Completed)}
>
Completed
</a>
</li>
</ul>
{isCompletedTodos && (
<button
type="button"
className="clear-completed"
onClick={cleanCompletedTodos}
>
Clear completed
</button>
)}
</footer>
);
};
45 changes: 45 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useCallback, useContext, useState } from 'react';
import { TodosContext } from '../TodosContext';
import { Todo } from '../types/Todo';

export const Header: React.FC = () => {
const { todos, setTodos, setIsToggleCheckedAll } = useContext(TodosContext);
const [title, setTitle] = useState('');

const handleSubmit = useCallback((
event: React.FormEvent<HTMLFormElement>,
) => {
event.preventDefault();

if (!title.trim()) {
return;
}

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

setTodos([...todos, newTodo]);
setTitle('');
setIsToggleCheckedAll(false);
}, [setIsToggleCheckedAll, setTodos, title, todos]);

return (
<header className="header">
<h1>todos</h1>

<form onSubmit={handleSubmit}>
<input
type="text"
data-cy="createTodo"
className="new-todo"
placeholder="What needs to be done?"
value={title}
onChange={event => setTitle(event.target.value)}
/>
</form>
</header>
);
};
41 changes: 41 additions & 0 deletions src/components/Main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React, { useContext } from 'react';
import { TodosContext } from '../TodosContext';
import { TodoList } from './TodoList';

export const Main: React.FC = () => {
const {
todos,
setTodos,
visibleTodos,
isToggleCheckedAll,
setIsToggleCheckedAll,
} = useContext(TodosContext);

const checkAllTodos = () => {
const newTodos = todos.map(todo => (
isToggleCheckedAll
? { ...todo, completed: false }
: { ...todo, completed: true }
));

setTodos(newTodos);
setIsToggleCheckedAll(!isToggleCheckedAll);
};

return (
<section className="main">
<input
type="checkbox"
id="toggle-all"
className="toggle-all"
data-cy="toggleAll"
checked={isToggleCheckedAll}
onChange={checkAllTodos}
/>
<label htmlFor="toggle-all">Mark all as complete</label>

<TodoList items={visibleTodos} />
</section>
);
};
23 changes: 23 additions & 0 deletions src/components/TodoApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { useContext } from 'react';
import { Header } from './Header';
import { Main } from './Main';
import { Footer } from './Footer';
import { TodosContext } from '../TodosContext';

export const TodoApp: React.FC = () => {
const { todos } = useContext(TodosContext);

return (
<div className="todoapp">
<Header />

{!!todos.length && (
<>
<Main />

<Footer />
</>
)}
</div>
);
};
Loading

0 comments on commit c91cc46

Please sign in to comment.