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
8 changes: 8 additions & 0 deletions .prettierrc
Copy link
Member

Choose a reason for hiding this comment

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

prettier 사용하신 거 좋은 거 같아요. 제가 사용하는 세팅도 공유드릴테니 해당 부분도 보시면 좋을 거 같아요

{
  "arrowParens": "always",
  "bracketSpacing": true,
  "endOfLine": "lf",
  "printWidth": 120,
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "useTabs": false,
  "plugins": ["prettier-plugin-tailwindcss"] // 이건 무시하셔도 됨
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false
}
261 changes: 261 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// 전역 변수
let todosByDate = {};
let currentDate = new Date();
let nextId = 1;
Copy link
Member

Choose a reason for hiding this comment

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

지금 nextId라는 변수를 선언하셔서 unique key로 사용하신 것으로 보이는데요, 동일한 날짜/텍스트여도 데이터를 구분한다는 점에서 좋은 선택인 것 같습니다. 다만 이렇게 하시는 것보다는 현재 Date()을 uuid 인코딩해서 사용하신다던지, 각 날짜별로 unique index를 구분한다던지 하시면 더 좋을 거 같아요. 지금 하시는 건 단순 index 처리여서 해당 key가 진정 의미가 있는가? 했을 때 더 좋은 방향으로 나아갈 여지가 있는 것 같습니다


// 페이지 로드 완료 시 실행
window.addEventListener('load', function () {
// 기존 데이터 삭제 (새 구조로 변경)
localStorage.removeItem('todoList');
Copy link
Member

Choose a reason for hiding this comment

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

이 부분은 지우셔도 될 것 같아요


initializeApp();
this.getSelection;
Copy link
Member

Choose a reason for hiding this comment

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

이 코드도 현재로써는 동작하지 않는 코드인 거 같아요

});

// 앱 초기화
function initializeApp() {
loadTodosFromStorage();
updateDateDisplay();
setupEventListeners();
renderTodoList();
updateProgress();
}

// 이벤트 리스너 설정
function setupEventListeners() {
// 이전 날짜 버튼
const prevBtn = document.getElementById('prevDay');
if (prevBtn) {
prevBtn.onclick = function () {
moveToDate(-1);
};
}

// 다음 날짜 버튼
const nextBtn = document.getElementById('nextDay');
if (nextBtn) {
nextBtn.onclick = function () {
moveToDate(1);
};
}

// 날짜 입력 필드
const dateInput = document.getElementById('dateInput');
if (dateInput) {
dateInput.onchange = function () {
selectDate(this.value);
};
}

// 할일 추가 폼
const todoForm = document.getElementById('todoForm');
if (todoForm) {
todoForm.onsubmit = function (e) {
e.preventDefault();
const input = document.getElementById('todoInput');
const text = input.value.trim();
if (text) {
addTodo(text);
input.value = '';
} else {
alert('할일을 입력해주세요!');
}
Copy link
Member

Choose a reason for hiding this comment

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

지금 input tag에 이미 required 속성이 존재하여 alert가 추가적으로 동작하지 않는 것 같아요. 해당 부분 확인해 주시면 좋을 거 같아요

};
}
}

// 날짜 이동 (-1: 어제, 1: 내일)
function moveToDate(direction) {
currentDate.setDate(currentDate.getDate() + direction);
updateDateDisplay();
renderTodoList();
updateProgress();
}
Comment on lines +68 to +73

Choose a reason for hiding this comment

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

eventListener를 통해 moveToDate 코드를 이전,이후에 삽입하는 방식 대신 하나의 함수화를 통해 더 깔끔한 코드인 것 같습니다


// 특정 날짜 선택
function selectDate(dateString) {
const newDate = new Date(dateString + 'T00:00:00');
if (!isNaN(newDate.getTime())) {
currentDate = newDate;
updateDateDisplay();
renderTodoList();
updateProgress();
}
}

