heyday2024 님의 블로그
마지막 퀘스트!! 스타벅스 주문서 본문
https://idkhowtonamethispage.netlify.app/
속성의 이름이 data-로 시작하는 모든 속성은 데이터 속성.
data-index 속성은 HTML 요소에 사용자 정의 데이터를 저장할 수 있는 속성
<점표기법과 대괄호 표기법 차이>
1. order.item1.name
- 점 표기법 (Dot Notation):
- 이 방식은 객체의 속성에 직접 이름을 사용하여 접근하는 방법.
- item1은 order 객체의 속성으로, 해당 속성은 객체여야함. 예를 들어, order 객체가 다음과 같다고 가정해보면,
-
let order = { item1: { name: "아메리카노", quantity: 2, } };
- 위와 같이 정의된 경우 order.item1.name은 "아메리카노"를 반환.
-
2. order[drink].quantity
- 대괄호 표기법 (Bracket Notation):
- 이 방식은 속성 이름을 변수로 사용하여 동적으로 접근하는 방법.
- drink는 변수로, 이 변수가 order 객체의 속성 이름을 포함하고 있어야 함. 예를 들어, drink 변수가 "아메리카노"라고 가정하면:
-
let order = { "아메리카노": { name: "아메리카노", quantity: 2, } }; const drink = "아메리카노"; console.log(order[drink].quantity); // 2를 반환
-
function convertToNumber(money) { return parseInt(money.replace(/[^\d]/g, ''), 10); }
코드 구성 요소
- function convertToNumber(money):
- convertToNumber라는 이름의 함수를 정의. 이 함수는 money라는 매개변수를 받음. 이 매개변수는 변환할 문자열(예: ₩4,100)을 나타냄.
- money.replace(/[^\d]/g, ''):
- replace 메서드를 사용하여 money 문자열에서 숫자가 아닌 모든 문자를 제거.
- 정규 표현식 /[^\d]/g:
- \d: 숫자를 의미.
- ^: 부정을 의미하여, "숫자가 아닌" 모든 것을 찾음.
- g: 전역 검색 플래그로, 문자열 전체에서 일치하는 모든 부분을 찾음.
- 따라서 이 부분은 ₩4,100을 4100으로 변환함.
- parseInt(..., 10):
- parseInt 함수는 문자열을 정수로 변환. 두 번째 인자인 10은 10진수 형식을 지정.
- 따라서 parseInt('4100', 10)은 4100이라는 숫자를 반환.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>스타벅스 주문 시스템</title>
<!-- 부트스트랩 -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"
></script>
<!-- 구글 아이콘 써보기 -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"
/>
<link rel="stylesheet" href="./styles.css" />
<script defer src="./script.js"></script>
</head>
<body>
<div class="top">
<img class="icon" src="./images/starbucks.png" alt="스타벅스 아이콘" />
<h1>스타벅스 주문 시스템</h1>
</div>
<div class="container">
<div class="button_area">
<div class="dropdown">
<button
class="btn btn-outline-light dropdown-toggle category_text"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
카테고리
</button>
<ul class="dropdown-menu">
<li>
<button class="dropdown-item all" type="button">전체</button>
</li>
<li>
<button class="dropdown-item coffee" type="button">
커피 & 에스프레소
</button>
</li>
<li>
<button class="dropdown-item frap" type="button">
프라푸치노
</button>
</li>
<li>
<button class="dropdown-item blended" type="button">
블렌디드
</button>
</li>
</ul>
</div>
<div class="filtering">
<input
type="text"
id="keyword"
placeholder="키워드(한 단어)를 입력해주세요. (ex) 카페라떼"
/>
<button type="button" class="btn btn-outline-light search-btn">
검색
</button>
</div>
</div>
<div class="menu" id="menu-list"></div>
</div>
<div class="shopping-cart">
<div class="material-symbols-outlined cart" id="cart-btn">
shopping_cart
</div>
</div>
<div class="order-space">
<button
class="material-symbols-outlined arrow-down btn btn-success"
id="hide-btn"
>
keyboard_double_arrow_down
</button>
<div class="order-title">주문내역</div>
<div class="order-record">
<ul id="order-list"></ul>
</div>
<div class="total">
<div class="price-area">총가격: ₩<span id="total-price">0</span></div>
</div>
<div class="submit-area">
<button class="btn btn-outline-success" id="submit-btn">
주문 제출
</button>
</div>
</div>
</body>
</html>
/* #00704a */
@import url("https://fonts.googleapis.com/css2?family=Gowun+Dodum&family=Nanum+Pen+Script&display=swap");
* {
font-family: "Gowun Dodum", sans-serif;
font-weight: 700;
font-style: normal;
font-size: 20px;
}
body {
display: flex;
justify-content: center;
/* 부모요소의 주 축(가로축)에서 자식 요소들을 가운데로 정렬 */
align-items: center;
/* 교차 축에서의 정렬(세로축 정렬) */
flex-direction: column;
padding: 50px 0;
margin: 0 40px;
background-color: #0b421a;
position: relative;
}
.top {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
padding-bottom: 30px;
border-bottom: solid 2px white;
width: 100%;
color: white;
gap: 20px;
}
img.icon {
width: 80px;
height: 80px;
border-radius: 50%;
}
.container {
display: flex;
justify-content: center;
align-items: center;
padding: 30px 0;
flex-direction: column;
}
.button_area {
display: flex;
width: 100%;
justify-content: space-between;
/* justify-content: space-between는 두 자식 요소를 양 끝에 배치하고 싶을 때 사용함 */
padding-bottom: 40px;
position: relative;
}
.filtering {
display: flex;
right: 0;
position: absolute;
right: 10px;
}
#keyword {
border-radius: 5px;
border: solid 1px white;
margin-right: 5px;
padding-left: 10px;
width: 400px;
}
.menu {
display: flex;
width: 100%;
gap: 10px;
/* gap으로 요소 간격 지정 가능 */
flex-wrap: wrap;
/* 요소들이 넘치면 다음 줄로 넘어가게 하기 위해 flex-wrap: wrap 사용 */
}
h4 {
font-weight: 700;
}
.card {
flex-basis: calc(33.33% - 10px);
/* 요소의 너비를 3등분함(33.33%)
각 요소의 너비를 3등분하되, 간격(gap)을 고려해 calc()를 사용하여 정확히 3개씩 배치되도록 함. */
margin-bottom: 10px; /* 줄 간격을 위해 아래 여백 추가 */
}
.card-img-top {
height: 250px;
/* 사진 사이즈를 일정하게 하고 싶었음 */
}
.card-body {
display: flex;
justify-content: space-between;
}
.order-space {
position: fixed;
width: 450px;
height: 800px;
background-color: lightgoldenrodyellow;
right: 20px;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
top: 100px;
}
.order-title {
font-weight: 700;
font-size: 40px;
padding: 20px 10px;
position: relative;
/* background-color: #0b421a; */
}
.total {
position: absolute;
bottom: 100px;
display: flex;
justify-content: space-around;
align-items: center;
width: 90%;
height: 80px;
/* background-color: aliceblue; */
}
.price-area {
font-size: 20px;
}
.order-record {
width: 90%;
max-height: 500px;
overflow: auto;
position: relative;
}
.submit-area {
position: absolute;
bottom: 30px;
width: 90%;
}
#submit-btn {
width: 100%;
font-size: 30px;
}
.order-btn {
font-size: 20px;
}
li {
padding: 1px;
}
.remove {
font-size: 18px;
padding: 0 5px;
position: absolute;
right: 0;
}
/* 주문내역을 fixed로 위치 고정 시켜놓아서 몇몇 화면 사이즈가 다른 기기에서는 메뉴선택이 어려워질 수도 있을 것 같다고 판단함
그래서, floating-button을 하나 만들어서 화면 상 클릭해야 주문내역 확인할 수 있도록 만들었음.
*/
.shopping-cart {
position: fixed;
background-color: #eac784;
border-radius: 50%;
width: 100px;
height: 100px;
bottom: 60px;
right: 60px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border: solid 5px #362415;
color: #0b421a;
}
.shopping-cart:hover {
color: #362415;
border: solid 5px #eac784;
background-color: #fffcfc;
}
.cart {
font-size: 50px;
color: #023826;
}
#hide-btn {
border: dashed 2px #023826;
/* position: absolute; */
margin-top: 10px;
width: 90%;
}
#hide-btn:hover {
border: solid 2px #023826;
color: #023826;
background-color: transparent;
}
document.addEventListener("DOMContentLoaded", () => {
const menuItems = [
{
name: "아메리카노",
price: "₩4,100",
category: "커피&에스프레소",
src: "./images/americano.png",
},
{
name: "카페라떼",
price: "₩4,600",
category: "커피&에스프레소",
src: "./images/cafeLatte.png",
},
{
name: "카푸치노",
price: "₩4,600",
category: "커피&에스프레소",
src: "./images/cappuchino.png",
},
{
name: "카라멜 마끼아또",
price: "₩5,800",
category: "커피&에스프레소",
src: "./images/caramelM.png",
},
{
name: "자바 칩",
price: "₩6,300",
category: "프라푸치노",
src: "./images/javachipF.png",
},
{
name: "화이트 초콜릿 모카",
price: "₩6,200",
category: "프라푸치노",
src: "./images/whiteChocolateF.png",
},
{
name: "모카/카라멜",
price: "₩6,100",
category: "프라푸치노",
src: "./images/caramelF.png",
},
{
name: "딸기 요거트",
price: "₩6,300",
category: "블렌디드",
src: "./images/strawberryB.png",
},
{
name: "익스트림 티",
price: "₩6,300",
category: "블렌디드",
src: "./images/extreamTeaB.png",
},
{
name: "망고 바나나",
price: "₩6,600",
category: "블렌디드",
src: "./images/mangoBananaB.png",
},
{
name: "피치 레몬",
price: "₩6,600",
category: "블렌디드",
src: "./images/peachLemonB.png",
},
];
// map은 배열을 순회하면서 각 요소를 변환하고 새로운 배열을 반환.
// forEach는 배열을 순회하면서 특정 작업을 수행하되, 새로운 배열을 반환하지 않음.
//₩4,100 이런 금액 표시를 숫자로 온전히 바꾸는 함수
function priceToNumber(money) {
return parseInt(money.replace(/[^\d]/g, ""), 10);
}
// const menuList = document.querySelector("#menu-list");
const menuList = document.getElementById("menu-list");
let option;
// 전체(0), 식사류(1), 면류(2), 음료(3), keyword(4)
function sortMenu(option, word = "") {
let filteredItems;
switch (option) {
case 0: // 전체
filteredItems = menuItems;
break;
case 1: // 식사류
// filter 메소드는 배열에만 쓰임
filteredItems = menuItems.filter(
(item) => item.category === "커피&에스프레소"
);
break;
case 2: // 면류
filteredItems = menuItems.filter(
(item) => item.category === "프라푸치노"
);
break;
case 3: // 음료
filteredItems = menuItems.filter(
(item) => item.category === "블렌디드"
);
break;
case 4: // 키워드 검색
filteredItems = menuItems.filter(
(item) =>
item.name.includes(word) ||
item.category.includes(word) ||
item.price.includes(word)
);
break;
default:
filteredItems = menuItems;
break;
}
// 기존 메뉴 리스트 내용 제거
// menuList.innerHTML = "";
// '주문 추가' 버튼에는 각 메뉴 아이템의 인덱스를 data-index 속성으로 저장.
// 클릭 시 해당 메뉴를 식별
// menuList.innerHTML = filteredItems
// .map(
// (item, index) => `<div class="card" style="width: 18rem">
// <img src=${item.src} class="card-img-top" alt="${
// item.name + " 이미지"
// }" />
// <div class="card-body">
// <h4 class="card-title menu_item">${item.name}</h4>
// <button class="btn btn-success order-btn" data-index="${index}">주문하기</button>
// </div>
// <ul class="list-group list-group-flush">
// <li class="list-group-item category">${item.category}</li>
// <li class="list-group-item price">${item.price}</li>
// </ul>
// </div>`
// )
// .join("");
menuList.innerHTML = filteredItems
.map(
(item, index) => `<div class="card" style="width: 18rem">
<img src=${item.src} class="card-img-top" alt="${
item.name + " 이미지"
}" />
<div class="card-body">
<h4 class="card-title menu_item">${item.name}</h4>
<button class="btn btn-success order-btn" data-name="${
item.name
}">주문하기</button>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item category">${item.category}</li>
<li class="list-group-item price">${item.price}</li>
</ul>
</div>`
)
.join("");
}
sortMenu(0); //default로 전체 메뉴 보여줌
//카테고리 별
const categoryBtn = document.querySelector(".category_text");
const all = document.querySelector(".all");
const meal = document.querySelector(".coffee");
const noodle = document.querySelector(".frap");
const drinks = document.querySelector(".blended");
all.addEventListener("click", function () {
categoryBtn.textContent = "전체";
sortMenu(0);
});
meal.addEventListener("click", function () {
categoryBtn.textContent = "커피&에스프레소";
sortMenu(1);
});
noodle.addEventListener("click", function () {
categoryBtn.textContent = "프라푸치노";
sortMenu(2);
});
drinks.addEventListener("click", function () {
categoryBtn.textContent = "블렌디드";
sortMenu(3);
});
//키워드 별
const keyword = document.getElementById("keyword");
const searchBtn = document.querySelector(".search-btn");
searchBtn.addEventListener("click", function () {
sortMenu(4, keyword.value);
});
////////////////////////////////////////////////////////////////////////////////////////////////////
/////////// 여기서부터 가격 관련 기능///////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
let order = {};
let totalPrice = 0;
const menuContainer = document.getElementById("menu-list");
const orderList = document.getElementById("order-list");
const totalPriceElement = document.getElementById("total-price");
const submitOrderButton = document.getElementById("submit-btn");
// TODO-2: 주문 추가 로직을 작성하세요.
// 힌트: menuContainer에 이벤트 리스너를 추가하고, 이벤트가 발생한 대상이 버튼인지 확인합니다.
// 버튼의 data-index 속성을 이용해 어떤 메뉴가 클릭되었는지 파악한 후,
// 해당 메뉴의 수량을 증가시키거나 새로 추가하세요.
menuContainer.addEventListener("click", (event) => {
if (event.target.tagName === "BUTTON") {
// 클릭된 버튼의 메뉴 아이템을 주문에 추가하는 로직 작성
const targetName = event.target.getAttribute("data-name");
const selectedItem = menuItems.find((item) => item.name === targetName);
const drink = selectedItem.name;
// const drink = menuItems[targetName].name;
const drink_price = priceToNumber(selectedItem.price);
// 메뉴 이름을 키로 사용하여 주문 추가
if (order[drink]) {
// 이미 존재하면 수량 증가
order[drink].quantity += 1;
totalPrice += drink_price;
} else {
// 새로 추가
order[drink] = {
quantity: 1,
price: drink_price,
};
totalPrice += drink_price;
}
updateOrderList();
}
});
// 이후, 총 가격(totalPrice)을 업데이트하고,
// 주문 목록을 업데이트하는 updateOrderList 함수를 호출하세요.
// 주문 내역 업데이트 함수
function updateOrderList() {
orderList.innerHTML = "";
for (let itemName in order) {
const orderItem = order[itemName];
const orderItemElement = document.createElement("li");
orderItemElement.innerHTML = `
${itemName} - ₩${orderItem.price.toLocaleString()} x${
" " + orderItem.quantity
}
<button class="remove btn btn-danger" data-item="${itemName}">삭제</button>
`;
orderList.appendChild(orderItemElement);
}
totalPriceElement.textContent = totalPrice.toLocaleString();
}
// 아이템 삭제 로직
orderList.addEventListener("click", (event) => {
const itemName = event.target.getAttribute("data-item");
if (event.target.classList.contains("remove")) {
totalPrice -= order[itemName].price * order[itemName].quantity;
delete order[itemName];
updateOrderList();
}
});
// 주문 제출 로직
submitOrderButton.addEventListener("click", () => {
if (Object.keys(order).length > 0) {
alert("주문해 주셔서 감사합니다!");
order = {};
totalPrice = 0;
updateOrderList();
} else {
alert("주문 내역이 비어 있습니다!");
}
});
// 카트 버튼 누르면 주문서 튀어나오기
let cartBtn = document.getElementById("cart-btn");
let orderArea = document.querySelector(".order-space");
let hideBtn = document.getElementById("hide-btn");
cartBtn.addEventListener("click", function () {
cartBtn.style.display = "none";
orderArea.style.display = "flex";
});
hideBtn.addEventListener("click", function () {
cartBtn.style.display = "flex";
orderArea.style.display = "none";
});
});
'프론트엔드 부트캠프' 카테고리의 다른 글
[첫주차 팀프로젝트] Members 페이지 만들면서 겪은 문제점들과 그 해결법 1 (5) | 2024.10.04 |
---|---|
첫 프로젝트- 팀 페이지 만들기 (4) | 2024.10.02 |
[사전캠프 5주차] 마지막! 파이썬 (1) | 2024.09.24 |
[사전캠프 5주차] Github으로 배포하기 (2) | 2024.09.23 |
[사전캠프 퀘스트] mbti 설문지 (6) | 2024.09.21 |