Skip to content

Commit

Permalink
add task solution
Browse files Browse the repository at this point in the history
  • Loading branch information
DariaFesiun committed Nov 5, 2024
1 parent 1d2a025 commit 31959cd
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 142 deletions.
77 changes: 72 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,101 @@
/* eslint-disable max-len */
import React from 'react';
import React, { useEffect, useMemo, 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 { Todo } from './types/Todo';
import { getTodos } from './api';
import { Completed, Filters } from './types/Filters';

export const App: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [selectedTodo, setSelectedTodo] = useState<Todo | undefined>(undefined);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [filters, setFilters] = useState<Filters>({
completedType: Completed.All,
searchByText: '',
});

const selectTodo = (todo: Todo | null) => {
setSelectedTodo(todo);
};

const updateFilters = (key: keyof Filters, value: string | Completed) => {
setFilters(prevFilters => ({
...prevFilters,
[key]: value,
}));
};

const filteredTodos = useMemo(() => {
return todos.filter(item => {
const matchesCompletedType =
filters.completedType === Completed.All ||
(filters.completedType === Completed.Completed && item.completed) ||
(filters.completedType === Completed.Active && !item.completed);

const matchesSearchText = item.title
.toLowerCase()
.includes(filters.searchByText.toLowerCase());

return matchesCompletedType && matchesSearchText;
});
}, [todos, filters]);

useEffect(() => {
getTodos()
.then(todosData => {
setTodos(todosData);
})
.catch(error => {
setErrorMessage(`Failed to fetch todos: ${error.message}`);
});
}, []);

return (
<>
<div className="section">
<div className="container">
<div className="box">
<h1 className="title">Todos:</h1>

{errorMessage && (
<p className="notification is-danger">{errorMessage}</p>
)}

<div className="block">
<TodoFilter />
<TodoFilter
filterOptions={filters}
onFilterChange={updateFilters}
/>
</div>

<div className="block">
<Loader />
<TodoList />
{todos.length === 0 ? (
<Loader />
) : (
<TodoList
todos={filteredTodos}
onSelectTodo={selectTodo}
activeTodo={selectedTodo}
/>
)}
</div>
</div>
</div>
</div>

<TodoModal />
{selectedTodo && (
<TodoModal
setModalOpen={() => selectTodo(null)}
todo={selectedTodo}
resetSelectedTodo={() => selectTodo(null)}
/>
)}
</>
);
};
93 changes: 65 additions & 28 deletions src/components/TodoFilter/TodoFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,67 @@
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>
import { Completed, Filters } from '../../types/Filters';

<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>
interface Props {
filterOptions: Filters;
onFilterChange: (key: keyof Filters, value: string | Completed) => void;
}

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

export const TodoFilter: React.FC<Props> = ({
filterOptions,
onFilterChange,
}) => {
const handleStatusChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const selectedValue = event.target.value as Completed;
onFilterChange('completedType', selectedValue);
};

const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const query = event.target.value.toLowerCase();
onFilterChange('searchByText', query);
};

const clearSearch = () => {
onFilterChange('searchByText', '');
};

return (
<form className="field has-addons">
<p className="control">
<span className="select">
<select
data-cy="statusSelect"
value={filterOptions.completedType}
onChange={handleStatusChange}
>
<option value={Completed.All}>All</option>
<option value={Completed.Active}>Active</option>
<option value={Completed.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..."
value={filterOptions.searchByText}
onChange={handleSearchChange}
/>
<span className="icon is-left">
<i className="fas fa-magnifying-glass" />
</span>
{filterOptions.searchByText && (
<span className="icon is-right" style={{ pointerEvents: 'all' }}>
<button
data-cy="clearSearchButton"
type="button"
className="delete"
onClick={clearSearch}
/>
</span>
)}
</p>
</form>
);
};
165 changes: 72 additions & 93 deletions src/components/TodoList/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,79 @@
import React from 'react';
import { Todo } from '../../types/Todo';