// 현재 날짜 문자열 반환
function getCurrentDateString() {
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
const day = String(currentDate.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}

// 날짜 표시 업데이트
function updateDateDisplay() {
const dateInfo = document.getElementById('dateInfo');
const dateInput = document.getElementById('dateInput');

if (!dateInfo || !dateInput) return;

const year = currentDate.getFullYear();
const month = currentDate.getMonth() + 1;
const date = currentDate.getDate();

const days = [
'일요일',
'월요일',
'화요일',
'수요일',
'목요일',
'금요일',
'토요일',
];
const dayName = days[currentDate.getDay()];

const displayText = `${year}년 ${month}월 ${date}일 ${dayName}`;
const dateValue = getCurrentDateString();

dateInfo.textContent = displayText;
dateInput.value = dateValue;
}

// 현재 날짜의 할일 목록 가져오기
function getCurrentTodoList() {
const dateStr = getCurrentDateString();
if (!todosByDate[dateStr]) {
todosByDate[dateStr] = [];
}
return todosByDate[dateStr];
}

// 할일 추가
function addTodo(text) {
const newTodo = {
id: nextId++,
text: text,
completed: false,
date: getCurrentDateString(),
};
Comment on lines +134 to +139

Choose a reason for hiding this comment

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

newTodo로 한번에 push할 수 있게 한 부분이 인상깊었습니다


const todos = getCurrentTodoList();
todos.push(newTodo);

saveTodosToStorage();
renderTodoList();
updateProgress();
}

// 할일 완료 토글
function toggleTodo(id) {
const todos = getCurrentTodoList();
const todo = todos.find((t) => t.id === id);

if (todo) {
todo.completed = !todo.completed;
saveTodosToStorage();
renderTodoList();
updateProgress();
}
}

// 할일 삭제
function deleteTodo(id) {
const dateStr = getCurrentDateString();
const todos = getCurrentTodoList();
const filteredTodos = todos.filter((t) => t.id !== id);

todosByDate[dateStr] = filteredTodos;

saveTodosToStorage();
renderTodoList();
updateProgress();
}
Copy link
Member

Choose a reason for hiding this comment

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

현재 날짜의 todo가 없을 때 empty list로 유지하는 대신 아예 삭제하는 것도 데이터 관리/최적화 측면에서 고려해볼만한 선택인 거 같아요


// 할일 목록 렌더링
function renderTodoList() {
const todoList = document.getElementById('todoList');
const emptyState = document.getElementById('emptyState');

if (!todoList) return;

const todos = getCurrentTodoList();

// 기존 목록 지우기
todoList.innerHTML = '';

if (todos.length === 0) {
if (emptyState) emptyState.style.display = 'block';
return;
}

if (emptyState) emptyState.style.display = 'none';

// 할일 항목들 생성
todos.forEach((todo) => {
const li = document.createElement('li');
li.className = `todoItem ${todo.completed ? 'completed' : ''}`;

li.innerHTML = `
<label class="todoLabel">
<input type="checkbox" class="todoCheckbox" ${
todo.completed ? 'checked' : ''
}
onchange="toggleTodo(${todo.id})">
<span class="todoText">${todo.text}</span>
</label>
<button class="deleteBtn" onclick="deleteTodo(${todo.id})">🗑️</button>
`;
Comment on lines +199 to +208

Choose a reason for hiding this comment

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

제가 찾아본 바로는 이 부분이 XSS 공격 위험이 있다고 합니다
DOM 방식을 사용해보면 좋을 것 같습니다

Copy link
Author

Choose a reason for hiding this comment

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

innerHTML 사용은 위험할수잇으니까 최소화해야함


todoList.appendChild(li);
});
}

// 진행상황 업데이트
function updateProgress() {
const progressText = document.getElementById('progressText');

if (!progressText) return;

const todos = getCurrentTodoList();
const totalCount = todos.length;
const completedCount = todos.filter((t) => t.completed).length;

const dateStr = getCurrentDateString();
const today = new Date().toISOString().split('T')[0];
const isToday = dateStr === today;
const prefix = isToday ? '오늘' : dateStr;
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 오늘 케이스 구분해 놓은 거 좋은 거 같아요


const text = `${prefix} 할일 ${totalCount}개 중 ${completedCount}개 완료`;
progressText.textContent = text;
}

// 데이터 저장
function saveTodosToStorage() {
try {
localStorage.setItem('todosByDate', JSON.stringify(todosByDate));
localStorage.setItem('nextId', nextId.toString());
} catch (error) {
console.error('저장 오류:', error);
}
}

// 데이터 불러오기
function loadTodosFromStorage() {
try {
const saved = localStorage.getItem('todosByDate');
const savedId = localStorage.getItem('nextId');

if (saved) {
todosByDate = JSON.parse(saved);
}

if (savedId) {
nextId = parseInt(savedId);
}
} catch (error) {
console.error('불러오기 오류:', error);
todosByDate = {};
nextId = 1;
}
Comment on lines +245 to +260

Choose a reason for hiding this comment

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

불러오는 과정에서의 exception 처리는 좋은 습관인것 같습니다

}
52 changes: 52 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>todo list</title>
<link rel="stylesheet" href="style.css" />
</head>

<body>
<main class="appContainer">
<header class="appHeader">
<h1 class="appTitle">✔️TO DO LIST✔️</h1>
<div class="dateSelector">
<button class="dateNavBtn" id="prevDay">◀</button>
<div class="dateDisplay">
<input type="date" class="dateInput" id="dateInput" />
<p class="dateInfo" id="dateInfo">2024년 12월 19일 목요일</p>
</div>
<button class="dateNavBtn" id="nextDay">▶</button>
Copy link
Member

Choose a reason for hiding this comment

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

css property 중 cursor: pointer;라는 속성이 있는데요, 해당 속성 (마우스 hover 시 포인터가 손 모양으로 바뀜) 잘 사용하시면 좀 더 사용자 친화적인 UI가 될 것 같아요

Copy link
Member

Choose a reason for hiding this comment

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

지금 일부 버튼에만 적용되어 있네요 (e.g., deleteBtn)

</div>
</header>

<section class="todoInputSection">
<form class="todoForm" id="todoForm">
<input
type="text"
class="todoInput"
id="todoInput"
placeholder="할일을 입력하세요"
maxlength="100"
required

Choose a reason for hiding this comment

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

required 가 경고 parameter라는 것을 알게 됐습니다
js에서 공백 exception 처리보다 간결한 것 같습니다

/>
<button type="submit" class="addButton">➕ 추가</button>
</form>
</section>

<section class="todoProgress">
<p class="progressText" id="progressText">오늘 할일 0개 중 0개 완료</p>
</section>

<section class="todoListSection">
<ul class="todoList" id="todoList"></ul>
<div class="emptyState" id="emptyState">
<p>📝 할일을 추가해보세요 📝</p>
</div>
</section>
</main>

<script src="app.js"></script>
</body>
</html>
Loading