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주차] 윤영준 미션 제출합니다. #2

Open
wants to merge 8 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
12 changes: 12 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"htmlWhitespaceSensitivity": "css",
"cssWhitespaceSensitivity": "css"
}
40,168 changes: 29,827 additions & 10,341 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"lucide-react": "^0.441.0",
"prettierrc": "^0.0.0-5",
"react": "^18.3.1",
"react-calendar": "^5.0.0",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"style-component": "^0.0.1",
"styled-component": "^2.8.0",
"styled-components": "^6.1.13",
"sweetalert2": "^11.14.0",
"uuid": "^10.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
Expand Down
7 changes: 6 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- nomalize.css set -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" integrity="sha512-NhSC1YmyruXifcj/KFRWoC561YpHpc5Jtzgvbuzx5VozKpWvQ+4nXhPdFgmx8xqexRcpAglTj9sIBWINXa8x5w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sofadi+One&display=swap" rel="stylesheet">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Expand Down
9 changes: 0 additions & 9 deletions src/App.js

This file was deleted.

32 changes: 32 additions & 0 deletions src/app/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@font-face {
font-family: 'SUIT-Regular';
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/SUIT-Regular.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
height: 100vh; /* 전체 화면 높이 */
background-color: #0000ff8d;
position: relative;
display: flex;
justify-content: center;
align-items: center;
font-family: 'SUIT-Regular', sans-serif;
}

/* 사용자 정의 SweetAlert2 스타일 */
.custom-popup {
font-family: 'SUIT-Regular', sans-serif;
position: relative;
border-radius: 10px; /* 둥근 모서리 */
padding: 20px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); /* 그림자 효과 */
overflow: hidden;
}


51 changes: 51 additions & 0 deletions src/app/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useState } from 'react';
import './App.css';
import CalendarContainer from '../component/Calendar/CalendarContainer';
import Task from '../component/Task/Task';
import styled from 'styled-components';

function App() {
// 선택된 날짜 상태 -> Date 객체 정의
const [selectedDate, setSelectedDate] = useState(new Date());
const [isTaskModalOpen, setIsTaskModalOpen] = useState(false);

// 날짜 선택 핸들러
const handleDateChange = (date) => {
setSelectedDate(date);
setIsTaskModalOpen(true);
};

const handleCloseModal = () => {
setIsTaskModalOpen(false); // 모달 닫기
};

return (
<>
<Container>
{/* Task가 보이는 상태면 달력을 숨기고, 그렇지 않으면 달력을 표시 */}
{!isTaskModalOpen ? (
<CalendarContainer onDateChange={handleDateChange} />
) : (
<>
<Task selectedDate={selectedDate} handleCloseModal={handleCloseModal} />
</>
)}
</Container>
</>
);
}
const Container = styled.div`
z-index: 1;
width: 560px;
height: 560px;
border-radius: 28px;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
transition: width 0.5s ease;

@media (max-width: 768px) {
width: 360px;
}
`;

export default App;
99 changes: 99 additions & 0 deletions src/component/Calendar/CalendarContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { useState } from 'react';
import Calendar from 'react-calendar';
import styled from 'styled-components';
import './custom-calendar.css'; // 커스텀 CSS 파일
import { getTodoProgressForDate } from '../../utils/LocalStorageUtil';
import CustomHeader from './components/CustomHeader';


