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

3조 과제 제출 (이정우, 이시우, 문현수, 문대현) #7

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

howooking
Copy link

@howooking howooking commented Jul 22, 2023

풀리퀘가 closed 되어서 다시 제출합니다.

KDT5-M6 가계부 토이 팀프로젝트

프로젝트 소개 💵

SOBI는 소비내역이 달력에 표시되며 월별 소비금액을 그래프로 볼 수 있는 웹 어플리케이션입니다.

SOBI 보러가기

로컬에서 테스트 하실 경우
root 폴더에 .env 파일 생성 후

VITE_USER_ID=team3

숫자 바꾸면 다른 조 데이터도 볼 수 있어요

문과이과 팀 소개

팀원 정우 시우 현수 대현
담당 총괄
차트
소비내역 생성
소비내역 수정
소비내역 삭제
소비내역 불러오기
달력
소비내역 검색
검색 자동완성

API

  1. 소비 기록 작성 API
    Request:
POST /expenses
Content-Type: application/json
{
  "amount": 100,
  "userId": "user123",
  "category": "food",
  "date": "2023-07-04T10:30:00.000Z"
}

Response:

Status: 201 Created
{
  "message": "Expense created successfully"
}
  1. 소비 품목 목록 API
    Request:
GET /categories?userId={userId}

Response:
Status: 200 OK

['food', 'clothing', 'electronics'];
  1. 검색어에 해당하는 소비 항목 및 금액 조회 API
    Request:
GET /expenses/search?q={keyword}&userId={userId}

Response:

Status: 200 OK
[
  {
    "amount": 100,
    "userId": "user123",
    "category": "food",
    "date": "2023-07-04T10:30:00.000Z"
  },
  {
    "amount": 80,
    "userId": "user456",
    "category": "food",
    "date": "2023-07-03T14:20:00.000Z"
  }
]
  1. 일별, 주별, 월별 소비 조회 API
    Request:
GET /expenses/summary?period={period}&userId={userId}
period : daily, weekly, monthly

Response:

Status: 200 OK
[
  {
    "_id": "2023-07-04",
    "totalAmount": 180
  },
  {
    "_id": "2023-07-03",
    "totalAmount": 80
  }
]
  1. 소비 기록 수정 API
    Request:
PUT /expenses/123
Content-Type: application/json
{
  "amount": 150,
  "userId": "user123",
  "category": "food",
  "date": "2023-07-04T10:30:00.000Z"
}

Response:

Status: 200 OK
{
  "message": "Expense updated successfully"
}
  1. 소비 기록 삭제 API
    Request:
DELETE / expenses / 123;

Response:

Status: 200 OK
{
  "message": "Expense deleted successfully"
}
  1. 소비 기록 달력 호출 API
    Request:
GET /expenses/calendar?year=2023&month=7&userId={userId}

Response:

Status: 200 OK
{
  "1": [
    {
      "amount": 100,
      "userId": "user123",
      "category": "food",
      "date": "2023-07-01T10:30:00.000Z"
    }
  ],
  "4": [
    {
      "amount": 80,
      "userId": "user456",
      "category": "food",
      "date": "2023-07-04T14:20:00.000Z"
    }
  ]
}



사용한 기술, 라이브러리

Environment






Config




Development











화면 구성

SOBI 웹페이지 tour

2023-07-21_11-26-16

  • Ant Design의 tour 기능을 활용.
  • 방문 여부를 로컬에 저장하여 첫방문시에만 활성화된다.

메인 화면

image

  • 일소비 금액별로 색이 다르게 표시된다.
  • 일소비 내역 횟수가 해당 날짜에 숫자로 표시된다.
  • 우측 하단 + 버튼을 눌러 소비내역을 등록할 수 있다.
  • 우측 상단 검색창을 통해 소비내역을 검색할 수 있다.

소비내역 등록 모달

image
  • Date picker로 날짜와 시간을 선택할 수 있다.
  • 유효성 검사를 통과해야 소비내역을 등록할 수 있다.
  • 유효성 검사 결과와 등록 결과 등을 팝업 메세지로 사용자에게 알려준다.

서랍 메뉴

image
  • 페이지를 이동할 수 있다.
  • 좌측 하단에 Color picker로 사용자가 원하는 강조색을 선택할 수 있다.

강조색 선택

