Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

heyday2024 님의 블로그

마지막 퀘스트!! 스타벅스 주문서 본문

프론트엔드 부트캠프

마지막 퀘스트!! 스타벅스 주문서

heyday2024 2024. 9. 30. 17:31

https://idkhowtonamethispage.netlify.app/

 

스타벅스 주문 시스템

 

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); }

코드 구성 요소

  1. function convertToNumber(money):
    • convertToNumber라는 이름의 함수를 정의. 이 함수는 money라는 매개변수를 받음. 이 매개변수는 변환할 문자열(예: ₩4,100)을 나타냄.
  2. money.replace(/[^\d]/g, ''):
    • replace 메서드를 사용하여 money 문자열에서 숫자가 아닌 모든 문자를 제거.
    • 정규 표현식 /[^\d]/g:
      • \d: 숫자를 의미.
      • ^: 부정을 의미하여, "숫자가 아닌" 모든 것을 찾음.
      • g: 전역 검색 플래그로, 문자열 전체에서 일치하는 모든 부분을 찾음.
    • 따라서 이 부분은 ₩4,100을 4100으로 변환함.
  3. 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";
  });
});