From 2393a22e4cd2b26bd282386de78633a58da481b1 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 01:12:20 +0900 Subject: [PATCH 01/38] =?UTF-8?q?chore=20:=20constants=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상품 리스트 (PRODUCT_LIST) 선언 --- src/data/prodList.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/data/prodList.js diff --git a/src/data/prodList.js b/src/data/prodList.js new file mode 100644 index 00000000..f4abb1e4 --- /dev/null +++ b/src/data/prodList.js @@ -0,0 +1,7 @@ +export const PRODUCT_LIST = [ + { id: "p1", name: "상품1", cost: 10000, stock: 50 }, + { id: "p2", name: "상품2", cost: 20000, stock: 30 }, + { id: "p3", name: "상품3", cost: 30000, stock: 20 }, + { id: "p4", name: "상품4", cost: 15000, stock: 0 }, + { id: "p5", name: "상품5", cost: 25000, stock: 10 }, +]; From c2b9252a56a6e0dfe7ab28117df5ce30aeef922d Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 01:13:03 +0900 Subject: [PATCH 02/38] =?UTF-8?q?refactor=20:=20main.origin.js=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 253 +++++++++++++++++++++++++++++++++++++++++++ src/main.original.js | 204 ---------------------------------- 2 files changed, 253 insertions(+), 204 deletions(-) create mode 100644 src/main.js delete mode 100644 src/main.original.js diff --git a/src/main.js b/src/main.js new file mode 100644 index 00000000..9128033c --- /dev/null +++ b/src/main.js @@ -0,0 +1,253 @@ +var prodList, sel, addBtn, cartDisp, sum, stockInfo; +var lastSel, + bonusPts = 0, + totalAmt = 0, + itemCnt = 0; +function main() { + prodList = [ + { id: "p1", name: "상품1", val: 10000, q: 50 }, + { id: "p2", name: "상품2", val: 20000, q: 30 }, + { id: "p3", name: "상품3", val: 30000, q: 20 }, + { id: "p4", name: "상품4", val: 15000, q: 0 }, + { id: "p5", name: "상품5", val: 25000, q: 10 }, + ]; + var root = document.getElementById("app"); + let cont = document.createElement("div"); + var wrap = document.createElement("div"); + let hTxt = document.createElement("h1"); + cartDisp = document.createElement("div"); + sum = document.createElement("div"); + sel = document.createElement("select"); + addBtn = document.createElement("button"); + stockInfo = document.createElement("div"); + cartDisp.id = "cart-items"; + sum.id = "cart-total"; + sel.id = "product-select"; + addBtn.id = "add-to-cart"; + stockInfo.id = "stock-status"; + cont.className = "bg-gray-100 p-8"; + wrap.className = + "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8"; + hTxt.className = "text-2xl font-bold mb-4"; + sum.className = "text-xl font-bold my-4"; + sel.className = "border rounded p-2 mr-2"; + addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; + stockInfo.className = "text-sm text-gray-500 mt-2"; + hTxt.textContent = "장바구니"; + addBtn.textContent = "추가"; + updateSelOpts(); + wrap.appendChild(hTxt); + wrap.appendChild(cartDisp); + wrap.appendChild(sum); + wrap.appendChild(sel); + wrap.appendChild(addBtn); + wrap.appendChild(stockInfo); + cont.appendChild(wrap); + root.appendChild(cont); + calcCart(); + setTimeout(function () { + setInterval(function () { + var luckyItem = prodList[Math.floor(Math.random() * prodList.length)]; + if (Math.random() < 0.3 && luckyItem.q > 0) { + luckyItem.val = Math.round(luckyItem.val * 0.8); + alert("번개세일! " + luckyItem.name + "이(가) 20% 할인 중입니다!"); + updateSelOpts(); + } + }, 30000); + }, Math.random() * 10000); + setTimeout(function () { + setInterval(function () { + if (lastSel) { + var suggest = prodList.find(function (item) { + return item.id !== lastSel && item.q > 0; + }); + if (suggest) { + alert( + suggest.name + "은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!" + ); + suggest.val = Math.round(suggest.val * 0.95); + updateSelOpts(); + } + } + }, 60000); + }, Math.random() * 20000); +} +function updateSelOpts() { + sel.innerHTML = ""; + prodList.forEach(function (item) { + var opt = document.createElement("option"); + opt.value = item.id; + opt.textContent = item.name + " - " + item.val + "원"; + if (item.q === 0) opt.disabled = true; + sel.appendChild(opt); + }); +} +function calcCart() { + totalAmt = 0; + itemCnt = 0; + var cartItems = cartDisp.children; + var subTot = 0; + for (var i = 0; i < cartItems.length; i++) { + (function () { + var curItem; + for (var j = 0; j < prodList.length; j++) { + if (prodList[j].id === cartItems[i].id) { + curItem = prodList[j]; + break; + } + } + var q = parseInt( + cartItems[i].querySelector("span").textContent.split("x ")[1] + ); + var itemTot = curItem.val * q; + var disc = 0; + itemCnt += q; + subTot += itemTot; + if (q >= 10) { + if (curItem.id === "p1") disc = 0.1; + else if (curItem.id === "p2") disc = 0.15; + else if (curItem.id === "p3") disc = 0.2; + else if (curItem.id === "p4") disc = 0.05; + else if (curItem.id === "p5") disc = 0.25; + } + totalAmt += itemTot * (1 - disc); + })(); + } + let discRate = 0; + if (itemCnt >= 30) { + var bulkDisc = totalAmt * 0.25; + var itemDisc = subTot - totalAmt; + if (bulkDisc > itemDisc) { + totalAmt = subTot * (1 - 0.25); + discRate = 0.25; + } else { + discRate = (subTot - totalAmt) / subTot; + } + } else { + discRate = (subTot - totalAmt) / subTot; + } + if (new Date().getDay() === 2) { + totalAmt *= 1 - 0.1; + discRate = Math.max(discRate, 0.1); + } + sum.textContent = "총액: " + Math.round(totalAmt) + "원"; + if (discRate > 0) { + var span = document.createElement("span"); + span.className = "text-green-500 ml-2"; + span.textContent = "(" + (discRate * 100).toFixed(1) + "% 할인 적용)"; + sum.appendChild(span); + } + updateStockInfo(); + renderBonusPts(); +} +const renderBonusPts = () => { + bonusPts = Math.floor(totalAmt / 1000); + var ptsTag = document.getElementById("loyalty-points"); + if (!ptsTag) { + ptsTag = document.createElement("span"); + ptsTag.id = "loyalty-points"; + ptsTag.className = "text-blue-500 ml-2"; + sum.appendChild(ptsTag); + } + ptsTag.textContent = "(포인트: " + bonusPts + ")"; +}; +function updateStockInfo() { + var infoMsg = ""; + prodList.forEach(function (item) { + if (item.q < 5) { + infoMsg += + item.name + + ": " + + (item.q > 0 ? "재고 부족 (" + item.q + "개 남음)" : "품절") + + "\n"; + } + }); + stockInfo.textContent = infoMsg; +} +main(); +addBtn.addEventListener("click", function () { + var selItem = sel.value; + var itemToAdd = prodList.find(function (p) { + return p.id === selItem; + }); + if (itemToAdd && itemToAdd.q > 0) { + var item = document.getElementById(itemToAdd.id); + if (item) { + var newQty = + parseInt(item.querySelector("span").textContent.split("x ")[1]) + 1; + if (newQty <= itemToAdd.q) { + item.querySelector("span").textContent = + itemToAdd.name + " - " + itemToAdd.val + "원 x " + newQty; + itemToAdd.q--; + } else { + alert("재고가 부족합니다."); + } + } else { + var newItem = document.createElement("div"); + newItem.id = itemToAdd.id; + newItem.className = "flex justify-between items-center mb-2"; + newItem.innerHTML = + "" + + itemToAdd.name + + " - " + + itemToAdd.val + + "원 x 1
" + + '' + + '' + + '
'; + cartDisp.appendChild(newItem); + itemToAdd.q--; + } + calcCart(); + lastSel = selItem; + } +}); +cartDisp.addEventListener("click", function (event) { + var tgt = event.target; + if ( + tgt.classList.contains("quantity-change") || + tgt.classList.contains("remove-item") + ) { + var prodId = tgt.dataset.productId; + var itemElem = document.getElementById(prodId); + var prod = prodList.find(function (p) { + return p.id === prodId; + }); + if (tgt.classList.contains("quantity-change")) { + var qtyChange = parseInt(tgt.dataset.change); + var newQty = + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + + qtyChange; + if ( + newQty > 0 && + newQty <= + prod.q + + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + ) { + itemElem.querySelector("span").textContent = + itemElem.querySelector("span").textContent.split("x ")[0] + + "x " + + newQty; + prod.q -= qtyChange; + } else if (newQty <= 0) { + itemElem.remove(); + prod.q -= qtyChange; + } else { + alert("재고가 부족합니다."); + } + } else if (tgt.classList.contains("remove-item")) { + var remQty = parseInt( + itemElem.querySelector("span").textContent.split("x ")[1] + ); + prod.q += remQty; + itemElem.remove(); + } + calcCart(); + } +}); diff --git a/src/main.original.js b/src/main.original.js deleted file mode 100644 index 0efffa28..00000000 --- a/src/main.original.js +++ /dev/null @@ -1,204 +0,0 @@ -var prodList, sel, addBtn, cartDisp, sum, stockInfo; -var lastSel, bonusPts=0, totalAmt=0, itemCnt=0; -function main() { - prodList=[ - {id: 'p1', name: '상품1', val: 10000, q: 50 }, - {id: 'p2', name: '상품2', val: 20000, q: 30 }, - {id: 'p3', name: '상품3', val: 30000, q: 20 }, - {id: 'p4', name: '상품4', val: 15000, q: 0 }, - {id: 'p5', name: '상품5', val: 25000, q: 10 } - ]; - var root=document.getElementById('app'); - let cont=document.createElement('div'); - var wrap=document.createElement('div'); - let hTxt=document.createElement('h1'); - cartDisp=document.createElement('div'); - sum=document.createElement('div'); - sel=document.createElement('select'); - addBtn=document.createElement('button'); - stockInfo=document.createElement('div'); - cartDisp.id='cart-items'; - sum.id='cart-total'; - sel.id='product-select'; - addBtn.id='add-to-cart'; - stockInfo.id='stock-status'; - cont.className='bg-gray-100 p-8'; - wrap.className='max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8'; - hTxt.className='text-2xl font-bold mb-4'; - sum.className='text-xl font-bold my-4'; - sel.className='border rounded p-2 mr-2'; - addBtn.className='bg-blue-500 text-white px-4 py-2 rounded'; - stockInfo.className='text-sm text-gray-500 mt-2'; - hTxt.textContent='장바구니'; - addBtn.textContent='추가'; - updateSelOpts(); - wrap.appendChild(hTxt); - wrap.appendChild(cartDisp); - wrap.appendChild(sum); - wrap.appendChild(sel); - wrap.appendChild(addBtn); - wrap.appendChild(stockInfo); - cont.appendChild(wrap); - root.appendChild(cont); - calcCart(); - setTimeout(function () { - setInterval(function () { - var luckyItem=prodList[Math.floor(Math.random() * prodList.length)]; - if(Math.random() < 0.3 && luckyItem.q > 0) { - luckyItem.val=Math.round(luckyItem.val * 0.8); - alert('번개세일! ' + luckyItem.name + '이(가) 20% 할인 중입니다!'); - updateSelOpts(); - } - }, 30000); - }, Math.random() * 10000); - setTimeout(function () { - setInterval(function () { - if(lastSel) { - var suggest=prodList.find(function (item) { return item.id !== lastSel && item.q > 0; }); - if(suggest) { - alert(suggest.name + '은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!'); - suggest.val=Math.round(suggest.val * 0.95); - updateSelOpts(); - } - } - }, 60000); - }, Math.random() * 20000); -}; -function updateSelOpts() { - sel.innerHTML=''; - prodList.forEach(function (item) { - var opt=document.createElement('option'); - opt.value=item.id; - opt.textContent=item.name + ' - ' + item.val + '원'; - if(item.q === 0) opt.disabled=true; - sel.appendChild(opt); - }); -} -function calcCart() { - totalAmt=0; - itemCnt=0; - var cartItems=cartDisp.children; - var subTot=0; - for (var i=0; i < cartItems.length; i++) { - (function () { - var curItem; - for (var j=0; j < prodList.length; j++) { - if(prodList[j].id === cartItems[i].id) { - curItem=prodList[j]; - break; - } - } - var q=parseInt(cartItems[i].querySelector('span').textContent.split('x ')[1]); - var itemTot=curItem.val * q; - var disc=0; - itemCnt += q; - subTot += itemTot; - if(q >= 10) { - if(curItem.id === 'p1') disc=0.1; - else if(curItem.id === 'p2') disc=0.15; - else if(curItem.id === 'p3') disc=0.2; - else if(curItem.id === 'p4') disc=0.05; - else if(curItem.id === 'p5') disc=0.25; - } - totalAmt += itemTot * (1 - disc); - })(); - } - let discRate=0; - if(itemCnt >= 30) { - var bulkDisc=totalAmt * 0.25; - var itemDisc=subTot - totalAmt; - if(bulkDisc > itemDisc) { - totalAmt=subTot * (1 - 0.25); - discRate=0.25; - } else { - discRate=(subTot - totalAmt) / subTot; - } - } else { - discRate=(subTot - totalAmt) / subTot; - } - if(new Date().getDay() === 2) { - totalAmt *= (1 - 0.1); - discRate=Math.max(discRate, 0.1); - } - sum.textContent='총액: ' + Math.round(totalAmt) + '원'; - if(discRate > 0) { - var span=document.createElement('span'); - span.className='text-green-500 ml-2'; - span.textContent='(' + (discRate * 100).toFixed(1) + '% 할인 적용)'; - sum.appendChild(span); - } - updateStockInfo(); - renderBonusPts(); -} -const renderBonusPts=() => { - bonusPts = Math.floor(totalAmt / 1000); - var ptsTag=document.getElementById('loyalty-points'); - if(!ptsTag) { - ptsTag=document.createElement('span'); - ptsTag.id='loyalty-points'; - ptsTag.className='text-blue-500 ml-2'; - sum.appendChild(ptsTag); - } - ptsTag.textContent='(포인트: ' + bonusPts + ')'; -}; -function updateStockInfo() { - var infoMsg=''; - prodList.forEach(function (item) { - if(item.q < 5) {infoMsg += item.name + ': ' + (item.q > 0 ? '재고 부족 ('+item.q+'개 남음)' : '품절') + '\n'; - } - }); - stockInfo.textContent=infoMsg; -} -main(); -addBtn.addEventListener('click', function () { - var selItem=sel.value; - var itemToAdd=prodList.find(function (p) { return p.id === selItem; }); - if(itemToAdd && itemToAdd.q > 0) { - var item=document.getElementById(itemToAdd.id); - if(item) { - var newQty=parseInt(item.querySelector('span').textContent.split('x ')[1]) + 1; - if(newQty <= itemToAdd.q) { - item.querySelector('span').textContent=itemToAdd.name + ' - ' + itemToAdd.val + '원 x ' + newQty; - itemToAdd.q--; - } else {alert('재고가 부족합니다.');} - } else { - var newItem=document.createElement('div'); - newItem.id=itemToAdd.id; - newItem.className='flex justify-between items-center mb-2'; - newItem.innerHTML='' + itemToAdd.name + ' - ' + itemToAdd.val + '원 x 1
' + - '' + - '' + - '
'; - cartDisp.appendChild(newItem); - itemToAdd.q--; - } - calcCart(); - lastSel=selItem; - } -}); -cartDisp.addEventListener('click', function (event) { - var tgt=event.target; - if(tgt.classList.contains('quantity-change') || tgt.classList.contains('remove-item')) { - var prodId=tgt.dataset.productId; - var itemElem=document.getElementById(prodId); - var prod=prodList.find(function (p) { return p.id === prodId; }); - if(tgt.classList.contains('quantity-change')) { - var qtyChange=parseInt(tgt.dataset.change); - var newQty=parseInt(itemElem.querySelector('span').textContent.split('x ')[1]) + qtyChange; - if(newQty > 0 && newQty <= prod.q + parseInt(itemElem.querySelector('span').textContent.split('x ')[1])) { - itemElem.querySelector('span').textContent=itemElem.querySelector('span').textContent.split('x ')[0] + 'x ' + newQty; - prod.q -= qtyChange; - } else if(newQty <= 0) { - itemElem.remove(); - prod.q -= qtyChange; - } else { - alert('재고가 부족합니다.'); - } - } else if(tgt.classList.contains('remove-item')) { - var remQty=parseInt(itemElem.querySelector('span').textContent.split('x ')[1]); - prod.q += remQty; - itemElem.remove(); - } - calcCart(); - } -}); \ No newline at end of file From e0a0c600d4576448271c49aea6c95aee2eda087e Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 01:20:39 +0900 Subject: [PATCH 03/38] =?UTF-8?q?refactor=20:=20index.html=EC=97=90=20main?= =?UTF-8?q?.js=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index 694c78b8..96e0c089 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,13 @@ - - - - - - 장바구니 - - - -
- - - \ No newline at end of file + + + + + + 장바구니 + + + +
+ + + From af02ff5bd19e9cf1e812af84632781be126e79e2 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 01:20:44 +0900 Subject: [PATCH 04/38] =?UTF-8?q?docs=20:=20main.js=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main.js b/src/main.js index 9128033c..75be0626 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,7 @@ function main() { { id: "p4", name: "상품4", val: 15000, q: 0 }, { id: "p5", name: "상품5", val: 25000, q: 10 }, ]; + //------------init HTML--------------- var root = document.getElementById("app"); let cont = document.createElement("div"); var wrap = document.createElement("div"); @@ -45,6 +46,8 @@ function main() { cont.appendChild(wrap); root.appendChild(cont); calcCart(); + + //---------TIMEOUT EVENT---------------- setTimeout(function () { setInterval(function () { var luckyItem = prodList[Math.floor(Math.random() * prodList.length)]; @@ -72,6 +75,8 @@ function main() { }, 60000); }, Math.random() * 20000); } + +//----------판매 상품 추가 함수----------- function updateSelOpts() { sel.innerHTML = ""; prodList.forEach(function (item) { @@ -82,6 +87,8 @@ function updateSelOpts() { sel.appendChild(opt); }); } + +//----------총액 계산 함수----------- function calcCart() { totalAmt = 0; itemCnt = 0; @@ -140,6 +147,8 @@ function calcCart() { updateStockInfo(); renderBonusPts(); } + +//----------보너스 포인트 계산과 렌더링 함수----------- const renderBonusPts = () => { bonusPts = Math.floor(totalAmt / 1000); var ptsTag = document.getElementById("loyalty-points"); @@ -151,6 +160,8 @@ const renderBonusPts = () => { } ptsTag.textContent = "(포인트: " + bonusPts + ")"; }; + +//----------재고 업데이트 및 품절메시지 표현 함수----------- function updateStockInfo() { var infoMsg = ""; prodList.forEach(function (item) { @@ -165,6 +176,8 @@ function updateStockInfo() { stockInfo.textContent = infoMsg; } main(); + +//----------addBtn 이벤트 핸들러 연결 함수----------- addBtn.addEventListener("click", function () { var selItem = sel.value; var itemToAdd = prodList.find(function (p) { @@ -208,6 +221,8 @@ addBtn.addEventListener("click", function () { lastSel = selItem; } }); + +//----------장바구니 삭제 함수----------- cartDisp.addEventListener("click", function (event) { var tgt = event.target; if ( From 76a57dffe601fbda083ac3d18e5480fa0829b721 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 02:50:25 +0900 Subject: [PATCH 05/38] =?UTF-8?q?refactor=20:=20=EC=9E=A5=EB=B0=94?= =?UTF-8?q?=EA=B5=AC=EB=8B=88=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=88=98=EB=9F=89=20=EC=A6=9D=EA=B0=80,=20?= =?UTF-8?q?=EC=88=98=EB=9F=89=20=EA=B0=90=EC=86=8C=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/events/cartEvent.js | 45 +++++++++++++++++++++++++++++++++++++++++ src/main.js | 35 ++++---------------------------- 2 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 src/events/cartEvent.js diff --git a/src/events/cartEvent.js b/src/events/cartEvent.js new file mode 100644 index 00000000..03f12364 --- /dev/null +++ b/src/events/cartEvent.js @@ -0,0 +1,45 @@ +import { PRODUCT_LIST } from "../data/prodList"; + +export const handleAddToCart = (target) => { + const productId = target.dataset.productId; + const product = PRODUCT_LIST.find((item) => item.id === productId); + console.log(target); + var itemElem = document.getElementById(productId); + + if (target.classList.contains("quantity-change")) { + var quantityChange = parseInt(target.dataset.change); + var newQuantity = + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + + quantityChange; + + if ( + newQuantity > 0 && + newQuantity <= + product.cost + + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + ) { + itemElem.querySelector("span").textContent = + itemElem.querySelector("span").textContent.split("x ")[0] + + "x " + + newQuantity; + product.cost -= quantityChange; + } else if (newQuantity <= 0) { + itemElem.remove(); + product.cost -= quantityChange; + } else { + alert("재고가 부족합니다."); + } + } +}; + +export const handleDeleteToCart = (target) => { + const productId = target.dataset.productId; + const product = PRODUCT_LIST.find((item) => item.id === productId); + var itemElem = document.getElementById(productId); + + var removeQuantity = parseInt( + itemElem.querySelector("span").textContent.split("x ")[1] + ); + product.cost += removeQuantity; + itemElem.remove(); +}; diff --git a/src/main.js b/src/main.js index 75be0626..3d4c75cd 100644 --- a/src/main.js +++ b/src/main.js @@ -1,3 +1,5 @@ +import { handleAddToCart, handleDeleteToCart } from "./events/cartEvent"; + var prodList, sel, addBtn, cartDisp, sum, stockInfo; var lastSel, bonusPts = 0, @@ -229,39 +231,10 @@ cartDisp.addEventListener("click", function (event) { tgt.classList.contains("quantity-change") || tgt.classList.contains("remove-item") ) { - var prodId = tgt.dataset.productId; - var itemElem = document.getElementById(prodId); - var prod = prodList.find(function (p) { - return p.id === prodId; - }); if (tgt.classList.contains("quantity-change")) { - var qtyChange = parseInt(tgt.dataset.change); - var newQty = - parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + - qtyChange; - if ( - newQty > 0 && - newQty <= - prod.q + - parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) - ) { - itemElem.querySelector("span").textContent = - itemElem.querySelector("span").textContent.split("x ")[0] + - "x " + - newQty; - prod.q -= qtyChange; - } else if (newQty <= 0) { - itemElem.remove(); - prod.q -= qtyChange; - } else { - alert("재고가 부족합니다."); - } + handleAddToCart(event.target); } else if (tgt.classList.contains("remove-item")) { - var remQty = parseInt( - itemElem.querySelector("span").textContent.split("x ")[1] - ); - prod.q += remQty; - itemElem.remove(); + handleDeleteToCart(event.target); } calcCart(); } From c73a4289952100d440fe9f53c58ba54298fc7b63 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 02:58:17 +0900 Subject: [PATCH 06/38] =?UTF-8?q?feat=20:=20createElement=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/domUtils.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/util/domUtils.js diff --git a/src/util/domUtils.js b/src/util/domUtils.js new file mode 100644 index 00000000..19602b84 --- /dev/null +++ b/src/util/domUtils.js @@ -0,0 +1,12 @@ +export const createElement = ( + tag, + className = "", + textContent = "", + id = "" +) => { + const elem = document.createElement(tag); + if (className) elem.className = className; + if (textContent) elem.textContent = textContent; + if (id) elem.id = id; + return elem; +}; From a703c593ddcaccb758518e0bf59f1f1a5bbc0ea8 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 02:58:33 +0900 Subject: [PATCH 07/38] =?UTF-8?q?feat=20:=20appendChildren=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/domUtils.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/util/domUtils.js b/src/util/domUtils.js index 19602b84..6d718fd1 100644 --- a/src/util/domUtils.js +++ b/src/util/domUtils.js @@ -10,3 +10,7 @@ export const createElement = ( if (id) elem.id = id; return elem; }; + +export const appendChildren = (parent, children) => { + children.forEach((child) => parent.appendChild(child)); +}; From 23a0c6da05e4090a570864da01b830ea5053175f Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 03:04:31 +0900 Subject: [PATCH 08/38] =?UTF-8?q?refactor=20:=20main.js=20createElement=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main.js b/src/main.js index 3d4c75cd..fa5d2103 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,5 @@ import { handleAddToCart, handleDeleteToCart } from "./events/cartEvent"; +import { createElement } from "./util/domUtils"; var prodList, sel, addBtn, cartDisp, sum, stockInfo; var lastSel, @@ -15,29 +16,34 @@ function main() { ]; //------------init HTML--------------- var root = document.getElementById("app"); - let cont = document.createElement("div"); - var wrap = document.createElement("div"); - let hTxt = document.createElement("h1"); - cartDisp = document.createElement("div"); - sum = document.createElement("div"); - sel = document.createElement("select"); - addBtn = document.createElement("button"); - stockInfo = document.createElement("div"); - cartDisp.id = "cart-items"; - sum.id = "cart-total"; - sel.id = "product-select"; - addBtn.id = "add-to-cart"; - stockInfo.id = "stock-status"; - cont.className = "bg-gray-100 p-8"; + let cont = createElement("div", "bg-gray-100 p-8"); + var wrap = createElement( + "div", + "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8" + ); + let hTxt = createElement("h1", "text-2xl font-bold mb-4", "장바구니"); + cartDisp = createElement("div", null, null, "cart-items"); + sel = createElement( + "select", + "border rounded p-2 mr-2", + null, + "product-select" + ); + sum = createElement("div", "text-xl font-bold my-4", null, "cart-total"); + addBtn = createElement( + "button", + "bg-blue-500 text-white px-4 py-2 rounded", + "추가", + "add-to-cart" + ); + stockInfo = createElement( + "div", + "text-sm text-gray-500 mt-2", + null, + "stock-status" + ); wrap.className = "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8"; - hTxt.className = "text-2xl font-bold mb-4"; - sum.className = "text-xl font-bold my-4"; - sel.className = "border rounded p-2 mr-2"; - addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; - stockInfo.className = "text-sm text-gray-500 mt-2"; - hTxt.textContent = "장바구니"; - addBtn.textContent = "추가"; updateSelOpts(); wrap.appendChild(hTxt); wrap.appendChild(cartDisp); From d98bac310f94fb37139e6d0d5b17f10ee3f8c161 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Wed, 8 Jan 2025 04:47:14 +0900 Subject: [PATCH 09/38] =?UTF-8?q?fix=20:=20cartEvent=20=EC=B4=9D=EC=95=A1?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A0=9C=ED=92=88=20=EA=B8=88=EC=95=A1=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EB=90=98=EB=8A=94=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/events/cartEvent.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/events/cartEvent.js b/src/events/cartEvent.js index 03f12364..2e33e3ee 100644 --- a/src/events/cartEvent.js +++ b/src/events/cartEvent.js @@ -15,17 +15,17 @@ export const handleAddToCart = (target) => { if ( newQuantity > 0 && newQuantity <= - product.cost + + product.stock + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) ) { itemElem.querySelector("span").textContent = itemElem.querySelector("span").textContent.split("x ")[0] + "x " + newQuantity; - product.cost -= quantityChange; + product.stock -= quantityChange; } else if (newQuantity <= 0) { itemElem.remove(); - product.cost -= quantityChange; + product.stock -= quantityChange; } else { alert("재고가 부족합니다."); } @@ -40,6 +40,6 @@ export const handleDeleteToCart = (target) => { var removeQuantity = parseInt( itemElem.querySelector("span").textContent.split("x ")[1] ); - product.cost += removeQuantity; + product.stock += removeQuantity; itemElem.remove(); }; From 3c8ad8906e304d751dcf20a6dc3b7d59ab918a28 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Thu, 9 Jan 2025 15:40:18 +0900 Subject: [PATCH 10/38] =?UTF-8?q?refactor=20:=20=EC=83=81=ED=92=88?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=83=81=EC=88=98=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 55 ++++++++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/main.js b/src/main.js index fa5d2103..e48cc17a 100644 --- a/src/main.js +++ b/src/main.js @@ -1,3 +1,4 @@ +import { PRODUCT_LIST } from "./data/prodList"; import { handleAddToCart, handleDeleteToCart } from "./events/cartEvent"; import { createElement } from "./util/domUtils"; @@ -7,13 +8,6 @@ var lastSel, totalAmt = 0, itemCnt = 0; function main() { - prodList = [ - { id: "p1", name: "상품1", val: 10000, q: 50 }, - { id: "p2", name: "상품2", val: 20000, q: 30 }, - { id: "p3", name: "상품3", val: 30000, q: 20 }, - { id: "p4", name: "상품4", val: 15000, q: 0 }, - { id: "p5", name: "상품5", val: 25000, q: 10 }, - ]; //------------init HTML--------------- var root = document.getElementById("app"); let cont = createElement("div", "bg-gray-100 p-8"); @@ -58,9 +52,10 @@ function main() { //---------TIMEOUT EVENT---------------- setTimeout(function () { setInterval(function () { - var luckyItem = prodList[Math.floor(Math.random() * prodList.length)]; - if (Math.random() < 0.3 && luckyItem.q > 0) { - luckyItem.val = Math.round(luckyItem.val * 0.8); + var luckyItem = + PRODUCT_LIST[Math.floor(Math.random() * PRODUCT_LIST.length)]; + if (Math.random() < 0.3 && luckyItem.stock > 0) { + luckyItem.cost = Math.round(luckyItem.cost * 0.8); alert("번개세일! " + luckyItem.name + "이(가) 20% 할인 중입니다!"); updateSelOpts(); } @@ -69,14 +64,14 @@ function main() { setTimeout(function () { setInterval(function () { if (lastSel) { - var suggest = prodList.find(function (item) { - return item.id !== lastSel && item.q > 0; + var suggest = PRODUCT_LIST.find(function (item) { + return item.id !== lastSel && item.stock > 0; }); if (suggest) { alert( suggest.name + "은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!" ); - suggest.val = Math.round(suggest.val * 0.95); + suggest.cost = Math.round(suggest.stock * 0.95); updateSelOpts(); } } @@ -87,11 +82,11 @@ function main() { //----------판매 상품 추가 함수----------- function updateSelOpts() { sel.innerHTML = ""; - prodList.forEach(function (item) { + PRODUCT_LIST.forEach(function (item) { var opt = document.createElement("option"); opt.value = item.id; - opt.textContent = item.name + " - " + item.val + "원"; - if (item.q === 0) opt.disabled = true; + opt.textContent = item.name + " - " + item.cost + "원"; + if (item.stock === 0) opt.disabled = true; sel.appendChild(opt); }); } @@ -105,16 +100,16 @@ function calcCart() { for (var i = 0; i < cartItems.length; i++) { (function () { var curItem; - for (var j = 0; j < prodList.length; j++) { - if (prodList[j].id === cartItems[i].id) { - curItem = prodList[j]; + for (var j = 0; j < PRODUCT_LIST.length; j++) { + if (PRODUCT_LIST[j].id === cartItems[i].id) { + curItem = PRODUCT_LIST[j]; break; } } var q = parseInt( cartItems[i].querySelector("span").textContent.split("x ")[1] ); - var itemTot = curItem.val * q; + var itemTot = curItem.cost * q; var disc = 0; itemCnt += q; subTot += itemTot; @@ -172,12 +167,12 @@ const renderBonusPts = () => { //----------재고 업데이트 및 품절메시지 표현 함수----------- function updateStockInfo() { var infoMsg = ""; - prodList.forEach(function (item) { - if (item.q < 5) { + PRODUCT_LIST.forEach(function (item) { + if (item.stock < 5) { infoMsg += item.name + ": " + - (item.q > 0 ? "재고 부족 (" + item.q + "개 남음)" : "품절") + + (item.stock > 0 ? "재고 부족 (" + item.stock + "개 남음)" : "품절") + "\n"; } }); @@ -188,18 +183,18 @@ main(); //----------addBtn 이벤트 핸들러 연결 함수----------- addBtn.addEventListener("click", function () { var selItem = sel.value; - var itemToAdd = prodList.find(function (p) { + var itemToAdd = PRODUCT_LIST.find(function (p) { return p.id === selItem; }); - if (itemToAdd && itemToAdd.q > 0) { + if (itemToAdd && itemToAdd.stock > 0) { var item = document.getElementById(itemToAdd.id); if (item) { var newQty = parseInt(item.querySelector("span").textContent.split("x ")[1]) + 1; - if (newQty <= itemToAdd.q) { + if (newQty <= itemToAdd.stock) { item.querySelector("span").textContent = - itemToAdd.name + " - " + itemToAdd.val + "원 x " + newQty; - itemToAdd.q--; + itemToAdd.name + " - " + itemToAdd.cost + "원 x " + newQty; + itemToAdd.stock--; } else { alert("재고가 부족합니다."); } @@ -211,7 +206,7 @@ addBtn.addEventListener("click", function () { "" + itemToAdd.name + " - " + - itemToAdd.val + + itemToAdd.cost + "원 x 1
" + '
'; cartDisp.appendChild(newItem); - itemToAdd.q--; + itemToAdd.stock--; } calcCart(); lastSel = selItem; From 577729ed8386977134f7e59596d43a91e83b222a Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Thu, 9 Jan 2025 15:54:37 +0900 Subject: [PATCH 11/38] =?UTF-8?q?refactor=20:=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=ED=95=98?= =?UTF-8?q?=EB=8A=94=20initApp=20=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main.js b/src/main.js index e48cc17a..c930602f 100644 --- a/src/main.js +++ b/src/main.js @@ -7,6 +7,29 @@ var lastSel, bonusPts = 0, totalAmt = 0, itemCnt = 0; + +function initApp() { + // 추가 버튼 + addBtn = document.createElement("button"); + addBtn.textContent = "추가"; + addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; + addBtn.addEventListener("click", handleAddToCart); + + // 재고 정보 버튼 + stockInfo = document.createElement("div"); + stockInfo.id = "stock-status"; + stockInfo.className = "text-sm text-gray-500 mt-2"; + + // 품절 상태 레이블 + const lowStockItems = PRODUCT_LIST.filter((item) => item.stock < 5).map( + (item) => + `${item.name}: ${ + item.stock > 0 ? `재고 부족 (${item.stock}개 남음)` : "품절" + }` + ); + + stockInfo.textContent = lowStockItems.join("\n"); +} function main() { //------------init HTML--------------- var root = document.getElementById("app"); @@ -24,18 +47,8 @@ function main() { "product-select" ); sum = createElement("div", "text-xl font-bold my-4", null, "cart-total"); - addBtn = createElement( - "button", - "bg-blue-500 text-white px-4 py-2 rounded", - "추가", - "add-to-cart" - ); - stockInfo = createElement( - "div", - "text-sm text-gray-500 mt-2", - null, - "stock-status" - ); + + initApp(); wrap.className = "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8"; updateSelOpts(); From 0557a0f0b88dc29916e44bcd1fb8797a7af8b378 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Thu, 9 Jan 2025 21:41:18 +0900 Subject: [PATCH 12/38] =?UTF-8?q?origin=20=EC=BD=94=EB=93=9C=20=EC=82=B4?= =?UTF-8?q?=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{main.js => main.origin.js} | 164 ++++++++++++++++---------------- 1 file changed, 81 insertions(+), 83 deletions(-) rename src/{main.js => main.origin.js} (63%) diff --git a/src/main.js b/src/main.origin.js similarity index 63% rename from src/main.js rename to src/main.origin.js index c930602f..9128033c 100644 --- a/src/main.js +++ b/src/main.origin.js @@ -1,56 +1,40 @@ -import { PRODUCT_LIST } from "./data/prodList"; -import { handleAddToCart, handleDeleteToCart } from "./events/cartEvent"; -import { createElement } from "./util/domUtils"; - var prodList, sel, addBtn, cartDisp, sum, stockInfo; var lastSel, bonusPts = 0, totalAmt = 0, itemCnt = 0; - -function initApp() { - // 추가 버튼 +function main() { + prodList = [ + { id: "p1", name: "상품1", val: 10000, q: 50 }, + { id: "p2", name: "상품2", val: 20000, q: 30 }, + { id: "p3", name: "상품3", val: 30000, q: 20 }, + { id: "p4", name: "상품4", val: 15000, q: 0 }, + { id: "p5", name: "상품5", val: 25000, q: 10 }, + ]; + var root = document.getElementById("app"); + let cont = document.createElement("div"); + var wrap = document.createElement("div"); + let hTxt = document.createElement("h1"); + cartDisp = document.createElement("div"); + sum = document.createElement("div"); + sel = document.createElement("select"); addBtn = document.createElement("button"); - addBtn.textContent = "추가"; - addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; - addBtn.addEventListener("click", handleAddToCart); - - // 재고 정보 버튼 stockInfo = document.createElement("div"); + cartDisp.id = "cart-items"; + sum.id = "cart-total"; + sel.id = "product-select"; + addBtn.id = "add-to-cart"; stockInfo.id = "stock-status"; - stockInfo.className = "text-sm text-gray-500 mt-2"; - - // 품절 상태 레이블 - const lowStockItems = PRODUCT_LIST.filter((item) => item.stock < 5).map( - (item) => - `${item.name}: ${ - item.stock > 0 ? `재고 부족 (${item.stock}개 남음)` : "품절" - }` - ); - - stockInfo.textContent = lowStockItems.join("\n"); -} -function main() { - //------------init HTML--------------- - var root = document.getElementById("app"); - let cont = createElement("div", "bg-gray-100 p-8"); - var wrap = createElement( - "div", - "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8" - ); - let hTxt = createElement("h1", "text-2xl font-bold mb-4", "장바구니"); - cartDisp = createElement("div", null, null, "cart-items"); - sel = createElement( - "select", - "border rounded p-2 mr-2", - null, - "product-select" - ); - sum = createElement("div", "text-xl font-bold my-4", null, "cart-total"); - - initApp(); + cont.className = "bg-gray-100 p-8"; wrap.className = "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8"; + hTxt.className = "text-2xl font-bold mb-4"; + sum.className = "text-xl font-bold my-4"; + sel.className = "border rounded p-2 mr-2"; + addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; + stockInfo.className = "text-sm text-gray-500 mt-2"; + hTxt.textContent = "장바구니"; + addBtn.textContent = "추가"; updateSelOpts(); wrap.appendChild(hTxt); wrap.appendChild(cartDisp); @@ -61,14 +45,11 @@ function main() { cont.appendChild(wrap); root.appendChild(cont); calcCart(); - - //---------TIMEOUT EVENT---------------- setTimeout(function () { setInterval(function () { - var luckyItem = - PRODUCT_LIST[Math.floor(Math.random() * PRODUCT_LIST.length)]; - if (Math.random() < 0.3 && luckyItem.stock > 0) { - luckyItem.cost = Math.round(luckyItem.cost * 0.8); + var luckyItem = prodList[Math.floor(Math.random() * prodList.length)]; + if (Math.random() < 0.3 && luckyItem.q > 0) { + luckyItem.val = Math.round(luckyItem.val * 0.8); alert("번개세일! " + luckyItem.name + "이(가) 20% 할인 중입니다!"); updateSelOpts(); } @@ -77,34 +58,30 @@ function main() { setTimeout(function () { setInterval(function () { if (lastSel) { - var suggest = PRODUCT_LIST.find(function (item) { - return item.id !== lastSel && item.stock > 0; + var suggest = prodList.find(function (item) { + return item.id !== lastSel && item.q > 0; }); if (suggest) { alert( suggest.name + "은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!" ); - suggest.cost = Math.round(suggest.stock * 0.95); + suggest.val = Math.round(suggest.val * 0.95); updateSelOpts(); } } }, 60000); }, Math.random() * 20000); } - -//----------판매 상품 추가 함수----------- function updateSelOpts() { sel.innerHTML = ""; - PRODUCT_LIST.forEach(function (item) { + prodList.forEach(function (item) { var opt = document.createElement("option"); opt.value = item.id; - opt.textContent = item.name + " - " + item.cost + "원"; - if (item.stock === 0) opt.disabled = true; + opt.textContent = item.name + " - " + item.val + "원"; + if (item.q === 0) opt.disabled = true; sel.appendChild(opt); }); } - -//----------총액 계산 함수----------- function calcCart() { totalAmt = 0; itemCnt = 0; @@ -113,16 +90,16 @@ function calcCart() { for (var i = 0; i < cartItems.length; i++) { (function () { var curItem; - for (var j = 0; j < PRODUCT_LIST.length; j++) { - if (PRODUCT_LIST[j].id === cartItems[i].id) { - curItem = PRODUCT_LIST[j]; + for (var j = 0; j < prodList.length; j++) { + if (prodList[j].id === cartItems[i].id) { + curItem = prodList[j]; break; } } var q = parseInt( cartItems[i].querySelector("span").textContent.split("x ")[1] ); - var itemTot = curItem.cost * q; + var itemTot = curItem.val * q; var disc = 0; itemCnt += q; subTot += itemTot; @@ -163,8 +140,6 @@ function calcCart() { updateStockInfo(); renderBonusPts(); } - -//----------보너스 포인트 계산과 렌더링 함수----------- const renderBonusPts = () => { bonusPts = Math.floor(totalAmt / 1000); var ptsTag = document.getElementById("loyalty-points"); @@ -176,38 +151,34 @@ const renderBonusPts = () => { } ptsTag.textContent = "(포인트: " + bonusPts + ")"; }; - -//----------재고 업데이트 및 품절메시지 표현 함수----------- function updateStockInfo() { var infoMsg = ""; - PRODUCT_LIST.forEach(function (item) { - if (item.stock < 5) { + prodList.forEach(function (item) { + if (item.q < 5) { infoMsg += item.name + ": " + - (item.stock > 0 ? "재고 부족 (" + item.stock + "개 남음)" : "품절") + + (item.q > 0 ? "재고 부족 (" + item.q + "개 남음)" : "품절") + "\n"; } }); stockInfo.textContent = infoMsg; } main(); - -//----------addBtn 이벤트 핸들러 연결 함수----------- addBtn.addEventListener("click", function () { var selItem = sel.value; - var itemToAdd = PRODUCT_LIST.find(function (p) { + var itemToAdd = prodList.find(function (p) { return p.id === selItem; }); - if (itemToAdd && itemToAdd.stock > 0) { + if (itemToAdd && itemToAdd.q > 0) { var item = document.getElementById(itemToAdd.id); if (item) { var newQty = parseInt(item.querySelector("span").textContent.split("x ")[1]) + 1; - if (newQty <= itemToAdd.stock) { + if (newQty <= itemToAdd.q) { item.querySelector("span").textContent = - itemToAdd.name + " - " + itemToAdd.cost + "원 x " + newQty; - itemToAdd.stock--; + itemToAdd.name + " - " + itemToAdd.val + "원 x " + newQty; + itemToAdd.q--; } else { alert("재고가 부족합니다."); } @@ -219,7 +190,7 @@ addBtn.addEventListener("click", function () { "" + itemToAdd.name + " - " + - itemToAdd.cost + + itemToAdd.val + "원 x 1
" + '
'; cartDisp.appendChild(newItem); - itemToAdd.stock--; + itemToAdd.q--; } calcCart(); lastSel = selItem; } }); - -//----------장바구니 삭제 함수----------- cartDisp.addEventListener("click", function (event) { var tgt = event.target; if ( tgt.classList.contains("quantity-change") || tgt.classList.contains("remove-item") ) { + var prodId = tgt.dataset.productId; + var itemElem = document.getElementById(prodId); + var prod = prodList.find(function (p) { + return p.id === prodId; + }); if (tgt.classList.contains("quantity-change")) { - handleAddToCart(event.target); + var qtyChange = parseInt(tgt.dataset.change); + var newQty = + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + + qtyChange; + if ( + newQty > 0 && + newQty <= + prod.q + + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + ) { + itemElem.querySelector("span").textContent = + itemElem.querySelector("span").textContent.split("x ")[0] + + "x " + + newQty; + prod.q -= qtyChange; + } else if (newQty <= 0) { + itemElem.remove(); + prod.q -= qtyChange; + } else { + alert("재고가 부족합니다."); + } } else if (tgt.classList.contains("remove-item")) { - handleDeleteToCart(event.target); + var remQty = parseInt( + itemElem.querySelector("span").textContent.split("x ")[1] + ); + prod.q += remQty; + itemElem.remove(); } calcCart(); } From c99acc8c2d6acc088804c0db2ff9535f526e87b9 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Thu, 9 Jan 2025 21:42:27 +0900 Subject: [PATCH 13/38] =?UTF-8?q?refactor=20:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- src/{ => basic}/data/prodList.js | 0 src/{ => basic}/events/cartEvent.js | 0 src/{ => basic}/util/domUtils.js | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename src/{ => basic}/data/prodList.js (100%) rename src/{ => basic}/events/cartEvent.js (100%) rename src/{ => basic}/util/domUtils.js (100%) diff --git a/index.html b/index.html index 96e0c089..3570956f 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,6 @@
- + diff --git a/src/data/prodList.js b/src/basic/data/prodList.js similarity index 100% rename from src/data/prodList.js rename to src/basic/data/prodList.js diff --git a/src/events/cartEvent.js b/src/basic/events/cartEvent.js similarity index 100% rename from src/events/cartEvent.js rename to src/basic/events/cartEvent.js diff --git a/src/util/domUtils.js b/src/basic/util/domUtils.js similarity index 100% rename from src/util/domUtils.js rename to src/basic/util/domUtils.js From d00de57de42ba6044aa48190210430e13ffb42ce Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Thu, 9 Jan 2025 22:12:43 +0900 Subject: [PATCH 14/38] =?UTF-8?q?(=EA=B3=BC=EC=A0=9C=20=EC=9E=AC=EC=8B=9C?= =?UTF-8?q?=EC=9E=91)=ED=94=84=EB=A6=AC=ED=8B=B0=EC=96=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 295 +++++++++++++++++++++++----------------- 1 file changed, 172 insertions(+), 123 deletions(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 0efffa28..9128033c 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -1,36 +1,40 @@ var prodList, sel, addBtn, cartDisp, sum, stockInfo; -var lastSel, bonusPts=0, totalAmt=0, itemCnt=0; +var lastSel, + bonusPts = 0, + totalAmt = 0, + itemCnt = 0; function main() { - prodList=[ - {id: 'p1', name: '상품1', val: 10000, q: 50 }, - {id: 'p2', name: '상품2', val: 20000, q: 30 }, - {id: 'p3', name: '상품3', val: 30000, q: 20 }, - {id: 'p4', name: '상품4', val: 15000, q: 0 }, - {id: 'p5', name: '상품5', val: 25000, q: 10 } + prodList = [ + { id: "p1", name: "상품1", val: 10000, q: 50 }, + { id: "p2", name: "상품2", val: 20000, q: 30 }, + { id: "p3", name: "상품3", val: 30000, q: 20 }, + { id: "p4", name: "상품4", val: 15000, q: 0 }, + { id: "p5", name: "상품5", val: 25000, q: 10 }, ]; - var root=document.getElementById('app'); - let cont=document.createElement('div'); - var wrap=document.createElement('div'); - let hTxt=document.createElement('h1'); - cartDisp=document.createElement('div'); - sum=document.createElement('div'); - sel=document.createElement('select'); - addBtn=document.createElement('button'); - stockInfo=document.createElement('div'); - cartDisp.id='cart-items'; - sum.id='cart-total'; - sel.id='product-select'; - addBtn.id='add-to-cart'; - stockInfo.id='stock-status'; - cont.className='bg-gray-100 p-8'; - wrap.className='max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8'; - hTxt.className='text-2xl font-bold mb-4'; - sum.className='text-xl font-bold my-4'; - sel.className='border rounded p-2 mr-2'; - addBtn.className='bg-blue-500 text-white px-4 py-2 rounded'; - stockInfo.className='text-sm text-gray-500 mt-2'; - hTxt.textContent='장바구니'; - addBtn.textContent='추가'; + var root = document.getElementById("app"); + let cont = document.createElement("div"); + var wrap = document.createElement("div"); + let hTxt = document.createElement("h1"); + cartDisp = document.createElement("div"); + sum = document.createElement("div"); + sel = document.createElement("select"); + addBtn = document.createElement("button"); + stockInfo = document.createElement("div"); + cartDisp.id = "cart-items"; + sum.id = "cart-total"; + sel.id = "product-select"; + addBtn.id = "add-to-cart"; + stockInfo.id = "stock-status"; + cont.className = "bg-gray-100 p-8"; + wrap.className = + "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8"; + hTxt.className = "text-2xl font-bold mb-4"; + sum.className = "text-xl font-bold my-4"; + sel.className = "border rounded p-2 mr-2"; + addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; + stockInfo.className = "text-sm text-gray-500 mt-2"; + hTxt.textContent = "장바구니"; + addBtn.textContent = "추가"; updateSelOpts(); wrap.appendChild(hTxt); wrap.appendChild(cartDisp); @@ -43,162 +47,207 @@ function main() { calcCart(); setTimeout(function () { setInterval(function () { - var luckyItem=prodList[Math.floor(Math.random() * prodList.length)]; - if(Math.random() < 0.3 && luckyItem.q > 0) { - luckyItem.val=Math.round(luckyItem.val * 0.8); - alert('번개세일! ' + luckyItem.name + '이(가) 20% 할인 중입니다!'); + var luckyItem = prodList[Math.floor(Math.random() * prodList.length)]; + if (Math.random() < 0.3 && luckyItem.q > 0) { + luckyItem.val = Math.round(luckyItem.val * 0.8); + alert("번개세일! " + luckyItem.name + "이(가) 20% 할인 중입니다!"); updateSelOpts(); } }, 30000); }, Math.random() * 10000); setTimeout(function () { setInterval(function () { - if(lastSel) { - var suggest=prodList.find(function (item) { return item.id !== lastSel && item.q > 0; }); - if(suggest) { - alert(suggest.name + '은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!'); - suggest.val=Math.round(suggest.val * 0.95); + if (lastSel) { + var suggest = prodList.find(function (item) { + return item.id !== lastSel && item.q > 0; + }); + if (suggest) { + alert( + suggest.name + "은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!" + ); + suggest.val = Math.round(suggest.val * 0.95); updateSelOpts(); } } }, 60000); }, Math.random() * 20000); -}; +} function updateSelOpts() { - sel.innerHTML=''; + sel.innerHTML = ""; prodList.forEach(function (item) { - var opt=document.createElement('option'); - opt.value=item.id; - opt.textContent=item.name + ' - ' + item.val + '원'; - if(item.q === 0) opt.disabled=true; + var opt = document.createElement("option"); + opt.value = item.id; + opt.textContent = item.name + " - " + item.val + "원"; + if (item.q === 0) opt.disabled = true; sel.appendChild(opt); }); } function calcCart() { - totalAmt=0; - itemCnt=0; - var cartItems=cartDisp.children; - var subTot=0; - for (var i=0; i < cartItems.length; i++) { + totalAmt = 0; + itemCnt = 0; + var cartItems = cartDisp.children; + var subTot = 0; + for (var i = 0; i < cartItems.length; i++) { (function () { var curItem; - for (var j=0; j < prodList.length; j++) { - if(prodList[j].id === cartItems[i].id) { - curItem=prodList[j]; + for (var j = 0; j < prodList.length; j++) { + if (prodList[j].id === cartItems[i].id) { + curItem = prodList[j]; break; } } - var q=parseInt(cartItems[i].querySelector('span').textContent.split('x ')[1]); - var itemTot=curItem.val * q; - var disc=0; + var q = parseInt( + cartItems[i].querySelector("span").textContent.split("x ")[1] + ); + var itemTot = curItem.val * q; + var disc = 0; itemCnt += q; subTot += itemTot; - if(q >= 10) { - if(curItem.id === 'p1') disc=0.1; - else if(curItem.id === 'p2') disc=0.15; - else if(curItem.id === 'p3') disc=0.2; - else if(curItem.id === 'p4') disc=0.05; - else if(curItem.id === 'p5') disc=0.25; + if (q >= 10) { + if (curItem.id === "p1") disc = 0.1; + else if (curItem.id === "p2") disc = 0.15; + else if (curItem.id === "p3") disc = 0.2; + else if (curItem.id === "p4") disc = 0.05; + else if (curItem.id === "p5") disc = 0.25; } totalAmt += itemTot * (1 - disc); })(); } - let discRate=0; - if(itemCnt >= 30) { - var bulkDisc=totalAmt * 0.25; - var itemDisc=subTot - totalAmt; - if(bulkDisc > itemDisc) { - totalAmt=subTot * (1 - 0.25); - discRate=0.25; + let discRate = 0; + if (itemCnt >= 30) { + var bulkDisc = totalAmt * 0.25; + var itemDisc = subTot - totalAmt; + if (bulkDisc > itemDisc) { + totalAmt = subTot * (1 - 0.25); + discRate = 0.25; } else { - discRate=(subTot - totalAmt) / subTot; + discRate = (subTot - totalAmt) / subTot; } } else { - discRate=(subTot - totalAmt) / subTot; + discRate = (subTot - totalAmt) / subTot; } - if(new Date().getDay() === 2) { - totalAmt *= (1 - 0.1); - discRate=Math.max(discRate, 0.1); + if (new Date().getDay() === 2) { + totalAmt *= 1 - 0.1; + discRate = Math.max(discRate, 0.1); } - sum.textContent='총액: ' + Math.round(totalAmt) + '원'; - if(discRate > 0) { - var span=document.createElement('span'); - span.className='text-green-500 ml-2'; - span.textContent='(' + (discRate * 100).toFixed(1) + '% 할인 적용)'; + sum.textContent = "총액: " + Math.round(totalAmt) + "원"; + if (discRate > 0) { + var span = document.createElement("span"); + span.className = "text-green-500 ml-2"; + span.textContent = "(" + (discRate * 100).toFixed(1) + "% 할인 적용)"; sum.appendChild(span); } updateStockInfo(); renderBonusPts(); } -const renderBonusPts=() => { +const renderBonusPts = () => { bonusPts = Math.floor(totalAmt / 1000); - var ptsTag=document.getElementById('loyalty-points'); - if(!ptsTag) { - ptsTag=document.createElement('span'); - ptsTag.id='loyalty-points'; - ptsTag.className='text-blue-500 ml-2'; + var ptsTag = document.getElementById("loyalty-points"); + if (!ptsTag) { + ptsTag = document.createElement("span"); + ptsTag.id = "loyalty-points"; + ptsTag.className = "text-blue-500 ml-2"; sum.appendChild(ptsTag); } - ptsTag.textContent='(포인트: ' + bonusPts + ')'; + ptsTag.textContent = "(포인트: " + bonusPts + ")"; }; function updateStockInfo() { - var infoMsg=''; + var infoMsg = ""; prodList.forEach(function (item) { - if(item.q < 5) {infoMsg += item.name + ': ' + (item.q > 0 ? '재고 부족 ('+item.q+'개 남음)' : '품절') + '\n'; + if (item.q < 5) { + infoMsg += + item.name + + ": " + + (item.q > 0 ? "재고 부족 (" + item.q + "개 남음)" : "품절") + + "\n"; } }); - stockInfo.textContent=infoMsg; + stockInfo.textContent = infoMsg; } main(); -addBtn.addEventListener('click', function () { - var selItem=sel.value; - var itemToAdd=prodList.find(function (p) { return p.id === selItem; }); - if(itemToAdd && itemToAdd.q > 0) { - var item=document.getElementById(itemToAdd.id); - if(item) { - var newQty=parseInt(item.querySelector('span').textContent.split('x ')[1]) + 1; - if(newQty <= itemToAdd.q) { - item.querySelector('span').textContent=itemToAdd.name + ' - ' + itemToAdd.val + '원 x ' + newQty; +addBtn.addEventListener("click", function () { + var selItem = sel.value; + var itemToAdd = prodList.find(function (p) { + return p.id === selItem; + }); + if (itemToAdd && itemToAdd.q > 0) { + var item = document.getElementById(itemToAdd.id); + if (item) { + var newQty = + parseInt(item.querySelector("span").textContent.split("x ")[1]) + 1; + if (newQty <= itemToAdd.q) { + item.querySelector("span").textContent = + itemToAdd.name + " - " + itemToAdd.val + "원 x " + newQty; itemToAdd.q--; - } else {alert('재고가 부족합니다.');} + } else { + alert("재고가 부족합니다."); + } } else { - var newItem=document.createElement('div'); - newItem.id=itemToAdd.id; - newItem.className='flex justify-between items-center mb-2'; - newItem.innerHTML='' + itemToAdd.name + ' - ' + itemToAdd.val + '원 x 1
' + - '' + - '' + - '
'; + var newItem = document.createElement("div"); + newItem.id = itemToAdd.id; + newItem.className = "flex justify-between items-center mb-2"; + newItem.innerHTML = + "" + + itemToAdd.name + + " - " + + itemToAdd.val + + "원 x 1
" + + '' + + '' + + '
'; cartDisp.appendChild(newItem); itemToAdd.q--; } calcCart(); - lastSel=selItem; + lastSel = selItem; } }); -cartDisp.addEventListener('click', function (event) { - var tgt=event.target; - if(tgt.classList.contains('quantity-change') || tgt.classList.contains('remove-item')) { - var prodId=tgt.dataset.productId; - var itemElem=document.getElementById(prodId); - var prod=prodList.find(function (p) { return p.id === prodId; }); - if(tgt.classList.contains('quantity-change')) { - var qtyChange=parseInt(tgt.dataset.change); - var newQty=parseInt(itemElem.querySelector('span').textContent.split('x ')[1]) + qtyChange; - if(newQty > 0 && newQty <= prod.q + parseInt(itemElem.querySelector('span').textContent.split('x ')[1])) { - itemElem.querySelector('span').textContent=itemElem.querySelector('span').textContent.split('x ')[0] + 'x ' + newQty; +cartDisp.addEventListener("click", function (event) { + var tgt = event.target; + if ( + tgt.classList.contains("quantity-change") || + tgt.classList.contains("remove-item") + ) { + var prodId = tgt.dataset.productId; + var itemElem = document.getElementById(prodId); + var prod = prodList.find(function (p) { + return p.id === prodId; + }); + if (tgt.classList.contains("quantity-change")) { + var qtyChange = parseInt(tgt.dataset.change); + var newQty = + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + + qtyChange; + if ( + newQty > 0 && + newQty <= + prod.q + + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + ) { + itemElem.querySelector("span").textContent = + itemElem.querySelector("span").textContent.split("x ")[0] + + "x " + + newQty; prod.q -= qtyChange; - } else if(newQty <= 0) { + } else if (newQty <= 0) { itemElem.remove(); prod.q -= qtyChange; } else { - alert('재고가 부족합니다.'); + alert("재고가 부족합니다."); } - } else if(tgt.classList.contains('remove-item')) { - var remQty=parseInt(itemElem.querySelector('span').textContent.split('x ')[1]); + } else if (tgt.classList.contains("remove-item")) { + var remQty = parseInt( + itemElem.querySelector("span").textContent.split("x ")[1] + ); prod.q += remQty; itemElem.remove(); } calcCart(); } -}); \ No newline at end of file +}); From 59282ee51b5cf9ceebbd92b72a63491b94873161 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Thu, 9 Jan 2025 22:16:41 +0900 Subject: [PATCH 15/38] =?UTF-8?q?chore=20:=20PRODUCT=5FLIST=20=EC=83=81?= =?UTF-8?q?=EC=88=98=ED=99=94,=20=EC=B9=B4=ED=8A=B8=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=20=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 110 +++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 9128033c..e98b8003 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -1,16 +1,10 @@ +import { PRODUCT_LIST } from "./data/prodList"; var prodList, sel, addBtn, cartDisp, sum, stockInfo; var lastSel, - bonusPts = 0, - totalAmt = 0, - itemCnt = 0; + bonusPoints = 0, + totalAmount = 0, + itemCount = 0; function main() { - prodList = [ - { id: "p1", name: "상품1", val: 10000, q: 50 }, - { id: "p2", name: "상품2", val: 20000, q: 30 }, - { id: "p3", name: "상품3", val: 30000, q: 20 }, - { id: "p4", name: "상품4", val: 15000, q: 0 }, - { id: "p5", name: "상품5", val: 25000, q: 10 }, - ]; var root = document.getElementById("app"); let cont = document.createElement("div"); var wrap = document.createElement("div"); @@ -74,91 +68,91 @@ function main() { } function updateSelOpts() { sel.innerHTML = ""; - prodList.forEach(function (item) { + PRODUCT_LIST.forEach(function (item) { var opt = document.createElement("option"); opt.value = item.id; - opt.textContent = item.name + " - " + item.val + "원"; + opt.textContent = item.name + " - " + item.cost + "원"; if (item.q === 0) opt.disabled = true; sel.appendChild(opt); }); } function calcCart() { - totalAmt = 0; - itemCnt = 0; + totalAmount = 0; + itemCount = 0; var cartItems = cartDisp.children; var subTot = 0; for (var i = 0; i < cartItems.length; i++) { (function () { var curItem; - for (var j = 0; j < prodList.length; j++) { - if (prodList[j].id === cartItems[i].id) { - curItem = prodList[j]; + for (var j = 0; j < PRODUCT_LIST.length; j++) { + if (PRODUCT_LIST[j].id === cartItems[i].id) { + curItem = PRODUCT_LIST[j]; break; } } - var q = parseInt( + var amount = parseInt( cartItems[i].querySelector("span").textContent.split("x ")[1] ); - var itemTot = curItem.val * q; - var disc = 0; - itemCnt += q; - subTot += itemTot; - if (q >= 10) { - if (curItem.id === "p1") disc = 0.1; - else if (curItem.id === "p2") disc = 0.15; - else if (curItem.id === "p3") disc = 0.2; - else if (curItem.id === "p4") disc = 0.05; - else if (curItem.id === "p5") disc = 0.25; + var itemTotalCost = curItem.cost * amount; + var discountRate = 0; + itemCount += amount; + subTot += itemTotalCost; + if (amount >= 10) { + if (curItem.id === "p1") discountRate = 0.1; + else if (curItem.id === "p2") discountRate = 0.15; + else if (curItem.id === "p3") discountRate = 0.2; + else if (curItem.id === "p4") discountRate = 0.05; + else if (curItem.id === "p5") discountRate = 0.25; } - totalAmt += itemTot * (1 - disc); + totalAmount += itemTotalCost * (1 - discountRate); })(); } - let discRate = 0; - if (itemCnt >= 30) { - var bulkDisc = totalAmt * 0.25; - var itemDisc = subTot - totalAmt; + let discountRate = 0; + if (itemCount >= 30) { + var bulkDisc = totalAmount * 0.25; + var itemDisc = subTot - totalAmount; if (bulkDisc > itemDisc) { totalAmt = subTot * (1 - 0.25); - discRate = 0.25; + discountRate = 0.25; } else { - discRate = (subTot - totalAmt) / subTot; + discountRate = (subTot - totalAmount) / subTot; } } else { - discRate = (subTot - totalAmt) / subTot; + discountRate = (subTot - totalAmount) / subTot; } if (new Date().getDay() === 2) { - totalAmt *= 1 - 0.1; - discRate = Math.max(discRate, 0.1); + totalAmount *= 1 - 0.1; + discountRate = Math.max(discountRate, 0.1); } - sum.textContent = "총액: " + Math.round(totalAmt) + "원"; - if (discRate > 0) { + sum.textContent = "총액: " + Math.round(totalAmount) + "원"; + if (discountRate > 0) { var span = document.createElement("span"); span.className = "text-green-500 ml-2"; - span.textContent = "(" + (discRate * 100).toFixed(1) + "% 할인 적용)"; + span.textContent = "(" + (discountRate * 100).toFixed(1) + "% 할인 적용)"; sum.appendChild(span); } updateStockInfo(); renderBonusPts(); } const renderBonusPts = () => { - bonusPts = Math.floor(totalAmt / 1000); - var ptsTag = document.getElementById("loyalty-points"); - if (!ptsTag) { - ptsTag = document.createElement("span"); - ptsTag.id = "loyalty-points"; - ptsTag.className = "text-blue-500 ml-2"; - sum.appendChild(ptsTag); + bonusPoints = Math.floor(totalAmount / 1000); + var pointTag = document.getElementById("loyalty-points"); + if (!pointTag) { + pointTag = document.createElement("span"); + pointTag.id = "loyalty-points"; + pointTag.className = "text-blue-500 ml-2"; + sum.appendChild(pointTag); } - ptsTag.textContent = "(포인트: " + bonusPts + ")"; + pointTag.textContent = "(포인트: " + bonusPoints + ")"; }; function updateStockInfo() { var infoMsg = ""; - prodList.forEach(function (item) { - if (item.q < 5) { + PRODUCT_LIST.forEach(function (item) { + if (item.stock < 5) { infoMsg += item.name + ": " + - (item.q > 0 ? "재고 부족 (" + item.q + "개 남음)" : "품절") + + (item.stock > 0 ? "재고 부족 (" + item.stock + "개 남음)" : "품절") + "\n"; } }); @@ -167,18 +161,18 @@ function updateStockInfo() { main(); addBtn.addEventListener("click", function () { var selItem = sel.value; - var itemToAdd = prodList.find(function (p) { + var itemToAdd = PRODUCT_LIST.find(function (p) { return p.id === selItem; }); - if (itemToAdd && itemToAdd.q > 0) { + if (itemToAdd && itemToAdd.stock > 0) { var item = document.getElementById(itemToAdd.id); if (item) { var newQty = parseInt(item.querySelector("span").textContent.split("x ")[1]) + 1; - if (newQty <= itemToAdd.q) { + if (newQty <= itemToAdd.stock) { item.querySelector("span").textContent = - itemToAdd.name + " - " + itemToAdd.val + "원 x " + newQty; - itemToAdd.q--; + itemToAdd.name + " - " + itemToAdd.cost + "원 x " + newQty; + itemToAdd.stock--; } else { alert("재고가 부족합니다."); } @@ -216,7 +210,7 @@ cartDisp.addEventListener("click", function (event) { ) { var prodId = tgt.dataset.productId; var itemElem = document.getElementById(prodId); - var prod = prodList.find(function (p) { + var prod = PRODUCT_LIST.find(function (p) { return p.id === prodId; }); if (tgt.classList.contains("quantity-change")) { From 6133bd714986e0f4e442593a21a0d95a67ed78d5 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Thu, 9 Jan 2025 22:39:33 +0900 Subject: [PATCH 16/38] =?UTF-8?q?fix=20:PRODUCT=5FLIST=20key=20=EA=B0=92?= =?UTF-8?q?=20=EC=A4=91,=20stock=20=EB=B9=A0=EC=A7=84=20=EA=B3=B3=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index e98b8003..64eeab78 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -72,7 +72,7 @@ function updateSelOpts() { var opt = document.createElement("option"); opt.value = item.id; opt.textContent = item.name + " - " + item.cost + "원"; - if (item.q === 0) opt.disabled = true; + if (item.stock === 0) opt.disabled = true; sel.appendChild(opt); }); } @@ -184,7 +184,7 @@ addBtn.addEventListener("click", function () { "" + itemToAdd.name + " - " + - itemToAdd.val + + itemToAdd.cost + "원 x 1
" + '
'; cartDisp.appendChild(newItem); - itemToAdd.q--; + itemToAdd.stock--; } calcCart(); lastSel = selItem; @@ -221,17 +221,17 @@ cartDisp.addEventListener("click", function (event) { if ( newQty > 0 && newQty <= - prod.q + + prod.stock + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) ) { itemElem.querySelector("span").textContent = itemElem.querySelector("span").textContent.split("x ")[0] + "x " + newQty; - prod.q -= qtyChange; + prod.stock -= qtyChange; } else if (newQty <= 0) { itemElem.remove(); - prod.q -= qtyChange; + prod.stock -= qtyChange; } else { alert("재고가 부족합니다."); } @@ -239,7 +239,7 @@ cartDisp.addEventListener("click", function (event) { var remQty = parseInt( itemElem.querySelector("span").textContent.split("x ")[1] ); - prod.q += remQty; + prod.stock += remQty; itemElem.remove(); } calcCart(); From 68f965fd2f6eb3f3852918a2740a0e53a5d82913 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 00:41:10 +0900 Subject: [PATCH 17/38] =?UTF-8?q?fix=20:=20main.origin.js=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- src/{main.origin.js => main.original.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{main.origin.js => main.original.js} (100%) diff --git a/index.html b/index.html index 3570956f..0ee95158 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,6 @@
- + diff --git a/src/main.origin.js b/src/main.original.js similarity index 100% rename from src/main.origin.js rename to src/main.original.js From 5193d20278b5bfa5fa9c883545f3ab6261fabb96 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 00:41:40 +0900 Subject: [PATCH 18/38] =?UTF-8?q?chore=20:=20PRODUCT=5FLIST=20=EC=97=90=20?= =?UTF-8?q?discount=20=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/data/prodList.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/basic/data/prodList.js b/src/basic/data/prodList.js index f4abb1e4..c9a61771 100644 --- a/src/basic/data/prodList.js +++ b/src/basic/data/prodList.js @@ -1,7 +1,7 @@ export const PRODUCT_LIST = [ - { id: "p1", name: "상품1", cost: 10000, stock: 50 }, - { id: "p2", name: "상품2", cost: 20000, stock: 30 }, - { id: "p3", name: "상품3", cost: 30000, stock: 20 }, - { id: "p4", name: "상품4", cost: 15000, stock: 0 }, - { id: "p5", name: "상품5", cost: 25000, stock: 10 }, + { id: "p1", name: "상품1", cost: 10000, stock: 50, discount: 0.1 }, + { id: "p2", name: "상품2", cost: 20000, stock: 30, discount: 0.15 }, + { id: "p3", name: "상품3", cost: 30000, stock: 20, discount: 0.2 }, + { id: "p4", name: "상품4", cost: 15000, stock: 0, discount: 0.05 }, + { id: "p5", name: "상품5", cost: 25000, stock: 10, discount: 0.25 }, ]; From 3d33a5c37c62c7476f5b72d5a0169859095bd4f6 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 00:42:23 +0900 Subject: [PATCH 19/38] =?UTF-8?q?refactor=20:=20calcCart=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 73 +++++++++++-------------------------- src/basic/util/calculate.js | 22 +++++++++++ 2 files changed, 44 insertions(+), 51 deletions(-) create mode 100644 src/basic/util/calculate.js diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 64eeab78..15cba906 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -1,4 +1,5 @@ import { PRODUCT_LIST } from "./data/prodList"; +import { calculateCartTotals } from "./util/calculate"; var prodList, sel, addBtn, cartDisp, sum, stockInfo; var lastSel, bonusPoints = 0, @@ -78,62 +79,32 @@ function updateSelOpts() { } function calcCart() { totalAmount = 0; - itemCount = 0; - var cartItems = cartDisp.children; - var subTot = 0; - for (var i = 0; i < cartItems.length; i++) { - (function () { - var curItem; - for (var j = 0; j < PRODUCT_LIST.length; j++) { - if (PRODUCT_LIST[j].id === cartItems[i].id) { - curItem = PRODUCT_LIST[j]; - break; - } - } - var amount = parseInt( - cartItems[i].querySelector("span").textContent.split("x ")[1] - ); - var itemTotalCost = curItem.cost * amount; - var discountRate = 0; - itemCount += amount; - subTot += itemTotalCost; - if (amount >= 10) { - if (curItem.id === "p1") discountRate = 0.1; - else if (curItem.id === "p2") discountRate = 0.15; - else if (curItem.id === "p3") discountRate = 0.2; - else if (curItem.id === "p4") discountRate = 0.05; - else if (curItem.id === "p5") discountRate = 0.25; - } - totalAmount += itemTotalCost * (1 - discountRate); - })(); - } - let discountRate = 0; - if (itemCount >= 30) { - var bulkDisc = totalAmount * 0.25; - var itemDisc = subTot - totalAmount; - if (bulkDisc > itemDisc) { - totalAmt = subTot * (1 - 0.25); - discountRate = 0.25; - } else { - discountRate = (subTot - totalAmount) / subTot; - } - } else { - discountRate = (subTot - totalAmount) / subTot; - } - if (new Date().getDay() === 2) { - totalAmount *= 1 - 0.1; - discountRate = Math.max(discountRate, 0.1); - } + const cartItems = Array.from(cartDisp.children); + const { subTotal, totalDiscount, itemCount, discountRate } = + calculateCartTotals(cartItems); + totalAmount = subTotal - totalDiscount; + + const bulkDiscountRate = itemCount >= 30 ? 0.25 : discountRate; + const tuesdayDiscountRate = new Date().getDay() === 2 ? 0.1 : 0; + + const finalDiscountRate = Math.max(bulkDiscountRate, tuesdayDiscountRate); + totalAmount *= 1 - finalDiscountRate; + + updateSummaryUI(totalAmount, finalDiscountRate); + updateStockInfo(); + renderBonusPts(); +} + +const updateSummaryUI = (totalAmount, finalDiscountRate) => { sum.textContent = "총액: " + Math.round(totalAmount) + "원"; - if (discountRate > 0) { + + if (finalDiscountRate > 0) { var span = document.createElement("span"); span.className = "text-green-500 ml-2"; - span.textContent = "(" + (discountRate * 100).toFixed(1) + "% 할인 적용)"; + span.textContent = `(${(finalDiscountRate * 100).toFixed(1)}% 할인 적용)`; sum.appendChild(span); } - updateStockInfo(); - renderBonusPts(); -} +}; const renderBonusPts = () => { bonusPoints = Math.floor(totalAmount / 1000); var pointTag = document.getElementById("loyalty-points"); diff --git a/src/basic/util/calculate.js b/src/basic/util/calculate.js new file mode 100644 index 00000000..dfcb489f --- /dev/null +++ b/src/basic/util/calculate.js @@ -0,0 +1,22 @@ +import { PRODUCT_LIST } from "../data/prodList"; + +export const calculateCartTotals = (cartItems) => { + let subTotal = 0; + let totalDiscount = 0; + let itemCount = 0; + let discountRate = 0; + cartItems.forEach((item) => { + const curItem = PRODUCT_LIST.find((product) => product.id === item.id); + const amount = parseInt( + item.querySelector("span").textContent.split("x ")[1] + ); + + const itemTotalCost = curItem.cost * amount; + discountRate = amount >= 10 ? curItem.discount : 0; + + subTotal += itemTotalCost; + totalDiscount += itemTotalCost * discountRate; + itemCount += amount; + }); + return { subTotal, totalDiscount, itemCount, discountRate }; +}; From 17556cff4ecaad62cfd12e696514782824c984fe Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 00:48:27 +0900 Subject: [PATCH 20/38] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A0=84=EC=97=AD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 15cba906..492cec00 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -2,9 +2,7 @@ import { PRODUCT_LIST } from "./data/prodList"; import { calculateCartTotals } from "./util/calculate"; var prodList, sel, addBtn, cartDisp, sum, stockInfo; var lastSel, - bonusPoints = 0, - totalAmount = 0, - itemCount = 0; + bonusPoints = 0; function main() { var root = document.getElementById("app"); let cont = document.createElement("div"); From 901f2abf23921211d6ff7704a81af689436e78e6 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 01:22:51 +0900 Subject: [PATCH 21/38] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A0=84=EC=97=AD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 492cec00..0245b130 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -1,8 +1,9 @@ import { PRODUCT_LIST } from "./data/prodList"; import { calculateCartTotals } from "./util/calculate"; -var prodList, sel, addBtn, cartDisp, sum, stockInfo; +var sel, addBtn, cartDisp, sum, stockInfo; var lastSel, bonusPoints = 0; + function main() { var root = document.getElementById("app"); let cont = document.createElement("div"); From 9a31f8a0c03821bedd3d49152fee17b7a66493a1 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 01:24:00 +0900 Subject: [PATCH 22/38] =?UTF-8?q?refactor=20:=20UI=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=A1=9C=EC=A7=81=EC=97=90=20=EB=B3=B4?= =?UTF-8?q?=EB=84=88=EC=8A=A4=ED=8F=AC=EC=9D=B8=ED=8A=B8,=EC=9E=AC?= =?UTF-8?q?=EA=B3=A0=ED=98=84=ED=99=A9=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 0245b130..6c812e86 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -77,7 +77,7 @@ function updateSelOpts() { }); } function calcCart() { - totalAmount = 0; + let totalAmount = 0; const cartItems = Array.from(cartDisp.children); const { subTotal, totalDiscount, itemCount, discountRate } = calculateCartTotals(cartItems); @@ -90,8 +90,6 @@ function calcCart() { totalAmount *= 1 - finalDiscountRate; updateSummaryUI(totalAmount, finalDiscountRate); - updateStockInfo(); - renderBonusPts(); } const updateSummaryUI = (totalAmount, finalDiscountRate) => { @@ -103,8 +101,10 @@ const updateSummaryUI = (totalAmount, finalDiscountRate) => { span.textContent = `(${(finalDiscountRate * 100).toFixed(1)}% 할인 적용)`; sum.appendChild(span); } + updateStockInfo(); + renderBonusPts(totalAmount); }; -const renderBonusPts = () => { +const renderBonusPts = (totalAmount) => { bonusPoints = Math.floor(totalAmount / 1000); var pointTag = document.getElementById("loyalty-points"); if (!pointTag) { From e25ef84a0b0e24ca7211ccfe381c0d03363fdcf4 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 08:58:21 +0900 Subject: [PATCH 23/38] =?UTF-8?q?fix=20:=20PRODUCT=5FLIST=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EC=95=88=EB=90=9C=20=EA=B3=B3=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 6c812e86..bd85e0ca 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -41,9 +41,10 @@ function main() { calcCart(); setTimeout(function () { setInterval(function () { - var luckyItem = prodList[Math.floor(Math.random() * prodList.length)]; - if (Math.random() < 0.3 && luckyItem.q > 0) { - luckyItem.val = Math.round(luckyItem.val * 0.8); + var luckyItem = + PRODUCT_LIST[Math.floor(Math.random() * PRODUCT_LIST.length)]; + if (Math.random() < 0.3 && luckyItem.stock > 0) { + luckyItem.cost = Math.round(luckyItem.cost * 0.8); alert("번개세일! " + luckyItem.name + "이(가) 20% 할인 중입니다!"); updateSelOpts(); } @@ -52,14 +53,14 @@ function main() { setTimeout(function () { setInterval(function () { if (lastSel) { - var suggest = prodList.find(function (item) { - return item.id !== lastSel && item.q > 0; + var suggest = PRODUCT_LIST.find(function (item) { + return item.id !== lastSel && item.stock > 0; }); if (suggest) { alert( suggest.name + "은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!" ); - suggest.val = Math.round(suggest.val * 0.95); + suggest.cost = Math.round(suggest.cost * 0.95); updateSelOpts(); } } From 11d837d1dd0bb62398d251686ba506a390c3053a Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 09:00:53 +0900 Subject: [PATCH 24/38] =?UTF-8?q?fix=20:=20=EC=82=AD=EC=A0=9C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/events/cartEvent.js | 96 +++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 9 deletions(-) diff --git a/src/basic/events/cartEvent.js b/src/basic/events/cartEvent.js index 2e33e3ee..07cade36 100644 --- a/src/basic/events/cartEvent.js +++ b/src/basic/events/cartEvent.js @@ -1,5 +1,4 @@ import { PRODUCT_LIST } from "../data/prodList"; - export const handleAddToCart = (target) => { const productId = target.dataset.productId; const product = PRODUCT_LIST.find((item) => item.id === productId); @@ -33,13 +32,92 @@ export const handleAddToCart = (target) => { }; export const handleDeleteToCart = (target) => { - const productId = target.dataset.productId; - const product = PRODUCT_LIST.find((item) => item.id === productId); - var itemElem = document.getElementById(productId); + var prodId = target.dataset.productId; + var itemElem = document.getElementById(prodId); + var product = PRODUCT_LIST.find((product) => { + return product.id === prodId; + }); + if (target.classList.contains("quantity-change")) { + var stockChange = parseInt(target.dataset.change); + var newStock = + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + + stockChange; + if ( + newStock > 0 && + newStock <= + product.stock + + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + ) { + itemElem.querySelector("span").textContent = + itemElem.querySelector("span").textContent.split("x ")[0] + + "x " + + newStock; + product.stock -= stockChange; + } else if (newStock <= 0) { + itemElem.remove(); + product.stock -= stockChange; + } else { + alert("재고가 부족합니다."); + } + } else if (target.classList.contains("remove-item")) { + var remQty = parseInt( + itemElem.querySelector("span").textContent.split("x ")[1] + ); + product.stock += remQty; + itemElem.remove(); + } +}; + +export const updateCartList = (itemToAdd) => { + if (itemToAdd && itemToAdd.stock > 0) { + var item = document.getElementById(itemToAdd.id); + if (item) { + var newQty = + parseInt(item.querySelector("span").textContent.split("x ")[1]) + 1; + if (newQty <= itemToAdd.stock) { + item.querySelector("span").textContent = + itemToAdd.name + " - " + itemToAdd.cost + "원 x " + newQty; + itemToAdd.stock--; + } else { + alert("재고가 부족합니다."); + } + } else { + return setNewItemUI(itemToAdd); + } + } +}; +const setNewItemUI = (itemToAdd) => { + let newItem = document.createElement("div"); + newItem.id = itemToAdd.id; + newItem.className = "flex justify-between items-center mb-2"; + newItem.innerHTML = Item(itemToAdd).trim(); + itemToAdd.stock--; + return newItem; +}; - var removeQuantity = parseInt( - itemElem.querySelector("span").textContent.split("x ")[1] - ); - product.stock += removeQuantity; - itemElem.remove(); +export const Item = (itemToAdd) => { + return ` + ${itemToAdd.name} - ${itemToAdd.cost}원 x 1 +
+ + + +
`; }; From 7b75f45078a0a4636b51ddb0373d7bf71dbcd671 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 09:02:27 +0900 Subject: [PATCH 25/38] =?UTF-8?q?refactor=20:=20addBtn=20=ED=95=B8?= =?UTF-8?q?=EB=93=A4=EB=9F=AC=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 44 ++++++++--------------------------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index bd85e0ca..10daa7f3 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -1,5 +1,6 @@ import { PRODUCT_LIST } from "./data/prodList"; import { calculateCartTotals } from "./util/calculate"; +import { handleDeleteToCart, updateCartList } from "./events/cartEvent"; var sel, addBtn, cartDisp, sum, stockInfo; var lastSel, bonusPoints = 0; @@ -130,49 +131,20 @@ function updateStockInfo() { stockInfo.textContent = infoMsg; } main(); + addBtn.addEventListener("click", function () { var selItem = sel.value; var itemToAdd = PRODUCT_LIST.find(function (p) { return p.id === selItem; }); - if (itemToAdd && itemToAdd.stock > 0) { - var item = document.getElementById(itemToAdd.id); - if (item) { - var newQty = - parseInt(item.querySelector("span").textContent.split("x ")[1]) + 1; - if (newQty <= itemToAdd.stock) { - item.querySelector("span").textContent = - itemToAdd.name + " - " + itemToAdd.cost + "원 x " + newQty; - itemToAdd.stock--; - } else { - alert("재고가 부족합니다."); - } - } else { - var newItem = document.createElement("div"); - newItem.id = itemToAdd.id; - newItem.className = "flex justify-between items-center mb-2"; - newItem.innerHTML = - "" + - itemToAdd.name + - " - " + - itemToAdd.cost + - "원 x 1
" + - '' + - '' + - '
'; - cartDisp.appendChild(newItem); - itemToAdd.stock--; - } - calcCart(); - lastSel = selItem; + const newItem = updateCartList(itemToAdd); + if (newItem) { + cartDisp.appendChild(newItem); + lastSel = itemToAdd; } + calcCart(); }); + cartDisp.addEventListener("click", function (event) { var tgt = event.target; if ( From a3d5a3fd115afb0d5bf2c18170556b9c430c3ba3 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 09:03:01 +0900 Subject: [PATCH 26/38] =?UTF-8?q?refactor=20:=20cartDisp=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic/main.basic.js | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 10daa7f3..4bd05f97 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -146,45 +146,12 @@ addBtn.addEventListener("click", function () { }); cartDisp.addEventListener("click", function (event) { - var tgt = event.target; + var target = event.target; if ( - tgt.classList.contains("quantity-change") || - tgt.classList.contains("remove-item") + target.classList.contains("quantity-change") || + target.classList.contains("remove-item") ) { - var prodId = tgt.dataset.productId; - var itemElem = document.getElementById(prodId); - var prod = PRODUCT_LIST.find(function (p) { - return p.id === prodId; - }); - if (tgt.classList.contains("quantity-change")) { - var qtyChange = parseInt(tgt.dataset.change); - var newQty = - parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + - qtyChange; - if ( - newQty > 0 && - newQty <= - prod.stock + - parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) - ) { - itemElem.querySelector("span").textContent = - itemElem.querySelector("span").textContent.split("x ")[0] + - "x " + - newQty; - prod.stock -= qtyChange; - } else if (newQty <= 0) { - itemElem.remove(); - prod.stock -= qtyChange; - } else { - alert("재고가 부족합니다."); - } - } else if (tgt.classList.contains("remove-item")) { - var remQty = parseInt( - itemElem.querySelector("span").textContent.split("x ")[1] - ); - prod.stock += remQty; - itemElem.remove(); - } + handleDeleteToCart(target); calcCart(); } }); From 5ef95694a75b15858b231b2bdf7a3fd975c16407 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 09:19:47 +0900 Subject: [PATCH 27/38] =?UTF-8?q?chore=20:=20react=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/package.json b/package.json index 02cd2326..a7490be2 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,14 @@ "jsdom": "^25.0.0", "vite": "^5.1.0", "vitest": "^2.1.1" + }, + "dependencies": { + "@testing-library/react": "^16.1.0", + "@types/react": "^19.0.4", + "@types/react-dom": "^19.0.2", + "@vitejs/plugin-react": "^4.3.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "typescript": "^5.7.3" } } From c298760dfed306d618e1a2d9ef7f1c3e84c49763 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 09:43:33 +0900 Subject: [PATCH 28/38] =?UTF-8?q?chore=20:=20eslint=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..7517e361 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,19 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint", "react", "react-hooks"], + "rules": { + "react/react-in-jsx-scope": "off" + }, + "settings": { + "react": { + "version": "detect" + } + } +} From da4e8303e520268ab6176e9762e091c57a4b4206 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 09:45:56 +0900 Subject: [PATCH 29/38] =?UTF-8?q?chore=20:=20=EC=8B=AC=ED=99=94=EA=B3=BC?= =?UTF-8?q?=EC=A0=9C=20=EC=85=8B=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/advanced/data/prodList.js | 7 ++ src/advanced/events/cartEvent.js | 123 ++++++++++++++++++++++++ src/advanced/main.advanced.js | 157 +++++++++++++++++++++++++++++++ src/advanced/util/calculate.js | 22 +++++ src/advanced/util/domUtils.js | 16 ++++ 5 files changed, 325 insertions(+) create mode 100644 src/advanced/data/prodList.js create mode 100644 src/advanced/events/cartEvent.js create mode 100644 src/advanced/util/calculate.js create mode 100644 src/advanced/util/domUtils.js diff --git a/src/advanced/data/prodList.js b/src/advanced/data/prodList.js new file mode 100644 index 00000000..c9a61771 --- /dev/null +++ b/src/advanced/data/prodList.js @@ -0,0 +1,7 @@ +export const PRODUCT_LIST = [ + { id: "p1", name: "상품1", cost: 10000, stock: 50, discount: 0.1 }, + { id: "p2", name: "상품2", cost: 20000, stock: 30, discount: 0.15 }, + { id: "p3", name: "상품3", cost: 30000, stock: 20, discount: 0.2 }, + { id: "p4", name: "상품4", cost: 15000, stock: 0, discount: 0.05 }, + { id: "p5", name: "상품5", cost: 25000, stock: 10, discount: 0.25 }, +]; diff --git a/src/advanced/events/cartEvent.js b/src/advanced/events/cartEvent.js new file mode 100644 index 00000000..07cade36 --- /dev/null +++ b/src/advanced/events/cartEvent.js @@ -0,0 +1,123 @@ +import { PRODUCT_LIST } from "../data/prodList"; +export const handleAddToCart = (target) => { + const productId = target.dataset.productId; + const product = PRODUCT_LIST.find((item) => item.id === productId); + console.log(target); + var itemElem = document.getElementById(productId); + + if (target.classList.contains("quantity-change")) { + var quantityChange = parseInt(target.dataset.change); + var newQuantity = + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + + quantityChange; + + if ( + newQuantity > 0 && + newQuantity <= + product.stock + + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + ) { + itemElem.querySelector("span").textContent = + itemElem.querySelector("span").textContent.split("x ")[0] + + "x " + + newQuantity; + product.stock -= quantityChange; + } else if (newQuantity <= 0) { + itemElem.remove(); + product.stock -= quantityChange; + } else { + alert("재고가 부족합니다."); + } + } +}; + +export const handleDeleteToCart = (target) => { + var prodId = target.dataset.productId; + var itemElem = document.getElementById(prodId); + var product = PRODUCT_LIST.find((product) => { + return product.id === prodId; + }); + if (target.classList.contains("quantity-change")) { + var stockChange = parseInt(target.dataset.change); + var newStock = + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + + stockChange; + if ( + newStock > 0 && + newStock <= + product.stock + + parseInt(itemElem.querySelector("span").textContent.split("x ")[1]) + ) { + itemElem.querySelector("span").textContent = + itemElem.querySelector("span").textContent.split("x ")[0] + + "x " + + newStock; + product.stock -= stockChange; + } else if (newStock <= 0) { + itemElem.remove(); + product.stock -= stockChange; + } else { + alert("재고가 부족합니다."); + } + } else if (target.classList.contains("remove-item")) { + var remQty = parseInt( + itemElem.querySelector("span").textContent.split("x ")[1] + ); + product.stock += remQty; + itemElem.remove(); + } +}; + +export const updateCartList = (itemToAdd) => { + if (itemToAdd && itemToAdd.stock > 0) { + var item = document.getElementById(itemToAdd.id); + if (item) { + var newQty = + parseInt(item.querySelector("span").textContent.split("x ")[1]) + 1; + if (newQty <= itemToAdd.stock) { + item.querySelector("span").textContent = + itemToAdd.name + " - " + itemToAdd.cost + "원 x " + newQty; + itemToAdd.stock--; + } else { + alert("재고가 부족합니다."); + } + } else { + return setNewItemUI(itemToAdd); + } + } +}; +const setNewItemUI = (itemToAdd) => { + let newItem = document.createElement("div"); + newItem.id = itemToAdd.id; + newItem.className = "flex justify-between items-center mb-2"; + newItem.innerHTML = Item(itemToAdd).trim(); + itemToAdd.stock--; + return newItem; +}; + +export const Item = (itemToAdd) => { + return ` + ${itemToAdd.name} - ${itemToAdd.cost}원 x 1 +
+ + + +
`; +}; diff --git a/src/advanced/main.advanced.js b/src/advanced/main.advanced.js index e69de29b..4bd05f97 100644 --- a/src/advanced/main.advanced.js +++ b/src/advanced/main.advanced.js @@ -0,0 +1,157 @@ +import { PRODUCT_LIST } from "./data/prodList"; +import { calculateCartTotals } from "./util/calculate"; +import { handleDeleteToCart, updateCartList } from "./events/cartEvent"; +var sel, addBtn, cartDisp, sum, stockInfo; +var lastSel, + bonusPoints = 0; + +function main() { + var root = document.getElementById("app"); + let cont = document.createElement("div"); + var wrap = document.createElement("div"); + let hTxt = document.createElement("h1"); + cartDisp = document.createElement("div"); + sum = document.createElement("div"); + sel = document.createElement("select"); + addBtn = document.createElement("button"); + stockInfo = document.createElement("div"); + cartDisp.id = "cart-items"; + sum.id = "cart-total"; + sel.id = "product-select"; + addBtn.id = "add-to-cart"; + stockInfo.id = "stock-status"; + cont.className = "bg-gray-100 p-8"; + wrap.className = + "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8"; + hTxt.className = "text-2xl font-bold mb-4"; + sum.className = "text-xl font-bold my-4"; + sel.className = "border rounded p-2 mr-2"; + addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; + stockInfo.className = "text-sm text-gray-500 mt-2"; + hTxt.textContent = "장바구니"; + addBtn.textContent = "추가"; + updateSelOpts(); + wrap.appendChild(hTxt); + wrap.appendChild(cartDisp); + wrap.appendChild(sum); + wrap.appendChild(sel); + wrap.appendChild(addBtn); + wrap.appendChild(stockInfo); + cont.appendChild(wrap); + root.appendChild(cont); + calcCart(); + setTimeout(function () { + setInterval(function () { + var luckyItem = + PRODUCT_LIST[Math.floor(Math.random() * PRODUCT_LIST.length)]; + if (Math.random() < 0.3 && luckyItem.stock > 0) { + luckyItem.cost = Math.round(luckyItem.cost * 0.8); + alert("번개세일! " + luckyItem.name + "이(가) 20% 할인 중입니다!"); + updateSelOpts(); + } + }, 30000); + }, Math.random() * 10000); + setTimeout(function () { + setInterval(function () { + if (lastSel) { + var suggest = PRODUCT_LIST.find(function (item) { + return item.id !== lastSel && item.stock > 0; + }); + if (suggest) { + alert( + suggest.name + "은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!" + ); + suggest.cost = Math.round(suggest.cost * 0.95); + updateSelOpts(); + } + } + }, 60000); + }, Math.random() * 20000); +} +function updateSelOpts() { + sel.innerHTML = ""; + PRODUCT_LIST.forEach(function (item) { + var opt = document.createElement("option"); + opt.value = item.id; + opt.textContent = item.name + " - " + item.cost + "원"; + if (item.stock === 0) opt.disabled = true; + sel.appendChild(opt); + }); +} +function calcCart() { + let totalAmount = 0; + const cartItems = Array.from(cartDisp.children); + const { subTotal, totalDiscount, itemCount, discountRate } = + calculateCartTotals(cartItems); + totalAmount = subTotal - totalDiscount; + + const bulkDiscountRate = itemCount >= 30 ? 0.25 : discountRate; + const tuesdayDiscountRate = new Date().getDay() === 2 ? 0.1 : 0; + + const finalDiscountRate = Math.max(bulkDiscountRate, tuesdayDiscountRate); + totalAmount *= 1 - finalDiscountRate; + + updateSummaryUI(totalAmount, finalDiscountRate); +} + +const updateSummaryUI = (totalAmount, finalDiscountRate) => { + sum.textContent = "총액: " + Math.round(totalAmount) + "원"; + + if (finalDiscountRate > 0) { + var span = document.createElement("span"); + span.className = "text-green-500 ml-2"; + span.textContent = `(${(finalDiscountRate * 100).toFixed(1)}% 할인 적용)`; + sum.appendChild(span); + } + updateStockInfo(); + renderBonusPts(totalAmount); +}; +const renderBonusPts = (totalAmount) => { + bonusPoints = Math.floor(totalAmount / 1000); + var pointTag = document.getElementById("loyalty-points"); + if (!pointTag) { + pointTag = document.createElement("span"); + pointTag.id = "loyalty-points"; + pointTag.className = "text-blue-500 ml-2"; + sum.appendChild(pointTag); + } + pointTag.textContent = "(포인트: " + bonusPoints + ")"; +}; +function updateStockInfo() { + var infoMsg = ""; + PRODUCT_LIST.forEach(function (item) { + if (item.stock < 5) { + infoMsg += + item.name + + ": " + + (item.stock > 0 ? "재고 부족 (" + item.stock + "개 남음)" : "품절") + + "\n"; + } + }); + stockInfo.textContent = infoMsg; +} +main(); + +addBtn.addEventListener("click", function () { + var selItem = sel.value; + var itemToAdd = PRODUCT_LIST.find(function (p) { + return p.id === selItem; + }); + const newItem = updateCartList(itemToAdd); + if (newItem) { + cartDisp.appendChild(newItem); + lastSel = itemToAdd; + } + calcCart(); +}); + +cartDisp.addEventListener("click", function (event) { + var target = event.target; + if ( + target.classList.contains("quantity-change") || + target.classList.contains("remove-item") + ) { + handleDeleteToCart(target); + calcCart(); + } +}); diff --git a/src/advanced/util/calculate.js b/src/advanced/util/calculate.js new file mode 100644 index 00000000..dfcb489f --- /dev/null +++ b/src/advanced/util/calculate.js @@ -0,0 +1,22 @@ +import { PRODUCT_LIST } from "../data/prodList"; + +export const calculateCartTotals = (cartItems) => { + let subTotal = 0; + let totalDiscount = 0; + let itemCount = 0; + let discountRate = 0; + cartItems.forEach((item) => { + const curItem = PRODUCT_LIST.find((product) => product.id === item.id); + const amount = parseInt( + item.querySelector("span").textContent.split("x ")[1] + ); + + const itemTotalCost = curItem.cost * amount; + discountRate = amount >= 10 ? curItem.discount : 0; + + subTotal += itemTotalCost; + totalDiscount += itemTotalCost * discountRate; + itemCount += amount; + }); + return { subTotal, totalDiscount, itemCount, discountRate }; +}; diff --git a/src/advanced/util/domUtils.js b/src/advanced/util/domUtils.js new file mode 100644 index 00000000..6d718fd1 --- /dev/null +++ b/src/advanced/util/domUtils.js @@ -0,0 +1,16 @@ +export const createElement = ( + tag, + className = "", + textContent = "", + id = "" +) => { + const elem = document.createElement(tag); + if (className) elem.className = className; + if (textContent) elem.textContent = textContent; + if (id) elem.id = id; + return elem; +}; + +export const appendChildren = (parent, children) => { + children.forEach((child) => parent.appendChild(child)); +}; From e000182d34d99f73c63e9096199b57d5c683e2ce Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 10:37:31 +0900 Subject: [PATCH 30/38] =?UTF-8?q?chore=20:=20ts=20=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/advanced/__tests__/advanced.test.js | 5 - src/advanced/__tests__/advanced.test.tsx | 12 ++ src/advanced/main.advanced.js | 157 ---------------- src/advanced/main.advanced.tsx | 167 ++++++++++++++++++ .../prodList.js => src/data/prodList.ts} | 0 src/advanced/{ => src}/events/cartEvent.js | 0 6 files changed, 179 insertions(+), 162 deletions(-) delete mode 100644 src/advanced/__tests__/advanced.test.js create mode 100644 src/advanced/__tests__/advanced.test.tsx delete mode 100644 src/advanced/main.advanced.js create mode 100644 src/advanced/main.advanced.tsx rename src/advanced/{data/prodList.js => src/data/prodList.ts} (100%) rename src/advanced/{ => src}/events/cartEvent.js (100%) diff --git a/src/advanced/__tests__/advanced.test.js b/src/advanced/__tests__/advanced.test.js deleted file mode 100644 index 717d259f..00000000 --- a/src/advanced/__tests__/advanced.test.js +++ /dev/null @@ -1,5 +0,0 @@ -import {describe} from "vitest"; - -describe('advanced test', () => { - -}); \ No newline at end of file diff --git a/src/advanced/__tests__/advanced.test.tsx b/src/advanced/__tests__/advanced.test.tsx new file mode 100644 index 00000000..08912542 --- /dev/null +++ b/src/advanced/__tests__/advanced.test.tsx @@ -0,0 +1,12 @@ +import React from "react"; // React import 추가 +import { render, screen } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import { App } from "../src/App"; + +describe("advanced test", () => { + it("초기 상태: 상품 목록이 올바르게 렌더링 되는지 확인", () => { + render(); + const select = screen.getByRole("combobox"); + expect(select).toBeInTheDocument(); + }); +}); diff --git a/src/advanced/main.advanced.js b/src/advanced/main.advanced.js deleted file mode 100644 index 4bd05f97..00000000 --- a/src/advanced/main.advanced.js +++ /dev/null @@ -1,157 +0,0 @@ -import { PRODUCT_LIST } from "./data/prodList"; -import { calculateCartTotals } from "./util/calculate"; -import { handleDeleteToCart, updateCartList } from "./events/cartEvent"; -var sel, addBtn, cartDisp, sum, stockInfo; -var lastSel, - bonusPoints = 0; - -function main() { - var root = document.getElementById("app"); - let cont = document.createElement("div"); - var wrap = document.createElement("div"); - let hTxt = document.createElement("h1"); - cartDisp = document.createElement("div"); - sum = document.createElement("div"); - sel = document.createElement("select"); - addBtn = document.createElement("button"); - stockInfo = document.createElement("div"); - cartDisp.id = "cart-items"; - sum.id = "cart-total"; - sel.id = "product-select"; - addBtn.id = "add-to-cart"; - stockInfo.id = "stock-status"; - cont.className = "bg-gray-100 p-8"; - wrap.className = - "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8"; - hTxt.className = "text-2xl font-bold mb-4"; - sum.className = "text-xl font-bold my-4"; - sel.className = "border rounded p-2 mr-2"; - addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; - stockInfo.className = "text-sm text-gray-500 mt-2"; - hTxt.textContent = "장바구니"; - addBtn.textContent = "추가"; - updateSelOpts(); - wrap.appendChild(hTxt); - wrap.appendChild(cartDisp); - wrap.appendChild(sum); - wrap.appendChild(sel); - wrap.appendChild(addBtn); - wrap.appendChild(stockInfo); - cont.appendChild(wrap); - root.appendChild(cont); - calcCart(); - setTimeout(function () { - setInterval(function () { - var luckyItem = - PRODUCT_LIST[Math.floor(Math.random() * PRODUCT_LIST.length)]; - if (Math.random() < 0.3 && luckyItem.stock > 0) { - luckyItem.cost = Math.round(luckyItem.cost * 0.8); - alert("번개세일! " + luckyItem.name + "이(가) 20% 할인 중입니다!"); - updateSelOpts(); - } - }, 30000); - }, Math.random() * 10000); - setTimeout(function () { - setInterval(function () { - if (lastSel) { - var suggest = PRODUCT_LIST.find(function (item) { - return item.id !== lastSel && item.stock > 0; - }); - if (suggest) { - alert( - suggest.name + "은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!" - ); - suggest.cost = Math.round(suggest.cost * 0.95); - updateSelOpts(); - } - } - }, 60000); - }, Math.random() * 20000); -} -function updateSelOpts() { - sel.innerHTML = ""; - PRODUCT_LIST.forEach(function (item) { - var opt = document.createElement("option"); - opt.value = item.id; - opt.textContent = item.name + " - " + item.cost + "원"; - if (item.stock === 0) opt.disabled = true; - sel.appendChild(opt); - }); -} -function calcCart() { - let totalAmount = 0; - const cartItems = Array.from(cartDisp.children); - const { subTotal, totalDiscount, itemCount, discountRate } = - calculateCartTotals(cartItems); - totalAmount = subTotal - totalDiscount; - - const bulkDiscountRate = itemCount >= 30 ? 0.25 : discountRate; - const tuesdayDiscountRate = new Date().getDay() === 2 ? 0.1 : 0; - - const finalDiscountRate = Math.max(bulkDiscountRate, tuesdayDiscountRate); - totalAmount *= 1 - finalDiscountRate; - - updateSummaryUI(totalAmount, finalDiscountRate); -} - -const updateSummaryUI = (totalAmount, finalDiscountRate) => { - sum.textContent = "총액: " + Math.round(totalAmount) + "원"; - - if (finalDiscountRate > 0) { - var span = document.createElement("span"); - span.className = "text-green-500 ml-2"; - span.textContent = `(${(finalDiscountRate * 100).toFixed(1)}% 할인 적용)`; - sum.appendChild(span); - } - updateStockInfo(); - renderBonusPts(totalAmount); -}; -const renderBonusPts = (totalAmount) => { - bonusPoints = Math.floor(totalAmount / 1000); - var pointTag = document.getElementById("loyalty-points"); - if (!pointTag) { - pointTag = document.createElement("span"); - pointTag.id = "loyalty-points"; - pointTag.className = "text-blue-500 ml-2"; - sum.appendChild(pointTag); - } - pointTag.textContent = "(포인트: " + bonusPoints + ")"; -}; -function updateStockInfo() { - var infoMsg = ""; - PRODUCT_LIST.forEach(function (item) { - if (item.stock < 5) { - infoMsg += - item.name + - ": " + - (item.stock > 0 ? "재고 부족 (" + item.stock + "개 남음)" : "품절") + - "\n"; - } - }); - stockInfo.textContent = infoMsg; -} -main(); - -addBtn.addEventListener("click", function () { - var selItem = sel.value; - var itemToAdd = PRODUCT_LIST.find(function (p) { - return p.id === selItem; - }); - const newItem = updateCartList(itemToAdd); - if (newItem) { - cartDisp.appendChild(newItem); - lastSel = itemToAdd; - } - calcCart(); -}); - -cartDisp.addEventListener("click", function (event) { - var target = event.target; - if ( - target.classList.contains("quantity-change") || - target.classList.contains("remove-item") - ) { - handleDeleteToCart(target); - calcCart(); - } -}); diff --git a/src/advanced/main.advanced.tsx b/src/advanced/main.advanced.tsx new file mode 100644 index 00000000..f3044502 --- /dev/null +++ b/src/advanced/main.advanced.tsx @@ -0,0 +1,167 @@ +// import { PRODUCT_LIST } from "./src/data/prodList"; +// import { calculateCartTotals } from "./src/util/calculate"; +// import { handleDeleteToCart, updateCartList } from "./src/events/cartEvent"; +// var sel, addBtn, cartDisp, sum, stockInfo; +// var lastSel, +// bonusPoints = 0; + +// function main() { +// var root = document.getElementById("app"); +// let cont = document.createElement("div"); +// var wrap = document.createElement("div"); +// let hTxt = document.createElement("h1"); +// cartDisp = document.createElement("div"); +// sum = document.createElement("div"); +// sel = document.createElement("select"); +// addBtn = document.createElement("button"); +// stockInfo = document.createElement("div"); +// cartDisp.id = "cart-items"; +// sum.id = "cart-total"; +// sel.id = "product-select"; +// addBtn.id = "add-to-cart"; +// stockInfo.id = "stock-status"; +// cont.className = "bg-gray-100 p-8"; +// wrap.className = +// "max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-8"; +// hTxt.className = "text-2xl font-bold mb-4"; +// sum.className = "text-xl font-bold my-4"; +// sel.className = "border rounded p-2 mr-2"; +// addBtn.className = "bg-blue-500 text-white px-4 py-2 rounded"; +// stockInfo.className = "text-sm text-gray-500 mt-2"; +// hTxt.textContent = "장바구니"; +// addBtn.textContent = "추가"; +// updateSelOpts(); +// wrap.appendChild(hTxt); +// wrap.appendChild(cartDisp); +// wrap.appendChild(sum); +// wrap.appendChild(sel); +// wrap.appendChild(addBtn); +// wrap.appendChild(stockInfo); +// cont.appendChild(wrap); +// root.appendChild(cont); +// calcCart(); +// setTimeout(function () { +// setInterval(function () { +// var luckyItem = +// PRODUCT_LIST[Math.floor(Math.random() * PRODUCT_LIST.length)]; +// if (Math.random() < 0.3 && luckyItem.stock > 0) { +// luckyItem.cost = Math.round(luckyItem.cost * 0.8); +// alert("번개세일! " + luckyItem.name + "이(가) 20% 할인 중입니다!"); +// updateSelOpts(); +// } +// }, 30000); +// }, Math.random() * 10000); +// setTimeout(function () { +// setInterval(function () { +// if (lastSel) { +// var suggest = PRODUCT_LIST.find(function (item) { +// return item.id !== lastSel && item.stock > 0; +// }); +// if (suggest) { +// alert( +// suggest.name + "은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!" +// ); +// suggest.cost = Math.round(suggest.cost * 0.95); +// updateSelOpts(); +// } +// } +// }, 60000); +// }, Math.random() * 20000); +// } +// function updateSelOpts() { +// sel.innerHTML = ""; +// PRODUCT_LIST.forEach(function (item) { +// var opt = document.createElement("option"); +// opt.value = item.id; +// opt.textContent = item.name + " - " + item.cost + "원"; +// if (item.stock === 0) opt.disabled = true; +// sel.appendChild(opt); +// }); +// } +// function calcCart() { +// let totalAmount = 0; +// const cartItems = Array.from(cartDisp.children); +// const { subTotal, totalDiscount, itemCount, discountRate } = +// calculateCartTotals(cartItems); +// totalAmount = subTotal - totalDiscount; + +// const bulkDiscountRate = itemCount >= 30 ? 0.25 : discountRate; +// const tuesdayDiscountRate = new Date().getDay() === 2 ? 0.1 : 0; + +// const finalDiscountRate = Math.max(bulkDiscountRate, tuesdayDiscountRate); +// totalAmount *= 1 - finalDiscountRate; + +// updateSummaryUI(totalAmount, finalDiscountRate); +// } + +// const updateSummaryUI = (totalAmount, finalDiscountRate) => { +// sum.textContent = "총액: " + Math.round(totalAmount) + "원"; + +// if (finalDiscountRate > 0) { +// var span = document.createElement("span"); +// span.className = "text-green-500 ml-2"; +// span.textContent = `(${(finalDiscountRate * 100).toFixed(1)}% 할인 적용)`; +// sum.appendChild(span); +// } +// updateStockInfo(); +// renderBonusPts(totalAmount); +// }; +// const renderBonusPts = (totalAmount) => { +// bonusPoints = Math.floor(totalAmount / 1000); +// var pointTag = document.getElementById("loyalty-points"); +// if (!pointTag) { +// pointTag = document.createElement("span"); +// pointTag.id = "loyalty-points"; +// pointTag.className = "text-blue-500 ml-2"; +// sum.appendChild(pointTag); +// } +// pointTag.textContent = "(포인트: " + bonusPoints + ")"; +// }; +// function updateStockInfo() { +// var infoMsg = ""; +// PRODUCT_LIST.forEach(function (item) { +// if (item.stock < 5) { +// infoMsg += +// item.name + +// ": " + +// (item.stock > 0 ? "재고 부족 (" + item.stock + "개 남음)" : "품절") + +// "\n"; +// } +// }); +// stockInfo.textContent = infoMsg; +// } +// main(); + +// addBtn.addEventListener("click", function () { +// var selItem = sel.value; +// var itemToAdd = PRODUCT_LIST.find(function (p) { +// return p.id === selItem; +// }); +// const newItem = updateCartList(itemToAdd); +// if (newItem) { +// cartDisp.appendChild(newItem); +// lastSel = itemToAdd; +// } +// calcCart(); +// }); + +// cartDisp.addEventListener("click", function (event) { +// var target = event.target; +// if ( +// target.classList.contains("quantity-change") || +// target.classList.contains("remove-item") +// ) { +// handleDeleteToCart(target); +// calcCart(); +// } +// }); +import ReactDOM from "react-dom/client"; +import { App } from "./src/App"; + +const container = document.getElementById("app"); +if (container) { + const root = ReactDOM.createRoot(container); + root.render(); +} else { + console.error("Target container 'app' not found in the DOM."); +} diff --git a/src/advanced/data/prodList.js b/src/advanced/src/data/prodList.ts similarity index 100% rename from src/advanced/data/prodList.js rename to src/advanced/src/data/prodList.ts diff --git a/src/advanced/events/cartEvent.js b/src/advanced/src/events/cartEvent.js similarity index 100% rename from src/advanced/events/cartEvent.js rename to src/advanced/src/events/cartEvent.js From 6ff2183b8cc88766ef7d5bd9eb1cb4c7848ecb5e Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 10:37:51 +0900 Subject: [PATCH 31/38] =?UTF-8?q?feat=20:=20type=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/advanced/src/types/types.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/advanced/src/types/types.ts diff --git a/src/advanced/src/types/types.ts b/src/advanced/src/types/types.ts new file mode 100644 index 00000000..2e09231a --- /dev/null +++ b/src/advanced/src/types/types.ts @@ -0,0 +1,10 @@ +export interface IProduct { + id: string; + name: string; + cost: number; + stock: number; + discount: number; +} +export interface ICart extends Omit { + count: number; +} From a26b0a0b1a9ec2f48d9491ad2df91a2b90121171 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 10:40:34 +0900 Subject: [PATCH 32/38] =?UTF-8?q?feat=20:=20=EC=83=81=ED=92=88=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20select=20box=20ui=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/advanced/src/components/ProductSelect.tsx | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/advanced/src/components/ProductSelect.tsx diff --git a/src/advanced/src/components/ProductSelect.tsx b/src/advanced/src/components/ProductSelect.tsx new file mode 100644 index 00000000..a85f5153 --- /dev/null +++ b/src/advanced/src/components/ProductSelect.tsx @@ -0,0 +1,44 @@ +import { FunctionComponent } from "react"; +import { IProduct } from "../types/types"; +interface IProductSelect { + products: IProduct[]; + handler: (productId: string) => void; +} + +const ProductSelect: FunctionComponent = ({ + products, + handler, +}: IProductSelect) => { + const handlerSelect = () => { + handler("test"); + }; + const handlerAddToCart = () => { + console.log("addToCart"); + }; + return ( + <> + + + + ); +}; + +export default ProductSelect; From b5a7367717c6cd22e2fba12e7771d65f12e4f884 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 10:42:44 +0900 Subject: [PATCH 33/38] =?UTF-8?q?feat=20:=20=ED=92=88=EC=A0=88=20=EB=B0=8F?= =?UTF-8?q?=20=ED=92=88=EC=A0=88=20=EC=9E=84=EB=B0=95=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20ui=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/advanced/src/components/SummaryStock.tsx | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/advanced/src/components/SummaryStock.tsx diff --git a/src/advanced/src/components/SummaryStock.tsx b/src/advanced/src/components/SummaryStock.tsx new file mode 100644 index 00000000..5874dc25 --- /dev/null +++ b/src/advanced/src/components/SummaryStock.tsx @@ -0,0 +1,30 @@ +import { FunctionComponent } from "react"; +import { IProduct } from "../types/types"; +interface ISummaryStock { + products: IProduct[]; +} + +const SummaryStock: FunctionComponent = ({ products }) => { + return ( + <> + {products.map((product, idx) => { + if (product.stock < 5) { + return ( +
+ {product.name}:{" "} + {product.stock > 0 + ? "재고 부족 (" + product.stock + "개 남음)" + : "품절"} +
+ ); + } + return; + })} + + ); +}; +export default SummaryStock; From c9bc151d15b69912fcc5845c983296bf921eabd8 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 10:54:09 +0900 Subject: [PATCH 34/38] =?UTF-8?q?feat=20:=20=EC=9E=A5=EB=B0=94=EA=B5=AC?= =?UTF-8?q?=EB=8B=88=20=EB=AA=A9=EB=A1=9D=20ui=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/advanced/src/components/Cart.tsx | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/advanced/src/components/Cart.tsx diff --git a/src/advanced/src/components/Cart.tsx b/src/advanced/src/components/Cart.tsx new file mode 100644 index 00000000..781cd41f --- /dev/null +++ b/src/advanced/src/components/Cart.tsx @@ -0,0 +1,52 @@ +import { FunctionComponent } from "react"; +import { ICart, IProduct } from "../types/types"; +interface ICartList { + products: ICart[]; +} +const CartList: FunctionComponent = ({ products }) => { + return ( +
+ {products.map((product, idx) => { + return ; // key를 고유 ID로 지정 + })} +
+ ); +}; + +interface ICartItem { + item: ICart; +} + +const CartItem: FunctionComponent = ({ item }) => { + return ( +
+ + {item.name} - {item.cost}원 x {item.count} + +
+ + + +
+
+ ); +}; + +export default CartList; From 06cbaaafeb0d78a5d81fa84d746989ae776fba63 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 10:54:37 +0900 Subject: [PATCH 35/38] =?UTF-8?q?feat=20:=20App.tsx=EC=97=90=20ui=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/advanced/src/App.tsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/advanced/src/App.tsx diff --git a/src/advanced/src/App.tsx b/src/advanced/src/App.tsx new file mode 100644 index 00000000..97fef347 --- /dev/null +++ b/src/advanced/src/App.tsx @@ -0,0 +1,23 @@ +import { FunctionComponent, useState } from "react"; +import { PRODUCT_LIST } from "./data/prodList"; +import { ICart, IProduct } from "./types/types"; +import CartList from "./components/Cart"; +import ProductSelect from "./components/ProductSelect"; +import SummaryStock from "./components/SummaryStock"; + +export const App: FunctionComponent = () => { + const [products, setProducts] = useState(PRODUCT_LIST); + const [selectedProducts, setSelectedProducts] = useState([ + { id: "p1", name: "상품1", cost: 10000, count: 5, discount: 0.1 }, + ]); + return ( +
+
+

장바구니

+ + {}} /> + +
+
+ ); +}; From 366e4e46132aa495c7df1e3336568959fe2892e5 Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 11:58:12 +0900 Subject: [PATCH 36/38] =?UTF-8?q?fix=20:=20index.advanced.html=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=EB=90=9C=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.advanced.html | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/index.advanced.html b/index.advanced.html index 1d45f718..67f227aa 100644 --- a/index.advanced.html +++ b/index.advanced.html @@ -1,13 +1,13 @@ - - - - 장바구니 - - - -
- - + + + + 장바구니 + + + +
+ + From be468967497c6237a469e1b7546ed197a4f9d69e Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 12:02:33 +0900 Subject: [PATCH 37/38] =?UTF-8?q?chore=20:=20tsconfig=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tsconfig.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tsconfig.json diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..136ca8a2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "allowJs": true, + "noEmit": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "types": ["vitest/globals"] // Vitest 타입 정의 추가 + }, + "include": ["src", "src/**/*.test.tsx", "src/**/*.test.ts"] // 테스트 파일 포함 +} From 8d9d58d6a3be0342169138899eb668c6a63a09dc Mon Sep 17 00:00:00 2001 From: JaeHyun Bang Date: Fri, 10 Jan 2025 12:02:58 +0900 Subject: [PATCH 38/38] =?UTF-8?q?chore=20:=20package=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a7490be2..69827006 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "start:basic": "vite serve --open ./index.basic.html", "start:advanced": "vite serve --open ./index.advanced.html", "test": "vitest", - "test:basic": "vitest basic.test.js", - "test:advanced": "vitest advanced.test.js", + "test:basic": "vitest basic.test", + "test:advanced": "vitest advanced.test", "test:ui": "vitest --ui" }, "devDependencies": {