2023-07-21_11-25-06

  • 리액트 내장 context api를 사용하여 강조색을 전역상태관리.
  • 로컬 저장소에 색의 hex값을 저장하여 새로고침을 하거나 브라우저를 종료하여도 강조색이 유지된다.

소비 통계 페이지

2023-07-21_11-23-55

  • 월별 소비액을 그래프로 나타낸다.
  • chartjs 라이브러리 사용.
  • 통신 중에는 skeleton 로딩 ui를 보여준다.
  • 기간을 선택할 수 있다.
  • api 통신 성공 여부를 배너 메세지로 표시한다.

고찰

이정우

  • 협업

    • 깃허브 이슈기능 활용
      image

      • 기능 단위로 이슈를 발행, 진행상황등을 깃허브 내에서 파악 할 수 있다.
      • 기능의 단위를 어느정도로 잡아야할지 판단이 어려움.
        • 예를 들어 월별 소비 금액 차트 내에서도 여러 세부 기능을 나뉘는데 세부 기능마다 이슈를 생성해야 하는지.
        • 그렇다고 너무 큰 단위의 기능의 경우 한번에 PR하는 양이 많아지는 문제가 발생.
    • commit 단위 및 메세지

      image
      • 컨벤션이 초반에는 비교적 잘 지켜졌지만 후반부에는 잘 지켜지지 않았다.


  • Ant Design

    • 리액트 컴포넌트 기반의 UI라이브러리.
    • 몇가지 규약만 지켜주면 빠른시간내에 완성도 있고 일관성있는 UI를 만들 수 있다.
    • 다양한 기능들을 최대한 사용해보고자 하였음.
      • Tour, Table, Modal, Skeleton, Drawer, Date Picker, Color Picker, Autocomplete 등...
    • 그러나 커스터마이징 제약이 있다는 점, CSS를 잘 다루는 못하는 초보자의 경우 antD 의존성이 크게 올라가므로 배우는 입장에서는 scss나 tailwind가 더 적절하다고 생각함.


  • ChartJS

    • 공식 문서를 보면서 필요한 기능들을 적용하였다.
    • 커스터마이징 항목이 매우 많은 것은 장점이자 단점.


  • Context API

  • SPA에서 server state와 client state 동기화 문제

    • 소비내역을 등록, 수정, 삭제가 발생할 때마다 서버로부터 최신의 데이터를 받아와야 함.

    • 본 프로젝트에서는 togglefetch라는 변수를 useState로 선언하고 소비내역의 변경이 발생하는 handling 함수에서 setToggleFetch((prev) => !prev). 그리고 통신이 일어나는 useEffect의 dependency에 togglefetch를 넣어주었다.

      const [togglefetch, setToggleFetch] = useState(false);
      
      useEffect(() => {
        const getData = async () => {
          setLoading(true);
          try {
            const response = await getExpenses(year, month);
            setMonthlyExpenses(response);
          } catch (error) {
            console.log(error);
          } finally {
            setLoading(false);
          }
        };
        getData();
      }, [month, year, togglefetch]);
    • 이 방법은 페이지전환이 발생하지 않는 SPA에서 server state와 client state 동기화 문제를 가장 쉽게(새로고침) 해결할 수 있는 방법이다.

    • 다른 방법으로 get 요청 없이 client state를 setState로 업데이트 시켜주는 방법이 있다.

      • 그러나 본프로젝트에서는 서버에서 _id가 생성되고 이 값으로 수정과 삭제를 진행하기 때문에 이 방법은 적절하지 않다.
      • 만약 모든 데이터를 클라이언트에서 생성하는 경우 이러한 전략이 유효.
      • 예를 들어 새로운 소비내역을 post 요청으로 db에 저장하고, 동시에 setState((prev) => [...prev, newData])를 통해 화면을 재랜더링.
      • 이러한 경우 post요청만 실행되고 get요청은 실행되지 않아도 되므로 자원을 아낄 수 있다.
    • 또 다른 방법으로 react-query 라이브러리를 사용하는 방법이 있다.


  • UTC시간, local 시간 문제

    • 서버에서 요구하는 시간의 형태는 ISO 8601이다.
    • 2023-07-22T17:51:36.506Z와 같이 표기가 되며 z는 UTC를 의미함.
    • UTC는 경도 0, 즉 그리니치 천문대를 기준으로 한 시간이며, 한국은 이 시간보다 9시간 빠르다.
    • 글로벌한 서비스들의 등장으로 UTC시간을 사용하는 것이 중요하다.
    • 본 프로젝트에서 월별 소비 내역을 get요청하면 다음과 같은 데이터가 온다.
    {
      "1": [
        {
          "_id": "64b8efd86f8d76dd4f24b5a4",
          "amount": 34000,
          "userId": "team3",
          "category": "피자헛",
          "date": "2023-07-01T17:24:32.781Z",
          "__v": 0
        }
      ],
      "21": [
        {
          "_id": "64baa68fcedf8391c1aa38e3",
          "amount": 4500,
          "userId": "team3",
          "category": "커피",
          "date": "2023-07-21T15:38:46.175Z",
          "__v": 0
        }
      ], ...
    }
    • 해당 객체의 key값이 되는 "일(day)"은 UTC 시간을 기준으로 생성된 값이다. 그러다 보니 실제로는 22일 오전 4시에 입력한 값이 9시간 전인 21일에 담겨서 오는 문제가 발생한다.
    • 서버코드를 손대지 못하는 상황에서 이러한 문제를 해결하기 위해 등록 및 수정시 시간 정보에 9시간을 더하여 서버에 전송하였다.
    • 그리고 서버로부터 받은 시간은 클라이언트에서 getUTC...()을 하여 UTC시간(9시간 더해진 UTC시간)을 변환 없이 그대로 사용한다.
    export default function formatDateAndTime(dateString: string) {
      const date = new Date(dateString);
      const year = date.getUTCFullYear();
      const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
      const day = date.getUTCDate().toString().padStart(2, '0');
      const hours = date.getUTCHours().toString().padStart(2, '0');
      const minutes = date.getUTCMinutes().toString().padStart(2, '0');
    
      const formattedDate = {
        date: `${year}${month}${day}일`,
        time: `${hours}:${minutes}`,
      };
    
      return formattedDate;
    }

