패스트캠퍼스 프론트엔드/과제

패스트캠퍼스X야놀자 부트캠프 과제 리팩토링(2)

JellyApple 2023. 8. 28. 17:17

https://fastcampus.co.kr/b2g_MegabyteSchool_frontend

 

패스트캠퍼스 X 야놀자 : 프론트엔드 개발 부트캠프 | 패스트캠퍼스

프론트엔드 개발자로 취업까지 함께 가는 패스트캠퍼스 X 야놀자 부트캠프를 만나보세요!

fastcampus.co.kr

패스트캠퍼스X야놀자 프론트엔드 부트캠프 두 번째 과제로 JS를 활용해 직원 관리 서비스를 구현하는 과제를 받았다. 
2023.08.08 ~ 2023.08.18 약 10일간 진행 됐고 나는 맨유 선수단에 선수를 추가하고 수정, 삭제 하는 기능을 구현 했다. 

 

1. 과제 내용


1) 과제 설명 
: 주어진 기간 동안 필수 요구사항 및 선택 요구사항을 고려해서 직원 관리 서비스를 구현하는 내용이다.
 
2) 과제 요구 사항 (구현 한 것들) 
☆필수 요구사항

  • “AWS S3 / Firebase 같은 서비스”를 이용하여 사진을 관리할 수 있는 페이지를 구현하세요.
  • 프로필 페이지를 개발하세요.
  • 스크롤이 가능한 형태의 리스팅 페이지를 개발하세요.
  • 전체 페이지 데스크탑-모바일 반응형 페이지를 개발하세요.
  • 사진을 등록, 수정, 삭제가 가능해야 합니다.
  • 유저 플로우를 제작하여 리드미에 추가하세요.
  •  CSS
    • 애니메이션 구현
    • 상대수치 사용(rem, em)
  • JavaScript
    • DOM event 조작

    선택 요구사항

  • 사진 관리 페이지와 관련된 기타 기능도 고려해 보세요.
  • 직원을 등록, 수정, 삭제가 가능하게 해보세요.
  • 직원 검색 기능을 추가해 보세요.
  • LocalStorage 사용

2. 과제 구현 

https://jinjongsufastcampus.web.app/index.html

 

맨유 선수 관리 서비스

 

jinjongsufastcampus.web.app

 

 

3. 제출 후 느낀 점 

"막연히 어렵기만 했던 자바스크립트가 정리가 되어가는 느낌이 들었지만 아직 부족하다는 점"

사실 자바스크립트라는 언어가 다른 공부해왔던 언어들에 비해 손에 익지 않고 계속 개념에 대한 이해가 붕 뜨는 기분이였는데 이번 과제와 함께 들었던 강의를 계기로 조금은 익숙해져가고 틀이 잡혀가는 기분이다. 그러나 아직은 부족하다는 점 또한 절실히 배운 과제였다. 

 

4. 피드백


: 멘토님과 스터디원들이 코드리뷰를 통해 피드백을 해주셨다. 이전 과제처럼 이번 과제도 상당히 자세하고 구체적으로 써주셔서 피드백 요소가 무척이나 많다는 점과 또 많은 것을 배울 수 있었다. 팀원들의 코드 또한 상당히 배울 점이 많았다. 운이 좋게도 그룹스터디 조에서 우수 과제 제출자로 뽑혔지만 다른 팀원들의 과제에 비하면 부족한데 운 좋게도 다들 좋게 봐주신 것같아 감사하다. 피드백 내용은 아래와 같다. 

- 코드 들여쓰기에 좀 더 신경썼으면 좋겠다. 
- 이벤트리스너를 한 번에 묶는 함수를 만들어주면 관리하기 편할 것같다.
- 복잡한 함수는 별도로 빼주는게 유지보수 및 확장성 측면에서 유리하다, 
- 전체 선택 , 전체 해제 같은 상수 데이터는 별도의 상수를 선언해주는 것이 좋다. 
- window.location.href 함수도 별도의 함수로 만들어주면 좋다. 
- style.css에 모든 페이지의 스타일을 넣었는데 각 페이지 별로 스타일을 나누는게 좋다. 
- input 태그의 이름을 btn 으로 지어서 헷갈리다. 클래스명과 변수명을 input으로 적어주는 것이 좋다.
- input 값과 같은 단일데이터는 querySelector보단 getElementById를 사용하는 것이 좋다. 
- 동일한 로직 있는 코드를 하나로 개선하면 좋겠다.

 

 5. 리팩토링

