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
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 3
Copy link
Member

Choose a reason for hiding this comment

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

tabWidth를 3으로 설정하신 이유가 있을까요 ? 일반적으로 html, js 같은 프론트엔드 기술 스택에서는 2를 주로 사용합니다

Copy link
Member

Choose a reason for hiding this comment

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

prettier 설정하실 때도 각 속성이 어떤 역할을 하는지 명확히 이해하신 후 사용하시는 걸 추천드립니다.

}
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# 2주차 과제: React Todo

# 서론

안녕하세요 🙌🏻 22기 프론트엔드 운영진 **권동욱**입니다.
Expand All @@ -25,7 +26,7 @@
- VSCode, Prettier를 이용하여 개발환경을 관리합니다.
- React의 기초를 이해합니다.
- React를 통한 어플리케이션 상태 관리 방법을 이해합니다.
- React Hooks에 대한 기초를 이해합니다.
- React Hooks에 대한 기초를 이해합니다.
- Vite를 통한 React 프로젝트 개발환경 구축을 익힙니다.
- Styled-Components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법을 익힙니다.

Expand All @@ -40,14 +41,15 @@
- React 컴포넌트 생명주기에 대해서 설명해주세요.

## 필수 요건

- 1주차 미션의 결과물을 그대로 React로 구현합니다. (‼️ todo / done 개수 잊지 마세요 ‼️)
- Styled-Component를 사용합니다.
- React Hooks만을 사용해 상태를 관리합니다.(전역 상태관리 라이브러리 사용 XX)
- React Hooks만을 사용해 상태를 관리합니다.(전역 상태관리 라이브러리 사용 XX)
- Vite를 활용하여 React 프로젝트 환경 구축을 진행합니다

## 선택 요건

- 기존 Todo-list에 여러분들이 추가하고 싶은 기능과 디자인을 자유롭게 추가해보세요.
- 기존 Todo-list에 여러분들이 추가하고 싶은 기능과 디자인을 자유롭게 추가해보세요.
- TypeScript를 활용하여 프로젝트를 진행해보세요.

