-
Notifications
You must be signed in to change notification settings - Fork 10
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
base: master
Are you sure you want to change the base?
Changes from all commits
07b9f7d
023f9c7
85915e3
5204c4e
83f196f
af07245
6716b44
c13e71e
5ef9b4e
5d82441
f01d18e
9a6df07
f15e37e
6f805eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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; | ||
` | ||
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)` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. styled에 BasicCalendar가 있는 것 배워가겠습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} | ||
`; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 두함수는 공통요소가 2개이고, boolean값만 다른데, 하나의 함수로 통일해서 리팩토링하면 좋을 것 같아요
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
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` | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||||||||||||||||||||
`; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}; | ||
|
||
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} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 스크롤바를 스타일링 할 수 있군요! 나중에 써먹겠습니당 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 저도 처음 알았네요...! |
||
scrollbar-color: #91d1ff71 transparent; | ||
`; | ||
|
||
const Img = styled.img` | ||
width: 40px; | ||
height: 40px; | ||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
가로 세로로 overflow가 조금 있는 것 같습니다! 여러 부분에서 미세하게 달라진 스타일들이 영향을 준 것 같아서 overflow가 사라진다면 더 깔끔한 투두리스트가 될 것 같아요!