From ea90e5ac4cc8511654ac35531bcedb1a188c5051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9C=A4=EC=84=9C?= Date: Wed, 11 Mar 2026 17:17:45 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Feat:=20=EB=82=A0=EC=A7=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.html | 37 +++++++++++++++++++++++++++++++++++++ todo.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 todo.html create mode 100644 todo.js diff --git a/todo.html b/todo.html new file mode 100644 index 0000000..2cf2b57 --- /dev/null +++ b/todo.html @@ -0,0 +1,37 @@ + + + + + + To-Do List + + +
+
+

To-Do List

+
+ +

날짜

+

요일

+ +
+
+
+
+ + 0개 + +
+ +

+
+
+ + + + diff --git a/todo.js b/todo.js new file mode 100644 index 0000000..79430f4 --- /dev/null +++ b/todo.js @@ -0,0 +1,47 @@ +/** + * [날짜 및 요일 렌더링] + */ + +let currentViewDate = new Date(); + +function renderDate() { + const year = currentViewDate.getFullYear(); + const month = currentViewDate.getMonth() + 1; + const date = currentViewDate.getDate(); + + const dayList = [ + "일요일", + "월요일", + "화요일", + "수요일", + "목요일", + "금요일", + "토요일", + ]; + const currentDayName = dayList[currentViewDate.getDay()]; + + document.getElementById("today-date").textContent = + `${year}년 ${month}월 ${date}일`; + document.getElementById("today-day").textContent = currentDayName; +} + +renderDate(); + +/** + * [날짜 이동 기능] + */ +function moveToPrevDay() { + currentViewDate.setDate(currentViewDate.getDate() - 1); + renderDate(); + renderTodo(); +} + +function moveToNextDay() { + currentViewDate.setDate(currentViewDate.getDate() + 1); + renderDate(); + renderTodo(); +} + +document.getElementById("prev-btn").addEventListener("click", moveToPrevDay); +document.getElementById("next-btn").addEventListener("click", moveToNextDay); +todoForm.addEventListener("submit", addTodo); From 5a59c07bc4c590db7ff9a8960d4062e08732eda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9C=A4=EC=84=9C?= Date: Wed, 11 Mar 2026 20:31:26 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Feat:=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.css | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ todo.html | 1 + 2 files changed, 175 insertions(+) create mode 100644 todo.css diff --git a/todo.css b/todo.css new file mode 100644 index 0000000..019efe4 --- /dev/null +++ b/todo.css @@ -0,0 +1,174 @@ +:root { + --bg-color: #fff5f0; + --text-color: #5d4037; + --container-bg: #ffffff; + --accent-color: #ffab91; + --button-bg: #ffccbc; + --delete-btn-bg: #e65272; + --delete-btn-hover-bg: #c4415b; + --add-btn-bg: #799567; + --add-btn-hover-bg: #5c7348; +} + +body { + background-color: var(--bg-color); + color: var(--text-color); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 20px; + font-family: Pretendard; + max-width: 500px; + margin: auto; +} + +.container { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + margin: auto; +} + +#date-container { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} + +title { + font-size: 24px; + font-weight: bold; +} + +#today-date { + font-size: 18px; +} +#today-day { + font-size: 18px; +} +#prev-btn, +#next-btn { + background-color: transparent; + border: none; + font-size: 18px; + cursor: pointer; + color: var(--text-color); +} +#prev-btn:hover, +#next-btn:hover { + color: var(--accent-color); +} + +#add-btn { + background-color: var(--add-btn-bg); + border: none; + padding: 6px 12px; + font-size: 14px; + cursor: pointer; + color: white; + border-radius: 100px; + text-align: center; +} +#add-btn:hover { + background-color: var(--add-btn-hover-bg); +} +#todo-input { + display: flex; + flex-direction: row; + border: none; +} +#todo-count { + font-size: 14px; + color: var(--text-color); + margin-left: 10px; + text-align: center; + display: flex; + align-items: center; +} +#todo-form { + display: flex; + flex-direction: row; + background-color: var(--container-bg); + border: none; + padding: 10px; + border-radius: 100px; + gap: 10px; +} + +/* 폰트 */ +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Thin.woff2") + format("woff2"); + font-weight: 100; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-ExtraLight.woff2") + format("woff2"); + font-weight: 200; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Light.woff2") + format("woff2"); + font-weight: 300; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Regular.woff2") + format("woff2"); + font-weight: 400; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Medium.woff2") + format("woff2"); + font-weight: 500; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-SemiBold.woff2") + format("woff2"); + font-weight: 600; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Bold.woff2") + format("woff2"); + font-weight: 700; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-ExtraBold.woff2") + format("woff2"); + font-weight: 800; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Black.woff2") + format("woff2"); + font-weight: 900; + font-display: swap; +} diff --git a/todo.html b/todo.html index 2cf2b57..cb77f63 100644 --- a/todo.html +++ b/todo.html @@ -4,6 +4,7 @@ To-Do List +
From 66964ee6856496d739f1531afeb57c1d158a08d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9C=A4=EC=84=9C?= Date: Wed, 11 Mar 2026 23:24:35 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Feat:=20=ED=95=A0=20=EC=9D=BC=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.css | 55 +++++++++++++++++++++++++++++++++-- todo.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/todo.css b/todo.css index 019efe4..679c0b1 100644 --- a/todo.css +++ b/todo.css @@ -78,9 +78,10 @@ title { background-color: var(--add-btn-hover-bg); } #todo-input { - display: flex; - flex-direction: row; + flex: 1; border: none; + outline: none; + font-size: 14px; } #todo-count { font-size: 14px; @@ -100,6 +101,56 @@ title { gap: 10px; } +.input-group { + display: flex; + align-items: center; + background-color: white; + padding: 8px 10px 8px 16px; + border-radius: 30px; /* 둥근 모양 */ + gap: 15px; +} + +.todo-list { + display: flex; + flex-direction: column; + justify-content: center; + width: auto; + margin: 20px; + width: 100%; +} +.todo-item { + display: flex; + justify-content: space-between; /* 양 끝 정렬 */ + align-items: center; + padding: 10px 20px; +} + +#todo-text { + word-break: break-all; + white-space: normal; + overflow: visible; + text-align: left; + line-height: 1.4; +} + +/* 완료 시 취소선 효과 */ +.todo-item.completed span { + text-decoration: line-through; + color: #aaa; +} + +.delete-btn { + background-color: var(--delete-btn-bg); + color: white; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; +} +.delete-btn:hover { + background-color: var(--delete-btn-hover-bg); +} + /* 폰트 */ @font-face { font-family: "Pretendard"; diff --git a/todo.js b/todo.js index 79430f4..f47c303 100644 --- a/todo.js +++ b/todo.js @@ -42,6 +42,94 @@ function moveToNextDay() { renderTodo(); } +/** + * [변수 및 데이터 관리] + */ +let todoData = JSON.parse(localStorage.getItem("todoData")) || {}; + +const todoForm = document.getElementById("todo-form"); +const todoInput = document.getElementById("todo-input"); +const todoListElement = document.getElementById("todo-list"); +const countElement = document.getElementById("todo-count"); + +const dateKey = `${currentViewDate.getFullYear()}-${currentViewDate.getMonth() + 1}-${currentViewDate.getDate()}`; + document.getElementById("prev-btn").addEventListener("click", moveToPrevDay); document.getElementById("next-btn").addEventListener("click", moveToNextDay); todoForm.addEventListener("submit", addTodo); + +/** + * [할 일 목록 및 개수 렌더링] + */ +function renderTodo() { + const dateKey = `${currentViewDate.getFullYear()}-${currentViewDate.getMonth() + 1}-${currentViewDate.getDate()}`; + const currentTodos = todoData[dateKey] || []; + + // 1. 개수 업데이트 + countElement.textContent = `${currentTodos.length}개`; + + // 2. 리스트 업데이트 + todoListElement.innerHTML = ""; + + currentTodos.forEach((todo, index) => { + const li = document.createElement("li"); + li.className = "todo-item"; + if (todo.completed) li.classList.add("completed"); + + li.innerHTML = ` +
+ +
+ + `; + + li.querySelector(".todo-text").textContent = todo.text; + todoListElement.appendChild(li); + }); +} + +function saveTodo() { + localStorage.setItem("todoData", JSON.stringify(todoData)); +} + +/** + * [할 일 추가 기능] + */ +function addTodo(event) { + event.preventDefault(); + const taskText = todoInput.value.trim(); + if (!taskText) return; + if (!todoData[dateKey]) { + todoData[dateKey] = []; + } + + // 텍스트와 완료 여부를 객체로 저장 + todoData[dateKey].push({ text: taskText, completed: false }); + + saveTodo(); + todoInput.value = ""; + renderTodo(); +} + +renderTodo(); + +/** + * [완료 체크 기능] + */ +function toggleTodo(index) { + todoData[dateKey][index].completed = !todoData[dateKey][index].completed; + saveTodo(); + renderTodo(); +} + +/** + * [삭제 기능] + */ +function deleteTodo(index) { + todoData[dateKey].splice(index, 1); + saveTodo(); + renderTodo(); +} From 132ac1bf24e174e12a2f3739fae13a1b59b428f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9C=A4=EC=84=9C?= Date: Wed, 11 Mar 2026 23:50:28 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Feat:=20=ED=85=8C=EB=A7=88=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.css | 84 ++++++++++++++++++++++++++++- todo.html | 4 +- todo.js | 158 +++++++++++++++++++++++++++++------------------------- 3 files changed, 170 insertions(+), 76 deletions(-) diff --git a/todo.css b/todo.css index 679c0b1..d7d5707 100644 --- a/todo.css +++ b/todo.css @@ -10,6 +10,17 @@ --add-btn-hover-bg: #5c7348; } +/* 다크 테마 - 블루베리 컨셉 */ +body.blueberry-mode { + --bg-color: #1a1c2c; + --text-color: #e0e0ff; + --container-bg: #252a41; + --accent-color: #7986cb; + --button-bg: #3f51b5; + --delete-btn-bg: #a180ad; + --delete-btn-hover-bg: #7b5e8c; +} + body { background-color: var(--bg-color); color: var(--text-color); @@ -21,6 +32,7 @@ body { font-family: Pretendard; max-width: 500px; margin: auto; + transition: all 0.4s ease; } .container { @@ -82,6 +94,8 @@ title { border: none; outline: none; font-size: 14px; + background-color: transparent; + color: var(--text-color); } #todo-count { font-size: 14px; @@ -109,7 +123,6 @@ title { border-radius: 30px; /* 둥근 모양 */ gap: 15px; } - .todo-list { display: flex; flex-direction: column; @@ -124,6 +137,13 @@ title { align-items: center; padding: 10px 20px; } +.todo-left { + display: flex; + align-items: center; + gap: 10px; + flex: 1; + margin-right: 10px; +} #todo-text { word-break: break-all; @@ -151,6 +171,68 @@ title { background-color: var(--delete-btn-hover-bg); } +/* 1. 기본 체크박스 숨기기 */ +.checkbox-container input { + display: none; +} + +/* 2. 커스텀 체크박스 외형 */ +.custom-checkbox { + width: 20px; + height: 20px; + border: 2px solid var(--accent-color); + border-radius: 6px; /* 살짝 둥근 사각형 */ + display: inline-block; + position: relative; + cursor: pointer; + transition: all 0.2s ease; + background-color: transparent; + margin: 2px; +} + +/* 3. 체크되었을 때 배경색 변경 */ +.checkbox-container input:checked + .custom-checkbox { + background-color: var(--accent-color); +} + +/* 4. 체크 표시(V) 그리기 (가상 요소 활용) */ +.custom-checkbox::after { + content: ""; + position: absolute; + display: none; + + /* 체크 모양 만들기 */ + left: 7px; + top: 2px; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +/* 5. 체크되었을 때 V 표시 보이기 */ +.checkbox-container input:checked + .custom-checkbox::after { + display: block; +} + +#theme-btn { + position: fixed; + bottom: 30px; + right: 30px; + width: 60px; + height: 60px; + border-radius: 50%; + background-color: var(--button-bg); + border: none; + font-size: 28px; + cursor: pointer; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; +} + /* 폰트 */ @font-face { font-family: "Pretendard"; diff --git a/todo.html b/todo.html index cb77f63..1e42af9 100644 --- a/todo.html +++ b/todo.html @@ -25,11 +25,13 @@

To-Do List

placeholder="오늘의 할 일은?" required /> - 0개 + 남은 할 일 0개

+ +
diff --git a/todo.js b/todo.js index f47c303..bcfec99 100644 --- a/todo.js +++ b/todo.js @@ -1,14 +1,30 @@ -/** - * [날짜 및 요일 렌더링] - */ - +/* --- 1. 데이터 및 기준 변수 관리 --- */ let currentViewDate = new Date(); +let todoData = JSON.parse(localStorage.getItem("todoData")) || {}; -function renderDate() { +const todoForm = document.getElementById("todo-form"); +const todoInput = document.getElementById("todo-input"); +const todoListElement = document.getElementById("todo-list"); +const countElement = document.getElementById("todo-count"); + +/* --- 2. 유틸리티 함수 (중복 로직 분리) --- */ + +// 현재 보고 있는 날짜를 'YYYY-MM-DD' 형식의 키로 반환하는 함수 +function getCurrentDateKey() { const year = currentViewDate.getFullYear(); const month = currentViewDate.getMonth() + 1; const date = currentViewDate.getDate(); + return `${year}-${month}-${date}`; +} + +// 로컬 스토리지에 현재 데이터 상태 저장 +function saveTodo() { + localStorage.setItem("todoData", JSON.stringify(todoData)); +} + +/* --- 3. 렌더링 함수 (화면 그리기) --- */ +function renderDate() { const dayList = [ "일요일", "월요일", @@ -18,57 +34,21 @@ function renderDate() { "금요일", "토요일", ]; - const currentDayName = dayList[currentViewDate.getDay()]; document.getElementById("today-date").textContent = - `${year}년 ${month}월 ${date}일`; - document.getElementById("today-day").textContent = currentDayName; -} - -renderDate(); - -/** - * [날짜 이동 기능] - */ -function moveToPrevDay() { - currentViewDate.setDate(currentViewDate.getDate() - 1); - renderDate(); - renderTodo(); + `${currentViewDate.getFullYear()}년 ${currentViewDate.getMonth() + 1}월 ${currentViewDate.getDate()}일`; + document.getElementById("today-day").textContent = + dayList[currentViewDate.getDay()]; } -function moveToNextDay() { - currentViewDate.setDate(currentViewDate.getDate() + 1); - renderDate(); - renderTodo(); -} - -/** - * [변수 및 데이터 관리] - */ -let todoData = JSON.parse(localStorage.getItem("todoData")) || {}; - -const todoForm = document.getElementById("todo-form"); -const todoInput = document.getElementById("todo-input"); -const todoListElement = document.getElementById("todo-list"); -const countElement = document.getElementById("todo-count"); - -const dateKey = `${currentViewDate.getFullYear()}-${currentViewDate.getMonth() + 1}-${currentViewDate.getDate()}`; - -document.getElementById("prev-btn").addEventListener("click", moveToPrevDay); -document.getElementById("next-btn").addEventListener("click", moveToNextDay); -todoForm.addEventListener("submit", addTodo); - -/** - * [할 일 목록 및 개수 렌더링] - */ function renderTodo() { - const dateKey = `${currentViewDate.getFullYear()}-${currentViewDate.getMonth() + 1}-${currentViewDate.getDate()}`; + const dateKey = getCurrentDateKey(); const currentTodos = todoData[dateKey] || []; - // 1. 개수 업데이트 - countElement.textContent = `${currentTodos.length}개`; + // 남은 할 일 개수 계산 (전체 개수를 원하면 .length만 사용) + const remainingTodos = currentTodos.filter((todo) => !todo.completed); + countElement.textContent = `남은 할 일 ${remainingTodos.length}개`; - // 2. 리스트 업데이트 todoListElement.innerHTML = ""; currentTodos.forEach((todo, index) => { @@ -77,36 +57,31 @@ function renderTodo() { if (todo.completed) li.classList.add("completed"); li.innerHTML = ` -
- -
- - `; +
+ + +
+ + `; li.querySelector(".todo-text").textContent = todo.text; todoListElement.appendChild(li); }); } -function saveTodo() { - localStorage.setItem("todoData", JSON.stringify(todoData)); -} +/* --- 4. 기능 함수 (데이터 수정) --- */ -/** - * [할 일 추가 기능] - */ function addTodo(event) { event.preventDefault(); const taskText = todoInput.value.trim(); if (!taskText) return; - if (!todoData[dateKey]) { - todoData[dateKey] = []; - } - // 텍스트와 완료 여부를 객체로 저장 + const dateKey = getCurrentDateKey(); // 함수 실행 시점의 날짜 키 가져오기 + if (!todoData[dateKey]) todoData[dateKey] = []; + todoData[dateKey].push({ text: taskText, completed: false }); saveTodo(); @@ -114,22 +89,57 @@ function addTodo(event) { renderTodo(); } -renderTodo(); - -/** - * [완료 체크 기능] - */ function toggleTodo(index) { + const dateKey = getCurrentDateKey(); todoData[dateKey][index].completed = !todoData[dateKey][index].completed; saveTodo(); renderTodo(); } -/** - * [삭제 기능] - */ function deleteTodo(index) { + const dateKey = getCurrentDateKey(); todoData[dateKey].splice(index, 1); saveTodo(); renderTodo(); } + +/* --- 5. 날짜 이동 및 이벤트 연결 --- */ + +function moveDay(offset) { + currentViewDate.setDate(currentViewDate.getDate() + offset); + renderDate(); + renderTodo(); +} + +document + .getElementById("prev-btn") + .addEventListener("click", () => moveDay(-1)); +document.getElementById("next-btn").addEventListener("click", () => moveDay(1)); +todoForm.addEventListener("submit", addTodo); + +/* --- 6. 테마 변경 --- */ +const themeToggle = document.getElementById("theme-btn"); + +function updateThemeUI(theme) { + if (theme === "blueberry") { + document.body.classList.add("blueberry-mode"); + themeToggle.textContent = "🫐"; + } else { + document.body.classList.remove("blueberry-mode"); + themeToggle.textContent = "🍑"; + } +} + +// 초기 테마 설정 +updateThemeUI(localStorage.getItem("theme")); + +themeToggle.addEventListener("click", () => { + const isBlueberry = document.body.classList.toggle("blueberry-mode"); + const theme = isBlueberry ? "blueberry" : "peach"; + localStorage.setItem("theme", theme); + updateThemeUI(theme); +}); + +// 첫 실행 +renderDate(); +renderTodo(); From d863b58d9663d0a994b3beb2cff51a730b8382fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9C=A4=EC=84=9C?= Date: Fri, 13 Mar 2026 14:07:04 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Deploy:=20=EC=9E=AC=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34a3ed5..f127f32 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,4 @@ - [좋은 코드리뷰 방법](https://tech.kakao.com/2022/03/17/2022-newkrew-onboarding-codereview/) - [MDN 공식문서-createElement()](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) - [MDN 공식문서-appendChild()](https://developer.mozilla.org/ko/docs/Web/API/Node/appendChild) -- [DOM 개념,HTML 요소 조작](https://poiemaweb.com/js-dom#3-dom-query--traversing-%EC%9A%94%EC%86%8C%EC%97%90%EC%9D%98-%EC%A0%91%EA%B7%BC) \ No newline at end of file +- [DOM 개념,HTML 요소 조작](https://poiemaweb.com/js-dom#3-dom-query--traversing-%EC%9A%94%EC%86%8C%EC%97%90%EC%9D%98-%EC%A0%91%EA%B7%BC)