짧으면 짧고 너무 짧다면 너무 짧았던 2주가 드디어 끝이 났다. 사실 첫 일주일은 이게 과연 끝이 나긴 할까..? 라는 막막한 생각이 많이 들었다면 2주차에는 마음이 급격히 조급해 지기 시작했던 것 같다. 그래도 별일 없이 프로젝트를 무사히 마친 팀원과 나 자신에게 수고했다고 말하고 싶고 어느때보다 치열하고 고통스러웠던 나의 2주를 돌아보려고 한다.
우선 팀원 부터 소개를 하자면 ,
이렇게 오순도순 다섯명으로 출발했다. 다른 팀에 비해 인원이 프론트,백엔드 1명씩 없었기 때문에 초반에는 괜찮을까? 라는 걱정이 있었지만 우리의 팀원들은 손이 빨랐고 백엔드 역시 두 분이 세명의 몫을 충분히 해내셨다.
우리 팀은 정육각 사이트를 클론코딩 했는데, 정육각에 대해서 간단히 소개하자면 육류를 주력을 판매하는 쇼핑몰이다. 사실 이 아이디어는 내가 낸 아이디어였는데 어떻게 하다보니 운이 좋게(?) 1차 프로젝트로 선정이 되었다.
내가 정육각을 선정한 이유는 우선 UI가 단순하고 깔끔해서 기능 구현에 집중할 수 있을 것 같다는 생각을 했고 첫 프로젝트로 하기에 부담이 없을 것 같았다.
먼저 팀원들과 함께 간단하게 사이트 분석을 마치고 필수 구현 페이지와 추가 구현 페이지로 나누어 구현할 페이지를 선정했다.
이 중에 나는 header
footer
장바구니
를 맡아서 진행하게 되었다.
정육쩜의 아이디어가 내가 낸 것이다 보니 자연스럽게 내가 PM이 되었다.
PM이라는 것이 낯설기도하고 어색하기도 했지만 프로젝트를 잘 끝내고자하는 욕심이 있었고 무엇보다 팀에 도움이 되는 PM이 되고 싶었기 때문에 팀을 잘 이끌어 보고자 노력을 했던 것 같다.
우리 팀은 진행상황을 Trello 로 서로 공유했고 회의를 할때 말로만 하다보니 회의 내용이 잘 정리가 되지 않아 소통의 어려움을 겪을 때가 있어 서로 공유해서 보는 화면이 있으면 좋을 것 같다는 생각이 들었고 Figma를 사용해서 구현할 레이아웃을 간단히 정리했다.
내가 맡은 header 와 footer는 사실상 레이아웃을 짜는 거라서 특별히 어려운 기능이 있지는 않아서 빨리 끝낼 수 있었다.
그래서 다른 팀원 들보다 빨리 끝내고 장바구니를 들어가게 되었는데 그때부터 나의 시련은 시작 되었을지도 모르겠다...
정육각의 장바구니에는 상품을 선택하는 기능이 따로 없었다. 그래서 나는 체크박스 기능을 추가해서 선택 삭제, 선택 주문 기능을 추가로 넣고 싶었고 백엔드와 이야기를 해본 결과 가능할 것 같다 그래서 진행하게 되었고, 기능 구현을 했던 것 중에 어려웠고 기억에 남는 코드 몇가지를 소개해보려고 한다.
체크 박스 기능은 총 3가지 조건을 만족해야한다.
내가 생각한 로직은 선택된 상품의 id값을 관리하는 state와 상품 전체를 관리하는 state를 생성해서 그 둘을 비교하면서 체크 박스의 상태를 관리하는 것이었다.
//Cart.js
const Cart = () => {
const [cartItem, setCartItem] = useState([]); // 장바구니에 담긴 상품
const [checkedItem, setCheckedItem] = useState([]); // 선택된 상품
const isAllChecked = checkedItem.length !== 0 && cartItem.length === checkedItem.length;
const handleSingleCheck = id => {
if (checkedItem.includes(id)) {
setCheckedItem(checkedItem.filter(el => el !== id));
} else {
setCheckedItem(checkedItem.concat(id));
}
};
const handleAllCheck = () => {
if (isAllChecked) {
setCheckedItem([]);
} else {
setCheckedItem(cartItem.map(({ id }) => id));
}
}
const getCartData = () => {
fetch(`${API.CART}/user`, {
headers: {
'Content-Type': 'application/json;charset=utf-8',
Authorization: localStorage.getItem('token'),
},
})
.then(res => res.json())
.then(data => setCartItem(data.getCartbyId));
};
useEffect(() => {
getCartData();
}, []);
return(
...
<div className="item_table">
<div className="item_header">
<div className="check_area">
<input
type="checkbox"
id="checkAll"
title="선택"
checked={isAllChecked}
onChange={handleAllCheck}
/>
<label htmlFor="checkAll" />
</div>
<p className="header_title">상품정보</p>
<p className="header_title">수량</p>
<p className="header_title">가격</p>
</div>
<ul className="item_list">
{cartItem.map(data => (
<CartItemList
key={data.id}
itemInfo={data}
handleSingleCheck={() => handleSingleCheck(data.id)}
/>
))}
</ul>
<button className="all_delete" onClick={deleteProduct}>
선택 상품 삭제
</button>
</div>
)
}
export default Cart;
위에 코드에 대해서 간단히 설명을 하자면,
checkedItem
배열의 길이과 cartItem
의 길이가 같다면 true값을 반환하는 변수(isAllChecked
)를 선언한다.isAllChecked
를 전체 체크 박스를 관리하는 input의 checked={isAllChecked}
로 넣어준다.handleAllCheck
를 만들고 check의 상태값을 인자로 받는다. setChekcedItem
에 cartItem
에 담긴 데이터들의 id 값들을 넣어 준다. setChekcedItem
에 빈배열을 넣어주어 값을 초기화 시켜준다.// CartItemList.js
const CartItemList = ({
itemInfo,
checkedItem,
handleSingleCheck,
}) => {
const {
id,
option_products_id,
tumbnail_url,
name,
standard_unit,
price,
thick,
quantity,
} = itemInfo;
return (
<li>
<div className="check_area">
<input
type="checkbox"
id={`check${id}`}
title="선택"
checked={checkedItem.includes(id)}
onChange={handleSingleCheck}
/>
<label htmlFor={`check${id}`} />
</div>
<img src={tumbnail_url} alt="sample" />
<div className="product_info">
<p className="name">
{name}
<span className="option">{standard_unit}</span>
</p>
<span className="standard">{thick}g 기준</span>
</div>
<div className="amount_box">
<button className="box" onClick={minusQuantity}>
<i className="fa-solid fa-minus" />
</button>
<p className="box">{quantity}</p>
<button className="box" onClick={plusQuantity}>
<i className="fa-solid fa-plus" />
</button>
</div>
<p className="price">{(price * quantity).toLocaleString()}원</p>
</li>
);
};
export default CartItemList;
CartItemList
컴포넌트는 장바구니의 상품 리스트를 나타내는 컴포넌트로 여기에는 상품 개별 체크박스가 있다.handleSingleCheck()
를 만들고 props로 전달 받는다. setChekcedItem
에 id값을 쌓고 checkItem
에 filter()
를 이용해서 id 값이 다른 요소들(체크되지 않은 상품의 id)을 걸러준다.선택한 상품의 합계 금액은 원래 처음에는 for문을 사용했었는데 평소에 잘 사용하지 않았던 reduce를 사용하여 리팩토링 했기 때문에 기억에 남는 코드 중 하나이다.
const [cartItem, setCartItem] = useState([]);
const [checkedItem, setCheckedItem] = useState([]);
//변경 전
const totalPrice = () => {
let price = 0;
for (let i = 0; i < checkedItem.length; i++){
price += cartItem[i].price * cartItem[i].amount;
}
reuturn price;
}
// 변경 후
const totalPrice = cartItem.reduce(
(acc, cart) =>
checkedItem.indexOf(cart.id) !== -1
? acc + Number(cart.price) * cart.quantity
: acc,
0
);
평소에 useNavigate는 페이지 이동할 때만 사용하는 hook인 줄 알았는데 장바구니에서 선택한 상품을 주문하기로 넘어갈 때 useNavigate를 사용하여 선택한 상품의 id값을 state로 주문하기 페이지로 넘겨줄 수 있다는 사실을 새롭게 알게 되어 기억에 남는다.
//Cart.js
import { useNavigate } from 'react-router-dom;
const navigate = useNavigate();
const postOrder = () => {
navigate('/payment', { state: { cartId: checkedItem } });
};
//Payment.js
import { useLocation } from 'react-router-dom';
const location = useLocation();
const { cartId } = location.state;
끝나지 않을 것 같았던 1차 프로젝트가 끝이나고 든 생각은 프로젝트는 나혼자만 잘 한다고 되는게 아니라는 것이었다.
프로젝트 전에는 대부분이 혼자서 작업하는 시간이었기 때문에 나만 잘하고 시간 맞춰 끝내면 됐지만 프로젝트는 팀으로 움직이기 때문에 나혼자 앞서나가는 것도 안되고 너무 뒤쳐져서도 안됐다. 그렇기 때문에 서로의 진행상황을 공유하고 문제가 있다면 함께 고민해서 헤쳐나가야지만 그 프로젝트를 성공 시킬 수 있다는 것을 다시 한번 깨달았다.
이번 프로젝트에서는 기술적인 부분에서도 많이 배울 수 있었다. 한달 전의 나와 비교하면 내가 이것까지 할 수 있다고?(예를 들어 장바구니 통신) 할 정도로 많은 성장을 이뤄냈다고 스스로 생각한다. 물론 아직 배워야 할 것은 태산이겠지만...ㅎㅎ 하지만 아직 나에게는 2차 프로젝트가 남아있고 성장할 기회가 아직 많이 있기 때문에 여기서 멈추지 않고 계속해서 달려나갈 예정이다
2차 프로젝트때는 1차에서 아쉬웠던 부분을 보충해나가면 좀 더 완성도 있는 결과물을 만들어내는 것이 목표인 것 같다. 1차에서는 백엔드와의 소통이 서툰점이 있어서 통신을 할 때 어려움이 있었는데(사전에 key값 맞춰놓기, 데이터를 어떤 형식으로 보낼지 혹은 받을지 등) 2차 때는 이런 점을 보완해보도록 해야겠다..!!
그럼 이제 다시 저는 열코딩하러 2000✋
1차 프로젝트 회고 끝✨
정육쩜 프로젝트 보러가기
정육쩜 넘 멋있습니닷😎🖤