Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
86 changes: 86 additions & 0 deletions app.js
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2주차 react 마이그레이션 과정에서는 localStorage 한번 활용해보셨으면 합니당!

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { todoActions, todos, currentDate } from "./todoLogic.js";

const dateElement = document.getElementById("today-date");
const prevBtn = document.getElementById("prev-day");
const nextBtn = document.getElementById("next-day");
const todoForm = document.getElementById("todo-form");
const todoInput = document.getElementById("todo-input");
const todoList = document.getElementById("todo-list");
const todoCount = document.getElementById("todo-count");

//날짜
function formatDate(date) {
return `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일`;
}

function getDateKey(date) {
return date.toISOString().split("T")[0];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

toISOString()을 사용하면 key로 로컬 날짜가 아니라, UTC 기준 날짜 기반 문자열을 반환해서 로컬 날짜와 다른 키로 저장될 수 있어요.

현재는 잘 동작하지만, 일관성 측면에서 ISOString() 사용보다 getFullYear(), getMonth(), getDate()를 활용해 로컬 기준'YYYY-MM-DD' 문자열을 직접 생성하는 방식이 더 좋을 수도 있을 것 같아요.

브라우저 콘솔에 코드 입력해보시면 확인하실 수 있습니다!

const testDate = new Date(2026, 2, 16, 1, 0, 0);

console.log("로컬:", testDate.toString());
console.log("ISO:", testDate.toISOString());
console.log("date key:", testDate.toISOString().split("T")[0]);

}

// 렌더링
function render() {
//날짜 관련
dateElement.innerText = formatDate(currentDate);
const currentKey = getDateKey(currentDate);
const filteredTodos = todos.filter((todo) => todo.date === currentKey);

todoList.innerHTML = "";

// map 함수 느낌
filteredTodos.forEach((todo) => {
const li = document.createElement("li");
li.className = `todo-item ${todo.completed ? "completed" : ""}`;

li.innerHTML = `
<label class="checkbox-container">
<input type="checkbox" class="todo-checkbox" ${
todo.completed ? "checked" : ""
}>
<span class="checkmark"></span>
</label>
<p class="todo-text">${todo.text}</p>
<button class="delete-button">삭제</button>
`;
Comment on lines +34 to +43
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment on lines +34 to +43
Copy link
Copy Markdown

@Yeobi00 Yeobi00 Mar 16, 2026

Choose a reason for hiding this comment

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

todo.text는 html input 태그에서 가져오는 사용자 입력값인데, innerHTML에 그대로 삽입하기보다는 textContent로 넣는 게 더 안전해보여요.

문서에서도 innerHTML은 문자열을 HTML로 그대로 파싱하는 API라서 injection 공격에 취약하다고 적혀있어요.
🔗 https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML

예를 들어, 사용자가 '<b>코드 리뷰하기</b>'라고 입력하면 Bold체로 할 일이 등록되는 것을 확인하실 수 있을 거에요.

Image


// 체크박스 클릭 이벤트
li.querySelector(".todo-checkbox").addEventListener("change", () => {
todoActions.toggleTodo(todo.id);
render();
});

// 삭제 버튼 클릭 이벤트
li.querySelector(".delete-button").addEventListener("click", () => {
todoActions.deleteTodo(todo.id);
render();
});

todoList.appendChild(li);
});

const remaining = filteredTodos.filter((todo) => !todo.completed).length;
todoCount.innerText = `${remaining}개`;
}

// 날짜 이동
prevBtn.addEventListener("click", () => {
todoActions.changeDate(-1); // 전날
render();
});

nextBtn.addEventListener("click", () => {
todoActions.changeDate(1); // 다음날
render();
});

//투두 등록
todoForm.addEventListener("submit", (e) => {
e.preventDefault();
const text = todoInput.value.trim();
if (text) {
todoActions.addTodo(text, getDateKey(currentDate));
todoInput.value = "";
render();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

입력값 앞뒤 공백을 제거하고 빈 문자열을 한 번 더 체크해주신 점이 좋은 것 같습니다!
추가로 등록 후 다시 바로 입력할 수 있게 todoInput.focus()까지 넣어주면 사용성이 더 좋아질 것 같아요 :)

});

render();
36 changes: 36 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="kor">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>TodoList</title>
</head>
<body>
<header>
<span>To Do</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

To Do는 페이지의 대표 제목이니, span보다는 h1 같은 heading 태그를 사용하는 것도 좋을 것 같아요.
시맨틱 구조가 더 명확해지고 문서 의미도 더 잘 드러날 것 같습니다!