: 2023.08.28  리팩토링 시간을 가졌고 위의 피드백을 토대로 코드를 수정했다. 
1) 코드 들여쓰기
: 코드 들여쓰기 부분은 당연히 잘 하고 있다 생각해서 생각치 못한 부분인데 이 부분을 놓쳤던 점이 조금 아쉬웠다. 지금이라도 이 부분에 대해 생각해볼 수 있는 좋은 기회라 생각하고 기본부터 다잡아야겠다고 다짐했다.

 

※ 기존 코드

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="./styles/styles.css">
  <link rel="favicon" type="image/x-icon" href="./assets/icon/favicon.png">
  <title>맨유 선수 관리 서비스</title>
</head>
<body class="index__body">
  <div class="container">
    <div class="index__border">
    <header class="header">
        <div class="header__maintitle">
            <h1>맨유 선수단 관리 서비스</h1>
        </div>
        <div class="header__subtitle">
            <h2>내가 원하는 선수로 선수단을 꾸려보세요!</h2>
        </div>
    </header>
    <section class="start__tab"> 
        <button class="start__button">시작하기</button>
    </section>
</div>
</div>
<script src="./js/index.js"></script>
</body>
</html>
 <header class="playerInfo__header">
        <Nav class="nav">
            <div class="nav__item">
               <div class="logo-tab__menu">
                <img class="logo__icon" src="./assets/icon/team_logo1.png" alt="team-logo">
                <ul class="logo-tab__button">
                    <li><a href="playerList.html">선수단 관리 페이지</a></li>
                    <li><a href="playerInfo.html">프로필 정보</a></li>
                    <li><a href="playerUpload.html">선수 업로드 페이지</a></li>
                </ul>
               </div>
            </div>
        </Nav>
    </header>

※ 수정 후

<body class="index__body">
  <div class="container">
    <div class="index__border">
        <header class="header"> // header class 내부 들여쓰기 완료 
            <div class="header__maintitle">
                <h1>맨유 선수단 관리 서비스</h1>
            </div>
            <div class="header__subtitle">
                <h2>내가 원하는 선수로 선수단을 꾸려보세요!</h2>
            </div>
        </header>
        <section class="start__tab"> 
            <button class="start__button">시작하기</button>
        </section>
    </div>
 </div>
<script src="./js/index.js"></script>
</body>
 <header class="playerInfo__header">
        <Nav class="nav">
            <div class="nav__item">
               <div class="logo-tab__menu">
                    <img class="logo__icon" src="./assets/icon/team_logo1.png" alt="team-logo"/>
                    <ul class="logo-tab__button">
                        <li><a href="playerList.html">선수단 관리 페이지</a></li>
                        <li><a href="playerInfo.html">프로필 정보</a></li>
                        <li><a href="playerUpload.html">선수 업로드 페이지</a></li>
                    </ul>
               </div>
            </div>
        </Nav>
    </header>
/* firebase 데이터 반영 */ 
       selectedPlayerId = selectedPlayerData.id;
       const db = firebase.firestore();
       const storage = firebase.storage();

       if(newImage){
           let storageRef = storage.ref();
           let storagePath = storageRef.child('image/'+ newImage.name);
           let updateWork = storagePath.put(newImage)
 
           updateWork.on('state_changed',
            null,
            (error)=>{
               console.error('Error : ', error);
            },
            ()=>{
               updateWork.snapshot.ref.getDownloadURL().then((updateUrl)=>{
                   updatedData.image = updateUrl;
                      db.collection('Player').doc(selectedPlayerId).update(updatedData)
                         .then(()=>{
                            updatePage(updatedData);
                            closeModal();
                            alert('선수 정보 수정이 완료됐습니다.');
                            console.log('업데이트 완료');
                         })
                         .catch((error)=>{
                            alert('선수 정보 수정을 실패했습니다.')
                            console.error('선수 정보 수정 실패', error);
                         });
                      localStorage.setItem('selectedPlayer',JSON.stringify(updatedData));
               });
            }
           )
  
       }else{
        db.collection('Player').doc(selectedPlayerId).update(updatedData)
             .then(()=>{
                updatePage(updatedData);
                closeModal();
                alert('선수 정보 수정이 완료됐습니다.');
                console.log('업데이트 완료');
             })
              .catch((error)=>{
                 alert('선수 정보 수정을 실패했습니다.')
                 console.error('선수 정보 수정 실패', error);
             });
        
        localStorage.setItem('selectedPlayer',JSON.stringify(updatedData));

       }
     
    
       }
    const SavePage = ()=>{
        const saveBtn = document.querySelector('.save__btn');

        saveBtn.addEventListener('click',()=>{
            window.location.href="playerList.html";
           })
    }

    saveModalBtn.addEventListener('click',saveModal)
    SavePage();
};