이시우

수정모달 등록모달
  • 형식 비슷한 기능의 모달(수정,등록)을 한가지 모달 폼 활용해 두가지 기능을 구현하는 부분에서 시행착오를 겪었지만, 코드의 효율을 높일수 있었습니다.


  • 이슈를 활용한 프로젝트 관리를 처음 접해보아서 초반에 커밋단위, PR단위를 어느정도로 해야할지 감을 잡기 힘들었지만, 이번 경험을 통해 추후 진행될 프로젝트에서 이슈,프로젝트를 활용 할 수 있을것 같습니다.


  • 주석 작성을 통해서 다시 한번 코드의 구조를 확실하게 이해할수 있었고, 이를 팀원들 간에 설명하는 시간을 통해 전체 프로젝트를 더 확실하게 이해할 수 있었습니다.

문현수

  • React calendar 라이브러리 사용
    • 이번 프로젝트에서 react-calendar라는 라이브러리를 사용했는데, 어느 요소에 어떤 기능이 들어있는지 잘 파악이 되지 않아서 어려웠습니다. 특히 생각한 기능이 있었는데, 생각한대로 하기에는 react-calendar을 사용하기엔 어려움이 있었습니다.
    • 그렇다고 해서 만들어서 사용하는 것은 시간이 더 오래걸렸을 것 같다는 생각이 들었습니다.
    • 다음 번에는 주어진 상황과 기획한 대로 구현할 수 있는 방법을 선택하면 좋을 것 같습니다.


  • 프로젝트를 진행한 이후에 팀원들끼리 모여서 본인이 맡은 부분에 대해서 설명하는 시간을 가졌는데, 굉장히 유의미한 시간이었던 것 같습니다. 물론 프로젝트 규모가 크다면 이렇게 하는 것은 어렵겠지만, 이렇게 브리핑하면서 전반적인 프로젝트의 코드나 맡지 않은 파트에 대해서도 이해할 수 있어서 좋았던 것 같습니다.


  • 프로젝트를 진행할 때, 타입스크립트에 대한 이해가 좀 낮아서 타입을 일단 any로 설정해놓고 진행한 적이 있는데, 타입스크립트에 대한 공부가 좀 더 필요하다고 생각했습니다.

