PARTY TIME JOB 프로젝트

ClydeHan·2024년 3월 28일
1

프로젝트 선택 이유

프로젝트 선택지는 투두 리스트와 알바 구인구직 서비스였다. 나는 개인 프로젝트로 실시간 공유 투두리스트 서비스를 만들어본 경험이 있기도 했고, 투두 리스트 보다는 실질적으로 고객의 입장에서 생각해보며 개선해 갈 수 있는 여지가 있는 알바 구인구직 서비스를 선택했다. 그리고 개인적으로 생각해 봤을 때 투두 리스트 기능보다는 실제 고객 서비스를 구현해 본 경험이 더 도움이 될 것 같다고 판단을 했다. (다른 팀 대부분이 투두 리스트를 선택한 것에 놀랐다.)

제공된 디자인 시안의 UI가 재미가 없고, 별로 이용하고 싶지 않다고 느꼈다. 그리고 이를 팀원들도 모두 인지하고 있었다. 원래는 the-julge (더 줄게)라는 서비스였는데, 디자인 변경 또한 자유이기 때문에 바꾸기로 결심했다.


프로젝트 소개

서비스 소개

일자리가 급한 사람들을 위한 구인구직 서비스. 구인구직 과정을 파티처럼 즐겁고, 더욱 쉽게 만들고자 합니다.
당신의 다음 파티는 어디에서 시작될까요? 지금 바로 시작해 보세요!
PARTY TIME JOB
깃허브 바로가기


주요 기능

랜딩페이지 및 로그인/회원가입 페이지

  • 사용자 경험을 개선하기 위한 랜딩페이지 제작 및 디자인 변경
  • 사장 및 알바 유형에 따른 가입 방식 선택 기능 (React Hook Form)

일반회원 페이지

  • 내 프로필 등록/편집 기능 (React Hook Form)
  • 공고 신청 내역 렌더링 (Pagination)
  • 신청 내역 없을 시 공고 페이지 이동 기능

사장님 페이지

  • 내 가게 등록하기 기능 (React Hook Form)
  • 공고 등록하기 기능 (React Hook Form)
  • 내 공고 클릭 시 공고 상세 페이지 이동

공고 페이지

  • 유저 지역에 따른 맞춤 공고 렌더링
  • 전체 공고 렌더링 (Pagination)
  • 전체 공고 필터 기능 (마감임박순, 시급많은순, 시간적은순, 가나다순)
  • 상세 필터 기능 (위치, 시작일, 금액)
  • 공고 검색 기능

공고 상세 페이지

  • 공고 지원자 목록 렌더링 (Pagination)
  • 지원자 승인/거부 기능

공통

  • 헤더 네비게이션 바 로그인 유형에 따라 다른 메뉴 렌더링
  • 헤더 공고 승인/거부 알림 모달창 팝업 기능
  • 전체 UI 디자인 변경

프로젝트 준비

개발기간

2024.03.07 ~ 2024.03.25


목표 설정 및 일정 계획

  • 학습 중점의 목표 설정
    처음 사용해보는 익숙치 않은 패키지, 라이브러리, 폴더구조 학습

  • 대략적 계획 수립
    R&R에 맞춰 각 팀원의 예상 작업완료 일정 취합 후 전체 플랜 수립

  • 계획 조율 및 유연한 일정 관리
    데일리 스크럼을 통하여 수시로 작업 진행률 체크, 계획 및 일정 상황에 맞게 유연하게 조율


FLOW CHART

로그인, 회원가입 페이지

공고 리스트, 상세 페이지

사장 페이지

일반 회원 페이지


R&R

역할 분배 기준

  • 첫 팀 프로젝트에서 기능 별로 역할을 분배했었고, 그에 따르는 여러 문제점들을 인지했기 때문에 페이지 별로 역할을 분배했다. 단, 작업이 일찍 끝나는 팀원은 다른 팀원의 작업에 합류하게끔 규칙을 정하여 효율적인 작업 플랜트를 구축했다.