## 로컬 실행방법
Expand All @@ -64,6 +66,6 @@
- [useState, useEffect hooks](https://velog.io/@velopert/react-hooks#1-usestate)
- [styled-component](https://styled-components.com/docs/basics#getting-started)

- [create react app (CRA) 지원종료 공식문서](https://react.dev/blog/2025/02/14/sunsetting-create-react-app)

- [create react app 지원종료관련 okky 커뮤니티 게시글](https://okky.kr/articles/1527414)
- [cra 대신에 vite로 React 프로젝트 시작하기](https://www.daleseo.com/vite-react/)
- [create react app 지원종료관련 okky 커뮤니티 게시글](https://okky.kr/articles/1527414)
- [cra 대신에 vite로 React 프로젝트 시작하기](https://www.daleseo.com/vite-react/)
- [Vite 실무 적용기 - 설명 + 프로젝트 설정](https://blog.hectodata.co.kr/bonjour-vite/)
233 changes: 233 additions & 0 deletions demoscript.js
Copy link
Member

Choose a reason for hiding this comment

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

이 파일 1주차 과제 더미 데이터로 보이는 데 맞을까요 ?

Copy link
Member

Choose a reason for hiding this comment

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

3주차 과제부터는 코딩 중간중간 / 제출 직전에 폴더 구조나 파일 구성 점검해보는 습관을 들이는 걸 추천드립니다

Copy link
Author

Choose a reason for hiding this comment

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

엇 그렇네요 수정하고 옮기는 과정에서 만들어뒀는데 지우는 걸 잊었습니다. 감사합니다

Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
document.addEventListener('DOMContentLoaded', () => {
//menu tab
const menuTab = document.getElementById('menuTab');
const openBtn = document.querySelector('.menu');
const closeBtn = document.getElementById('closeMenu');

//open and close menu drawer
const openDrawer = () => {
menuTab.classList.add('active');
};
const closeDrawer = () => {
menuTab.classList.remove('active');
};

openBtn.addEventListener('click', openDrawer);
closeBtn.addEventListener('click', closeDrawer);

//close menu when clicked outside of menu tab
document.addEventListener('click', (e) => {
if (
menuTab.classList.contains('active') &&
!menuTab.contains(e.target) &&
!openBtn.contains(e.target)
) {
closeDrawer();
}
});

//close menu when user presses esc
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && menuTab.classList.contains('active')) closeDrawer();
});

//define date manipulating buttons
const today = document.querySelector('.today');
const prevBtn = document.getElementById('yesterday');
const nextBtn = document.getElementById('tomorrow');

//get date
let current = new Date();
const fmt = { year: 'numeric', month: 'long', day: 'numeric' };

//Date manipulation
function render() {
today.textContent = current.toLocaleDateString(undefined, fmt);
}
render();

//define next and last week buttons in menu
const nWeek = document.querySelector('.nWeek');
const lWeek = document.querySelector('.lWeek');

//피드백 주신 대로 changWeek함수를 만들고 +7/-7을 인자로 남겨 처리했습니다
function changeDays(offSetDays) {
current.setDate(current.getDate() + offSetDays);
render();
loadList(current);
ifNoEvents();
}
nWeek.addEventListener('click', () => {
changeDays(7);
});
lWeek.addEventListener('click', () => {
changeDays(-7);
});

prevBtn.addEventListener('click', () => {
changeDays(-1);
});

nextBtn.addEventListener('click', () => {
changeDays(1);
});

//when the date is clicked on, change current date to today
today.addEventListener('click', () => {
current = new Date();
render();
loadList(current);
});

const dateKey = (d) => new Date(d).toLocaleDateString('en-CA');
// "YYYY-MM-DD" in local time
const storageKey = (d) => `${dateKey(d)}`;

const numEvent = document.querySelector('.numEvent');

//saves the events for a date
function saveList(date) {
const dateData = {
dateTodoEl: todoEl.innerHTML,
dateTodos: todos,
};
sessionStorage.setItem(storageKey(date), JSON.stringify(dateData));
}

//loads the events for a date
function loadList(date) {
const raw = sessionStorage.getItem(storageKey(date) || '');
if (raw) {
const parsed = JSON.parse(raw);
todoEl.innerHTML = parsed.dateTodoEl;
todos = parsed.dateTodos;
} else {
todos = [];
todoEl.innerHTML = '';
}
getNumEvent();
}

const input = document.querySelector('.input');
const add = document.querySelector('.register');
const todoEl = document.querySelector('.todoEl');
const clearAll = document.querySelector('.clearAll');

let todos = [];

//event listener that is restored once loaded from storage
todoEl.addEventListener('click', (e) => {
//선택된 li
const li = e.target.closest('li');
//선택된 li의 index number을 idx라 명명
const id = li.dataset.id;
const idx = todos.findIndex((t) => t.id === id);

//delete button
if (e.target && e.target.classList.contains('delEvent')) {
todos.splice(idx, 1);
}

//done button
if (e.target && e.target.classList.contains('doneEvent')) {
todos[idx].done = !todos[idx].done;
console.log('done');
}
//pin and unpin events
if (e.target && e.target.classList.contains('pinEvent')) {
todos[idx].pinned = !todos[idx].pinned;
}

renderTodos();
saveList(current);
ifNoEvents();
getNumEvent();
});

//add event using enter and button click
add.addEventListener('click', () => {
if (input.value !== '') {
addToList(input.value);
input.value = '';
saveList(current);
}
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && input.value !== '') {
addToList(input.value);
input.value = '';
saveList(current);
}
});

function addToList(text) {
const newTodo = {
id: crypto.randomUUID(),
done: false,
pinned: false,
text: text,
};
todos.push(newTodo);
saveList();
renderTodos();
}

function renderTodos() {
const sorted = [...todos].sort((a, b) => b.pinned - a.pinned);
todoEl.innerHTML = '';

for (const t of sorted) {
const li = document.createElement('li');
li.dataset.id = t.id;
li.className = t.pinned ? 'pinned' : '';
li.innerHTML = `
<button class="doneEvent">${t.done ? 'Undone' : 'Done'}</button>
<span class="text ${t.done ? 'markDone' : ''}">${t.text}</span>
<button class="pinEvent ${t.pinned ? 'markPin' : ''}">
${t.pinned ? 'Unpin' : 'Pin'}
</button>
<button class="delEvent">Delete</button>
`;
todoEl.appendChild(li);
}
getNumEvent();
}

//clear all events for a date
function clearALlEvents(date) {
todos = [];
renderTodos();
saveList(current);
ifNoEvents();
}
clearAll.addEventListener('click', () => {
clearALlEvents(current);
getNumEvent();
});

function ifNoEvents() {
if (todos.length === 0) {
sessionStorage.removeItem(storageKey(current));
}
}

//get the number of Events
function getNumEvent() {
numEvent.textContent = 'To-do: ' + todoEl.children.length;
}

//calendar manipulation
const menuContent = document.getElementById('menuContent');
const datePickerEl = document.createElement('input');
datePickerEl.type = 'date';
datePickerEl.className = 'menuDatePicker'; // make it obvious
menuContent.appendChild(datePickerEl);
datePickerEl.addEventListener('change', () => {
if (!datePickerEl.value) return;
const [y, m, d] = datePickerEl.value.split('-').map(Number);
saveList(current);
current = new Date(y, m - 1, d);
render();
loadList(current);
});
});
14 changes: 14 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
Copy link
Member

Choose a reason for hiding this comment

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

추후 프로젝트 진행하시게 되면 높은 확률로 대상 국가가 대한민국일텐데요. 검색 엔진 최적화 등 다양한 요소를 고려하였을 때 lang="ko" 로 지정하시면 좀 더 완성도 높은 결과물이 나올 거 같아요

Copy link
Member

Choose a reason for hiding this comment

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

개인적으로는 이런 사소한 것 하나하나부터 확실히 이해하는 게 중요하다고 생각합니다

<head>
<meta charset="UTF-8" />
<title>react-todo 백승선</title>
</head>
<body>
<!-- React가 붙을 자리 -->
<div id="root"></div>

<!-- Vite 권장: 루트 기준 절대 경로 -->
<script type="module" src="src/main.jsx"></script>
</body>
</html>
Loading