function CalendarContainer({ onDateChange }) {
const [date, setDate] = useState(new Date());
const [month, setMonth] = useState(date.getMonth());

const handleChange = (selectedDate) => {
setDate(selectedDate);
onDateChange(selectedDate); // 부모 컴포넌트로 선택된 날짜 전달
};
// 월 변경 핸들러
const handleMonthChange = (e) => {
const selectedMonth = parseInt(e.target.value);
const newDate = new Date(date.getFullYear(), selectedMonth, 1);
setDate(newDate);
setMonth(selectedMonth);
};
// 각 타일에 내용을 추가하는 함수
const tileContent = ({ date, view }) => {
if (view === 'month') { // 월간 뷰일 때만 표시
const { completed, total } = getTodoProgressForDate(date); // 성취도 계산
Copy link

Choose a reason for hiding this comment

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

구조분해할당 잘 쓰신 것 같습니다!

if (total > 0) { // 할 일이 있는 경우에만 표시
return (
<TodoProgress>
{completed}/{total}
</TodoProgress>
);
}
}
return null;
};

// 특정 조건에 따라 날짜에 클래스 이름을 할당하는 함수
const getTileClassName = ({ date, view }) => {
Copy link

Choose a reason for hiding this comment

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

요 함수 useCallback으로 묶어주어도 괜찮을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

month 기준으로 타일이 세팅되는 구조라 말씀대로 useCallback으로 묶고, month를 의존성 배열값으로 주면 더 효율적일 수 있을 것 같아요! 감사합니다!

if (view === 'month') {
const { completed, total } = getTodoProgressForDate(date); // 성취도 계산
if (date === new Date().toISOString().split('T')[0]) {
Copy link

Choose a reason for hiding this comment

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

그리고 혹시 Date를 포맷팅할 때 toLocalDateString 이나 date-fns를 안 쓰신 이유가 있으신가요?

Copy link
Author

Choose a reason for hiding this comment

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

아뇨! 큰 이유는 없습니다. 계속 쓰던 걸 썼던 것이라 더 효율적인 게 있다면 그걸로 갈아타겠습니다!

return 'today'; // 오늘 날짜를 하이라이트
Comment on lines +43 to +44
Copy link

Choose a reason for hiding this comment

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

여기서 date는 date 객체이고, new Date().toISOString().split('T')[0] 는 문자열이라서, 아마 TS에서는 에러가 날 수도 있을 것 같다는 생각이 들었습니다! date도 문자열로 변경해서 타입을 맞춘 다음에, 비교를 하는 건 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

좋은 지적 감사합니다! js를 사용하더라도 ts에 대한 의식을 했어야 했는데 놓치고 있었던 것 같습니다. 깨닫게 해주셔서 감사합니다!

} else if (completed === total && completed > 0 && total > 0) {
return 'great'; // 모든 할 일이 완료된 경우
Comment on lines +45 to +46

Choose a reason for hiding this comment

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

지금 조건으로도 모든 할 일이 완료된 경우에 대한 if문이 충분히 잘 기능하지만 조건을 조금 더 간결하게 completed === total && total > 0로도 작성할 수 있을 것 같아요!

Suggested change
} else if (completed === total && completed > 0 && total > 0) {
return 'great'; // 모든 할 일이 완료된 경우
} else if (completed === total && total > 0) {
return 'great'; // 모든 할 일이 완료된 경우

} else if (completed !== total && total > 0) {
return 'sorry'; // 할 일이 없는 경우
} else if (completed === 0 && total === 0) {
return 'none'; // 할 일이 없는 경우
}
}
return null;
};

return (
<>
<Wrapper>
<CustomHeader
date={date}
month={month}
setDate={setDate}
handleMonthChange={handleMonthChange}
/>
<Calendar
locale='en-GB' // 영국 버전 -> 월요일부터 시작
onChange={handleChange}
value={date}
tileContent={tileContent} // 타일 콘텐츠 추가
tileClassName={getTileClassName} // tileClassName 속성 사용
showNeighboringMonth={true} // 이전 및 다음 달 날짜 표시
showNavigation={false} // 기본 네비게이션 숨기기
/>
</Wrapper>
</>
);
}
const Wrapper = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
margin: auto;
background-color: white; /* 배경 색상 */
border-radius: 28px;
padding: 20px;
`;
const TodoProgress = styled.div`
position: absolute;
top: 40px;
font-size: 0.75rem;
color: #666;
margin-top: 5px;

`;
export default CalendarContainer;
107 changes: 107 additions & 0 deletions src/component/Calendar/components/CustomHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from "react";
import styled from "styled-components";

export default function CustomHeader({ date, setDate, month, handleMonthChange }) {
return (
<Wrapper>
<ColorBox>
<span className='today'>Today</span>
<span className='great'>Great</span>
<span className='sorry'>Sorry</span>
</ColorBox>
<Title>
<h2>To Do Calendar</h2>
<span>({new Date().toISOString().split('T')[0]})</span>
</Title>
<SelectMonth value={month} onChange={handleMonthChange}>
{Array.from({ length: 12 }, (_, i) => (
<option key={i} value={i}>
Copy link

Choose a reason for hiding this comment

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

key값을 index보다는 다른 더 유니크한 값으로 바꿔 주셔도 좋을 것 같아요!

{new Date(0, i).toLocaleString('en-US', { month: 'long' })} {/* 월 이름 표시 */}
</option>
))}
</SelectMonth>
</Wrapper>
);
}
const Wrapper = styled.div`
display: flex;
justify-content: space-between;
padding: 0 20px;
align-items: center;
width: 100%;
height: 10%;
margin-top: 10px;
margin-bottom: 10px;
`;
const ColorBox = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 5px;
span {
width: 50px;
height: 20px;
border-radius: 10px;
padding: 5px;
text-align: center;
font-size: 0.75rem;
color: white;
}
@media (max-width: 768px) {
span {
width: 35px;
height: 15px;
font-size: 0.5rem;

}
}
.today {
background-color: #FF8200;
}
.great {
background-color: #32AAFF;
}
.sorry {
background-color: #FF5A5A;
}
Comment on lines +59 to +67

Choose a reason for hiding this comment

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

지금 코드도 충분히 직관적이고 간결하지만 ColorBox 내부의 클래스들의 색상 값과 텍스트를 상수로 분리하면 유지 보수성을 조금 더 높일 수 있을 것 같아요!

Suggested change
.today {
background-color: #FF8200;
}
.great {
background-color: #32AAFF;
}
.sorry {
background-color: #FF5A5A;
}
const colorState = {
today: '#FF8200',
great: '#32AAFF',
sorry: '#FF5A5A',
};

`;
const Title = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 5px;
font-family: "Sofadi One", system-ui;
font-weight: 400;
font-style: normal;
color: rgb(62, 76, 247);
h2 {
font-size: 1.5rem;
margin: 0;
@media (max-width: 768px) {
font-size: 1.0rem;
}
}
span {
font-size: 0.75rem;
@media (max-width: 768px) {
font-size: 0.6rem;
}
}
`;

const SelectMonth = styled.select`
padding: 5px;
font-size: 1rem;
border: none;
color: rgb(62, 76, 247);
background-color: transparent;
box-shadow: inset 0 -1px 0 0 rgb(62, 76, 247);
&:focus {
outline: none;
}
@media (max-width: 768px) {
font-size: 0.75rem;
}
`;
Loading