내 역할

  • 팀장
  • 보일러 플레이트 세팅
  • 깃허브 세팅
  • 공고 상세 페이지 구현 (pagination)
  • 로그인, 회원가입 페이지 구현 (react hook form)
  • 헤더 메뉴 구현 (유저 유형 별 다른 메뉴 렌더링)
  • 헤더 알림 기능 구현 (알바 유저 공고 승인 및 거부 알림 모달 기능 구현)
  • 헤더, 푸터 퍼블리싱
  • 랜딩페이지 퍼블리싱
  • 전체 페이지 디자인 리팩토링
  • 발표 자료 준비 및 발표

기술스택

개발


라이브러리


협업

배포


폴더구조

  • FSD(Feature-Sliced Design) 채택
src
├── app
│   └─ detail
│   └─ notice
│   └─ profile
│       └── page.tsx
├── pages
│   └─ EmployerPage
│       └── api
│   └─ NoticePage
│       └── api
├── widgets
│   └─ api
│       └── getApplication.ts
│   └─ Header
│   └─ Footer
├── features
│   └─ AcceptModal
│   └─ Filter
│   └─ Sort
├── entities
│   └─ Post
│       └── hooks
│       └── utils
├── shared
│   └─ api
│   └─ hooks
│   └─ utils
│   └─ ui
│       └── Table
│       └── Button
└──     └── Input
  • FSD 채택 이유
    주된 이유는 코드의 재사용성에 대한 학습이었고, 기능별로 코드를 조직화할 수 있다는 점에서 모듈화에 대한 개념도 이번 기회에 제대로 학습할 수 있다고 판단했다. 이로 인한 유지보수와 확장성에 대한 이점 또한 함께 가져갈 수 있기에 FSD 폴더구조를 채택했다.

협업 툴

  • github: project, issues, wiki, discussions 기능 사용

  • 모든 팀원이 owner 권한으로 속한 organization을 생성했다.

  • RepositoriesIssues 기능을 사용해 작업을 관리했다.

  • Project 기능으로 일정과 플랜트를 관리했습니다.

  • Wiki 기능으로 Party-Time-Job 프로젝트 상세를 기술했습니다.

  • Discussions 기능으로 회의록과 회고록을 관리했습니다.


  • discord 사용
    오후 1시 ~ 5시까지를 코어 타임으로 정했고, 해당 시간에는 항상 디스코드 팀 채널에 상주하며 서로 의견을 주고받았다. 일요일은 휴무로 정했지만, 작업할 인원은 자유롭게 작업했다.


Git Branch 전략

github flow


이슈 및 문제 해결

공동 컴포넌트 이슈

  • 사전에 공동 컴포넌트 부분의 역할을 명확하게 나누지 않아서, 각 각 다른 사람이 만든 버튼 공동 컴포넌트끼리 충돌이 일어났다. 그리고 그 공동 컴포넌트에 내려줄 props의 종류들과, 어떤 방식으로 프롭스를 내려줄지에 대한 협의가 되어있지 않은 상황이라서, 기능은 똑같지만 전혀 다른 방식의 버튼 공동 컴포넌트가 만들어졌다.

  • 공동 컴포넌트는 한 사람이 작업을 해서, 그 사람이 공동 컴포넌트 사용 방법을 팀원들에게 뿌려주는 방식으로 진행을 해야될 것 같다. 그게 아니라면, 팀원 모두가 공동 컴포넌트에 대해서 얘기를 제대로 마친 상태에서 접근을 하는 것이 옳은 방법인 것 같다.