export const TodoList: React.FC = () => (
<table className="table is-narrow is-fullwidth">
<thead>
<tr>
<th>#</th>
<th>
<span className="icon">
<i className="fas fa-check" />
</span>
</th>
<th>Title</th>
<th> </th>
</tr>
</thead>
type Props = {
todos: Todo[];
activeTodo: Todo | null;
onSelectTodo: (val: Todo) => void;
};

<tbody>
<tr data-cy="todo" className="">
<td className="is-vcentered">1</td>
<td className="is-vcentered" />
<td className="is-vcentered is-expanded">
<p className="has-text-danger">delectus aut autem</p>
</td>
<td className="has-text-right is-vcentered">
<button data-cy="selectButton" className="button" type="button">
<span className="icon">
<i className="far fa-eye" />
</span>
</button>
</td>
</tr>
<tr data-cy="todo" className="has-background-info-light">
<td className="is-vcentered">2</td>
<td className="is-vcentered" />
<td className="is-vcentered is-expanded">
<p className="has-text-danger">quis ut nam facilis et officia qui</p>
</td>
<td className="has-text-right is-vcentered">
<button data-cy="selectButton" className="button" type="button">
<span className="icon">
<i className="far fa-eye-slash" />
</span>
</button>
</td>
</tr>
export const TodoList: React.FC<Props> = ({
todos,
activeTodo,
onSelectTodo,
}) => {
const handleSelectTodo = (todo: Todo) => {
onSelectTodo(todo);
};

<tr data-cy="todo" className="">
<td className="is-vcentered">1</td>
<td className="is-vcentered" />
<td className="is-vcentered is-expanded">
<p className="has-text-danger">delectus aut autem</p>
</td>
<td className="has-text-right is-vcentered">
<button data-cy="selectButton" className="button" type="button">
return (
<table className="table is-narrow is-fullwidth">
<thead>
<tr>
<th>#</th>
<th>
<span className="icon">
<i className="far fa-eye" />
<i className="fas fa-check" />
</span>
</button>
</td>
</tr>
</th>
<th>Title</th>
<th> </th>
</tr>
</thead>

<tr data-cy="todo" className="">
<td className="is-vcentered">6</td>
<td className="is-vcentered" />
<td className="is-vcentered is-expanded">
<p className="has-text-danger">
qui ullam ratione quibusdam voluptatem quia omnis
</p>
</td>
<td className="has-text-right is-vcentered">
<button data-cy="selectButton" className="button" type="button">
<span className="icon">
<i className="far fa-eye" />
</span>
</button>
</td>
</tr>
<tbody>
{todos.map(todo => {
const isActive = todo.id === activeTodo?.id;
const selectIconClass = `far ${isActive ? 'fa-eye-slash' : 'fa-eye'}`;
const statusTextClass = todo.completed
? 'has-text-success'
: 'has-text-danger';

<tr data-cy="todo" className="">
<td className="is-vcentered">8</td>
<td className="is-vcentered">
<span className="icon" data-cy="iconCompleted">
<i className="fas fa-check" />
</span>
</td>
<td className="is-vcentered is-expanded">
<p className="has-text-success">quo adipisci enim quam ut ab</p>
</td>
<td className="has-text-right is-vcentered">
<button data-cy="selectButton" className="button" type="button">
<span className="icon">
<i className="far fa-eye" />
</span>
</button>
</td>
</tr>
</tbody>
</table>
);
return (
<tr
data-cy="todo"
className={isActive ? 'has-background-info-light' : ''}
key={todo.id}
>
<td className="is-vcentered">{todo.id}</td>
<td className="is-vcentered">

{todo.completed && (
<span className="icon" data-cy="iconCompleted">
<i className="fas fa-check" />
</span>
)}
</td>
<td className="is-vcentered is-expanded">
<p className={statusTextClass}>
{todo.title}
</p>
</td>
<td className="has-text-right is-vcentered">
<button
data-cy="selectButton"
className="button"
type="button"
onClick={() => handleSelectTodo(todo)}
>
<span className="icon">
<i className={selectIconClass} />
</span>
</button>
</td>
</tr>
);
})}
</tbody>
</table>
);
};
Loading

0 comments on commit 31959cd

Please sign in to comment.