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

[17/05/2022][Nguyen Xuan Son][Frontend] #273

Open
wants to merge 1 commit 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
14 changes: 9 additions & 5 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
button {
outline: none;
border: none;
box-shadow: 2px 0 2px currentColor;
box-shadow: 1px 2px 3px currentColor;
border-radius: 4px;
min-height: 32px;
min-width: 80px;
Expand Down Expand Up @@ -45,7 +45,7 @@ input:focus {
box-shadow: 1px 0 9px rgba(0,0,0, 0.25);
}

.ToDo__container {
.Todo__container {
border: 1px solid rgba(0,0,0, 0.13);
border-radius: 8px;
width: 500px;
Expand All @@ -63,19 +63,19 @@ input:focus {
flex: 1 1;
}

.ToDo__list {
.Todo__list {
display: flex;
flex-direction: column;
margin-top: 1.5rem;
}

.ToDo__item {
.Todo__item {
display: flex;
align-items: center;
justify-content: space-between;
}

.ToDo__item > span {
.Todo__item > span {
flex: 1 1;
text-align: left;
margin-left: 8px;
Expand Down Expand Up @@ -122,4 +122,8 @@ input:focus {
}

.Action__btn {
}

.Todo__edit {
margin-right: 8px;
}
228 changes: 139 additions & 89 deletions src/ToDoPage.tsx
Original file line number Diff line number Diff line change
@@ -1,107 +1,157 @@
import React, {useEffect, useReducer, useRef, useState} from 'react';
import React, { useEffect, useMemo, useReducer, useRef, useState } from 'react';

import reducer, {initialState} from './store/reducer';
import reducer, { initialState } from './store/reducer';
import {
setTodos,
createTodo,
toggleAllTodos,
deleteAllTodos,
updateTodoStatus
setTodos,
createTodo,
toggleAllTodos,
deleteAllTodos,
updateTodoStatus,
} from './store/actions';
import Service from './service';
import {TodoStatus} from './models/todo';
import { TodoStatus } from './models/todo';
import { deleteTodo, updateTodoContent } from './store/actions';
import { isTodoCompleted, getLocalStorage } from './utils/index';

type EnhanceTodoStatus = TodoStatus | 'ALL';


const ToDoPage = () => {
const [{todos}, dispatch] = useReducer(reducer, initialState);
const [showing, setShowing] = useState<EnhanceTodoStatus>('ALL');
const inputRef = useRef<any>(null);

useEffect(()=>{
(async ()=>{
const resp = await Service.getTodos();

dispatch(setTodos(resp || []));
})()
}, [])

const onCreateTodo = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' ) {
const resp = await Service.createTodo(inputRef.current.value);
dispatch(createTodo(resp));
}
}
const [{ todos }, dispatch] = useReducer(reducer, initialState);
const [showing, setShowing] = useState<EnhanceTodoStatus>('ALL');
const [todoEditId, setTodoEditId] = useState<string>('');
const inputRef = useRef<any>(null);

const onUpdateTodoStatus = (e: React.ChangeEvent<HTMLInputElement>, todoId: any) => {
dispatch(updateTodoStatus(todoId, e.target.checked))
}
const onCreateTodo = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && inputRef.current.value) {
const isDuplicate = todos.find(
(todo) =>
todo.content.toLowerCase() === inputRef.current.value.toLowerCase()
);
if (isDuplicate) {
alert('Duplicate task!');
return;
}

const onToggleAllTodo = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(toggleAllTodos(e.target.checked))
if (todoEditId) {
dispatch(updateTodoContent(todoEditId, inputRef.current.value));
setTodoEditId('');
} else {
const resp = await Service.createTodo(inputRef.current.value);
dispatch(createTodo(resp));
}
inputRef.current.value = '';
}
};

const onDeleteAllTodo = () => {
dispatch(deleteAllTodos());
}
const onUpdateTodoStatus = (
e: React.ChangeEvent<HTMLInputElement>,
todoId: string
) => {
dispatch(updateTodoStatus(todoId, e.target.checked));
};

const onToggleAllTodo = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(toggleAllTodos(e.target.checked));
};

return (
<div className="ToDo__container">
<div className="Todo__creation">
<input
ref={inputRef}
className="Todo__input"
placeholder="What need to be done?"
onKeyDown={onCreateTodo}
/>
</div>
<div className="ToDo__list">
{
todos.map((todo, index) => {
return (
<div key={index} className="ToDo__item">
<input
type="checkbox"
checked={showing === todo.status}
onChange={(e) => onUpdateTodoStatus(e, index)}
/>
<span>{todo.content}</span>
<button
className="Todo__delete"
>
X
</button>
</div>
);
})
}
</div>
<div className="Todo__toolbar">
{todos.length > 0 ?
<input
type="checkbox"
onChange={onToggleAllTodo}
/> : <div/>
}
<div className="Todo__tabs">
<button className="Action__btn">
All
</button>
<button className="Action__btn" onClick={()=>setShowing(TodoStatus.ACTIVE)}>
Active
</button>
<button className="Action__btn" onClick={()=>setShowing(TodoStatus.COMPLETED)}>
Completed
</button>
</div>
<button className="Action__btn" onClick={onDeleteAllTodo}>
Clear all todos
</button>
const onDeleteAllTodo = () => {
dispatch(deleteAllTodos());
};

const deleteTodoById = (id: string) => {
dispatch(deleteTodo(id));
};

const discardContent = () => {
inputRef.current.value = '';
};

const editTodoById = (id: string) => {
const todo = todos.find((todo) => todo.id === id);
inputRef.current.value = todo?.content;
setTodoEditId(id);
};

const todosShowing = useMemo(() => {
return todos.filter((todo) => showing === 'ALL' || todo.status === showing);
}, [showing, todos]);

useEffect(() => {
(async () => {
const todos = getLocalStorage();
dispatch(setTodos(todos));
})();
}, []);

return (
<div className="Todo__container">
<div className="Todo__creation">
<input
ref={inputRef}
className="Todo__input"
placeholder="What need to be done?"
onKeyDown={onCreateTodo}
onBlur={discardContent}
autoFocus
/>
</div>
<div className="Todo__list">
{todosShowing.map((todo, index) => {
return (
<div key={index} className="Todo__item">
<input
type="checkbox"
checked={isTodoCompleted(todo)}
onChange={(e) => onUpdateTodoStatus(e, todo.id)}
/>
<span>{todo.content}</span>
<button
className="Todo__edit"
onClick={() => editTodoById(todo.id)}
>
Edit
</button>
<button
className="Todo__delete"
onClick={() => deleteTodoById(todo.id)}
>
X
</button>
</div>
);
})}
</div>
<div className="Todo__toolbar">
{todos.length > 0 ? (
<input type="checkbox" onChange={onToggleAllTodo} />
) : (
<div />
)}

<div className="Todo__tabs">
<button className="Action__btn" onClick={() => setShowing('ALL')}>
All
</button>
<button
className="Action__btn"
onClick={() => setShowing(TodoStatus.ACTIVE)}
>
Active
</button>
<button
className="Action__btn"
onClick={() => setShowing(TodoStatus.COMPLETED)}
>
Completed
</button>
</div>
);

<button className="Action__btn" onClick={onDeleteAllTodo}>
Clear all todos
</button>
</div>
</div>
);
};

export default ToDoPage;
export default ToDoPage;
10 changes: 7 additions & 3 deletions src/models/todo.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
export enum TodoStatus {
ACTIVE = 'ACTIVE',
COMPLETED = 'COMPLETED'
COMPLETED = 'COMPLETED',
}

export interface Todo {
[key: string]: any
}
id: string;
user_id: string;
content: string;
status: TodoStatus;
created_date: Object;
}
46 changes: 23 additions & 23 deletions src/service/api-frontend.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { IAPI } from "./types";
import { Todo, TodoStatus } from "../models/todo";
import shortid from "shortid";
import { IAPI } from './types';
import { Todo, TodoStatus } from '../models/todo';
import shortid from 'shortid';

class ApiFrontend extends IAPI {
async createTodo(content: string): Promise<Todo> {
return Promise.resolve({
content: content,
created_date: new Date().toISOString(),
status: TodoStatus.ACTIVE,
id: shortid(),
user_id: "firstUser",
} as Todo);
}
async createTodo(content: string): Promise<Todo> {
return Promise.resolve({
content: content,
created_date: new Date().toISOString(),
status: TodoStatus.ACTIVE,
id: shortid(),
user_id: 'firstUser',
} as Todo);
}

async getTodos(): Promise<Todo[]> {
return [
{
content: "Content",
created_date: new Date().toISOString(),
status: TodoStatus.ACTIVE,
id: shortid(),
user_id: "firstUser",
} as Todo,
];
}
async getTodos(): Promise<Todo[]> {
return [
{
content: 'Content',
created_date: new Date().toISOString(),
status: TodoStatus.ACTIVE,
id: shortid(),
user_id: 'firstUser',
} as Todo,
];
}
}

export default new ApiFrontend();
Loading