-
Notifications
You must be signed in to change notification settings - Fork 11
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
[1주차] 김철흥 미션 제출합니다. #8
base: main
Are you sure you want to change the base?
Changes from all commits
2d113f2
92794b7
b347e1f
4867e4f
b3c07d5
4f2a7d6
667f8b3
3d752cb
e28b796
dd2b65c
aee61f2
5effcda
82cde2b
fd5045c
37c7760
60c2e62
b5d9d4d
f796480
e20ca56
9f1b512
3856ed4
1aca14c
0968126
fcb9b56
610beb3
ad0cece
f83052b
6f05b05
ba21f13
521d0af
3cef316
ea45e49
2724f5e
113763e
10535b8
2b31ee7
de5b2f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
document.addEventListener('DOMContentLoaded', () => { | ||
// Date-Picker trigger | ||
const calendarButton = document.querySelector('.calendar-button'); | ||
const datePicker = document.getElementById('date-picker'); | ||
|
||
// 캘린더 열기 | ||
calendarButton.addEventListener('click', () => { | ||
datePicker.showPicker ? datePicker.showPicker() : datePicker.click(); | ||
}); | ||
|
||
// 렌더링할 날짜 형식 변환 | ||
const formatSelectedDate = (selectedDate) => { | ||
const year = selectedDate.getFullYear(); | ||
const month = String(selectedDate.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 +1 | ||
const day = String(selectedDate.getDate()).padStart(2, '0'); | ||
|
||
const weekdays = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']; | ||
const weekday = weekdays[selectedDate.getDay()]; | ||
|
||
return `${year}.${month}.${day} (${weekday})`; | ||
}; | ||
|
||
let selectedDateStr = | ||
localStorage.getItem('selectedDate') || | ||
new Date().toISOString().split('T')[0]; | ||
let selectedDateObj = new Date(selectedDateStr); | ||
|
||
// 초기 날짜 설정 (현재 날짜) | ||
document.querySelector('.today-date').textContent = | ||
formatSelectedDate(selectedDateObj); | ||
|
||
// 날짜 선택 이벤트 처리 | ||
datePicker.addEventListener('change', (e) => { | ||
selectedDateStr = e.target.value; | ||
|
||
// 선택한 날짜 저장 | ||
localStorage.setItem('selectedDate', selectedDateStr); | ||
|
||
const dateParts = selectedDateStr.split('-'); | ||
const selectedDateObj = new Date( | ||
Number(dateParts[0]), // 연도 | ||
Number(dateParts[1]) - 1, // 월 (0부터 시작) | ||
Number(dateParts[2]) // 일 | ||
); | ||
|
||
document.querySelector('.today-date').textContent = | ||
formatSelectedDate(selectedDateObj); | ||
|
||
renderTasksForSelectedDate(); | ||
updateTaskCount(); | ||
}); | ||
|
||
// Task 추가 관련 요소들 | ||
const addTaskForm = document.getElementById('add-task-form'); | ||
const addTaskInput = document.querySelector('.add-task-input'); | ||
const toDoList = document.getElementById('to-do-list'); | ||
const doneList = document.getElementById('done-list'); | ||
const noTasksToDo = document.getElementById('no-tasks-to-do'); | ||
const noTasksDone = document.getElementById('no-tasks-done'); | ||
|
||
// Task 개수 업데이트 | ||
const updateTaskCount = () => { | ||
const todoListCount = toDoList.childElementCount; | ||
const doneListCount = doneList.childElementCount; | ||
|
||
if (todoListCount === 0) { | ||
noTasksToDo.style.display = 'block'; | ||
noTasksToDo.textContent = 'Add Your Task!'; | ||
} else { | ||
noTasksToDo.style.display = 'none'; | ||
} | ||
|
||
if (doneListCount === 0) { | ||
noTasksDone.style.display = 'block'; | ||
noTasksDone.textContent = 'No Tasks Done Yet!'; | ||
} else { | ||
noTasksDone.style.display = 'none'; | ||
} | ||
|
||
document.getElementById( | ||
'to-do-list-title' | ||
).textContent = `To Do (${todoListCount})`; | ||
document.getElementById( | ||
'done-list-title' | ||
).textContent = `Done (${doneListCount})`; | ||
}; | ||
|
||
// Task 목록 | ||
let tasks = JSON.parse(localStorage.getItem('tasks')) || []; | ||
|
||
// Task 추가 | ||
const addTask = () => { | ||
const taskValue = addTaskInput.value.trim(); | ||
if (taskValue === '') return; | ||
|
||
// Task 객체 생성 | ||
const task = { | ||
id: Date.now(), | ||
text: taskValue, | ||
completed: false, | ||
date: selectedDateStr, | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 task 객체 unique ID로 관리하게 되면 장점이 많다고 생각합니다. 다만 Date.now()로 값을 할당하게 되면 date와 겹치는 부분이 있다고 생각해 hash 등을 사용해 암호화하는 방식은 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 시간을 고유한 ID로 사용한 후에, 날짜 선택 기능을 추가하면서 id의 값이 중복된 의미의 필드가 된 것 같네요! |
||
|
||
tasks.push(task); | ||
localStorage.setItem('tasks', JSON.stringify(tasks)); | ||
|
||
// Task 렌더링 | ||
if (task.date === selectedDateStr) { | ||
renderTask(task); | ||
} | ||
|
||
addTaskInput.value = ''; | ||
updateTaskCount(); | ||
}; | ||
|
||
// Task 렌더링 | ||
const renderTask = (taskObj) => { | ||
const { id, text, completed } = taskObj; | ||
|
||
// 성능 최적화를 위해 DocumentFragment 사용 | ||
const fragment = document.createDocumentFragment(); | ||
|
||
// Task Box + Delete Button | ||
const taskDeleteContainer = document.createElement('li'); | ||
taskDeleteContainer.classList.add('task-delete-container'); | ||
taskDeleteContainer.dataset.id = id; // Task ID 저장 | ||
|
||
// Task Box | ||
const taskItem = document.createElement('div'); | ||
taskItem.classList.add('task-item', 'to-do-item'); | ||
|
||
// Checkbox | ||
const checkboxContainer = document.createElement('label'); | ||
checkboxContainer.classList.add('checkbox-container'); | ||
|
||
const checkbox = document.createElement('input'); | ||
checkbox.type = 'checkbox'; | ||
checkbox.classList.add('task-checkbox'); | ||
checkbox.checked = completed; | ||
|
||
// Task 내용 | ||
const taskText = document.createElement('span'); | ||
taskText.classList.add('task-text'); | ||
taskText.textContent = text; | ||
|
||
if (completed) { | ||
taskItem.classList.add('done-item'); | ||
taskText.classList.add('done-text'); | ||
|
||
// 완료된 Task에 체크 아이콘 추가 | ||
const checkIcon = document.createElement('img'); | ||
checkIcon.src = 'icons/check.svg'; | ||
checkIcon.alt = 'Check Icon'; | ||
checkIcon.classList.add('check-icon'); | ||
checkboxContainer.appendChild(checkIcon); | ||
} | ||
|
||
// 삭제 버튼 | ||
const taskDeleteButton = document.createElement('button'); | ||
taskDeleteButton.classList.add('task-delete-button'); | ||
taskDeleteButton.innerHTML = | ||
'<img src="icons/trash.svg" alt="Delete Icon">'; | ||
|
||
// Task 삭제 Event Listener | ||
taskDeleteButton.addEventListener('click', () => { | ||
removeTask(id); | ||
}); | ||
|
||
// Task 완료 상태 변경 Event Listener | ||
checkbox.addEventListener('change', () => { | ||
toggleTask( | ||
id, | ||
taskDeleteContainer, | ||
taskItem, | ||
taskText, | ||
checkboxContainer | ||
); | ||
}); | ||
|
||
checkboxContainer.appendChild(checkbox); | ||
taskItem.appendChild(checkboxContainer); | ||
taskItem.appendChild(taskText); | ||
taskDeleteContainer.appendChild(taskItem); | ||
taskDeleteContainer.appendChild(taskDeleteButton); | ||
|
||
fragment.appendChild(taskDeleteContainer); | ||
|
||
if (completed) { | ||
doneList.appendChild(fragment); | ||
} else { | ||
toDoList.appendChild(fragment); | ||
} | ||
|
||
updateTaskCount(); | ||
}; | ||
|
||
// Task 삭제 | ||
const removeTask = (id) => { | ||
tasks = tasks.filter((task) => task.id !== id); | ||
localStorage.setItem('tasks', JSON.stringify(tasks)); | ||
|
||
document.querySelector(`[data-id="${id}"]`).remove(); | ||
updateTaskCount(); | ||
}; | ||
|
||
// Task 완료 상태 변경 | ||
const toggleTask = ( | ||
id, | ||
taskDeleteContainer, | ||
taskItem, | ||
taskText, | ||
checkboxContainer | ||
) => { | ||
const task = tasks.find((task) => task.id === id); | ||
task.completed = !task.completed; | ||
localStorage.setItem('tasks', JSON.stringify(tasks)); | ||
|
||
taskItem.classList.toggle('done-item'); | ||
taskText.classList.toggle('done-text'); | ||
|
||
if (task.completed) { | ||
const checkIcon = document.createElement('img'); | ||
checkIcon.src = 'icons/check.svg'; | ||
checkIcon.alt = 'Check Icon'; | ||
checkIcon.classList.add('check-icon'); | ||
checkboxContainer.appendChild(checkIcon); | ||
|
||
doneList.appendChild(taskDeleteContainer); | ||
} else { | ||
const checkIcon = document.querySelector('.check-icon'); | ||
if (checkIcon) checkIcon.remove(); | ||
|
||
toDoList.appendChild(taskDeleteContainer); | ||
} | ||
|
||
updateTaskCount(); | ||
}; | ||
|
||
// 선택된 날짜에 대한 Task 렌더링 | ||
const renderTasksForSelectedDate = () => { | ||
toDoList.innerHTML = ''; | ||
doneList.innerHTML = ''; | ||
|
||
const filteredTasks = tasks.filter((task) => task.date === selectedDateStr); | ||
filteredTasks.forEach((task) => renderTask(task)); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 과제에서 그럴 일은 없겠지만, date 값을 객체 속성으로 두면 O(N)이 걸릴 것 같습니다. {id, date, text, completed} -> date: {id, text, completed} 이렇게 2차원으로 만드시는 건 어떠실까요? (보통 N >> k 이기 때문에0 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위에서 date 필드와 id 필드를 리팩토링할 소요가 있었는데, date 필드 안에 task 객체 배열을 두는 방식으로 리팩토링하면 코드가 더 깔끔해질 것 같네요! 좋은 피드백 감사합니다 :) |
||
|
||
renderTasksForSelectedDate(); | ||
|
||
addTaskForm.addEventListener('submit', (e) => { | ||
e.preventDefault(); | ||
|
||
if (addTaskInput.value.trim() === '') { | ||
alert('Please Enter Your Task!'); | ||
return; | ||
} else { | ||
addTask(); | ||
} | ||
}); | ||
|
||
addTaskForm.addEventListener('keypress', (e) => { | ||
if (e.key === 'Enter') { | ||
e.preventDefault(); | ||
addTask(); | ||
} | ||
}); | ||
|
||
// 초기 화면 로딩 시 Task 개수 업데이트 | ||
updateTaskCount(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 리액트에서 전체 코드 순서를
변수 -> 함수 -> useEffect -> 렌더링
과 같이 작성해 왔는데요, 바닐라에서도 마찬가지로변수 -> 함수
순서로 작성하게 되더라고요. 그런 점에서변수 -> 함수 -> 변수 -> 함수
같은 흐름은 관심사를 분리할 때라는 좋은 신호인 것 같습니다.이 위로는 date 관련 코드이니 date.js에, 아래는 task와 관련된 코드이니 task.js로 분리해서, app.js에서는 그 함수들을 import하여 document에 이벤트 리스너를 등록하는 형식은 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 코드를 작성하면서 모듈화의 필요성을 느꼈는데, 이 부분 반영해서 리팩토링해보겠습니다 :)