Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add task solution #1869

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ loaded and show them using `TodoList` (check the code in the `api.ts`);
- Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline).
- Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript).
- Open one more terminal and run tests with `npm test` to ensure your solution is correct.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://<your_account>.github.io/react_dynamic-list-of-todos/) and add it to the PR description.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://dpidlutska.github.io/react_dynamic-list-of-todos/) and add it to the PR description.
55 changes: 50 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
/* eslint-disable max-len */
import React from 'react';
import React, { useState, useEffect, useMemo } 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 { TodosFilter } from './types/TodosFilter';
import { getTodos } from './api';
import { getPreparedTodos } from './services/todos';

export const App: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [query, setQuery] = useState('');
const [filter, setFilter] = useState(TodosFilter.All);
Comment on lines +17 to +18

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can group it into one object, like filterParams

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be kept in three different states.

const [isLoading, setIsLoading] = useState(false);
const [selectedTodo, setSelectedTodo] = useState<Todo | null>(null);

useEffect(() => {
setIsLoading(true);

getTodos()
.then(setTodos)
.catch(error => {
// eslint-disable-next-line no-console
console.log(error);
Comment on lines +28 to +29

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove console, show error message on the page

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is optional.

})
.finally(() => setIsLoading(false));
}, []);

const filteredTodos = useMemo(() => {
return getPreparedTodos(todos, query, filter);
}, [todos, query, filter]);

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

<div className="block">
<TodoFilter />
<TodoFilter
query={query}
onQueryChange={setQuery}
filter={filter}
onFilterChange={setFilter}
/>
</div>

<div className="block">
<Loader />
<TodoList />
{isLoading
? (
<Loader />
) : (
<TodoList
todos={filteredTodos}
selectedTodo={selectedTodo}
onSelectTodo={setSelectedTodo}
/>
)}

</div>
</div>
</div>
</div>

<TodoModal />
{selectedTodo && (
<TodoModal
selectedTodo={selectedTodo}
onSelectTodo={setSelectedTodo}
/>
)}
</>
);
};
101 changes: 70 additions & 31 deletions src/components/TodoFilter/TodoFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,73 @@
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 React from 'react';
import { TodosFilter } from '../../types/TodosFilter';

<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>
type Props = {
query: string;
filter: TodosFilter;
onQueryChange: (value: string) => void;
onFilterChange: (value: TodosFilter) => 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"
export const TodoFilter: React.FC<Props> = ({
query,
filter,
onQueryChange,
onFilterChange,
}) => {
const handleReset = () => {
onQueryChange('');
onFilterChange(TodosFilter.All);
};

return (
<form className="field has-addons">
<p className="control">
<span className="select">
<select
data-cy="statusSelect"
className="is-capitalized"
value={filter}
onChange={event => (
onFilterChange(event?.target.value as TodosFilter)
)}
Comment on lines +30 to +32

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass a handler as prop from app, which will change ur filterParam to event.target.value and set proper types, it will shorten ur code and will make it look simpler

>
{Object.values(TodosFilter).map(value => (
<option
value={value}
key={value}
>
{value}
</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={query}
onChange={(event) => onQueryChange(event.target.value)}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as with select

/>
</span>
</p>
</form>
);
<span className="icon is-left">
<i className="fas fa-magnifying-glass" />
</span>

<span className="icon is-right" style={{ pointerEvents: 'all' }}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using inline styles is a bad practice, use css instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was already implemented in code of the task.

{query && (
<button
data-cy="clearSearchButton"
type="button"
className="delete"
aria-label="Clear Search"
onClick={handleReset}
/>
)}
</span>
</p>
</form>
);
};
62 changes: 62 additions & 0 deletions src/components/TodoItem/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import cn from 'classnames';
import { Todo } from '../../types/Todo';

type Props = {
todo: Todo;
selectedTodo: Todo | null;
onSelectTodo: (todo: Todo) => void;
};

export const TodoItem: React.FC<Props> = ({
todo,
selectedTodo,
onSelectTodo,
}) => {
const { id, title, completed } = todo;
const isTodoSelected = selectedTodo === todo;

return (
<tr
data-cy="todo"
className={cn({ 'has-background-info-light': isTodoSelected })}
>
<td className="is-vcentered">{id}</td>
<td className="is-vcentered">
{completed && (
<span className="icon" data-cy="iconCompleted">
<i className="fas fa-check" />
</span>
)}
</td>
<td className="is-vcentered is-expanded">
<p className={cn({
'has-text-success': completed,
'has-text-danger': !completed,
})}
>
{title}
</p>
</td>
<td className="has-text-right is-vcentered">
<button
data-cy="selectButton"
className="button"
type="button"
onClick={() => onSelectTodo(todo)}
>
<span className="icon">
<i className={cn(
'far',
{
'fa-eye': !isTodoSelected,
'fa-eye-slash': isTodoSelected,
},
)}
/>
</span>
</button>
</td>
</tr>
);
};
1 change: 1 addition & 0 deletions src/components/TodoItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './TodoItem';
101 changes: 21 additions & 80 deletions src/components/TodoList/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import React from 'react';
import { TodoItem } from '../TodoItem';
import { Todo } from '../../types/Todo';

export const TodoList: React.FC = () => (
type Props = {
todos: Todo[];
selectedTodo: Todo | null;
onSelectTodo: (todo: Todo) => void;
};

export const TodoList: React.FC<Props> = ({
todos,
selectedTodo,
onSelectTodo,
}) => (
<table className="table is-narrow is-fullwidth">
<thead>
<tr>
Expand All @@ -16,85 +28,14 @@ export const TodoList: React.FC = () => (
</thead>

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

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

<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>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
selectedTodo={selectedTodo}
onSelectTodo={onSelectTodo}
/>
))}
</tbody>
</table>
);
Loading