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

[2주차] 이가빈 미션 제출합니다. #4

Open
wants to merge 14 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
23,424 changes: 5,120 additions & 18,304 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.3.1",
"react-calendar": "^5.0.0",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"styled-components": "^6.1.13",
"web-vitals": "^2.1.4"
},
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="ko">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
Expand Down
24 changes: 21 additions & 3 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
import React, { useState } from "react";
import styled from "styled-components";
import Calendar from './components/Calendar';
import TodoListComponent from './components/TodoListComponent';
import '../src/style.css';

function App() {
const [currentDate, setCurrentDate] = useState(new Date());

return (
<div className="App">
<h1>🐶CEOS 20기 프론트엔드 최고🐶</h1>
</div>
<Container>
<Calendar currentDate={currentDate} setCurrentDate={setCurrentDate}/>
<TodoListComponent currentDate={currentDate} setCurrentDate={setCurrentDate}/>
</Container>
);
}

export default App;

const Container = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
min-width: 800px;
max-width: 1000px;
margin: 0 auto;
`
Comment on lines +20 to +27
Copy link

Choose a reason for hiding this comment

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

가로 세로로 overflow가 조금 있는 것 같습니다! 여러 부분에서 미세하게 달라진 스타일들이 영향을 준 것 같아서 overflow가 사라진다면 더 깔끔한 투두리스트가 될 것 같아요!

4 changes: 4 additions & 0 deletions src/assets/checked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/deleteBtn.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/toTomorrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/toYesterday.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/unchecked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
96 changes: 96 additions & 0 deletions src/components/Calendar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import BasicCalendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';
import styled from 'styled-components';

const Calendar = ({ currentDate, setCurrentDate }) => {

return (
<MainContainer>
<StyledCalendar
calendarType="gregory"
value={currentDate}
onChange={setCurrentDate}
formatDay={(locale, date) => date.getDate()}
tileClassName={({ date, view }) =>
view === 'month' && date.toDateString() === currentDate.toDateString()
? 'selected'
: null
}
/>
</MainContainer>
);
};

export default Calendar;

const MainContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 40%;
margin-top: 12%;
margin-right: 5%;

.react-calendar {
padding: 5% 5%;
border: none;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
flex-grow: 1;
}
`;

const StyledCalendar = styled(BasicCalendar)`
Copy link

Choose a reason for hiding this comment

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

styled에 BasicCalendar가 있는 것 배워가겠습니다!

Copy link

Choose a reason for hiding this comment

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

오 저도 BasicCalendar 배워갑니다👍🏻👍🏻


// 네비게이션 스타일
.react-calendar__navigation {
margin: 5px;
button {
font-size: 16px;
font-weight: bold;
}
}

// 요일 밑줄 제거
.react-calendar__month-view__weekdays abbr {
text-decoration: none;
}

// 토요일 텍스트 색
.react-calendar__month-view__days__day--weekend {
&:nth-child(7n) {
color: #91d1ff;
}
}

// 날짜 타일 스타일
.react-calendar__tile {
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
border-radius: 15%;
border: 1px solid #91d1ff;
Copy link

Choose a reason for hiding this comment

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

hover했을 때 border의 크기가 추가되어 캘린더에서 약간의 움직임이 있는 것 같습니다! border의 활용도 좋다고 생각되지만, 스타일이 변하지 않는 걸 신경쓸 때는 box-shadow: inset을 활용하는 방안도 괜찮았던 것 같아요!

background-color: transparent;
}
}

// 오늘 날짜 표시
.react-calendar__tile--now {
background-color: #e0f7ff;
border-radius: 15%;
}

// 선택된 날짜 표시
.react-calendar__tile.selected {
background-color: #91d1ff;
border-radius: 15%;
// 토요일
&:nth-child(7n) {
color: white;
}
}
`;
55 changes: 55 additions & 0 deletions src/components/TodoItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from "react";
import styled from "styled-components";
import checked from "../assets/checked.svg";
import unchecked from "../assets/unchecked.svg";
import deleteBtn from "../assets/deleteBtn.svg";

