Skip to content
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
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,71 @@
# react-todo-list-precourse
# 2차 미니과제 - TODO List 만들기 (React)

<table>
<tr>
<td align="center">
<a href="https://www.github.com/jasper200207">
<img src="https://github.com/jasper200207.png" width="80" alt="main manager"/>
<br/><b>jasper200207</b>
</a>
</td>
</tr>
</table>

## 사용 스택

<table>
<tr>
<td align="center">
<img src="https://img.shields.io/badge/React-61DAFB?style=flat-square&logo=React&logoColor=white" />
</td>
<td align="center">
v18
</td>
</tr>
<tr>
<td align="center">
<img src="https://img.shields.io/badge/Node.js-5FA04E?style=flat-square&logo=nodedotjs&logoColor=white" />
</td>
<td align="center">
v18.17.1
</td>
</tr>
<tr>
<td align="center">
<img src="https://img.shields.io/badge/TypeScript-0769AD?style=flat-square&logo=TypeScript&logoColor=white" />
</td>
<td align="center" />
</tr>
</table>

## 실행 방법

```bash
npm install
npm run start
```

## 작업 리스트

❗️는 필수 구현 사항

### Todo List UI 구성

- [x] ❗️Todo List
- [x] ❗️Todo Item 제목
- [x] ❗️Todo Item 상태 ( 완료, 진행중 )
- [x] ❗️Todo Item 삭제
- [x] ❗️Todo Item 생성

### Todo List 기능 삽입

- [x] ❗️Todo CRUD
- [x] ❗️Todo 생성
- [x] ❗️Todo 조회
- [x] ❗️Todo 수정 ( 상태 변경, 내용 변경 )
- [x] ❗️Todo 단일 삭제
- [ ] Todo 전체 삭제
- [ ] Todo 목록 캐싱 ( 새로고침시에도 유지 )
- [ ] Todo 필터링
- [ ] All, Todo, onProgress, Done ( 상태별 필터링 )
- [ ] Drag & Drop can change status and order ( 상태 및 순서 변경 )
6 changes: 5 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Jua&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script type="module" src="/src/App.js"></script>
</body>
</html>
12 changes: 12 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import url("./styles/reset.css");
@import url("./styles/todoitem.css");
@import url("./styles/todolist.css");
@import url("./styles/statuscircle.css");

* {
color: #1f4e5f;
}

*:not(.material-icons) {
font-family: "Jua", sans-serif !important;
}
9 changes: 9 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ReactDOM from "react-dom/client";
import App from "./App";

function createApp() {
const app = ReactDOM.createRoot(document.getElementById("app"));
app.render(App());
}

createApp();
12 changes: 12 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import TodoList from "./components/TodoList";
import "./App.css";

function App() {
return (
<div>
<TodoList />
</div>
);
}

export default App;
23 changes: 23 additions & 0 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type InputProps = {
value: string;
placeholder: string;
onChange: (value: string) => void;
onEndInput: (value: string) => void;
};

function Input({ value, placeholder, onChange, onEndInput }: InputProps) {
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && value !== "") onEndInput(value);
};
return (
<input
type="text"
onKeyDown={handleKeyDown}
onChange={e => onChange(e.target.value)}
value={value}
placeholder={placeholder}
/>
);
}

export default Input;
21 changes: 21 additions & 0 deletions src/components/StatusCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Status from "../types/Status";

type StatusCircleProps = {
status: Status;
onClick: () => void;
};

const StatusIconMap = {
Todo: "radio_button_unchecked",
Done: "check_circle",
};

function StatusCircle({ status, onClick }: StatusCircleProps) {
return (
<button type="button" onClick={onClick} className="material-icons statuscircle">
{StatusIconMap[status]}
</button>
);
}

export default StatusCircle;
32 changes: 32 additions & 0 deletions src/components/TodoInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState } from "react";
import Todo from "../types/Todo";
import Input from "./Input";

type TodoInputProps = {
onAddTodo: (todo: Todo) => void;
};

const defaultTodo: Todo = {
id: "",
title: "",
status: "Todo",
};

function TodoInput({ onAddTodo }: TodoInputProps) {
const [todo, setTodo] = useState<Todo>(defaultTodo);
return (
<div className="todoitem">
<Input
value={todo.title}
placeholder="What is next Todo? (Enter to Add)"
onChange={v => setTodo(prev => ({ ...prev, title: v }))}
onEndInput={() => {
onAddTodo(todo);
setTodo(defaultTodo);
}}
/>
</div>
);
}

export default TodoInput;
20 changes: 20 additions & 0 deletions src/components/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Todo from "../types/Todo";
import StatusCircle from "./StatusCircle";

type TodoItemProps = {
todo: Todo;
onCheck: (todo: Todo) => void;
onDelete: (todo: Todo) => void;
};

function TodoItem({ todo, onCheck, onDelete }: TodoItemProps) {
return (
<div className="todoitem">
<p>{todo.title}</p>
<StatusCircle status={todo.status} onClick={() => onCheck(todo)} />
<button aria-label="delete" className="todoitem--delete" type="button" onClick={() => onDelete(todo)} />
</div>
);
}

export default TodoItem;
18 changes: 18 additions & 0 deletions src/components/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useState } from "react";
import Todo from "../types/Todo";
import TodoListTitleContent from "./TodoList/TodoListTitleContent";
import TodoListInputContent from "./TodoList/TodoListInputContent";
import TodoListItemContent from "./TodoList/TodoListItemContent";

function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
return (
<div className="todolist">
<TodoListTitleContent />
<TodoListInputContent setTodos={setTodos} />
<TodoListItemContent todos={todos} setTodos={setTodos} />
</div>
);
}

export default TodoList;
18 changes: 18 additions & 0 deletions src/components/TodoList/TodoListInputContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Todo from "../../types/Todo";
import TodoInput from "../TodoInput";

type TodoListInputContentProps = {
setTodos: React.Dispatch<React.SetStateAction<Todo[]>>;
};

function TodoListInputContent({ setTodos }: TodoListInputContentProps) {
return (
<TodoInput
onAddTodo={todo => {
setTodos(prev => [...prev, { ...todo, id: `todo:${Date.now()}` }]);
}}
/>
);
}

export default TodoListInputContent;
37 changes: 37 additions & 0 deletions src/components/TodoList/TodoListItemContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Todo from "../../types/Todo";
import TodoItem from "../TodoItem";

type TodoListItemContentProps = {
todos: Todo[];
setTodos: React.Dispatch<React.SetStateAction<Todo[]>>;
};

const findAndCheck = (todos: Todo[], id: string): Todo[] => {
return todos.map(todo => {
if (todo.id === id) {
return { ...todo, status: todo.status === "Todo" ? "Done" : "Todo" };
}
return todo;
});
};

const findAndDelete = (todos: Todo[], id: string) => {
return todos.filter(todo => todo.id !== id);
};

function TodoListItemContent({ todos, setTodos }: TodoListItemContentProps) {
return (
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onCheck={() => setTodos(prev => findAndCheck(prev, todo.id))}
onDelete={() => setTodos(prev => findAndDelete(prev, todo.id))}
/>
))}
</div>
);
}

export default TodoListItemContent;
5 changes: 5 additions & 0 deletions src/components/TodoList/TodoListTitleContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function TodoListTitleContent() {
return <h1>Todo List</h1>;
}

export default TodoListTitleContent;
Empty file removed src/main.js
Empty file.
Loading