2) 함수 정리(복잡한 함수 따로 빼기 + 이벤트리스너 함수 통합 setup 함수 생성) 
:  복잡한 함수를 따로 component로 빼고 같은 파일 내 이벤트리스너의 반복되는 것을 모아서 통합 하는 함수를 만들어줬다. 코드의 가독성 및 확장성과 유지보수에 용이하도록 코드를 수정했다. component 폴더를 따로 만들어서 Logo.js와 SearchPlayer.js를 만들어서 import 해주었다. 

 

※ 기존 코드

SearchBtn.addEventListener('keyup',()=>{
  const filterPlayer = SearchBtn.value.toLowerCase();
  const filterRow = document.querySelectorAll('.player__cell');

  filterRow.forEach((cell)=>{
    let cellText = cell.textContent.toLowerCase();
    if(cellText.includes(filterPlayer)){
      cell.style.display='';
    }else{
      cell.style.display = 'none';
    }
  });
SearchBtn.addEventListener('keyup',()=>{
  const filterPlayer = SearchBtn.value.toLowerCase();
  const filterRow = document.querySelectorAll('.player__cell');

  filterRow.forEach((cell)=>{
    let cellText = cell.textContent.toLowerCase();
    if(cellText.includes(filterPlayer)){
      cell.style.display='';
    }else{
      cell.style.display = 'none';
    }
  });
  
  const moveToPlayerUpload = () =>{
    addBtn.addEventListener("click", ()=>{
        window.location.href="playerUpload.html;
          });
}

removeBtn.addEventListener('click',()=>{
    removePlayer();
});

allSelectBtn.addEventListener('click',()=>{
  const allCheckboxes = document.querySelectorAll('.checkbox__container input[type="checkbox"]');
  const isSelected = allCheckboxes[0].checked;


  allCheckboxes.forEach(checkbox => {
      checkbox.checked = !isSelected;
  });

※ 수정 후 

component 형식으로 바꿔줌

import {searchPlayer} from './component/SearchPlayer.js';

function setup(){
  addBtn.addEventListener("click", ()=> moveToExternalPage('playerUpload.html'));
  removeBtn.addEventListener('click', removePlayer);
  searchBtn.addEventListener('keyup', ()=> searchPlayer(searchBtn));
  allSelectBtn.addEventListener('click', allSelectedPlayers);
}
setup();

3) 상수 처리 
: 전체 선택 및 전체 삭제는 TextContent로 내용만 바꿔주는 삼항연산자로 작성했는데 나중에 코드가 비대해질 때를 대비해서 이를 상수처리 하였다. selectAllPlayer과 deleteAllPlayer 변수를 새로 만들어 주었다. 

 

※ 기존 코드

const allSelectedPlayers = ()=>{
  const allCheckboxes = document.querySelectorAll('.checkbox__container input[type="checkbox"]');
  const isSelected = allCheckboxes[0].checked;
  allCheckboxes.forEach(checkbox => {
      checkbox.checked = !isSelected;
  });

  allSelectBtn.textContent = isSelected ? '전체 선택' : '전체 해제';
}

※ 수정 후 

const selectAllPlayer = '전체 선택';
const deleteAllPlayer = '전체 해제';
const allSelectedPlayers = ()=>{
  const allCheckboxes = document.querySelectorAll('.checkbox__container input[type="checkbox"]');
  const isSelected = allCheckboxes[0].checked;
  allCheckboxes.forEach(checkbox => {
      checkbox.checked = !isSelected;
  });

  allSelectBtn.textContent = isSelected ? selectAllPlayer : deleteAllPlayer; 
}

4) window 함수 생성 및 변수명 재정의 

: window.location.href 코드를 따로 함수로 만들어서 다른 페이지로 넘어갈 때 page 변수를 통해 넘어가게 해주었고, 나도 모르게 input 요소를 Btn이라고 이름을 정의해서 이를 수정하고 input 값과 같은 단일데이터에는 querySelector보단 getElementById를 추천해주셔서 이 부분 또한 수정했다. 

 

 기존 코드

const lookBtn = document.querySelector('.transfer__btn');
const nameBtn = document.querySelector('.name__btn');
const nationBtn = document.querySelector('.nation__btn');
const ageBtn = document.querySelector('.age__btn');
const positionBtn = document.querySelector('.position__btn');
const imgBtn = document.querySelector('.img__btn');
const uploadBtn = document.querySelector('.upload__btn');

const moveToPlayerUpload = () =>{
    addBtn.addEventListener("click", ()=>{
        window.location.href="playerUpload.html";
      });
      }

 수정 후 

const lookBtn = document.querySelector('.transfer__btn');
const nameInput = document.getElementById('name__input');
const nationInput = document.getElementById('nation__input');
const ageInput = document.getElementById('age__input');
const positionInput = document.getElementById('position__input');
const imgInput = document.getElementById('img__input');
const uploadBtn = document.querySelector('.upload__btn');

function moveToExternalPage(page) {
  window.location.href = page;
}


const createPlayer = (player)=>{
  const row = document.createElement("tr");
  row.classList.add("player__cell");
  row.setAttribute('data-id', player.id);
  row.innerHTML = `
           <td>
           <div class="player__info">
               <div class="checkbox__container">
                   <input type="checkbox">
               </div>
               <img  class="player__image" src="${player.image}" alt="${player.image}">
               <span class="player__name">${player.Name}</span>
           </div>
           </td>
           <td>${player.Nation}</td>
           <td>${player.Age}</td>
           <td>${player.position}</td>
  `;
    const playerImage = row.querySelector(".player__image");
    playerImage.addEventListener('click',(event)=>{
      event.stopPropagation();
      localStorage.setItem('selectedPlayer', JSON.stringify(player));
      moveToExternalPage('playerInfo.html')// window.location.href= "playerInfo.html";
  })
  addPlayer.appendChild(row);
};

5) CSS 분리 및 동일 로직 개선 
: styles.css에 모아둔 것을 playerInfo.css , playerList.css 등으로 구분해주었고 modal 창 부분에서 동일 로직 부분을 개선 했다 멘토님이 알려주신 방법으로 조금 수정 했다. 

 

※ 기존 코드 

const  closeModal = () =>{
        openModal.style.display= "none";
        document.body.style.overflow = "auto";
    }

    window.addEventListener('click',(event)=>{
        if(event.target===openModal){
            openModal.style.display= "none";
            document.body.style.overflow = "auto";
            console.log('click');
        }
    })

※ 수정 후

 const closeModal = () =>{
        openModal.style.display= "none";
        document.body.style.overflow = "auto";
    }
    
    window.addEventListener('click',(event)=>{
        if(event.target===openModal){
            closeModal();
        }
    })

6. 느낀 점

: 두 번의 과제를 거치고 나니 어느 정도 기본이 다져지는 느낌을 받았다. 역시 가장 중요한 건 내가 얼마나 구현을 위한 생각을 하고 또 얼만큼의 정성을 쏟아 코드를 치는 지에 따라 결과물이 다르다는 점을 배웠다. 그리고 강의를 듣고 실제로 응용해보는 시간이 매우 도움이 된다는 점을 다시 한 번 느꼈다. 다른 스터디원 및 사람들의 구현물을 보면서 자극을 받고 아직 많이 부족함을 느꼈다. 누군가에게 도움이 될 수 있는 그런 실력을 쌓고자 더더욱 노력해야 함을 느꼈다. 그래도 JS에 대해 무지했던 내가 조금의 만족감을 가질 수 있던 뜻 깊은 기회였던 것 같다. 앞으로의 팀 프로젝트 및 지금 개인적으로 하고 있는 프로젝트의 팀원들에게 민폐가 되지 않도록 현실에 안주하지말고 노력해야 함을 느꼈다. 

앞서 첫 번째 리팩토링 과제 포스팅 때 적었던 마지막 문구가 생각 났다.

"항상 부족하다 느끼는 이 마음가짐을 원동력으로 삼아 점점 더 발전하는 사람으로 기억되고 싶다. "

아직 잘하는 사람은 아니지만 이 때 적었던 문구처럼 발전해 나가는 것 같아 조금의 만족감을 가지고 이 만족감을 더 원동력으로 삼아 더 노력하는 사람이 될 것이다.