const TodoItem = ({ todo, date, onTodoChange }) => {

// 투두 완료 토글
const toggleTodoComplete = () => {
onTodoChange(date, todo, false);
};

// 투두 삭제
const deleteTodo = () => {
onTodoChange(date, todo, true);
};
Comment on lines +9 to +17
Copy link
Member

Choose a reason for hiding this comment

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

이 두함수는 공통요소가 2개이고, boolean값만 다른데, 하나의 함수로 통일해서 리팩토링하면 좋을 것 같아요

Suggested change
// 투두 완료 토글
const toggleTodoComplete = () => {
onTodoChange(date, todo, false);
};
// 투두 삭제
const deleteTodo = () => {
onTodoChange(date, todo, true);
};
const handleAction = (action) => {
onTodoChange({ date, todo, action });
};


return (
<TodoItemContainer>
<Img
src={todo.completed ? checked : unchecked}
onClick={toggleTodoComplete}
/>
<TodoText completed={todo.completed}>{todo.text}</TodoText>
<Img
src={deleteBtn}
onClick={deleteTodo}
/>
</TodoItemContainer>
);
};

export default TodoItem;

const TodoItemContainer = styled.li`
Copy link
Member

Choose a reason for hiding this comment

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

만약 TodoItem이 ul 또는 ol 내부에서 사용되지 않는다면, 시맨틱을 위해 div로 변경하면 좋을 것 같아요

display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #C0C0C0;
margin: 5px 0;
`;

const Img = styled.img`
width: 20px;
height: 20px;
cursor: pointer;
`;

const TodoText = styled.span`
margin-left: 5%;
width: 80%;
color: ${(props) => (props.completed ? "#C0C0C0" : "#000000")};
word-break: break-all;;
billy0904 marked this conversation as resolved.
Show resolved Hide resolved
`;
157 changes: 157 additions & 0 deletions src/components/TodoListComponent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import toYesterday from "../assets/toYesterday.svg";
import toTomorrow from "../assets/toTomorrow.svg";
import TodoItem from "./TodoItem";
import { formatDate, formatDay, getTodoList, saveTodoList } from "../utils/Utils";

