Skip to content

Commit

Permalink
finishing the challenge
Browse files Browse the repository at this point in the history
  • Loading branch information
italomagno committed Nov 18, 2024
1 parent 1f50892 commit fac32f0
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 133 deletions.
91 changes: 89 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
"license": "GPL-3.0",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.2",
"@reduxjs/toolkit": "^2.3.0",
"bulma": "^1.0.1",
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2",
"react-transition-group": "^4.4.5"
},
"devDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ export const App: React.FC = () => {

useEffect(() => {
if (filter === 'completed') {
setFilteredTodos(filteredTodos.filter(t => t.completed));
setFilteredTodos(prev => prev.filter(t => t.completed));
}

if (filter === 'active') {
setFilteredTodos(filteredTodos.filter(t => !t.completed));
setFilteredTodos(prev => prev.filter(t => !t.completed));
}
}, [filter, filteredTodos.length, todos.length]);
}, [filter, filteredTodos, todos]);

if (!USER_ID) {
return <UserWarning />;
Expand Down
2 changes: 1 addition & 1 deletion src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function Footer({
filter,
setFilter,
}: FooterProps) {
async function handleFilterTodos(filterString: string) {
function handleFilterTodos(filterString: string) {
setFilter(filterString);
switch (filterString) {
case 'all':
Expand Down
75 changes: 30 additions & 45 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,54 +77,39 @@ export function Header({
}
}

async function handleToggleAllTodos() {
const sucessfulUpdatedTodo: number[] = [];
function handleToggleAllTodos() {
const successfulUpdatedTodo: number[] = [];
const hasAllSameStatus = todos.every(t => t.completed);
const todosToUpdate = hasAllSameStatus
? todos
: todos.filter(t => !t.completed);

setEditingTodosId([...editingTodosId, ...todosToUpdate.map(t => t.id)]);

const updatePromises = todosToUpdate.map(todo =>
updateTodo({
...todo,
completed: !todo.completed,
})
.then(res => {
successfulUpdatedTodo.push(res.id);
})
.catch(() => {
setError('Unable to update todo');
}),
);

Promise.all(updatePromises).then(() => {
const newTodosUpdated = todos.map(t =>
successfulUpdatedTodo.includes(t.id)
? { ...t, completed: !t.completed }
: t,
);

hasAllSameStatus
? setEditingTodosId([...editingTodosId, ...todos.map(t => t.id)])
: setEditingTodosId([
...editingTodosId,
...todos.filter(t => !t.completed).map(t => t.id),
]);

try {
for (const todo of todos) {
if (hasAllSameStatus) {
await updateTodo({
...todo,
completed: !todo.completed,
});
sucessfulUpdatedTodo.push(todo.id);
} else {
if (todo.completed) {
continue;
}

await updateTodo({
...todo,
completed: !todo.completed,
});
sucessfulUpdatedTodo.push(todo.id);
}

sucessfulUpdatedTodo.push(todo.id);
}
} catch (e) {
setError('Unable to update todo');
}

const newTodosUpdated = todos.map(t => {
if (sucessfulUpdatedTodo.includes(t.id)) {
return { ...t, completed: !t.completed };
}

return t;
setTodos(newTodosUpdated);
setFilteredTodos(newTodosUpdated);
setEditingTodosId([]);
});

setTodos(newTodosUpdated);
setFilteredTodos(newTodosUpdated);
setEditingTodosId([]);
}

useEffect(() => {
Expand Down
91 changes: 91 additions & 0 deletions src/components/TodoComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import { Todo } from '../types/Todo';

interface TodoProps {
todo: Todo;
handleUpdateTodoStatus: (todoId: number) => void;
setSelectedTodo(todo: Todo): void;
handleEditTodoTitle: (
e: React.FormEvent<HTMLFormElement>,
todo: Todo,
) => void;
handleRemoveTodo: (todoId: number) => void;
editingTodosId: number[];
selectedTodo: Todo | null;
TodoTitleFieldRef: React.RefObject<HTMLInputElement>;
handleSelectedTodoChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

export function TodoComponent({
editingTodosId,
handleEditTodoTitle,
handleRemoveTodo,
handleUpdateTodoStatus,
setSelectedTodo,
todo,
selectedTodo,
TodoTitleFieldRef,
handleSelectedTodoChange,
}: TodoProps) {
return (
<div data-cy="Todo" className={`todo ${todo.completed ? 'completed' : ''}`}>
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
checked={todo.completed}
onClick={() => handleUpdateTodoStatus(todo.id)}
/>
</label>
{!selectedTodo || selectedTodo.id !== todo.id ? (
<span
onDoubleClick={() => setSelectedTodo(todo)}
data-cy="TodoTitle"
className="todo__title"
>
{todo.title}
</span>
) : (
<form onSubmit={e => handleEditTodoTitle(e, todo)}>
<input
data-cy="TodoTitleField"
type="text"
ref={TodoTitleFieldRef}
className="todo__title-field"
onChange={handleSelectedTodoChange}
placeholder="Empty todo will be deleted"
onBlur={e =>
handleEditTodoTitle(
e as unknown as React.FormEvent<HTMLFormElement>,
todo,
)
}
value={selectedTodo?.title}
/>
</form>
)}

{/* Remove button appears only on hover */}
{!selectedTodo || selectedTodo.id !== todo.id ? (
<button
onClick={() => handleRemoveTodo(todo.id)}
type="button"
className="todo__remove"
data-cy="TodoDelete"
>
×
</button>
) : null}

{/* overlay will cover the todo while it is being deleted or updated */}
<div
data-cy="TodoLoader"
className={`modal overlay ${editingTodosId.includes(todo.id) ? 'is-active' : ''}`}
>
<div className="modal-background has-background-white-ter" />
<div className="loader" />
</div>
</div>
);
}
Loading

0 comments on commit fac32f0

Please sign in to comment.