카테고리 없음

카트 페이지, 주문 내역 만들기 - javascript + node.js (feat. 가격 숫자에 콤마 표시하기)

designer DK 2024. 11. 4. 23:05
728x90

 

카트 가격 합산

리듀스 어큐뮬레이트 합산법

포맷커런시 적용

 

카트에 담았으니 이제 카트에 있는 리스트의 가격 총합을 이용해서 주문 내역을 만들고 결제를 해야한다.

카트 페이지를 가져오기 위해서 장바구니 버튼을 누르면 이렇게 getCartPage라는 함수를 실행시키게 했다.

//장바구니 버튼
const cartBTN = document.getElementById('cart-btn');

cartBTN.addEventListener('click', () => {
getCartPage();
});

 

 

getCartPage라는 함수는 이렇게 길게 구성되었는데 이는 주문내역까지 한번에 구성하느라 길어진 것도 있고 가격들의 합산을 위해서 코드가 좀 복잡해졌다.

function getCartPage() {
history.pushState(null, null, `/cart`);

const contentContainer = document.getElementById("content-container");
const paginationContainer = document.getElementById("pagination-container");

contentContainer.innerHTML = "";
paginationContainer.innerHTML = "";

fetch(`${URI}/api/cart`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${sessionStorage.getItem("token")}`
}
})
.then((response) => response.json())
.then((data) => {
console.log("카트페이지!", data);

if (data.data.length === 0) {
contentContainer.innerHTML = "<p>카트가 비었습니다.</p>";
return;
}

let overallTotalPrice = 0; // 총 가격 계산을 위한 변수

// 카트 항목 추가
data.data.forEach((item, i) => {
const itemTotalPrice = item.productId.price * item.qty;
const cartList = `
<div class="cart-list" id="cart-list${i}">
<img src="${item.productId.image}" alt="">
<span>${item.productId.name}</span>
<span>price : ${formatCurrency(item.productId.price)}</span>
<span>size : ${item.size}</span>
<span id="total-price${i}">total : ${formatCurrency(itemTotalPrice)}</span>
<input type="number" id="cart-qty${i}" value="${item.qty}">
<button id="cart-delete${i}">삭제</button>
</div>
`;
contentContainer.insertAdjacentHTML("beforeend", cartList); // 카트 리스트를 추가

// 총 가격 계산
overallTotalPrice += itemTotalPrice;

// 각 qty input에 이벤트 리스너 추가
const qtyInput = document.getElementById(`cart-qty${i}`);
const totalPriceElement = document.getElementById(`total-price${i}`);
 
qtyInput.addEventListener('input', () => {
const newQty = parseInt(qtyInput.value);
const newTotal = item.productId.price * newQty;
totalPriceElement.textContent = `total : ${formatCurrency(newTotal)}`;

// 주문 리스트의 가격도 업데이트
const orderPriceElement = document.getElementById(`order-price${i}`);
if (orderPriceElement) {
orderPriceElement.textContent = formatCurrency(newTotal);
}

// 총 가격 업데이트
overallTotalPrice = data.data.reduce((acc, item, j) => {
const qty = parseInt(document.getElementById(`cart-qty${j}`).value);
return acc + (item.productId.price * qty);
}, 0);

// 총 가격 요소 업데이트
const totalOrderPrice = document.getElementById("total-order-price");
totalOrderPrice.textContent = `총 가격: ${formatCurrency(overallTotalPrice)}`;
});

// 카트 아이템 삭제
const cartItemDelete = document.getElementById(`cart-delete${i}`);
cartItemDelete.addEventListener("click", () => {
console.log("카트아이템삭제", item._id);
deleteCartItem(item._id);
});
});

// 주문 내역과 총 가격을 보여줄 컨테이너 생성
const orderReceipt = `
<div>
<p>주문내역</p>
<div id="order-list-container"></div>
<br>
<div id="total-order-price">총 가격: ${formatCurrency(overallTotalPrice)}</div>
<button>결제하기</button>
</div>
`;
contentContainer.insertAdjacentHTML("beforeend", orderReceipt); // 주문 내역을 추가

// 주문 리스트 추가
data.data.forEach((item, i) => {
const itemTotalPrice = item.productId.price * item.qty;
const orderList = `
<div id="order-list${i}">
<span>${item.productId.name}</span>
<span id="order-price${i}">${formatCurrency(itemTotalPrice)}</span>
</div>
`;
document.getElementById("order-list-container").insertAdjacentHTML("beforeend", orderList);
});

// 총 가격 업데이트
const totalOrderPrice = document.getElementById("total-order-price");
totalOrderPrice.textContent = `총 가격: ${formatCurrency(overallTotalPrice)}`;
})
.catch((error) => console.error("Error:", error));
}

 

 

먼저 함수가 실행되면 페이지네이션이나 콘텐츠 컨테이너를 비워주고 cart api를 GET 요청한다.

가져온 응답 데이터를 통해 카트 리스트가 만들어진다.

function getCartPage() {
history.pushState(null, null, `/cart`);

const contentContainer = document.getElementById("content-container");
const paginationContainer = document.getElementById("pagination-container");

contentContainer.innerHTML = "";
paginationContainer.innerHTML = "";

fetch(`${URI}/api/cart`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${sessionStorage.getItem("token")}`
}
})
.then((response) => response.json())
.then((data) => {
console.log("카트페이지!", data);

if (data.data.length === 0) {
contentContainer.innerHTML = "<p>카트가 비었습니다.</p>";
return;
}

let overallTotalPrice = 0; // 총 가격 계산을 위한 변수

// 카트 항목 추가
data.data.forEach((item, i) => {
const itemTotalPrice = item.productId.price * item.qty;
const cartList = `
<div class="cart-list" id="cart-list${i}">
<img src="${item.productId.image}" alt="">
<span>${item.productId.name}</span>
<span>price : ${formatCurrency(item.productId.price)}</span>
<span>size : ${item.size}</span>
<span id="total-price${i}">total : ${formatCurrency(itemTotalPrice)}</span>
<input type="number" id="cart-qty${i}" value="${item.qty}">
<button id="cart-delete${i}">삭제</button>
</div>
`;
contentContainer.insertAdjacentHTML("beforeend", cartList); // 카트 리스트를 추가

 

 

 

 

문제는 각 카트 리스트마다 갯수 인풋이 있어서 여기서 최종 갯수를 설정할 수 있다보니 그에 따라 최종 가격도 변하게 된다.

그에 따른 코드 구성을 이렇게 진행했다.

// 총 가격 계산
overallTotalPrice += itemTotalPrice;

// 각 qty input에 이벤트 리스너 추가
const qtyInput = document.getElementById(`cart-qty${i}`);
const totalPriceElement = document.getElementById(`total-price${i}`);
 
qtyInput.addEventListener('input', () => {
const newQty = parseInt(qtyInput.value);
const newTotal = item.productId.price * newQty;
totalPriceElement.textContent = `total : ${formatCurrency(newTotal)}`;

// 주문 리스트의 가격도 업데이트
const orderPriceElement = document.getElementById(`order-price${i}`);
if (orderPriceElement) {
orderPriceElement.textContent = formatCurrency(newTotal);
}

// 총 가격 업데이트
overallTotalPrice = data.data.reduce((acc, item, j) => {
const qty = parseInt(document.getElementById(`cart-qty${j}`).value);
return acc + (item.productId.price * qty);
}, 0);

// 총 가격 요소 업데이트
const totalOrderPrice = document.getElementById("total-order-price");
totalOrderPrice.textContent = `총 가격: ${formatCurrency(overallTotalPrice)}`;

 

이렇게 인풋 값 이벤트에 따라 가격에 갯수가 곱해지는 구조이다.

여기서 좀 어려운 코드가 나오는데  바로 reduce이다. (배열의 각 요소를 순회하면서 하나의 값으로 줄이는 역할)

overallTotalPrice = data.data.reduce((acc, item, j) => {
const qty = parseInt(document.getElementById(`cart-qty${j}`).value);
return acc + (item.productId.price * qty);
}, 0);

 

이렇게 하면 data의 item들을 j라는 인덱스를 통해 순회하게 된다. 여기서 j는 일반적으로 쓰이는 순회용 i의 역할인데 i가 상위 코드에 있어서 겹칠 수 있어서 j가 사용되었다. cart-qty${j}를 통해 각 카트 아이템 밸류 값을 가져와서 가격과 곱하고 이를 acc(accumulate)라는 곳에 누적시켜주면 최종 합산 값을 계산한다.

 

// 주문 내역과 총 가격을 보여줄 컨테이너 생성
const orderReceipt = `
<div>
<p>주문내역</p>
<div id="order-list-container"></div>
<br>
<div id="total-order-price">총 가격: ${formatCurrency(overallTotalPrice)}</div>
<button>결제하기</button>
</div>
`;
contentContainer.insertAdjacentHTML("beforeend", orderReceipt); // 주문 내역을 추가

// 주문 리스트 추가
data.data.forEach((item, i) => {
const itemTotalPrice = item.productId.price * item.qty;
const orderList = `
<div id="order-list${i}">
<span>${item.productId.name}</span>
<span id="order-price${i}">${formatCurrency(itemTotalPrice)}</span>
</div>
`;
document.getElementById("order-list-container").insertAdjacentHTML("beforeend", orderList);
});

 

최종 합산 값은 이렇게 주문내역을 만들어서 함께 보여주게 된다.

 

 

 

const cartSchema = Schema({
userId: { type: mongoose.ObjectId, ref: User },
items: [{
productId: { type: mongoose.ObjectId, ref: Product },
size: { type: String, required: true },
qty: { type: Number, default: 1, required: true }
}]
}, { timestamps: true });

 

백엔드 코드를 보면 카트 스키마는 프로덕트의 id만 가지고있고 상세한 데이터를 가지고 있지 않다. 그래서 상품의 상세한 정보를 가져오기 위해서 populate가 사용되었다. path와 model을 정확히 입력하여 원하는 데이터를 가지고 온다.

//카트 읽기
cartController.getCart = async (req, res) => {

try {
const { userId } = req;
let cart = await Cart.findOne({ userId: userId }).populate({
path: "items",
populate: {
path: "productId",
model: "Product"
}
});
// 카트가 없을 때 빈 배열과 함께 상태를 반환
if (!cart) {
return res.status(200).json({ status: "ok", data: [], cartItemQty: 0 });
}

res.status(200).json({ status: "ok", data: cart.items, cartItemQty: cart.items.length });
} catch (error) {
res.status(400).json({ status: "fail", message: error.message });
};
};

 

//카트 아이템 삭제
cartController.deleteCartItem = async (req, res) => {
try {
const { id } = req.params;
const { userId } = req;
const cart = await Cart.findOne({ userId });
cart.items = cart.items.filter((item) => !item._id.equals(id));
 
await cart.save();
res.status(200).json({ status: 200, cartItemQty: cart.items.length });
} catch (error) {
return res.status(400).json({ status: "fail", error: error.message });
}
};

 

참고로 삭제 백엔드의 경우도 카트가 유저 아이디 기반으로 만들어져있기 때문에 먼저 카트에서 유저아이디를 찾고 그 값 안에 items 안에 _id를 요청 온 아이디와 비교해서 맞는것을 제외한 나머지들만 저장시킨다.

즉 해당 아이템을 삭제한다는건 그 아이템 빼고 나머지들만 다 골라서 저장한다는 것.

 

 

마지막으로 가격 합산들을 진행하면서 숫자가 가격으로 직관적으로 읽힐 수 있게 가격 숫자 콤마 작업을 진행했다. 찾아봤을 때 여러가지 방법들이 있었는데 나는 포맷 커런시(formatCurrency)라는 함수를 사용해서 구현했다.

// 포맷 커런시 함수
function formatCurrency(amount) {
return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(amount);
}

 

이렇게 세팅 후에 원하는 숫자를 함수에 이런식으로 적용하면

totalOrderPrice.textContent = `총 가격: ${formatCurrency(overallTotalPrice)}`;

 

숫자가 10000과 같은 타입에서 10,000과 같은 타입으로 변하게 된다.