FSD 폴더구조 이슈

  • FSD를 처음 접하다보니, 작은 단위부터 쪼개는 방식이 익숙치가 않았다. 폴더구조를 나눠야된다는 생각에 사로잡혀서 기본 기능을 구현 하지도 못하고, 계속 자료를 찾아봤다. FSD 폴더구조는 명확한 솔루션 보다는 프로젝트의 상황에 맞게 조정해가며 사용하라는 가이드가 있어서 조금 더 어려웠던 것 같다. 또한, 명확하게 FSD 폴더 구조에 대한 이해가 없다보니, 팀원들마다 폴더구조를 다르게 작성하여 생기는 문제 또한 있었다.

  • FSD 폴더구조는 개인이 공부를 하고, 팀원들에게 알려주는 방식은 옳지 않은 것 같다. 팀원 모두가 FSD 폴더 구조에 대해서 함께 공부하고, 명확하게 이해를 한 상태에서 팀 프로젝트 상황에 맞게 팀원들 모두가 함께 조정해가며 사용하는 것이 맞는 것 같다.

useSearchParams 이슈

  • 리스트도 정상적으로 나눠서 불러와지고, 페이지네이션 ui도 불러온 리스트에 맞춰서 잘 나눠져서 생성이 됐지만, 아무리 클릭해도 렌더링이 변하지를 않았다.
    이 작업은 팀원이 작성한 코드들을 모아와서 페이지를 새로 생성하는 과정이었다. 그래서 코드에 대한 이해도 가 부족했다.
    문제는 URL 쿼리를 제대로 읽지를 못했던 것이었다. 팀원이 작성한 페이지네이션 컴포넌트의 로직은 아래와 같다.



  • 내가 작성한 코드가 아니기도하고, 기한에 맞춰 기능 구현을 끝내야 하기 때문에 기존에 작성되어 있는 로직을 그대로 따라하려고 했다.
    하지만 저렇게 그대로 따라서 searchParams를 받아오는 코드를 사용하니, pageconsole.log 했을 때, undefined가 계속 떠서 아예 페이지네이션이 작동을 하지 않았던 것이다. (UI 부분은 숫자만 줘도 생성이 되기에 큰 문제없이 렌더링 됐지만)