문대현

  • 검색 기능을 구현하면서 서버에 등록되어 있는 데이터를 불러오는 데 있어 어려움이 있었습니다. 검색 결과를 모달 화면에 표시하고 표시된 데이터를 클릭 시 클릭한 날짜에 해당하는 모든 데이터를 불러오는 과정에 어려움이 있었고 그 외에도 코드를 구현하는 데 있어 어려움이 있을 때마다 팀장님께서 도움을 주셔서 문제 해결을 하는데 많은 도움이 되었습니다. 이번 프로젝트를 진행하며 쌓은 경험이 추후 프로젝트를 진행할 때 많은 도움이 될 것 같습니다.


  • 이번에 antd 라이브러리를 처음 사용해 보며 antd라는 유용한 라이브러리 경험을 하게 되어 추후 진행될 프로젝트에 유용하게 사용할 수 있을 것 같습니다.


  • 프로젝트 마무리 정리를 하며 팀원들 간에 본인이 맡은 파트에 대해 설명하는 유익한 시간을 가졌습니다. 다른 팀원의 파트는 주석 처리로 설명을 잘 해 놓았다 하더라도 텍스트 만으로는 이해하기 어려운 부분이 있지만 이번 발표로 각자 맡은 파트를 설명하는 시간을 가져 각자의 코드를 이해하는데 많은 도움이 된 것 같습니다.

@howooking howooking changed the title 3조 과제 제출 (이저우, 이시우, 문현수, 문대현) 3조 과제 제출 (이정우, 이시우, 문현수, 문대현) Jul 22, 2023
Comment on lines +1 to +60
// 사진들
import jw from '@/assets/profileImage/jw.jpg';
import sw from '@/assets/profileImage/sw.png';
import hs from '@/assets/profileImage/hs.jpg';
import dh from '@/assets/profileImage/dh.png';

export const API_BASE_URL = 'https://chickenlecture.xyz';

// 사이드바 메뉴 아이템
import {
CarryOutOutlined,
AreaChartOutlined,
SmileOutlined,
} from '@ant-design/icons';

export const SIDEBAR_ITEMS = [
{ label: '소비기록', href: '/', icon: CarryOutOutlined },
{ label: '소비통계', href: '/statistics', icon: AreaChartOutlined },
{ label: 'ABOUT', href: '/about', icon: SmileOutlined },
];

// 통계페이지에서 월구간 옵션
export const MONTH_RANGE_OPTIONS = [
{ value: '6', label: '최근 6개월' },
{ value: '12', label: '최근 12개월' },
{ value: '18', label: '최근 18개월' },
{ value: '24', label: '최근 24개월' },
];

// 어바웃 페이지 팀원 소개
export const TEAM_MEMBERS = [
{
name: '이정우',
github: 'https://github.com/howooking',
imgUrl: jw,
roles: ['팀장', '소비통계', '호우킹'],
comment: '수학의 정석 한권',
},
{
name: '이시우',
github: 'https://github.com/cuconveniencestore',
imgUrl: sw,
roles: ['소비 CRUD', 'CU편의점', '모범생'],
comment: '24시간이 모자라',
},
{
name: '문현수',
github: 'https://github.com/96uoow',
imgUrl: hs,
roles: ['달력', '개블리셔'],
comment: '자바스크립트 쉽다',
},
{
name: '문대현',
github: 'https://github.com/dhmoon11',
imgUrl: dh,
roles: ['검색', '예비군수석'],
comment: '개발이 제일 쉽다',
},
];

Choose a reason for hiding this comment

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

constants파일을 따로 만든 것은 정말 좋은 시도였던거 같습니다! 상수파일을 사용하면 코드 유지 보수성과 가독성을 크게 향상시킬 수 있는데 상수를 효과적으로 관리하고 응용프로그램이 확장될 때 업데이트 및 수정하기 쉽도록 할 수 있습니다! 타입스크립트에서는 특히 중요하죠!

Comment on lines +1 to +6
export default function getAccentColorFromLocalStorage() {
// local 저장소에서 key값이 accentColor를 가져옴
const accentColor = localStorage.getItem('accentColor');
// accentColor라는 값이 있다면 해당 값을 json parsing해서 변수에 저장하여 return 없으면 #87e4ac색 return
return accentColor ? JSON.parse(accentColor) : '#87e4ac';
}

Choose a reason for hiding this comment

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

의미있게 아키텍쳐를 짠 보람이 있는거 같네요! 커스텀훅과 모듈화된 함수를 의미있게 분리하고 각각의 역할대로 잘 import해서
쓰게 하는 것이 굉장히 클린한 코드처럼 보입니다

