Skip to content
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주차] 이지인 미션 제출합니다. #12

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 1주차 미션: Vanilla-Todo

배포 링크 : https://vanilla-todo-19th-five.vercel.app/

# 서론

안녕하세요 🙌🏻 19기 프론트엔드 운영진 **변지혜**입니다.
Expand Down
28 changes: 17 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vanilla Todo</title>
<link rel="stylesheet" href="style.css" />
</head>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>ToDo List App</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="app">
<div class="calendar-section">
Copy link
Member

Choose a reason for hiding this comment

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

캘린더까지 구현해주신 점 정말 좋습니다!

<input type="date" id="datePicker" />
</div>
<input type="text" id="sectionInput" placeholder="Category Name" />
<button id="addSectionBtn">카테고리 추가</button>
<div id="todoSections"></div>
</div>

<body>
<div class="container"></div>
</body>
<script src="script.js"></script>
<script src="script.js"></script>
</body>
</html>
149 changes: 149 additions & 0 deletions script.js
Copy link

Choose a reason for hiding this comment

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

div로 element를 많이 만들어주셨는데, 시맨틱 태그로 리팩토링 해주신다면 웹 페이지 접근성에 더 도움이 될 거 같아요~

Original file line number Diff line number Diff line change
@@ -1 +1,150 @@
//CEOS 19기 프론트엔드 파이팅🔥 ദ്ദി˶ˊᵕˋ˵)