그래서 아래와 같이 해결했다.

  • useSearchParams 를 사용해서 현재 페이지의 URL 쿼리 파라미터searchParams 변수에 저장한다. (URL이 ‘?page=2’ 와 같은 쿼리를 포함하고 있다면, 이를 통해 페이지 번호 등의 정보를 얻을 수 있다.)

  • searchParamsnull인지 여부를 검사한다. 만약 페이지가 완전히 로드가 되지 않아서 Null이라면 currentPage는 기본값은 1로 설정된다.
    parseInt(searchParams.get('page')를 통해서 ‘page’ 쿼리 파라미터 값을 문자열로 반환한다. 끝에 10은 십진수로 변환한다는 뜻.
    요약하자면, URL 쿼리 파라미터에 따라 현재 페이지 번호(currentPage)를 동적으로 결정한다.


해당 변수를 그대로 팀원이 만들어놓은 페이지네이션 속성으로 줬더니 정상작동한다.

상태관리 이슈


  • 사진과 같이 상태관리를 하니까 모달이 열리기만하고, 닫기 버튼을 눌러도 닫히지는 않고 ‘알림 모달 닫기’ console.log만 출력된다.

  • onClick 함수에서 setIsOpen 세터 함수에 값을 !isOpen 으로 바꿨더니, 바로 해결이 되었다.

  • 문제 원인
    비동기적 상태 업데이트:
    React의 상태 업데이트(setIsOpen)는 비동기적으로 수행된다. 이는 setIsOpen 호출 후 바로 isOpen의 값이 변경되지 않음을 의미한다. 따라서 onClick 함수 내에서 setIsOpen(true);를 호출하고 바로 이후에 isOpen의 값을 확인하려고 하면, 변경 전의 값이 출력될 수 있다.

    상태 업데이트 로직의 현재 상태 의존성:
    onClick 함수에서 모달의 상태를 열기 위해 setIsOpen(true);를 사용하고, onClose에서는 setIsOpen(false);를 사용하는 방식은 상태 변경 로직이 현재 상태에 기반하지 않고 고정된 값에 의존하게 만든다. 이 방식의 문제는, 상태를 토글하는 로직이 현재 상태를 무시하고 항상 같은 값을 설정하려고 하므로, 상황에 따라 예상치 못한 동작을 할 수 있다.

상태관리 이슈 (recoil)

useState 값을 props로 받아와서 사용하는데, 알림 모달의 리스트를 클릭해서 읽음 처리로 만들고, 알림 모달 리스트에서 지워가는 로직을 구현했다. 그런데 새로고침을 해야지만 리스트에서 사라지는 이슈가 발생했다. 즉, useState를 통한 리렌더링이 작동하지 않았다.

const [alerts, setAlerts] = useRecoilState<AlertItemType[]>(alertsState);
Props로 받아오던 상태 관리를 recoil로 전역 관리를 해줘서 해결했다.

스크롤 끝까지 당겼을 때 배경색 노출되는 이슈 (tailwind css)

현재 배경색은 검정색으로 설정했지만, 기본 background는 흰색으로 설정이 되어있다.
그래서 스크롤을 끝까지 당겼을 때, 기본 배경색이 살짝 노출되는 이슈가 있었다.
tailwind css 를 사용하고 있고, global.css 를 사용해서 root 스타일을 지정하고 있는 상태이다.

rootbackground를 원하는 색(검정색)으로 바꿨다. 스크롤을 끝까지 당겼을 때 나오는 기본 색상이 바꾼 색(검정색)으로 적용이 됐다.

인풋 데이트 타입 달력 아이콘 스타일 이슈

  • input type='date' 의 달력 아이콘 색상을 변경해야했다.
    tailwind css에서 해당 기능은 없었기에, global.css 에서 설정을 변경하기로 했다.

input[type='date']::-webkit-calendar-picker-indicator { filter: invert(1);}

  • global.css에서 해당 코드를 입력해서 달력 아이콘의 색상을 흰색으로 변경했다.
    하지만 해당 방법은 임시방편. 그리고 원하는 색상을 사용하기 위한 방법이 많이 복잡했다.HEX 색상 코드를 사용할 수 없었고, filter: invert(1)는 색상을 반전하는 코드이기 때문에 만약 다른 색상을 원한다면 hue-rotate 코드를 사용해서 HSL(Hue, Saturation, Lightness)값을 찾아 입력해야 한다.

  • 대처 방안: React-datepicker 또는 react-dates 라이브러리를 사용하면, 달력 아이콘을 포함한 모든 요소의 스타일을 자유롭게 변경할 수 있다. 이번 프로젝트에서는 임시방편으로 해결했지만, 다음번에는 라이브러리를 사용해볼 예정이다.


후기 (KPT 회고 커스텀)

얻은 것과 유지하고 싶은 것들

현업에 투입 됐을 때 클린코드와 가독성보다 중요하다고 생각하는 것이 마감 기한을 지키는 것인데 해당 부분을 우선순위로 두고 작업을 했다. 결과적으로 이러한 선택이 옳았다. 마감 기한을 맞추지 못해서 미구현 상태로 발표한 팀이 많았고, 우리 팀은 촉박한 기간 임에도 성공적으로 프로젝트를 완성했다. 다음 프로젝트로 넘어갈 때 까지 리팩토링을 통한 학습 또한 수월하게 진행했다. 올바른 선택 덕분에 두 마리의 토끼를 모두 잡았다.

FSD 폴더 구조를 채택하고 사용함으로써 코드 재사용성에 대한 기초 틀이 잡혔다. 전에는 공동 컴포넌트라고 하면 다른 곳에서 썼던 컴포넌트를 가져다 쓰는 것 정도로 여기고 사용했었는데, 이제는 props를 사용하여 범용성 있는 component를 설계할 수 있게 되었다. 그리고, 기능 별로 나누는 폴더 구조의 특성 덕분에 유지보수에 대해서 깊게 고민하고 코드를 작성하게 되는 계기가 되었다.

첫 프로젝트 이후에 이번에도 보일러 플레이트와 깃허브 세팅을 맡게 되면서 필요성에 따라서 프로젝트를 구성하는 방법을 학습할 수 있었다. 처음 세팅 때는 '해야 하니까' 흘러가는대로 기술 스택에 맞춰서 의무적으로 세팅을 했지만, 이번 세팅 때는 어떤 라이브러리를 사용해서 어떤 방향으로 프로젝트를 흘러가게 할까 라는 생산적인 고민을 할 수 있게 되었다.

테일윈드 숙련도가 눈에 띄게 많이 높아졌다. '실시간 공유 투두리스트' 개인 프로젝트에서 처음 tailwind css를 사용하고 익혔지만, 이번 프로젝트에서는 tailwind css의 처음 보는 기능들을 접했고, 덕분에 css를 아주 빠르게 만들어 갈 수 있는 숙련도와, 복잡한 animation을 구현할 수 있는 능력, 그리고 global.css 등을 학습하며 tailwind css의 단점인 자유도를 극복할 수 있었다. 결과적으로, 기능 구현을 하기에도 아주 촉박한 프로젝트 기간이지만 원래 시안에는 없는 랜딩페이지와 전체 디자인 리팩토링까지 성공적으로 완성할 수 있었다.

커뮤니케이션이 잘 되는 팀이 코딩을 잘하는 팀보다 더욱 생산적이라는 것을 알 수 있었다. 단편적인 예시 두 가지, 전에 했던 팀 프로젝트와의 비교, 그리고 현재 다른 팀과의 비교가 있다. 첫번째, 전에 했던 팀은 처음으로 하는 팀 프로젝트이기도 하고, 리액트에 대한 충분한 교육이 이행되지 않은 상태였다. 즉, 기술적인 이해가 매우 부족한 상태였다. 그런데 팀장을 맡게 되었으니 내가 제시하는 모든 의견들에 힘이 실리지 않았고 근거도 부족했으니 대부분 리젝트가 되었었다. 자연스레 주눅이 들게 되었고, 내 목소리 하나를 내는 것 하나 조차도 힘들어지는 상황이 되었었다. 후반에 가서는 내 의견에 아무 근거도 없이 그냥 거절하는 상황까지 이르렀다. 결과적으로, 이렇게 결정된 사안들이 팀에 좋지 않은 영향을 끼쳤다. (한 가지 예시: 페이지 별로 R&R을 구성하지 않고 기능 별로 R&R을 구성해버렸다.) 하지만 이번 프로젝트에서는 의견에 대한 자유로운 토론 분위기를 형성하기 위해서 많이 노력했다. 가벼운 사안일지라도 팀원 모두의 의견을 제시하도록 하였고, 이는 곧 팀원 모두가 팀의 모든 선택에 대한 책임감을 갖게 함으로써 모두가 한 가지 목표를 바라보며 갈 수 있게 되는 계기가 되었다. 프로젝트의 방향이 원래 의도와 다르게 흘러간다면, 눈치보지 않고 서로 방안을 제안할 수 있는 분위기가 형성 되면서 결과적으로 팀에 아주 좋은 영향을 끼쳤다. (한 가지 예시: FSD 폴더구조를 잘못 이해하고 사용하고 있어서 초반에 대대적인 리팩토링이 있었다. 신경쓰지 않고 넘어갔다면, 공동 컴포넌트를 사용하지 못함으로써 개발 시간을 단축할 수 없게 되었을 것이다.) 그리고 두번째, 아주 잘 하는 팀원 두 명이 배치된 팀과의 비교이다. (해당 팀에 친한 사람이 있어서 얘기를 전해들었다.) 해당 팀의 팀미팅은 평균 4시간 정도였던 것 같다. 하지만 팀 프로젝트에 실질적으로 도움이 되는 회의는 아니고, 원론적인 질문에 대한 회의를 주로 한 것 같다. 그로 인해 팀원들의 피로가 많이 높아져 있는 상태가 대다수였다. 또한 기능 구현에 문제가 있었을 때에도 서로가 서로를 어려워해서 도움을 요청하지 못했다고 한다. 결과적으로 그 팀은 기능 구현을 모두 하지 못한 채 발표를 진행하게 되었다. (이번 부트캠프 기수에서 제일 잘 하는 사람 2명이 있는 팀인데, 완성을 하지 못했다는 것에 많은 관심이 생겼다.) 그에 반해 우리 팀의 회의 시간은 평균 20분 정도, 프로젝트에 필요한 회의만을 진행했고, 필요한 경우에는 실시간으로 짧게 회의를 소집하는 등 불필요한 에너지 소비를 줄이며 효율적인 팀 활동을 이어갔다. 이러한 반례들을 경험하고 접하며 나에게 무엇이 중요했고, 실제 협업에 있어서 무엇이 중요한지 완벽하지는 않지만 어느정도 틀이 형성이 되었다.

문제 및 앞으로의 계획

NextJsApp Router를 채택하고 사용했지만, 지식이 많이 부족한 상태로 사용하다보니 놓친 것이 정말 많았다. 마감 기한을 준수하며 기능 구현을 하기에는 처음부터 학습을 해야해서 데드라인을 넘어갈 것이 눈에 훤했다. 그래서 내린 선택은 일단은 NextJs안에서 App Router의 기능을 사용할 수 있는 부분도 React로 기능 구현을 먼저 하고, (내가 아는 부분은 최대한 NextJs기능을 사용했다. 예를 들면, 동적라우팅 등) 발표가 끝나면 리팩토링을 하며 제대로 하나하나 사용하며 학습하기로 했다. 원래는 프로젝트가 끝나면 NextJs 개인 프로젝트를 진행하며 숙련도를 쌓아갈 계획이었지만, 리팩토링을 통한 학습 계획으로 노선을 변경했다.

FSD 폴더 구조 아키텍처를 채택하고 사용함으로써 여러 문제들을 직면했다. 아주 작은 단위로 분리하는 폴더 구조를 사용하는 것이 처음이라서 이해하고 적응하는 것이 어려웠고, 프로젝트의 상황과 환경에 따라서 유연하게 설계하라는 지침 또한 어떻게 적용을 시켜야 할지 와닿지 않았다. 그리고 애초에 FSD는 대형 프로젝트를 위한 폴더구조 아키텍처이기 때문에 작은 규모의 프로젝트를 진행하는 데에 있어서 FSD 폴더 구조를 채택함은 오버 엔지니어링이 발생할 수가 있었고, 실제로 그렇게 되었다. 폴더 구조 설계로 인해 개발 속도가 많이 느려진 것이다. 그리고 팀원들 모두 FSD에 대한 이해가 없이 시작을 해서 각자 일관되지 않은 FSD를 적용을 하고 있었고, 이대로 간다면 혼란과 비효율적인 협업으로 이어질 것이 뻔했다. 이를 해결하기 위하여 긴급 회의를 소집했다. FSD의 엄격한 원칙 적용 보다는 프로젝트의 요구 사항에 적응할 수 있도록 폴더 구조를 유연하게 적용시키되, FSD의 핵심은 지키고자 우리 팀만의 폴더 구조를 다시 설계했고, 모든 팀원이 동일한 방식으로 폴더 구조를 적용하기 위해 노력했다.

src
├── app
│   └─ detail
│   └─ notice
│   └─ profile
│       └── page.tsx
├── pages
│   └─ EmployerPage
│       └── api
│   └─ NoticePage
│       └── api
├── widgets
│   └─ api
│       └── getApplication.ts
│   └─ Header
│   └─ Footer
├── features
│   └─ AcceptModal
│   └─ Filter
│   └─ Sort
├── entities
│   └─ Post
│       └── hooks
│       └── utils
├── shared
│   └─ api
│   └─ hooks
│   └─ utils
│   └─ ui
│       └── Table
│       └── Button
└──     └── Input

0개의 댓글