<div class="date-controller">
<button id="prev-day">◀</button>
<time id="today-date">0000년 00월 00일</time>
<button id="next-day">▶</button>
</div>
</header>
<main>
<form id="todo-form">
<input
type="text"
id="todo-input"
placeholder="오늘의 할일을 적어주세요!"
required
/>
<div>
<span id="todo-count">0개</span>
<button type="submit" id="submit-button">등록</button>
</div>
</form>
<ul id="todo-list"></ul>
</main>

<script src="app.js" type="module"></script>
</body>
</html>
152 changes: 152 additions & 0 deletions style.css
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2주차때는 pretendard 폰트도 사용해보셨으면 합니당~

Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/* body */
body {
background-color: #9bbbd4;
display: flex;
flex-direction: column;
align-items: center;
}

/* header */
header {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
gap: 20px;
}

header span {
font-size: 32px;
font-weight: bold;
color: #3a1d1d;
margin-bottom: 20px;
}

.date-controller {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
}
Comment on lines +25 to +30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

민교님 리뷰 코멘트
민교님 리뷰랑 동일한 내용인데, date 값이 변함에 따라 양 옆 버튼이 움직여서, 이 점 수정하면 좋을거 같습니당!


#today-date {
font-size: 20px;
font-weight: 600;
color: #3a1d1d;
}

.date-controller button {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #3a1d1d;
transition: transform 0.2s;
}

.date-controller button:hover {
transform: scale(1.2);
}

/* todo form */
#todo-form {
display: flex;
align-items: center;
background: white;
border-radius: 15px;
padding: 10px 20px;
gap: 20px;
width: 300px;
box-sizing: border-box;
margin: 0 auto;
}

/* todo input */
#todo-input {
flex-grow: 1;
border: 0;
}

#submit-button {
background-color: #f7e600;
color: #3a1d1d;
border: 0;
border-radius: 10px;
padding: 5px 10px;
font-weight: 500;
}
Comment on lines +70 to +77
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

여기에도 cursor: pointer; 추가해주시면 좋을거 같아용!

추가로 deleteButton 처럼 hover시에 효과도 넣어주면 좋을거 같습니다~


/* todo item */
#todo-list {
padding: 0;
margin: 0;
list-style: none;
}
/* ul 안의 li들은 기본적으로 paddiing left 40px을 가짐.. 이걸 없애줘야 가운데 정렬됨 */

.todo-item {
display: flex;
align-items: center;
justify-content: center;
background-color: #f7e600;
color: #3a1d1d;
border-radius: 15px;
margin-top: 15px;
padding: 10px;
width: 300px;
box-sizing: border-box;
}

.todo-item.completed .todo-text {
text-decoration: line-through;
color: gray;
}

.todo-checkbox {
display: none;
}

.checkmark {
width: 25px;
height: 25px;
background-color: white;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-right: 15px;
position: relative;
}

.todo-checkbox:checked + .checkmark {
background-color: #5bd15f;
}

.todo-checkbox:checked + .checkmark::after {
content: "✔";
color: white;
font-size: 16px;
font-weight: bold;
}

.todo-text {
flex-grow: 1;
text-align: center;
margin: 0 10px;
word-break: break-all;
}

.delete-button {
background-color: #3a1d1d;
color: #f7e600;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

color: #3a1d1d;
background-color: #f7e600;

색상 값이 여러 군데 반복되고 있어서, 이후 스타일 수정 시 한 번에 관리하기 쉽도록 CSS 변수로 빼두는 것도 좋을 것 같아요!

border: none;
border-radius: 12px;
padding: 8px 15px;
cursor: pointer;
font-weight: bold;
}
Comment on lines +140 to +148
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

image

텍스트가 길어졌을때, 삭제 버튼의 공간이 부족하게 됩니당~!
flex-shrink: 0; 추가해주시면 좋을거 같아요


.delete-button:hover {
background-color: black;
}
34 changes: 34 additions & 0 deletions todoLogic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export let todos = [];
export let currentDate = new Date();

export const todoActions = {
//날짜 변경
changeDate: (offset) => {
currentDate.setDate(currentDate.getDate() + offset);
return currentDate;
},

// 투두 추가
addTodo: (text, dateStr) => {
const newTodo = {
id: Date.now(),
text: text,
completed: false,
date: dateStr,
};
todos = [...todos, newTodo];
return todos;
},

deleteTodo: (id) => {
todos = todos.filter((todo) => todo.id !== id);
return todos;
},

toggleTodo: (id) => {
todos = todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
);
return todos;
},
};