const TodoListComponent = ({ currentDate, setCurrentDate }) => {
const [todoList, setTodoList] = useState([]);
const [newTodo, setNewTodo] = useState("");

// 투두 렌더링
useEffect(() => {
loadTodoList(currentDate);
}, [currentDate]);

const loadTodoList = (date) => {
const todos = getTodoList(date);
setTodoList(todos);
};
Comment on lines +13 to +20
Copy link

Choose a reason for hiding this comment

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

함수의 선언 위치관련해서 기존 자바스크립트에서는 화살표 함수로 선언된 함수가 호출된 곳보다 아래에서 선언되었을 때 호이스팅 문제로 에러가 발생할 가능성이 있습니다. 다만 useEffect 특성상 렌더링 이후에 useEffect가 실행되기 때문에 이미 모든 함수가 렌더링된 후에 useEffect 안에서 함수가 호출되어 에러가 발생하지 않습니다. 코드상 에러가 발생하지는 않으나 호출 전에 선언하는 것도 코드 가독성같은 측면에서 좋을 수 있을 것 같습니다!

저도 습관을 들이는 중입니다!


// 남은 할 일 개수
const updateLeftNum = () => {
const leftNum = todoList.filter((todo) => !todo.completed).length;
return `할 일 ${leftNum}개`;
};

Comment on lines +22 to +27
Copy link

Choose a reason for hiding this comment

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

const leftTodoText = useMemo(() => {
    const leftNum = todoList.filter((todo) => !todo.completed).length;
    return `할 일 ${leftNum}개`;
}, [todoList]);

useMemo를 사용해 todoList가 변경될 때만 계산하도록 하는 방법도 좋을 거 같습니다~!

// 투두 추가
const addTodoItem = (e) => {
e.preventDefault();
if (!newTodo.trim())
return;
const newTodoList = [...todoList, { text: newTodo, completed: false}];
setTodoList(newTodoList);
saveTodoList(currentDate, newTodoList);
setNewTodo("");
};

// 투두 삭제 or 완료 토글 처리
const handleTodoChange = (date, changedTodo, isDelete) => {
const updatedTodoList = isDelete
// 삭제 처리
? todoList.filter((todo) => todo !== changedTodo)
// 완료 토글 처리
: todoList.map((todo) =>
todo === changedTodo ? { ...todo, completed: !todo.completed } : todo
Copy link
Member

Choose a reason for hiding this comment

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

객체를 비교할 때 참조 동등성(===)을 사용하면 예상치 못한 결과가 발생할 수 있는데, 특히 데이터가 로컬 스토리지에서 불러와지는 경우 객체의 참조가 변경될 수 있다고 합니다.

id를 이용해서 고유한 값으로 객체를 비교하는 것도 예상치 못한 오류를 방지하는 방법일 것 같습니다!

);

setTodoList(updatedTodoList);
saveTodoList(date, updatedTodoList);
};

// 날짜 한 칸 씩 이동
const moveDate = (days) => {
const newDate = new Date(currentDate);
newDate.setDate(newDate.getDate() + days);
setCurrentDate(newDate);
Comment on lines +54 to +57
Copy link

Choose a reason for hiding this comment

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

const newDate = addDays(currentDate, days);
라는 유틸함수도 있는 것 같아요! Date 메서드들 중에서 유용한 게 많은 것 같아서 같이 활용해보면 좋을 것 같습니다!

};

return (
<MainContainer>
<Img src={toYesterday} onClick={() => moveDate(-1)} />
<ListContainer>
<LeftNum>{updateLeftNum()}</LeftNum>
<DateText>{formatDate(currentDate)}</DateText>
<DayText>{formatDay(currentDate)}</DayText>

<TodoInputForm onSubmit={addTodoItem}>
<TodoInput
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="To Do 입력 후 Enter"
/>
</TodoInputForm>

<TodoList>
{todoList.map((todo, index) => (
<TodoItem
key={index}
Copy link

Choose a reason for hiding this comment

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

index도 좋지만, todo에 고유한 id값을 넣어서 관리한다면 여러 방면으로 활용할 수 있는 범위가 넓어질 수 있을 것 같아요!

todo={todo}
date={currentDate}
onTodoChange={handleTodoChange}
/>
))}
</TodoList>
</ListContainer>
<Img src={toTomorrow} onClick={() => moveDate(1)} />
</MainContainer>
);
};

export default TodoListComponent;

const MainContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 60%;
min-width: 530px;
margin-top: 12%;
`;

const ListContainer = styled.div`
text-align: left;
background: white;
padding: 7% 10%;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
flex-grow: 1;
margin: 0 20px;
height: 600px;
`;

const LeftNum = styled.p`
color: #91d1ff;
margin-bottom: 8px;
font-weight: 500;
`;

const DateText = styled.h1`
font-weight: 500;
`;

const DayText = styled.p`
margin-top: 8px;
font-weight: 500;
`;

const TodoInputForm = styled.form`
margin-top: 20px;
`;

const TodoInput = styled.input`
width: 100%;
padding: 10px;
border: 1px solid #91d1ff;
border-radius: 10px;
`;

const TodoList = styled.ul`
list-style: none;
margin-top: 20px;
max-height: 370px;
overflow-y: auto;
padding-right: 20px;

// 스크롤바 스타일링
scrollbar-width: thin;
Copy link

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.

오 저도 처음 알았네요...!

scrollbar-color: #91d1ff71 transparent;
`;

const Img = styled.img`
width: 40px;
height: 40px;
`;
Loading