Skip to content

Commit

Permalink
develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Stepan Rad authored and Stepan Rad committed Sep 23, 2024
1 parent 71932b1 commit fd31fda
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 302 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ components (no need to write them in the store file)

## Instructions
- Install Prettier Extention and use this [VSCode settings](https://mate-academy.github.io/fe-program/tools/vscode/settings.json) to enable format on save.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://<your_account>.github.io/react_redux-list-of-todos/)
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://Stepan-R.github.io/react_redux-list-of-todos/)
- Follow the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline)
38 changes: 33 additions & 5 deletions package-lock.json

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

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"@reduxjs/toolkit": "^2.2.6",
"bulma": "^1.0.1",
"classnames": "^2.5.1",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2",
Expand All @@ -18,9 +20,11 @@
},
"devDependencies": {
"@cypress/react18": "^2.0.1",
"@mate-academy/scripts": "^1.8.5",
"@mate-academy/scripts": "^1.9.12",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/lodash": "^4.17.7",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand Down
80 changes: 63 additions & 17 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,72 @@
/* eslint-disable max-len */
import React, { useEffect, useState } from 'react';
import 'bulma/css/bulma.css';
import '@fortawesome/fontawesome-free/css/all.css';
import { Loader, TodoFilter, TodoList, TodoModal } from './components';

export const App = () => (
<>
<div className="section">
<div className="container">
<div className="box">
<h1 className="title">Todos:</h1>
import { TodoList } from './components/TodoList';
import { TodoFilter } from './components/TodoFilter';
import { Todo } from './types/Todo';
import { getTodos } from './api';
import { Loader } from './components/Loader';
import { TodoModal } from './components/TodoModal';
import { useAppDispatch, useAppSelector } from './app/store';
import { todosSlice } from './features/todos';

<div className="block">
<TodoFilter />
</div>
export const App: React.FC = () => {
const todos = useAppSelector(state => state.todos);
const filter = useAppSelector(state => state.filter);
const currentTodo = useAppSelector(state => state.currentTodo);
const [loader, setLoader] = useState(false);
const [searchFilter, setSeacrhFilter] = useState('');
const dispatch = useAppDispatch();

useEffect(() => {
setLoader(true);
getTodos()
.then((data: Todo[]) => dispatch(todosSlice.actions.setTodos(data)))
.finally(() => setLoader(false));
}, [dispatch]);

const visibleTodos = () => {
let visTodos = [];

switch (filter) {
case 'active':
visTodos = todos.filter(todo => !todo.completed);
break;

case 'completed':
visTodos = todos.filter(todo => todo.completed);
break;

default:
return todos;
}

return visTodos.filter(todo =>
todo.title.toLowerCase().includes(searchFilter.toLowerCase()),
);
};

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

<div className="block">
<TodoFilter setSearchFilter={setSeacrhFilter} />
</div>

<div className="block">
<Loader />
<TodoList />
<div className="block">
{loader ? <Loader /> : <TodoList todos={visibleTodos()} />}
</div>
</div>
</div>
</div>
</div>

<TodoModal />
</>
);
{currentTodo && <TodoModal />}
</>
);
};
9 changes: 8 additions & 1 deletion src/app/store.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { combineSlices, configureStore } from '@reduxjs/toolkit';
import { todosSlice } from '../features/todos';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { filterSlice } from '../features/filter';
import { currentTodoSlice } from '../features/currentTodo';

const rootReducer = combineSlices();
const rootReducer = combineSlices(todosSlice, filterSlice, currentTodoSlice);

export const store = configureStore({
reducer: rootReducer,
});

export type RootState = ReturnType<typeof rootReducer>;
export type AppDispatch = typeof store.dispatch;

export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = () => useDispatch<AppDispatch>();
67 changes: 49 additions & 18 deletions src/components/TodoFilter/TodoFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
import React from 'react';
import React, { useMemo, useState } from 'react';
import { useAppDispatch } from '../../app/store';
import { filterSlice } from '../../features/filter';
import { Status } from '../../types/Status';
import { debounce } from 'lodash';

type Props = {
setSearchFilter: (a: string) => void;
};

export const TodoFilter: React.FC<Props> = ({ setSearchFilter }) => {
const [title, setTitle] = useState('');
const dispatch = useAppDispatch();

const applyTitle = useMemo(
() => debounce(setSearchFilter, 1000),
[setSearchFilter],
);

const setFilter = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(filterSlice.actions.setFilter(e.target.value));
};

const handleInputFilter = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
applyTitle(e.target.value);
};

const clearSearch = () => {
setTitle('');
applyTitle('');
};

export const TodoFilter: React.FC = () => {
return (
<form
className="field has-addons"
onSubmit={event => event.preventDefault()}
>
<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 data-cy="statusSelect" onChange={() => setFilter}>
<option value={'all' as Status}>All</option>
<option value={'active' as Status}>Active</option>
<option value={'completed' as Status}>Completed</option>
</select>
</span>
</p>
Expand All @@ -22,19 +49,23 @@ export const TodoFilter: React.FC = () => {
type="text"
className="input"
placeholder="Search..."
value={title}
onChange={handleInputFilter}
/>
<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>
{title.length !== 0 && (
<span className="icon is-right" style={{ pointerEvents: 'all' }}>
<button
data-cy="clearSearchButton"
type="button"
className="delete"
onClick={clearSearch}
/>
</span>
)}
</p>
</form>
);
Expand Down
63 changes: 63 additions & 0 deletions src/components/TodoItem/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import classNames from 'classnames';
import { Todo } from '../../types/Todo';
import { useAppDispatch } from '../../app/store';
import { currentTodoSlice } from '../../features/currentTodo';

type Props = {
todo: Todo;
currentTodoId: number;
};

export const TodoItem: React.FC<Props> = ({ todo, currentTodoId }) => {
const { id, completed, title } = todo;
const dispatch = useAppDispatch();

const add = (item: Todo) => {
dispatch(currentTodoSlice.actions.setCurrentTodo(item));
};

return (
<tr
key={id}
data-cy="todo"
className={classNames({
'has-background-info-light': id === currentTodoId,
})}
>
<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={classNames({
'has-text-danger': !completed,
'has-text-success': completed,
})}
>
{title}
</p>
</td>
<td className="has-text-right is-vcentered">
<button
data-cy="selectButton"
className="button"
type="button"
onClick={() => add(todo)}
>
<span className="icon">
{id !== currentTodoId ? (
<i className="far fa-eye" />
) : (
<i className="far fa-eye-slash" />
)}
</span>
</button>
</td>
</tr>
);
};
Loading

0 comments on commit fd31fda

Please sign in to comment.