Skip to content

Commit

Permalink
dynamic list of todos
Browse files Browse the repository at this point in the history
  • Loading branch information
yzhyhaliuk committed Feb 4, 2025
1 parent 8bf0f2b commit a433fd7
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 137 deletions.
60 changes: 55 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
/* eslint-disable max-len */
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import 'bulma/css/bulma.css';
import '@fortawesome/fontawesome-free/css/all.css';

import { TodoList } from './components/TodoList';
import { TodoFilter } from './components/TodoFilter';
import { TodoModal } from './components/TodoModal';
import { Loader } from './components/Loader';
import { getTodos } from './api';
import { Todo } from './types/Todo';

export const App: React.FC = () => {
const [allTodos, setAllTodos] = useState<Todo[]>([]);
const [todos, setTodos] = useState<Todo[]>([]);
const [loading, setLoading] = useState(false);
const [selectedId, setSelectedId] = useState(0);

useEffect(() => {
setLoading(true);
getTodos().then(todosFromServer => {
setAllTodos(todosFromServer);
setTodos(todosFromServer);
setLoading(false);
});
}, []);

const handleFilterAll = useCallback(() => {
setTodos(allTodos);
}, [allTodos]);

const handleFilterActive = useCallback(() => {
setTodos(allTodos.filter(todo => !todo.completed));
}, [allTodos]);

const handleFilterComplete = useCallback(() => {
setTodos(allTodos.filter(todo => todo.completed));
}, [allTodos]);

const filterByTitle = useCallback(
(query: string) => {
setTodos(
todos.filter(todo =>
todo.title.toLowerCase().includes(query.toLowerCase()),
),
);
},
[todos],
);

return (
<>
<div className="section">
Expand All @@ -17,18 +56,29 @@ export const App: React.FC = () => {
<h1 className="title">Todos:</h1>

<div className="block">
<TodoFilter />
<TodoFilter
onFilterActive={handleFilterActive}
onFilterAll={handleFilterAll}
onFilterCompleted={handleFilterComplete}
onFilterByTitle={filterByTitle}
/>
</div>

<div className="block">
<Loader />
<TodoList />
{loading && <Loader />}
<TodoList
todos={todos}
onSelect={setSelectedId}
selectedId={selectedId}
/>
</div>
</div>
</div>
</div>

<TodoModal />
{selectedId && (
<TodoModal id={selectedId} todos={todos} onClose={setSelectedId} />
)}
</>
);
};
148 changes: 118 additions & 30 deletions src/components/TodoFilter/TodoFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,118 @@
export const TodoFilter = () => (
<form className="field has-addons">
<p className="control">
<span className="select">
<select data-cy="statusSelect">
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
</span>
</p>

<p className="control is-expanded has-icons-left has-icons-right">
<input
data-cy="searchInput"
type="text"
className="input"
placeholder="Search..."
/>
<span className="icon is-left">
<i className="fas fa-magnifying-glass" />
</span>

<span className="icon is-right" style={{ pointerEvents: 'all' }}>
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
<button data-cy="clearSearchButton" type="button" className="delete" />
</span>
</p>
</form>
);
import React, { ChangeEvent, useEffect, useState } from 'react';

type Props = {
onFilterAll: () => void;
onFilterActive: () => void;
onFilterCompleted: () => void;
onFilterByTitle: (query: string) => void;
};

export const TodoFilter: React.FC<Props> = ({
onFilterAll,
onFilterActive,
onFilterCompleted,
onFilterByTitle,
}) => {
const [isQuery, setIsQuery] = useState(false);
const [query, setQuery] = useState('');
const [filterStatus, setFilterStatus] = useState('all');

function handleChange(event: ChangeEvent<HTMLSelectElement>) {
const status = event.target.value;

setFilterStatus(status);

switch (status) {
case 'all':
onFilterAll();
break;

case 'active':
onFilterActive();
break;

case 'completed':
onFilterCompleted();
break;

default:
break;
}
}

function handleTitleChange(event: React.ChangeEvent<HTMLInputElement>) {
const newQuery = event.target.value;

setQuery(newQuery);
onFilterByTitle(newQuery.toLowerCase());
setIsQuery(newQuery.length > 0);
}

function clearInput() {
setQuery('');
setIsQuery(false);
onFilterByTitle('');
}

useEffect(() => {
if (!query) {
switch (filterStatus) {
case 'all':
onFilterAll();
break;
case 'active':
onFilterActive();
break;
case 'completed':
onFilterCompleted();
break;
default:
break;
}
}
}, [query, filterStatus, onFilterActive, onFilterAll, onFilterCompleted]);

return (
<form className="field has-addons">
<p className="control">
<span className="select">
<select
data-cy="statusSelect"
value={filterStatus}
onChange={handleChange}
>
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
</span>
</p>

<p className="control is-expanded has-icons-left has-icons-right">
<input
data-cy="searchInput"
type="text"
value={query}
className="input"
placeholder="Search..."
onChange={handleTitleChange}
/>
<span className="icon is-left">
<i className="fas fa-magnifying-glass" />
</span>

<span className="icon is-right" style={{ pointerEvents: 'all' }}>
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
{isQuery && (
<button
data-cy="clearSearchButton"
type="button"
className="delete"
onClick={clearInput}
/>
)}
</span>
</p>
</form>
);
};
Loading

0 comments on commit a433fd7

Please sign in to comment.