diff --git a/README.md b/README.md index 846a729..1ac71ed 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # 1주차 미션: Vanilla-Todo +배포 링크 : https://vanilla-todo-19th-five.vercel.app/ + # 서론 안녕하세요 🙌🏻 19기 프론트엔드 운영진 **변지혜**입니다. diff --git a/index.html b/index.html index d241b1b..6aa1598 100644 --- a/index.html +++ b/index.html @@ -1,14 +1,20 @@ - - - - - Vanilla Todo - - + + + + ToDo List App + + + +
+
+ +
+ + +
+
- -
- - + + diff --git a/script.js b/script.js index 52d54cd..9ac5cd6 100644 --- a/script.js +++ b/script.js @@ -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'); + + // dom 로딩 완료 후, -> 날짜 선택-> 해당 날짜의 투두리스트 화면에 로드 + datePicker.addEventListener('change', loadSectionsForDate); + // dom 로딩 완료 후, -> 섹션 추가되면, -> 섹션 추가 + addSectionBtn.addEventListener('click', addTodoSection); + + // ** 함수 기능 : 로컬 스토리지에서 선택된 날의 전체 투두리스트 불러오기 -> html로 띄우기 + function loadSectionsForDate() { + const selectedDate = datePicker.value; + todoSectionsDiv.innerHTML = ''; // 특정 날 투두리스트 불러올 준비 + + // 로컬 스토리지에서 존재하는 섹션들 파악 + const sections = getSectionsForDate(selectedDate); + + sections.forEach((section, index) => { + // 모든 섹션을 화면에 보여주기위해 모든 존재하는 섹션에 대해 각각 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'; + + const sectionTitle = document.createElement('h3'); + + // 섹션 타이틀 업데이트 함수 호출해서 섹션 별 할일 개수 타이틀에 반영 + 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 = '완료'; + 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); + + /* 할일 아이템 요소들 다 추가된 만들어진 섹션 요소 반환해서 + 섹션을 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; +} \ No newline at end of file diff --git a/style.css b/style.css index 599136a..ef37893 100644 --- a/style.css +++ b/style.css @@ -1 +1,89 @@ /* 본인의 디자인 감각을 최대한 발휘해주세요! */ +@font-face { + font-family: 'SejonghospitalBold'; + src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2312-1@1.1/SejonghospitalBold.woff2') + format('woff2'); +} +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%; + height: 98%; + + margin: 30px auto; + + padding: 20px auto; + overflow-y: auto; +} + +.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; + 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; +}