diff options
| author | Mike Vink <mike1994vink@gmail.com> | 2021-06-10 18:41:21 +0200 |
|---|---|---|
| committer | Mike Vink <mike1994vink@gmail.com> | 2021-06-10 18:41:21 +0200 |
| commit | 22ee6924ca6a59ecf4ca0030daab302f697b3445 (patch) | |
| tree | 7990b4cf02754ca42e27585d28dd67fddaaf0bf3 /client | |
| parent | fd46c28539cf7c130dfbb0458f7b9057ad4f1e46 (diff) | |
big progress
Diffstat (limited to 'client')
| -rw-r--r-- | client/admin.html | 42 | ||||
| -rw-r--r-- | client/index.html | 17 | ||||
| -rw-r--r-- | client/orders.html | 16 | ||||
| -rw-r--r-- | client/shoppingbasket.html | 8 | ||||
| -rw-r--r-- | client/src/admin.js | 28 | ||||
| -rw-r--r-- | client/src/adminArticle.js | 117 | ||||
| -rw-r--r-- | client/src/attractionArticle.js | 253 | ||||
| -rw-r--r-- | client/src/functions.js | 21 | ||||
| -rw-r--r-- | client/src/index.js | 264 | ||||
| -rw-r--r-- | client/src/map.js | 22 | ||||
| -rw-r--r-- | client/src/myorders.js | 24 | ||||
| -rw-r--r-- | client/src/orderArticle.js | 34 | ||||
| -rw-r--r-- | client/src/shoppingbasket.js | 49 | ||||
| -rw-r--r-- | client/src/templateImplementations.js | 73 | ||||
| -rw-r--r-- | client/src/utils.js | 79 | ||||
| -rw-r--r-- | client/style/admin.css | 29 | ||||
| -rw-r--r-- | client/style/main.css | 29 | ||||
| -rw-r--r-- | client/style/map.css | 119 |
18 files changed, 846 insertions, 378 deletions
diff --git a/client/admin.html b/client/admin.html index 291f79f..eb8f9a5 100644 --- a/client/admin.html +++ b/client/admin.html @@ -8,6 +8,7 @@ <meta name="description" content="The ticket shop for Sogyo employees"> <link rel="stylesheet" href="style/main.css"> + <link rel="stylesheet" href="style/admin.css"> </head> <body> @@ -41,9 +42,44 @@ </nav> <main> + <div class="newattraction" style=""> + <button class="orderbutton" id="addbutton"> + <span class="front"> + Add + </span> + </button> + <div class="newattractioninput"><label>New name:</label><input id="newattractionname" placeholder="new attraction name"/></div> + <div class="newattractioninput"><label>Description:</label><input id="newattractiondescription" placeholder="description"/></div> + <div class="newattractioninput"><label>Lat:</label><input .class="newattractioninput" id="newlat" placeholder="lat gps position"/></div> + <div class="newattractioninput"><label>Long:</label><input .class="newattractioninput" id="newlong" placeholder="long gps position"/></div> + </div> + </main> + + <template id="adminattraction"> + <article> + <div class="parkname"></div> + <div class="info"> + <div class="prices"> + <div class="adultprice">Adult price: <span class="sign">€</span><input type="number" class="adultPrice"/></div> + <div class="kidsprice">Kids price: <span class="sign">€</span><input type="number" class="kidsPrice"/></div> + <div class="discountrequirement"> + <em>Discount requirement:</em> + Buy <input type="number" class="minimumNumberOfAdults"/> adult tickets & <input type="number" class="minimumNumberOfKids"/> kid tickets for a <input type="number" class="discount"/>% discount! + </div> + </div> + <div class="availabletickets">Available tickets: <input type="number" class="available"/></div> + </div> + <button class="orderbutton" id="deletebutton"> + <span class="front"> + Delete + </span> + </button> + </article> + </template> + + + <script type="module" src="src/admin.js"></script> - - </main> </body> -</html>
\ No newline at end of file +</html> diff --git a/client/index.html b/client/index.html index af3f5dd..62021bf 100644 --- a/client/index.html +++ b/client/index.html @@ -42,6 +42,23 @@ </header> <main> + <div id="sortmenu"> + <div class="sorter" id="pricesorter"> + <label>Sort on price:</label> + <button class="orderbutton" id="sortprice"> + <span class="front"> + </span> + </button> + </div> + <div class="sorter" id="locationsorter"> + <label>Sort on location:</label> + <button class="orderbutton" id="sortprice"> + <span class="front"> + </span> + </button> + </div> + </div> + <div id="center-articles"> </div> diff --git a/client/orders.html b/client/orders.html index ab936be..31c6686 100644 --- a/client/orders.html +++ b/client/orders.html @@ -45,6 +45,18 @@ </main> - <script src="src/myorders.js"></script> + <template id="ticket"> + <article> + <div class="parkname"></div> + <div class="orderdescription"> + <div class="numberofadults"></div> + <div class="numberofkids"></div> + <div class="price"></div> + </div> + </article> + </template> + + + <script type="module" src="src/myorders.js"></script> </body> -</html>
\ No newline at end of file +</html> diff --git a/client/shoppingbasket.html b/client/shoppingbasket.html index ae7ebb3..06143b3 100644 --- a/client/shoppingbasket.html +++ b/client/shoppingbasket.html @@ -51,9 +51,11 @@ <template id="ticket"> <article> <div class="parkname"></div> - <div class="numberofadults"></div> - <div class="numberofkids"></div> - <div class="price"></div> + <div class="orderdescription"> + <div class="numberofadults"></div> + <div class="numberofkids"></div> + <div class="price"></div> + </div> <button class="orderbutton cancel"> <span class="front cancel"> Cancel order diff --git a/client/src/admin.js b/client/src/admin.js index e69de29..a857055 100644 --- a/client/src/admin.js +++ b/client/src/admin.js @@ -0,0 +1,28 @@ +import { AdminAttraction } from "./templateImplementations.js" + +import { + fetchAttractions, +} from "./utils.js" + +import { + addAttractionInDatabase +} from "./adminArticle.js" + +function displayAdminAttractions(attractions) { + console.log(attractions); + const main = document.querySelector("main"); + const template = document.querySelector("#adminattraction"); + + for (let i = 0; i < attractions.length; i++) { + const attraction = attractions[i]; + const adminAttraction = new AdminAttraction(attraction, template); + adminAttraction.addToNode(main); + } +} + +const newattraction = document.querySelector(".newattraction"); +addbutton.addEventListener("click", addAttractionInDatabase(newattraction)); + +console.log("displaying AdminAttractions") +fetchAttractions() + .then(displayAdminAttractions) diff --git a/client/src/adminArticle.js b/client/src/adminArticle.js new file mode 100644 index 0000000..bc50915 --- /dev/null +++ b/client/src/adminArticle.js @@ -0,0 +1,117 @@ +import { + AdminAttraction +} from "./templateImplementations.js" + +import { + kill, + findParent +} from "./utils.js" + +export function addAttractionInDatabase(newattraction) { + + return async function addAttraction(event) { + const name = newattraction.querySelector("#newattractionname").value; + const description = newattraction.querySelector("#newattractiondescription").value; + const lat = Number.parseFloat(newattraction.querySelector("#newlat").value); + const lon = Number.parseFloat(newattraction.querySelector("#newlong").value); + if (!name || !description) return; + if (Number.isNaN(lat)) return; + if (Number.isNaN(lon)) return; + + console.log("adding " + name + " at lat: " + lat + " and long: " + lon); + try { + const response = await fetch("api/admin/add", + { + method: 'POST', // *GET, POST, PUT, DELETE, etc. + mode: 'cors', // no-cors, *cors, same-origin + cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + credentials: 'same-origin', // include, *same-origin, omit + headers: { + 'Content-Type': 'application/json' + // 'Content-Type': 'application/x-www-form-urlencoded', + }, + redirect: 'follow', // manual, *follow, error + referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + body: JSON.stringify({name: name, description: description, lat: lat, lon: lon}) // body data type must match "Content-Type" header + }); + + if (!response.ok) { + const message = `An error has occured: ${response.status}`; + throw new Error(message); + } + + const main = findParent(parent => {return parent.tagName === "MAIN"})(event.target) + const template = main.parentNode.querySelector("#adminattraction"); + const attractionJSON = await response.json(); + console.log(attractionJSON) + const newAttraction = new AdminAttraction(attractionJSON, template); + newAttraction.addToNode(main); + + } catch(error) { + console.log("Something went wrong when deleting an attraction: ", error); + } + + } +} + +export function deleteAttractionInDatabase(name) { + return async function deleteAttraction(event) { + console.log("deleting " + name); + try { + const response = await fetch("api/admin/delete", + { + method: 'POST', // *GET, POST, PUT, DELETE, etc. + mode: 'cors', // no-cors, *cors, same-origin + cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + credentials: 'same-origin', // include, *same-origin, omit + headers: { + 'Content-Type': 'application/json' + // 'Content-Type': 'application/x-www-form-urlencoded', + }, + redirect: 'follow', // manual, *follow, error + referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + body: JSON.stringify({name: name}) // body data type must match "Content-Type" header + }); + + if (!response.ok) { + const message = `An error has occured: ${response.status}`; + throw new Error(message); + } + + } catch(error) { + console.log("Something went wrong when deleting an attraction: ", error); + } + kill(findParent(parent => {return parent.tagName === "ARTICLE"})(event.target)); + } +} + + +export function updateAttractionInDatabase(name, field) { + return async function update(event) { + console.log("updating " + field + " field of " + name); + try { + const response = await fetch("api/admin/edit", + { + method: 'POST', // *GET, POST, PUT, DELETE, etc. + mode: 'cors', // no-cors, *cors, same-origin + cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + credentials: 'same-origin', // include, *same-origin, omit + headers: { + 'Content-Type': 'application/json' + // 'Content-Type': 'application/x-www-form-urlencoded', + }, + redirect: 'follow', // manual, *follow, error + referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + body: JSON.stringify({name: name, field: field, value: event.target.value}) // body data type must match "Content-Type" header + }); + + if (!response.ok) { + const message = `An error has occured: ${response.status}`; + throw new Error(message); + } + + } catch(error) { + console.log("Something went wrong when editing an attraction: ", error); + } + } +} diff --git a/client/src/attractionArticle.js b/client/src/attractionArticle.js new file mode 100644 index 0000000..493417e --- /dev/null +++ b/client/src/attractionArticle.js @@ -0,0 +1,253 @@ +import { + fetchAttractions, + findParent, + dutchCurrencyFormat, + dutchCurrencyFormatWithSign, + displayNumberOfItemsInShoppingBasketWithBadge +} from "./utils.js" + +export function disableButton(name, button) { + return function receiver(payload) { + console.log('checking if button should be disabled'); + const inputs = button.parentNode.querySelectorAll("input"); + var inputTickets = 0; + for (let i = 0; i < inputs.length; i++) { + if (inputs[i].value > 0) { + inputTickets = inputTickets + Number.parseInt(inputs[i].value); + } + } + + const shoppingBasketArray = JSON.parse(localStorage.getItem("shoppingBasketArray")); + // console.log(shoppingBasketArray); + var shoppingBasketTickets = 0; + + if (shoppingBasketArray) { + for (let i = 0; i < shoppingBasketArray.length; i++) { + if (shoppingBasketArray[i].name === name) { + shoppingBasketTickets = shoppingBasketTickets + shoppingBasketArray[i].numberOfKids + shoppingBasketArray[i].numberOfAdults; + } + } + } + const totalTickets = inputTickets + shoppingBasketTickets; + + function disablerHelper(payload) { + var attraction; + if (Array.isArray(payload)) { + for (let i = 0; i < payload.length; i++) { + if (payload[i].name === name) { + attraction = payload[i]; + } + } + } else attraction = payload; + + var front = button.querySelector(".front"); + if (attraction.available < totalTickets || attraction.available === 0 || attraction.available === shoppingBasketTickets) { + console.log("---> disabling " + attraction.name + " button since the available tickets are " + attraction.available + " and tickets among current order is " + totalTickets); + front.classList.add("disabled"); + button.removeEventListener("click", orderButtonClicked); + } else { + console.log("---> enabling " + attraction.name + " button since the available tickets are " + attraction.available + " and tickets among current order is " + totalTickets); + front.classList.remove("disabled"); + button.addEventListener("click", orderButtonClicked); + } + + } + + // console.log(payload) + if (payload && payload.name) { + if (payload.name) { + // console.log("prevented unnecesary fetch") + disablerHelper(payload); + } + } else { + // console.log("unnecesary fetch") + fetchAttractions() + .then(disablerHelper) + .catch(error => {console.error(error)}); + } + } +} + +export function orderButtonClicked(event) { + console.log("button click"); + var button; + + if (event.target.classList.contains("orderbutton")) { + button = event.target; + } else { + button = event.target.parentNode; + } + + const order = button.parentNode; + const parkArticle = order.parentNode; + + const orderClientSideInfo = { + name: parkArticle.querySelector(".parkname").textContent, + numberOfKids: Number(order.querySelector(".numberofkids").value), + numberOfAdults: Number(order.querySelector(".numberofadults").value), + } + + console.log("---> found this info on the client side:") + console.log(orderClientSideInfo); + + if ((orderClientSideInfo.numberOfKids > 0 && orderClientSideInfo.numberOfAdults >= 0) || (orderClientSideInfo.numberOfAdults > 0 && orderClientSideInfo.numberOfKids >= 0)) { + fetchAttractions() + .then(checkTicketAvailability(button, orderClientSideInfo)) + .then(saveOrderInShoppingBasket(orderClientSideInfo)) + .then(disableButton(orderClientSideInfo.name, button)) + .catch((error) => {console.error(error)}) + } +} + +class TicketsNotAvailableError extends Error { + constructor(message) { + super(message); + this.name = "TicketsNotAvailableError"; + } +} + + +export function checkTicketAvailability(button, orderClientSideInfo) { + + return function serverAttractionsAccepter(serverAttractionsArray) { + var attraction; + for (let i = 0; i < serverAttractionsArray.length; i++) { + if (serverAttractionsArray[i].name === orderClientSideInfo.name) { + attraction = serverAttractionsArray[i] + } + } + + if (attraction.available < orderClientSideInfo.numberOfKids + orderClientSideInfo.numberOfAdults) { + throw new TicketsNotAvailableError("The tickets of the order exceed the available tickets!"); + } + + return serverAttractionsArray; + } +} + +export function saveOrderInShoppingBasket(orderClientSideInfo) { + console.log("---> ---> saving info in shopping basket"); + + return function serverAttractionsAccepter(serverAttractionsArray) { + // const orderClientSideInfo = this; + + var price; + var attraction; + for (let i = 0; i < serverAttractionsArray.length; i++) { + if (serverAttractionsArray[i].name === orderClientSideInfo.name) { + price = calulateTotal( + orderClientSideInfo.numberOfKids, + orderClientSideInfo.numberOfAdults, + serverAttractionsArray[i] + ); + attraction = serverAttractionsArray[i]; + } + } + + orderClientSideInfo.price = price; + console.log("---> ---> price is saved in the shopping basket order"); + console.log(orderClientSideInfo); + + var shoppingBasketArray; + + if (localStorage.getItem("shoppingBasketArray") === null) { + shoppingBasketArray = []; + shoppingBasketArray.push(orderClientSideInfo); + } else { + shoppingBasketArray = JSON.parse(localStorage.getItem("shoppingBasketArray")); + shoppingBasketArray.push(orderClientSideInfo); + } + + localStorage.setItem("shoppingBasketArray", JSON.stringify(shoppingBasketArray)); + console.log("---> ---> order is saved in array in localstorage"); + console.log(localStorage); + displayNumberOfItemsInShoppingBasketWithBadge(); + return attraction; + } +} + +export function calulateTotal(numberOfKids, numberOfAdults, serverSideAttraction) { + console.log("---> calculating total of order in shoppingbasket!"); + console.log("---> ---> fetched this attraction to calculate actual prices!"); + console.log(serverSideAttraction); + + const adultPrice = serverSideAttraction.adultPrice; + const kidsPrice = serverSideAttraction.kidsPrice; + + const discountPercentage = serverSideAttraction.discount; + const minNumberkids = serverSideAttraction.minimumNumberOfKids; + const minNumberAdults = serverSideAttraction.minimumNumberOfAdults; + + var totalPrice = 0; + if (numberOfKids > 0) { + totalPrice = totalPrice + numberOfKids * kidsPrice; + } + if (numberOfAdults > 0) { + totalPrice = totalPrice + numberOfAdults * adultPrice; + } + + if (numberOfKids >= minNumberkids && numberOfAdults >= minNumberAdults) { + var discount = totalPrice * discountPercentage / 100; + totalPrice = totalPrice - discount; + } + + if (discount) { + return {total:totalPrice, discount: discount} + } else { + return {total: totalPrice}; + } +} + +export function displayTotal(event) { + console.log("displaying a total price based on client side info"); + var order = findParent(parent => {return parent.classList.contains("order")})(event.target) + console.log(order) + var total = order.querySelector(".total"); + + var kids = order.querySelector(".numberofkids").value; + var adults = order.querySelector(".numberofadults").value; + if (kids === "") kids = 0; + if (adults === "") adults = 0; + + let re = /\d+/; + var kidsPrice = order.querySelector(".kidsprice") + .textContent + .match(re)[0]; + var adultPrice = order.querySelector(".adultprice") + .textContent + .match(re)[0]; + + var discountReq = order.querySelector(".discountrequirement") + var minNumberkids = discountReq.querySelector(".child") + .textContent + .match(re)[0] + var minNumberadults = discountReq.querySelector(".adults") + .textContent + .match(re)[0] + var discountPercentage = discountReq.querySelector(".percentage") + .textContent + .match(re)[0]; + // console.log(discountPercentage); + + var value = 0; + if (kids > 0) { + value = value + Number.parseInt(kids) * Number.parseFloat(kidsPrice); + } + if (adults > 0) { + value = value + Number.parseInt(adults) * Number.parseFloat(adultPrice); + } + + var discount; + if (Number.parseInt(kids) >= Number.parseInt(minNumberkids) && Number.parseInt(adults) >= Number.parseInt(minNumberadults)) { + discount = value * Number.parseFloat(discountPercentage) / 100 + value = value - discount; + console.log("---> applying the discount of " + discountPercentage + "%, resulting in " + discount + " discount") + } + // console.log(value); + + var priceString = dutchCurrencyFormat(value); + if (!(discount === undefined)) { + priceString = priceString + " discount: " + dutchCurrencyFormatWithSign(discount); + } + total.querySelector(".price").textContent = priceString; +} diff --git a/client/src/functions.js b/client/src/functions.js index a43dbac..e69de29 100644 --- a/client/src/functions.js +++ b/client/src/functions.js @@ -1,21 +0,0 @@ -// client/src/function.js -export async function fetchAttractions() { - - try { - - const response = await fetch("api/attractions"); - - if (!response.ok) { - const message = `An error has occured: ${response.status}`; - throw new Error(message); - } - - const attractions = await response.json(); - return attractions; - - - } catch(error) { - console.log("something went wrong when fetching attractions: ", error); - } - -} diff --git a/client/src/index.js b/client/src/index.js index 0b8e676..5743b5e 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -1,11 +1,13 @@ import { ParkArticle } from "./templateImplementations.js"; + import { + fetchAttractions, displayNumberOfItemsInShoppingBasketWithBadge, dutchCurrencyFormat, dutchCurrencyFormatWithSign, - findParent + findParent, + killChildren, } from "./utils.js"; -import { fetchAttractions } from "./functions.js" // ik struggle een beetje met de database connecten met de api... @@ -14,266 +16,14 @@ import { fetchAttractions } from "./functions.js" function displayArticles(articles) { console.log("displaying attraction articles"); - const parkArticleFunctionality = { - orderButtonClick: orderButtonClicked, - displayTotal: displayTotal, - disableButton: disableButton, - } for (var i = 0; i < articles.length; i++) { var parkArticle = new ParkArticle(articles[i], document.querySelector("#parkarticle")); - parkArticle.addToNode(document.querySelector("#center-articles"), parkArticleFunctionality); - } - -} - -function disableButton(name, button) { - return function receiver(payload) { - console.log('checking if button should be disabled'); - const inputs = button.parentNode.querySelectorAll("input"); - var inputTickets = 0; - for (let i = 0; i < inputs.length; i++) { - if (inputs[i].value > 0) { - inputTickets = inputTickets + Number.parseInt(inputs[i].value); - } - } - - const shoppingBasketArray = JSON.parse(localStorage.getItem("shoppingBasketArray")); - // console.log(shoppingBasketArray); - var shoppingBasketTickets = 0; - - if (shoppingBasketArray) { - for (let i = 0; i < shoppingBasketArray.length; i++) { - if (shoppingBasketArray[i].name === name) { - shoppingBasketTickets = shoppingBasketTickets + shoppingBasketArray[i].numberOfKids + shoppingBasketArray[i].numberOfAdults; - } - } - } - const totalTickets = inputTickets + shoppingBasketTickets; - - function disablerHelper(payload) { - var attraction; - if (Array.isArray(payload)) { - for (let i = 0; i < payload.length; i++) { - if (payload[i].name === name) { - attraction = payload[i]; - } - } - } else attraction = payload; - - var front = button.querySelector(".front"); - if (attraction.available < totalTickets || attraction.available === 0 || attraction.available === shoppingBasketTickets) { - console.log("---> disabling " + attraction.name + " button since the available tickets are " + attraction.available + " and tickets among current order is " + totalTickets); - front.classList.add("disabled"); - button.removeEventListener("click", orderButtonClicked); - } else { - console.log("---> enabling " + attraction.name + " button since the available tickets are " + attraction.available + " and tickets among current order is " + totalTickets); - front.classList.remove("disabled"); - button.addEventListener("click", orderButtonClicked); - } - - } - - // console.log(payload) - if (payload && payload.name) { - if (payload.name) { - // console.log("prevented unnecesary fetch") - disablerHelper(payload); - } - } else { - // console.log("unnecesary fetch") - fetchAttractions() - .then(disablerHelper) - .catch(error => {console.error(error)}); - } - } -} - -function orderButtonClicked(event) { - console.log("button click"); - var button; - - if (event.target.classList.contains("orderbutton")) { - button = event.target; - } else { - button = event.target.parentNode; - } - - const order = button.parentNode; - const parkArticle = order.parentNode; - - const orderClientSideInfo = { - name: parkArticle.querySelector(".parkname").textContent, - numberOfKids: Number(order.querySelector(".numberofkids").value), - numberOfAdults: Number(order.querySelector(".numberofadults").value), - } - - console.log("---> found this info on the client side:") - console.log(orderClientSideInfo); - - if ((orderClientSideInfo.numberOfKids > 0 && orderClientSideInfo.numberOfAdults >= 0) || (orderClientSideInfo.numberOfAdults > 0 && orderClientSideInfo.numberOfKids >= 0)) { - fetchAttractions() - .then(checkTicketAvailability(button, orderClientSideInfo)) - .then(saveOrderInShoppingBasket(orderClientSideInfo)) - .then(disableButton(orderClientSideInfo.name, button)) - .catch((error) => {console.error(error)}) - } -} - -class TicketsNotAvailableError extends Error { - constructor(message) { - super(message); - this.name = "TicketsNotAvailableError"; - } -} - - -export function checkTicketAvailability(button, orderClientSideInfo) { - - return function serverAttractionsAccepter(serverAttractionsArray) { - var attraction; - for (let i = 0; i < serverAttractionsArray.length; i++) { - if (serverAttractionsArray[i].name === orderClientSideInfo.name) { - attraction = serverAttractionsArray[i] - } - } - - if (attraction.available < orderClientSideInfo.numberOfKids + orderClientSideInfo.numberOfAdults) { - throw new TicketsNotAvailableError("The tickets of the order exceed the available tickets!"); - } - - return serverAttractionsArray; - } -} - -function saveOrderInShoppingBasket(orderClientSideInfo) { - console.log("---> ---> saving info in shopping basket"); - - return function serverAttractionsAccepter(serverAttractionsArray) { - // const orderClientSideInfo = this; - - var price; - var attraction; - for (let i = 0; i < serverAttractionsArray.length; i++) { - if (serverAttractionsArray[i].name === orderClientSideInfo.name) { - price = calulateTotal( - orderClientSideInfo.numberOfKids, - orderClientSideInfo.numberOfAdults, - serverAttractionsArray[i] - ); - attraction = serverAttractionsArray[i]; - } - } - - orderClientSideInfo.price = price; - console.log("---> ---> price is saved in the shopping basket order"); - console.log(orderClientSideInfo); - - var shoppingBasketArray; - - if (localStorage.getItem("shoppingBasketArray") === null) { - shoppingBasketArray = []; - shoppingBasketArray.push(orderClientSideInfo); - } else { - shoppingBasketArray = JSON.parse(localStorage.getItem("shoppingBasketArray")); - shoppingBasketArray.push(orderClientSideInfo); - } - - localStorage.setItem("shoppingBasketArray", JSON.stringify(shoppingBasketArray)); - console.log("---> ---> order is saved in array in localstorage"); - console.log(localStorage); - displayNumberOfItemsInShoppingBasketWithBadge(); - return attraction; - } -} - -function calulateTotal(numberOfKids, numberOfAdults, serverSideAttraction) { - console.log("---> calculating total of order in shoppingbasket!"); - console.log("---> ---> fetched this attraction to calculate actual prices!"); - console.log(serverSideAttraction); - - const adultPrice = serverSideAttraction.adultPrice; - const kidsPrice = serverSideAttraction.kidsPrice; - - const discountPercentage = serverSideAttraction.discount; - const minNumberkids = serverSideAttraction.minimumNumberOfKids; - const minNumberAdults = serverSideAttraction.minimumNumberOfAdults; - - var totalPrice = 0; - if (numberOfKids > 0) { - totalPrice = totalPrice + numberOfKids * kidsPrice; - } - if (numberOfAdults > 0) { - totalPrice = totalPrice + numberOfAdults * adultPrice; - } - - if (numberOfKids >= minNumberkids && numberOfAdults >= minNumberAdults) { - var discount = totalPrice * discountPercentage / 100; - totalPrice = totalPrice - discount; + parkArticle.addToNode(document.querySelector("#center-articles")); } - if (discount) { - return {total:totalPrice, discount: discount} - } else { - return {total: totalPrice}; - } } -function displayTotal(event) { - console.log("displaying a total price based on client side info"); - var order = findParent(parent => {return parent.classList.contains("order")})(event.target) - console.log(order) - var total = order.querySelector(".total"); - - var kids = order.querySelector(".numberofkids").value; - var adults = order.querySelector(".numberofadults").value; - if (kids === "") kids = 0; - if (adults === "") adults = 0; - - let re = /\d+/; - var kidsPrice = order.querySelector(".kidsprice") - .textContent - .match(re)[0]; - var adultPrice = order.querySelector(".adultprice") - .textContent - .match(re)[0]; - - var discountReq = order.querySelector(".discountrequirement") - var minNumberkids = discountReq.querySelector(".child") - .textContent - .match(re)[0] - var minNumberadults = discountReq.querySelector(".adults") - .textContent - .match(re)[0] - var discountPercentage = discountReq.querySelector(".percentage") - .textContent - .match(re)[0]; - // console.log(discountPercentage); - - var value = 0; - if (kids > 0) { - value = value + Number.parseInt(kids) * Number.parseFloat(kidsPrice); - } - if (adults > 0) { - value = value + Number.parseInt(adults) * Number.parseFloat(adultPrice); - } - - var discount; - if (Number.parseInt(kids) >= Number.parseInt(minNumberkids) && Number.parseInt(adults) >= Number.parseInt(minNumberadults)) { - discount = value * Number.parseFloat(discountPercentage) / 100 - value = value - discount; - console.log("---> applying the discount of " + discountPercentage + "%, resulting in " + discount + " discount") - } - // console.log(value); - - var priceString = dutchCurrencyFormat(value); - if (!(discount === undefined)) { - priceString = priceString + " discount: " + dutchCurrencyFormatWithSign(discount); - } - total.querySelector(".price").textContent = priceString; -} - - function setStickyNavBar() { console.log("making the navbar sticky"); // Get the header @@ -301,5 +51,9 @@ function setStickyNavBar() { displayNumberOfItemsInShoppingBasketWithBadge(); setStickyNavBar(); +const sortmenu = document.querySelector("#sortmenu"); + +console.log(sortmenu) + fetchAttractions() .then(displayArticles); diff --git a/client/src/map.js b/client/src/map.js index 10f2d66..6af7619 100644 --- a/client/src/map.js +++ b/client/src/map.js @@ -1,4 +1,4 @@ -import { fetchAttractions } from "./functions.js" +import { fetchAttractions } from "./utils.js" import { ParkArticle } from "./templateImplementations.js" // replace "toner" here with "terrain" or "watercolor" @@ -10,6 +10,8 @@ var map = new L.Map("discoverablemap", { map.addLayer(layer); function addMarkersForAttractions(map) { + const template = document.querySelector("#parkpopup"); + return function addMarkers(attractions) { for (let i = 0; i < attractions.length; i++) { const attraction = attractions[i]; @@ -17,20 +19,14 @@ function addMarkersForAttractions(map) { const marker = L.marker([location.lat, location.lon]).addTo(map); - const articleObj = new ParkArticle(attraction, document.querySelector("parkpopup")) - console.log(articleObj) - const parkArticleFunctionality = { - orderButtonClick: orderButtonClicked, - displayTotal: displayTotal, - disableButton: disableButton, - } - const articleHTML = articleObj.toHTML(parkArticleFunctionality); - console.log(articleHTML); + const articleObj = new ParkArticle(attraction, template); + const articleHTML = articleObj.toHTML(); + var div = L.DomUtil.create('div', 'popupcontent'); + const popupcontent = articleHTML; + div.appendChild(popupcontent); - marker.bindPopup( - articleHTML - ) + marker.bindPopup(div) } } diff --git a/client/src/myorders.js b/client/src/myorders.js index e69de29..302cb55 100644 --- a/client/src/myorders.js +++ b/client/src/myorders.js @@ -0,0 +1,24 @@ +import { Order } from "./templateImplementations.js" +import { + displayNumberOfItemsInShoppingBasketWithBadge, + fetchOrders, +} from "./utils.js"; + + +async function displayOrders() { + displayNumberOfItemsInShoppingBasketWithBadge(); + console.log("displaying orders in database"); + + var orders = await fetchOrders(); + if (orders === null || orders.length === 0) { + } + + var main = document.querySelector("main"); + + for (let i = 0; i < orders.length; i++) { + var orderObj = new Order(orders[i], document.querySelector("#ticket")); + orderObj.addToNode(main); + } +} + +displayOrders(); diff --git a/client/src/orderArticle.js b/client/src/orderArticle.js new file mode 100644 index 0000000..be5a75b --- /dev/null +++ b/client/src/orderArticle.js @@ -0,0 +1,34 @@ +import { + fetchOrders, + readOrderArrayFromLocalStorage, + kill, + findParent, + displayNumberOfItemsInShoppingBasketWithBadge +} from "./utils.js" + +export function cancelOrder(event) { + //console.log(event.target); + console.log("cancel button clicked"); + const article = findParent(parent => {return parent.tagName === "ARTICLE"})(event.target); + + var previous = article.previousSibling; + var i = 0; + while (previous) { + if (previous.tagName === "ARTICLE") { + i = i+1; + } + previous = previous.previousSibling; + } + + var orders = readOrderArrayFromLocalStorage(); + console.log("---> canceling order "); + console.log(orders[i]); + console.log("---> removing it from shoppingbasket") + orders.splice(i, 1); + localStorage.setItem("shoppingBasketArray", JSON.stringify(orders)); + + var main = document.querySelector("main"); + console.log("---> refreshing displayed orders") + kill(article); + displayNumberOfItemsInShoppingBasketWithBadge(); +} diff --git a/client/src/shoppingbasket.js b/client/src/shoppingbasket.js index 56e717a..434b8cf 100644 --- a/client/src/shoppingbasket.js +++ b/client/src/shoppingbasket.js @@ -1,20 +1,15 @@ import { Order } from "./templateImplementations.js" -import { displayNumberOfItemsInShoppingBasketWithBadge, findParentWithTag, childKillerUsingTags } from "./utils.js"; +import { + displayNumberOfItemsInShoppingBasketWithBadge, + readOrderArrayFromLocalStorage, +} from "./utils.js"; -function getOrderArray() { - var orders = JSON.parse(localStorage.getItem("shoppingBasketArray")); - return orders; -} - function displayOrders() { displayNumberOfItemsInShoppingBasketWithBadge(); console.log("displaying orders in shopppingbasket"); - const orderFunctionality = { - cancel: cancelOrder, - }; - var orders = getOrderArray(); + var orders = readOrderArrayFromLocalStorage(); if (orders === null || orders.length === 0) { var button = document.querySelector("#finalizepaymentbutton"); var front = button.querySelector(".front"); @@ -30,40 +25,10 @@ function displayOrders() { for (let i = 0; i < orders.length; i++) { var orderObj = new Order(orders[i], document.querySelector("#ticket")); - orderObj.addToNode(main, orderFunctionality); + orderObj.addToNode(main); } } -function cancelOrder(event) { - //console.log(event.target); - console.log("cancel button clicked"); - const article = findParentWithTag.bind(event.target)("article"); - - var previous = article.previousSibling; - var i = 0; - while (previous) { - if (previous.tagName === "ARTICLE") { - i = i+1; - } - previous = previous.previousSibling; - } - - var orders = getOrderArray(); - console.log("---> canceling order "); - console.log(orders[i]); - console.log("---> removing it from shoppingbasket") - orders.splice(i, 1); - localStorage.setItem("shoppingBasketArray", JSON.stringify(orders)); - - var main = document.querySelector("main"); - console.log("---> refreshing displayed orders") - childKillerUsingTags(main)(main.firstChild)("article"); - displayOrders(); -} - - - - function finalizePayment(event) { console.log("finalizing payments"); @@ -71,8 +36,6 @@ function finalizePayment(event) { window.location.replace("orderplaced.html"); } - - document.querySelector("#finalizepaymentbutton").addEventListener("click", finalizePayment); displayOrders(); diff --git a/client/src/templateImplementations.js b/client/src/templateImplementations.js index 5767a84..5940d7e 100644 --- a/client/src/templateImplementations.js +++ b/client/src/templateImplementations.js @@ -1,5 +1,24 @@ -import { dutchCurrencyFormat, dutchCurrencyFormatWithSign } from "./utils.js" -import { fetchAttractions } from "./functions.js" +import { + dutchCurrencyFormat, + dutchCurrencyFormatWithSign, +} from "./utils.js" + +import { + disableButton, + calulateTotal, + checkTicketAvailability, + displayTotal +} from "./attractionArticle.js" + +import { + cancelOrder +} from "./orderArticle.js" + +import { + updateAttractionInDatabase, + deleteAttractionInDatabase +} from "./adminArticle.js" + /** * Abstract class * @@ -21,10 +40,38 @@ class TemplatedNode { } } +export class AdminAttraction extends TemplatedNode { + addToNode(node) { + var clone = this.template.content.cloneNode(true); + clone.querySelector(".parkname").textContent = this.name; + + clone.querySelector(".adultprice").querySelector(".adultPrice").value = this.adultPrice; + clone.querySelector(".kidsprice").querySelector(".kidsPrice").value = this.kidsPrice; + + clone.querySelector(".discountrequirement").querySelector(".minimumNumberOfAdults").value = this.minimumNumberOfAdults; + clone.querySelector(".discountrequirement").querySelector(".minimumNumberOfKids").value = this.minimumNumberOfKids; + clone.querySelector(".discountrequirement").querySelector(".discount").value = this.discount; + + clone.querySelector(".availabletickets").querySelector(".available").value = this.available; + + const inputElements = clone.querySelectorAll("input"); + for (let i = 0; i < inputElements.length; i++) { + const input = inputElements[i]; + const fieldToUpdate = input.className; + input.addEventListener("input", updateAttractionInDatabase(this.name, fieldToUpdate)); + } + + const deletebutton = clone.querySelector("#deletebutton"); + deletebutton.addEventListener("click", deleteAttractionInDatabase(this.name)); + + node.appendChild(clone); + } +} + export class Order extends TemplatedNode { - addToNode(node, orderFunctionality) { + addToNode(node) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template var clone = this.template.content.cloneNode(true); @@ -40,7 +87,9 @@ export class Order extends TemplatedNode { } clone.querySelector(".price").textContent = priceString; - clone.querySelector("button").addEventListener("click", orderFunctionality.cancel); + var button = clone.querySelector("button"); + + if (button) button.addEventListener("click", cancelOrder); node.appendChild(clone); } @@ -49,17 +98,17 @@ export class Order extends TemplatedNode { export class ParkArticle extends TemplatedNode { - addToNode(node, parkArticleFunctionality) { - const clone = this.cloneTemplateAndFillInHTML(parkArticleFunctionality); + addToNode(node) { + const clone = this.cloneTemplateAndFillInHTML(); node.appendChild(clone); } - toHTML(parkArticleFunctionality) { - return this.cloneTemplateAndFillInHTML(parkArticleFunctionality); + toHTML() { + return this.cloneTemplateAndFillInHTML(); } - cloneTemplateAndFillInHTML(parkArticleFunctionality) { + cloneTemplateAndFillInHTML() { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template var clone = this.template.content.cloneNode(true); @@ -94,12 +143,12 @@ export class ParkArticle extends TemplatedNode { } var button = clone.querySelector(".orderbutton"); - parkArticleFunctionality.disableButton(this.name, button)(null); + disableButton(this.name, button)(null); var inputElements = clone.querySelectorAll("input"); for (let i = 0; i < inputElements.length; i++) { - inputElements[i].addEventListener("input", parkArticleFunctionality.displayTotal); - inputElements[i].addEventListener("input", parkArticleFunctionality.disableButton(this.name, button)); + inputElements[i].addEventListener("input", displayTotal); + inputElements[i].addEventListener("input", disableButton(this.name, button)); } return clone; } diff --git a/client/src/utils.js b/client/src/utils.js index 5a0423d..b58da6c 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -1,3 +1,52 @@ +// client/src/function.js +export async function fetchOrders() { + + try { + + const response = await fetch("api/myorders"); + + if (!response.ok) { + const message = `An error has occured: ${response.status}`; + throw new Error(message); + } + + const orders = await response.json(); + return orders; + + + } catch(error) { + console.log("something went wrong when fetching orders: ", error); + } + +} + +export async function fetchAttractions() { + + try { + + const response = await fetch("api/attractions"); + + if (!response.ok) { + const message = `An error has occured: ${response.status}`; + throw new Error(message); + } + + const attractions = await response.json(); + return attractions; + + + } catch(error) { + console.log("something went wrong when fetching attractions: ", error); + } + +} + +export function readOrderArrayFromLocalStorage() { + var orders = JSON.parse(localStorage.getItem("shoppingBasketArray")); + return orders; +} + + export function displayNumberOfItemsInShoppingBasketWithBadge() { var shoppingBasketArray = JSON.parse(localStorage.getItem("shoppingBasketArray")); if (shoppingBasketArray === null) { @@ -20,14 +69,6 @@ export function dutchCurrencyFormatWithSign(number) { } -export function findParentWithTag(tagName) { - if (this.tagName === tagName.toUpperCase()) { - return this; - } else { - return findParentWithTag.bind(this.parentNode)(tagName); - } -} - export function findParent(func) { return function startingFromThisNode(node) { if (func(node)) { @@ -38,22 +79,12 @@ export function findParent(func) { } } +export function kill(node) { + node.parentNode.removeChild(node); +} -export function childKillerUsingTags(parent) { - - return function oneOfMyChildren(child) { - - return function killChildrenWithTag(tag) { - if (child === null) { - return - } else if (child.tagName === tag.toUpperCase()) { - var next = child.nextSibling; - parent.removeChild(child); - return oneOfMyChildren(next)(tag); - } else { - return oneOfMyChildren(child.nextSibling)(tag); - } - } - +export function killChildren(func) { + return function startingFromThisChildNode(node) { } } + diff --git a/client/style/admin.css b/client/style/admin.css new file mode 100644 index 0000000..c2d3f0e --- /dev/null +++ b/client/style/admin.css @@ -0,0 +1,29 @@ +.info { + margin: 15px; +} + +input { + width: 50px; +} + +.newattraction { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: left; + align-items: center; + margin: 15px 2.3% 15px 2.3%; + border: 1px solid var(--primary-color); +} + +.newattractioninput { + margin: 5px; + width: auto; +} + +.newattractioninput input { + min-width: 140px; + resize: both; + height: 30px; +} + diff --git a/client/style/main.css b/client/style/main.css index 0159545..abebf9c 100644 --- a/client/style/main.css +++ b/client/style/main.css @@ -28,7 +28,7 @@ article { border: 1px solid var(--primary-color); color: var(--light-text-color); width: 96%; - margin: 2% 0 0 2%; + margin: 2% 0 10px 2%; } #logo-header { display: none; @@ -117,7 +117,7 @@ article { border-radius: 25px; border: 1px solid var(--primary-color); color: var(--light-text-color); - margin: 10px 0 0 2.3%; + margin: 10px 0 10px 2.3%; display: inline-block; width: 30%; position: relative; @@ -302,3 +302,28 @@ main { font-size: 1rem; padding: 6px 15px; } + +.orderdescription { + display: block; + text-align: center; + margin: 5px; +} + +#sortmenu { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; + margin: 15px 2.3% 15px 2.3%; + border: 1px solid var(--primary-color); +} + +#sortmenu .sorter { + width: 50%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: row; + flex-wrap: wrap; +} diff --git a/client/style/map.css b/client/style/map.css index e64a4cd..3d9dd1f 100644 --- a/client/style/map.css +++ b/client/style/map.css @@ -15,3 +15,122 @@ main { position: relative; height:100%; } + +/* Describes the styling of all <article> elements */ +article { + color: var(--light-text-color); + border: none; + display: block; + width: 100%; + position: relative; +} + +/* Describes the styling of all elements with class="parkname", note the . in front of parkname */ +.parkname { + background-color: white; + color: black; + font-size: 1rem; + font-weight: bold; + text-transform: none; + display: block; + height: auto; +} + +/* Describes the styling of all elements with class="parkname", note the . in front of parkname */ +.parkdescription { + font-size: 1rem; + text-align: left; + height: 30px; + margin: 15px 15px 0 15px; +} + +.order { + padding-top: 25px; + text-align:center; +} + +.prices { + margin-bottom: 15px; + margin-left: 15px; + margin-right: 15px; + font-size: 0.9rem; +} + +.total { + margin: 10px; + font-size: 0.9rem; +} + +.adultinput { + width:100%; +} + +.kidsinput { + width:100%; +} + +.orderbutton { + background: hsl(170deg 0% 64%); + border-radius: 12px; + border: none; + padding: 0; + cursor: pointer; + outline-offset: 4px; + margin: 20px; +} + +.front { + font-size: 1rem; + padding: 6px 15px; + + padding: 10px 9px; + font-size: 1.2rem; + + display: block; + border-radius: 12px; + background: var(--primary-color); + color: white; + transform: translateY(-4px); + transition: + transform 600ms + cubic-bezier(.3, .7, .4, 1); +} + +.disabled { + display: block; + border-radius: 12px; + background: grey; + color: white; + transform: translateY(-2px); + padding: 10px 9px; + font-size: 1.2rem; +} + +.orderbutton:hover .front { + transform: translateY(-6px); + transition: + transform + 250ms + cubic-bezier(.3, .7, .4, 1.5); +} + + +.orderbutton:active .front { + transform: translateY(-2px); +} + +.orderbutton:active .disabled { + transform: translateY(-2px); +} + +.orderbutton:hover .disabled { + transform: translateY(-2px); +} + + + + +.cancel .front { + font-size: 1rem; + padding: 6px 15px; +} |