document.addEventListener('DOMContentLoaded', function () {
/* 조작할 DOM 요소 아이디로 참조 ->
달력 / 섹션 추가 버튼 / 섹션명 입력 인풋 필드 / 섹션 컨테이너 */
const datePicker = document.getElementById('datePicker');
const addSectionBtn = document.getElementById('addSectionBtn');
const sectionInput = document.getElementById('sectionInput');
const todoSectionsDiv = document.getElementById('todoSections');

Comment on lines +6 to +10
Copy link

Choose a reason for hiding this comment

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

주로 getElementById를 사용하셨네요! 찾아보니 querySelector가 getElementById보다 느리다고 하네요.. 몰랐던 사실..!! 저는 주로 querySelector를 사용했는데, 지인님 덕분에 찾아보게 됐습니다 ㅎㅎ

저는 그냥 init 함수를 사용했었는데, 지인님처럼 DOMContentLoaded를 사용해서 dom이 완전히 로드된 다음 접근하는 게 더 좋은 거 같네요!

// dom 로딩 완료 후, -> 날짜 선택-> 해당 날짜의 투두리스트 화면에 로드
datePicker.addEventListener('change', loadSectionsForDate);
Copy link
Member

Choose a reason for hiding this comment

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

default값으로 dataPicker의 value 값을 당일 날짜로 해주면 사용하기에 더 좋을 것 같습니다~!

// dom 로딩 완료 후, -> 섹션 추가되면, -> 섹션 추가
addSectionBtn.addEventListener('click', addTodoSection);

// ** 함수 기능 : 로컬 스토리지에서 선택된 날의 전체 투두리스트 불러오기 -> html로 띄우기
function loadSectionsForDate() {
const selectedDate = datePicker.value;
todoSectionsDiv.innerHTML = ''; // 특정 날 투두리스트 불러올 준비
Copy link

Choose a reason for hiding this comment

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

오늘 날짜로 작성하는 것뿐만 아니라 날짜를 선택해서 날짜별로 투두리스트를 작성하고 불러오는 기능 너무 좋은 것 같아요!


// 로컬 스토리지에서 존재하는 섹션들 파악
const sections = getSectionsForDate(selectedDate);

sections.forEach((section, index) => {
Copy link
Member

Choose a reason for hiding this comment

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

자바스크립트 고차함수 사용까지 해주신 점 좋습니다~!

// 모든 섹션을 화면에 보여주기위해 모든 존재하는 섹션에 대해 각각 createSectionDiv 호출
const sectionDiv = createSectionDiv(section, index);
// 호출 결과(섹션별 준비된 구조) -> html에 추가
todoSectionsDiv.appendChild(sectionDiv);
});
}

// 투두 리스트 섹션 기능 1. 섹션 추가 / 2. 섹션 삭제
// 투두 리스트 섹션 추가
function addTodoSection() {
const selectedDate = datePicker.value;
const sectionName = sectionInput.value.trim();
if (!selectedDate || !sectionName) {
alert('날짜를 선택하고 섹션 이름을 입력해주세요.');
return;
}

const sections = getSectionsForDate(selectedDate);
sections.push({ name: sectionName, items: [] });
saveSectionsForDate(selectedDate, sections);
// 로컬 스토리지에 추가한 섹션 -> loadSectionsForDate()로 html에 반영하기
loadSectionsForDate();
sectionInput.value = '';
}

// 투두 리스트 섹션 삭제
function deleteSection(sectionIndex) {
const selectedDate = datePicker.value;
let sections = getSectionsForDate(selectedDate);
sections.splice(sectionIndex, 1);
// 로컬 스토리지에서 삭제한 섹션 -> loadSectionsForDate()로 html에 반영하기
saveSectionsForDate(selectedDate, sections);
loadSectionsForDate();
}

// 투두 리스트 아이템 기능 1. 아이템 추가 / 2. 아이템 삭제
function addTodoItem(sectionIndex, itemText) {
if (!itemText.trim()) return;
const selectedDate = datePicker.value;
const sections = getSectionsForDate(selectedDate);
sections[sectionIndex].items.push(itemText);
saveSectionsForDate(selectedDate, sections);
loadSectionsForDate();
}

function deleteTodoItem(sectionIndex, itemIndex) {
const selectedDate = datePicker.value;
let sections = getSectionsForDate(selectedDate);
sections[sectionIndex].items.splice(itemIndex, 1);
saveSectionsForDate(selectedDate, sections);
loadSectionsForDate();
}

// createSectionDiv : html에 특정 섹션 표시하기 위해, 필요한 모든 요소 구조 준비

function createSectionDiv(section, index) {
const sectionDiv = document.createElement('div');
sectionDiv.className = 'todo-section';
Copy link

Choose a reason for hiding this comment

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

주로 className 속성으로 접근하셔서 스타일링을 해주셨는데, 이 방법을 사용하면 class 속성 값 전체가 덮어씌워져 버려서 여러 가지 class를 적용하려면 classList.add 메소드를 사용하는 게 좋다고 하네요~


const sectionTitle = document.createElement('h3');
Copy link
Member

Choose a reason for hiding this comment

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

섹션까지 나눠서 그냥 투두리스트가 아니라 각각 필요한 부분의 투두리스트를 분리할 수 있게 하신 부분에서 사용자 경험을 더 고민하고 기능구현해주신 것 같아서 좋아요..!!


// 섹션 타이틀 업데이트 함수 호출해서 섹션 별 할일 개수 타이틀에 반영
Copy link
Member

Choose a reason for hiding this comment

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

알아보기 쉽게 코드에 주석 작성해주신 점 좋습니다~!

updateSectionTitle(sectionTitle, section, index);

const deleteSectionBtn = document.createElement('button');
deleteSectionBtn.textContent = '섹션 삭제';
deleteSectionBtn.onclick = () => deleteSection(index);
sectionTitle.appendChild(deleteSectionBtn);

sectionDiv.appendChild(sectionTitle);

// 선택된 섹션에 할 일 아이템들을 먼저 추가
section.items.forEach((item, itemIndex) => {
const itemDiv = document.createElement('div');
itemDiv.className = 'todo-item';
itemDiv.textContent = item;

const deleteItemBtn = document.createElement('button');
deleteItemBtn.textContent = '완료';
Copy link

Choose a reason for hiding this comment

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

할 일 아이템별로 완료 버튼 말고도 삭제 버튼으로 삭제할 수 있는 기능이 있으면 좋을 것 같아요!

deleteItemBtn.onclick = () => deleteTodoItem(index, itemIndex);
itemDiv.appendChild(deleteItemBtn);

sectionDiv.appendChild(itemDiv);

return sectionDiv;
});

// 현재 할 일 아이템들 아래에 할 일 추가를 위한 입력 필드와 버튼 추가
const itemInput = document.createElement('input');
itemInput.type = 'text';
itemInput.placeholder = '할 일 추가';
sectionDiv.appendChild(itemInput);

const addItemBtn = document.createElement('button');
addItemBtn.textContent = '+';
addItemBtn.onclick = () => addTodoItem(index, itemInput.value);
sectionDiv.appendChild(addItemBtn);
Copy link

Choose a reason for hiding this comment

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

append 메소드를 이용하면 여러 노드를 한번에 추가할 수 있어 더 간결하게 작성할 수 있어요!

Copy link

Choose a reason for hiding this comment

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

사용자가 입력 필드에서 엔터키를 눌러도 할 일이 추가되도록 itemInput에 이벤트 리스너를 추가해도 좋을 것 같아요!


/* 할일 아이템 요소들 다 추가된 만들어진 섹션 요소 반환해서
섹션을 loadSectionsForDate()에서 forEach()로 화면에 표시할 수 있도록 함
*/
return sectionDiv;
}

// 로컬 스토리지에서 특정 날짜 투두 리스트 가져오기
function getSectionsForDate(date) {
const sectionsJSON = localStorage.getItem(date);
return sectionsJSON ? JSON.parse(sectionsJSON) : [];
}

// 로컬 스토리지에 특정 날짜 투두 리스트 업데이트하기
function saveSectionsForDate(date, sections) {
localStorage.setItem(date, JSON.stringify(sections));
}
});

// 남은 할 일의 수 섹션 타이틀에 반영해주는 함수
function updateSectionTitle(sectionTitle, section, index) {
const remainingCount = countSectionItems(section); // 남은 할 일 수 계산
sectionTitle.textContent = `${section.name} (남은 할 일: ${remainingCount})`;
}

function countSectionItems(section) {
// 완료되지 않은 할 일의 수를 계산
return section.items.filter((item) => !item.completed).length;
}
88 changes: 88 additions & 0 deletions style.css
Copy link

Choose a reason for hiding this comment

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

* {
  font-family: SejonghospitalBold, "sans-serif";
  box-sizing: border-box;
}

이렇게 적어주시면 중복된 코드 줄일 수 있을 거 같아요!

그리고 보통 box-sizing: border-box; 같은 경우도, global.css로 따로 빼서 설정해주니, * 안으로 들어가면 좋을 거 같네요~

Copy link

Choose a reason for hiding this comment

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

reset.css나 global.css라는 파일을 둬서 브라우저의 stylesheet를 초기화하고 공통적으로 적용할 css를 정의해준다면 좋을 거 같습니다!

Original file line number Diff line number Diff line change
@@ -1 +1,89 @@
/* 본인의 디자인 감각을 최대한 발휘해주세요! */
@font-face {
font-family: 'SejonghospitalBold';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/[email protected]/SejonghospitalBold.woff2')
format('woff2');
}
Copy link

Choose a reason for hiding this comment

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

저는 HTML에서 요소를 사용하여 외부 폰트를 불러오는 방법을 사용했는데요..!
CSS의 @font-face 규칙을 사용하여 직접 폰트를 정의하고 적용하는 방법도 배우고 갑니다!

body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: SejonghospitalBold, sans-serif;
}

html {
width: 100%;
height: 95%;
margin: 0;
padding: 0;
font-family: SejonghospitalBold, sans-serif;
}

#app {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-content: space-around;
width: 80%;
Copy link
Member

Choose a reason for hiding this comment

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

%를 활용해 좀 더 반응형이 되도록 신경써주신 점도 좋습니다~!

height: 98%;

margin: 30px auto;

padding: 20px auto;
overflow-y: auto;
Copy link
Member

Choose a reason for hiding this comment

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

Overflow 되는 경우까지 꼼꼼히 처리해주셨네용👍

}

.calendar-section,
.todo-section,
.add-item {
width: 100%; /* 컨테이너 내부 너비 */
box-sizing: border-box; /* 패딩과 보더가 너비에 포함되도록 설정 */
}

.todo-section {
margin-bottom: 20px 10px;
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
}

input[type='text'],
input[type='date'],
button {
font-family: SejonghospitalBold, sans-serif;
padding: 8px;
margin: 5px 0;
border-radius: 5px;
border: 1px solid #ccc;
}

button {
background-color: #007bff;
color: white;
cursor: pointer;
}

button:hover {
background-color: #0056b3;
}

.todo-section h3 {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
Copy link
Member

Choose a reason for hiding this comment

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

👍👍👍

align-content: space-around;
font-size: 13px;
}
.todo-item {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-content: space-around;
font-size: 10px;
}

#todoSections {
max-height: 98vh;
overflow-y: auto;
}