Comment on lines +1 to +11
export interface MontlyExpensesType {
[key: string]: DailyExpensesType[];
}
export interface DailyExpensesType {
_id: string;
amount: number;
userId: string;
category: string;
date: string;
__v: number;
}

Choose a reason for hiding this comment

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

타입스크립트의 핵심 기능 중 하나가 Type-checking인데 지금처럼 컴포넌트 내부에 지정해주거나 공통으로 쓰이는 interface의 경우엔 이렇게 따로 파일을 만들어 관리해주는 것이 베스트인거 같습니다
expense.ts / seacrh.ts로 어떤 종류의 interface가 선언되었는지 확실히 명명해줌으로써 좋은 개발을 하는데 도움이 될거 같네요!

Comment on lines +65 to +77
const dailyExpenses = useMemo(() => {
if (monthlyExpenses && monthlyExpenses[day]) {
return monthlyExpenses[day].map((expense) => ({
// 기존 객체를 다 가져오고
...expense,

// 필요한 속성을 삽입
key: expense._id,
time: formatDateAndTime(expense.date).time,
}));
}
return [];
}, [day, monthlyExpenses]);

Choose a reason for hiding this comment

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

메모이제이션을 잘 하는 것이 현업에서의 프론트엔드 개발자의 역량중 중요한 부분이 될텐데
useMemo를 적절히 활용하셨네요

Choose a reason for hiding this comment

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

useCallBack과 useMemo의 차이점은 무엇일까요?

Comment on lines +1 to +14
import { AccentColorContext } from '@/context/AccentColorContext';
import { useContext } from 'react';

export const useAccentColor = () => {
const accentColorContext = useContext(AccentColorContext);

// accentColorContext가 undefined인 경우 === wrapping을 벗어난 곳에서 사용하는 경우
if (!accentColorContext) {
throw new Error('래핑 잘해라');
}
const { accentColor, handleAccentColor } = accentColorContext;

return { accentColor, handleAccentColor };
};

Choose a reason for hiding this comment

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

context api를 사용하여 강조색을 전역관리 하는 건 거의 본적이 없는데
커스텀훅으로 따로 만들어서 매우 유용하게 사용하신 것이 인상이 깊었습니다
styled-components를 안 썼을땐 이런 방법도 좋을거 같네요!

Comment on lines +1 to +54
.react-calendar {
width: 80vw;
max-width: 1200px;
background: white;
font-family: Arial, Helvetica, sans-serif;
line-height: 1.125em;
position:absolute;
top:10%;
border-radius: 15px 15px 0 0;
}

.react-calendar--doubleView {
width: 700px;
}

.react-calendar--doubleView .react-calendar__viewContainer {
display: flex;
margin: -0.5em;
}

.react-calendar--doubleView .react-calendar__viewContainer > * {
width: 50%;
margin: 0.5em;
}

.react-calendar,
.react-calendar *,
.react-calendar *:before,
.react-calendar *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}

.react-calendar button {
margin: 0;
border: 0;
outline: none;
}

.react-calendar button:enabled:hover {
cursor: pointer;
}

.react-calendar__navigation {
display: flex;
height: 60px;
}
.react-calendar__navigation__arrow{
width:60px;
font-size:45px;
padding-bottom:5px;
}

Choose a reason for hiding this comment

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

class 네이밍 컨벤션부터 vw, em을 쓰는 디테일까지 깔끔한 스타일링 실력이 돋보입니다!

Comment on lines +27 to +46
const handleDelete = async () => {
await new Promise((resolve) => setTimeout(resolve, 500)); // 억지 0.5초
try {
const response = await fetch(
`${API_BASE_URL}/api/expenses/${selectedData?._id}`,
{ method: 'DELETE', headers: { 'content-type': 'application/json' } },
);
if (!response.ok) {
console.log('서버로 부터 응답이 왔는데 에러임.');
message.error('오류가 발생하였습니다');
return;
}
// 삭제 성공
message.success('소비기록 삭제 완료');
setToggleFetch((prev) => !prev);
} catch (error) {
console.log('서버로 부터 응답 안옴', error);
message.error('오류가 발생하였습니다');
}
};

Choose a reason for hiding this comment

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

비동기를 다루는 것이 프론트엔드 개발자에게 가장 중요한 능력중에 하나라고 생각하는데
async await promise 연산자를 사용하셨는데
억지라고는 주석에 들어가있지만
아마도 네트워크 통신이나 파일 읽어오는 시간에 대해 고민하고 쓰신 것 같습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants