diff --git a/dist/index.html b/dist/index.html index 35332a6f9..3b294f9a7 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,16 +1,77 @@ - - - - - Webpack Starter Kit - - - turing logo - - - - - + + + + + + + + + + + Overlook Hotel + + +
+

Overlook Hotel

+
+
+
+
+
+
+
+ +
+
+
+ + + + +
+
+
+ + + + diff --git a/src/api-calls.js b/src/api-calls.js new file mode 100644 index 000000000..576d47486 --- /dev/null +++ b/src/api-calls.js @@ -0,0 +1,56 @@ +//<><>fetch calls<><> +const getAllUsers = fetch('http://localhost:3001/api/v1/customers') +.then(response => response.json()); +const getAllRooms = fetch('http://localhost:3001/api/v1/rooms') +.then(response => response.json()); +const getAllBookings = fetch('http://localhost:3001/api/v1/bookings') +.then(response => response.json()); +const promises = [getAllUsers, getAllRooms, getAllBookings]; + +//<><>functions<><> +function getAllData() { + return Promise.all(promises) + .then(data => { + return data}) + .catch(error => console.log(error)) + } + +function addBooking(bookingData) { + return fetch('http://localhost:3001/api/v1/bookings', { + method: 'POST', + body: JSON.stringify(bookingData), + headers: { "Content-Type": "application/json" + } + }) + .then(response => { + if (!response.ok) { + throw new Error(error) + } else { + return response.json() + } + }) + .catch(error => console.log('this errror', error)) +} + +function cancelBooking(bookingId) { + return fetch(`http://localhost:3001/api/v1/bookings/${bookingId}`, { + method: 'DELETE', + headers: { + "Content-Type": "application/json" + } + }) + .then(response => { + if (!response.ok) { + throw new Error(error) + } else { + return response.json() + } + }) + .catch(error => console.log('this errror', error)) +} + +export { + getAllData, + addBooking, + cancelBooking +} \ No newline at end of file diff --git a/src/booking.js b/src/booking.js new file mode 100644 index 000000000..3ca702310 --- /dev/null +++ b/src/booking.js @@ -0,0 +1,36 @@ +function getAvailableRooms(bookings, rooms, date) { + date = date.split('-').join('/') + const filteredRoomsByDate = bookings.reduce((availableBookings, booking) => { + if (booking.date.toString() !== date) { + availableBookings.push(booking) + } + return availableBookings + }, []) + const getRooms = filteredRoomsByDate.map((room) => { + const foundRoom = rooms.find((eachRoom) => { + return eachRoom.number === room.roomNumber + }) + return foundRoom + }) + if (getRooms.length === 0) { + return 'We apologize, but unfortunately there are no rooms for your selected date' + } else { + return getRooms + } +} + +function filterAvailableRoomsByType(availableBookings, type) { + const filteredBookings = availableBookings.filter((booking) => { + return booking.roomType.includes(type) + }) + if (filteredBookings.length === 0) { + return 'We apologize, but unfortunately there are no rooms by that type available' + } else { + return filteredBookings + } +} + + + + +export {getAvailableRooms, filterAvailableRoomsByType} \ No newline at end of file diff --git a/src/css/styles.scss b/src/css/styles.scss index d0637ec3a..3467be268 100644 --- a/src/css/styles.scss +++ b/src/css/styles.scss @@ -1,4 +1,205 @@ +@mixin logoStripes($color) { + background: $color; + width: 100%; + height: 15px; +} body { - background: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%); + margin: 0; +} + +.title-heading { + background-color: rgb(123, 243, 243); + height: 150px; + display: flex; + flex-direction: column; + justify-content: space-between; + margin: 0; + padding: 0; +} + +h1 { + color: red; + margin-top: 25px; + margin-left: 50px; + font-family: Monoton, sans-serif; +} + +h2, +h3 { + font-family: Roboto, sans-serif; + font-weight: 600; + color: red; + text-align: center; +} + +.stripe1 { + @include logoStripes(rgb(248, 241, 13)); +} + +.stripe2 { + @include logoStripes(orange); +} + +.stripe3 { + @include logoStripes(rgb(255, 0, 0)); +} + +.content { + display: flex; + margin-top: 9.4px; +} + +.nav-bar { + background-color: rgb(240, 226, 148); + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + width: 25%; + height: 100vh; +} + +.main-section { + background-image: url("https://cdn.kiwicollection.com/media/property/PR119439/xl/119439-01-Pool_Sunset_Beach_Villa_Baglioni_Resort_Maldives%201-Baglioni%20Maldives.jpg?cb=1634851275"); + background-repeat: no-repeat; + background-size: cover; + width: 100%; + height: 100vh; + display: flex; + justify-content: center; +} + +.bookings-display { + background-color: rgba(123, 243, 243, 0.7); + border-radius: 25px; + width: 60%; + height: auto; + overflow: auto; + margin-top: 75px; + margin-bottom: 25px; + display: flex; + flex-direction: column; + align-items: center; +} + +.buttons { + background-color: rgb(123, 243, 243); + font-family: Roboto, sans-serif; + font-weight: 600; + color: red; + margin-top: 60px; + margin-bottom: 20px; + width: 175px; + height: 50px; + border-radius: 25px; + cursor: pointer; +} + +.submit-button, +.filter-submit-button { + background-color: rgb(244, 124, 19); + color: rgb(123, 243, 243); + border-radius: 10px; + width: 150px; + cursor: pointer; +} + +.footer { + background-color: orange; + color: rgb(244, 124, 19); + text-align: center; + font-family: Monoton, sans-serif; +} + +p { + margin-top: 0; + margin-left: 250px; +} + +.user-booked-card, +.available-booking-card { + font-family: Roboto, sans-serif; + background-color: rgb(240, 226, 148); + border: 2px solid orange; + color: red; + width: 400px; + height: 250px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + margin-top: 15px; + border-radius: 25px; + cursor: pointer; +} + +.single-booking-display { + font-family: Roboto, sans-serif; + background-color: rgb(240, 226, 148); + border: 2px solid orange; + border-radius: 25px; + color: red; + height: 500px; + width: 400px; + margin-top: 25px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + text-align: center; +} + +.total-spent { + font-family: Roboto, sans-serif; + background-color: rgb(123, 243, 243); + color: red; + width: 175px; + height: 100px; + margin-top: 50px; + border: 2px solid black; + border-radius: 25px; + text-align: center; + padding-top: 15px; +} + +.date-form, +.type-search-form { + font-family: Roboto, sans-serif; + font-weight: 600; + margin-top: 60px; + background-color: rgb(123, 243, 243); + color: red; + height: 100px; + width: 90%; + border: 2px solid black; + border-radius: 25px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; +} + +.input-forms { + background-color: rgb(240, 226, 148); + color: red; + border-radius: 25px; + cursor: pointer; +} + +img { + height: 250px; + width: 250px; + margin-top: 35px; + border: 2px solid orange; + border-radius: 25px; +} + +.hidden { + display: none; +} + +.disabled-button { + cursor: not-allowed; } diff --git a/src/dom-updates.js b/src/dom-updates.js new file mode 100644 index 000000000..8321e9110 --- /dev/null +++ b/src/dom-updates.js @@ -0,0 +1,268 @@ +import { + getAllCustomerRoomBookings, + getTotalCostForAllBookings, +} from "./user.js"; +import { getAvailableRooms, filterAvailableRoomsByType } from "./booking.js"; +import { getAllData, addBooking, cancelBooking } from "./api-calls.js"; + +//<><>query selectors<><> +const myBookingsButton = document.getElementById("my-bookings-button"); +const bookARoomButton = document.getElementById("book-a-room-button"); +const bookThisRoomButton = document.getElementById("book-room-button"); +const cancelBookingButton = document.getElementById("cancel-room-button"); +const dateInput = document.getElementById("date"); +const roomTypeInput = document.getElementById("room-type"); +const bookedText = document.getElementById("booked-text"); +const canceledText = document.getElementById("canceled-text"); +const bookingDisplay = document.querySelector(".content-display"); +const totalSpentDisplay = document.querySelector(".total-spent"); +const submitButton = document.querySelector(".submit-button"); +const filterSearchButton = document.querySelector(".filter-submit-button"); +const dateForm = document.querySelector(".date-form"); +const filterByRoomTypeDisplay = document.querySelector(".type-search-form"); + +//<><>data model<><> +let allData; +let customer; +let bookingsByDate; +let filteredBookings; +let currentBooking; +let date; +const images = [ + "https://www.cvent.com/sites/default/files/image/2021-10/hotel%20room%20with%20beachfront%20view.jpg", + "https://www.rd.com/wp-content/uploads/2023/05/GettyImages-1445292736.jpg", + "https://www.travelandleisure.com/thmb/OiDnPGo3k9QLRT9__TPhFZcr7PU=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/rosewood-carlyle-presidential-suite-LUXESUITE0122-0046808a88924e57922d78c7f1d9ca60.jpg", + "https://hoteldel.com/wp-content/uploads/2021/03/hotel-del-coronado-views-suite-K1TOS1-K1TOJ1-1600x1000-1.jpg", +]; + +//<><>event listeners<><> +myBookingsButton.addEventListener("click", () => { + bookingDisplay.innerHTML = ""; + let bookings = allData[2].bookings; + let rooms = allData[1].rooms; + customer.bookings = getAllCustomerRoomBookings(customer, bookings, rooms); + let bookingCards = createUserBookedRoomsCard(customer.bookings); + populateContentDisplay(bookingCards); + showElements([totalSpentDisplay]); + hideElements([ + dateForm, + filterByRoomTypeDisplay, + bookThisRoomButton, + cancelBookingButton, + bookedText, + canceledText, + ]); + let totalSpentByCustomer = getTotalCostForAllBookings(customer.bookings); + totalSpentDisplay.innerText = `You have spent a total of $${totalSpentByCustomer} on ${customer.bookings.length} rooms`; + console.log("cust", customer); +}); + +bookARoomButton.addEventListener("click", () => { + showElements([dateForm]); + hideElements([totalSpentDisplay, bookedText]); +}); + +submitButton.addEventListener("click", function (event) { + event.preventDefault(); + bookingDisplay.innerHTML = ""; + showElements([filterByRoomTypeDisplay]); + const dateInput = document.getElementById("date"); + date = dateInput.value.toString(); + let bookings = allData[2].bookings; + let rooms = allData[1].rooms; + bookingsByDate = getAvailableRooms(bookings, rooms, date); + let bookingCards = createAvailableBookingsCard(bookingsByDate); + // let bookingCards = "We apologize, but unfortunately there are no rooms for your selected date"; + populateContentDisplay(bookingCards); + dateForm.reset(); +}); + +filterSearchButton.addEventListener("click", (event) => { + event.preventDefault(); + bookingDisplay.innerHTML = ""; + const filteredType = document.getElementById("room-type"); + const roomType = filteredType.value; + filteredBookings = filterAvailableRoomsByType(bookingsByDate, roomType); + let bookingCards = createAvailableBookingsCard(filteredBookings); + // let bookingCards = 'We apologize, but unfortunately there are no rooms by that type available'; + populateContentDisplay(bookingCards); + filterByRoomTypeDisplay.reset(); +}); + +dateInput.addEventListener("input", () => { + disableButton(dateInput, submitButton); +}); + +roomTypeInput.addEventListener("input", () => { + disableButton(roomTypeInput, filterSearchButton); +}); + +bookingDisplay.addEventListener("click", (event) => { + if (event.target.classList.contains("user-booked-card")) { + currentBooking = findBooking(event.target.id, customer.bookings); + const bookingToDisplay = renderSingleBooking(currentBooking); + bookingDisplay.innerHTML = bookingToDisplay; + bookThisRoomButton.innerText = "Book Room"; + showElements([cancelBookingButton]); + hideElements([bookThisRoomButton]); + } else if (event.target.classList.contains("available-booking-card")) { + currentBooking = findBooking(event.target.id, bookingsByDate); + const bookingToDisplay = renderSingleBooking(currentBooking); + bookingDisplay.innerHTML = bookingToDisplay; + bookThisRoomButton.innerText = "Book Room"; + showElements([bookThisRoomButton]); + hideElements([cancelBookingButton]); + } +}); + +bookThisRoomButton.addEventListener("click", () => { + date = date.split("-").join("/"); + let bookingToAdd = { + userID: customer.id, + date: date, + roomNumber: currentBooking.number, + }; + addBooking(bookingToAdd) + .then((response) => { + console.log(response); + const newBooking = response.newBooking; + allData[2].bookings.push(newBooking); + showElements([bookedText, cancelBookingButton]); + hideElements([bookThisRoomButton]); + return getAllData(); + }) + .then((apiData) => { + allData = apiData; + console.log("allagain", allData); + }); +}); + +cancelBookingButton.addEventListener("click", () => { + cancelBooking(currentBooking.id) + .then((response) => { + let bookings = allData[2].bookings; + let bookingToRemove = removeBooking(bookings, currentBooking); + bookings.splice(bookingToRemove, 1); + showElements([canceledText]); + hideElements([cancelBookingButton]); + return getAllData(); + }) + .then((apiData) => { + allData = apiData; + }); +}); + +//<><>event handlers<><> +export const load = () => { + document.addEventListener("DOMContentLoaded", function () { + getAllData().then((apiData) => { + allData = apiData; + customer = getRandomUser(allData[0].customers); + let bookings = allData[2].bookings; + let rooms = allData[1].rooms; + customer.bookings = getAllCustomerRoomBookings(customer, bookings, rooms); + console.log('all data', allData) + }); + }); +}; + +function populateContentDisplay(bookings) { + if ( + bookings === + "We apologize, but unfortunately there are no rooms by that type available" || + bookings === + "We apologize, but unfortunately there are no rooms for your selected date" + ) { + bookingDisplay.innerHTML = `

${bookings}

`; + } else { + bookings.forEach((booking) => { + bookingDisplay.innerHTML += booking; + }); + } +} + +function showElements(elements) { + const shownElement = elements.forEach((element) => { + element.classList.remove("hidden"); + }); + return shownElement; +} + +function hideElements(elements) { + const hiddenElement = elements.forEach((element) => { + element.classList.add("hidden"); + }); + return hiddenElement; +} + +function renderSingleBooking(booking) { + console.log("thisbooking", booking); + const singleBooking = `
+

${booking.roomType.toUpperCase()}

+
Number of Beds: ${booking.numBeds}
+
Bed Size: ${booking.bedSize}
+
Cost Per Night: ${booking.costPerNight}
+ hotel room with bed +
`; + return singleBooking; + } + + function disableButton(field, button) { + if (field.value !== "") { + button.disabled = false; + } else { + button.disabled = true; + } + } + +//<><>functions<><> +function getRandomUser(users) { + let randomIndex = Math.floor(Math.random() * users.length); + let randomUser = users[randomIndex]; + return randomUser; +} + +function createUserBookedRoomsCard(bookings) { + const userBookingsCards = bookings.map((booking, i) => { + let card = `
+

${booking.roomType.toUpperCase()} - ${booking.bedSize.toUpperCase()} BED

+
Number of Beds: ${booking.numBeds}
+
You have booked this on ${booking.dateBooked} at a cost of $${ + booking.costPerNight + } per night
+
`; + return card; + }); + return userBookingsCards; +} + +function createAvailableBookingsCard(bookings) { + const availableBookingCards = bookings.map((booking, i) => { + let card = `
+

${booking.roomType.toUpperCase()}

+
Number of Beds: ${booking.numBeds}
+
Bed Size: ${booking.bedSize}
+
Cost Per Night: ${booking.costPerNight}
+
`; + return card; + }); + return availableBookingCards; +} + +function findBooking(target, bookings) { + let booking = bookings[target]; + return booking; +} + +function generateRandomImage(images) { + let randomIndex = Math.floor(Math.random() * images.length); + let randomImage = images[randomIndex]; + return randomImage; +} + +function removeBooking(bookings, currentBooking) { + const bookingToRemove = bookings.findIndex((booking) => { + return booking.id === currentBooking.id; + }); + return bookingToRemove +} diff --git a/src/images/.DS_Store b/src/images/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/src/images/.DS_Store differ diff --git a/src/images/Overlook-logo.png b/src/images/Overlook-logo.png new file mode 100644 index 000000000..286b72eb5 Binary files /dev/null and b/src/images/Overlook-logo.png differ diff --git a/src/scripts.js b/src/scripts.js index 1e4d0f75c..b57bd43c4 100644 --- a/src/scripts.js +++ b/src/scripts.js @@ -5,7 +5,13 @@ import './css/styles.scss'; // An example of how you tell webpack to use an image (also need to link to it in the index.html) -import './images/turing-logo.png' +// import './images/turing-logo.png' +// import {getAllData, getAllUsers, getAllRooms, getAllBookings} from './api-calls.js' -console.log('This is the JavaScript entry file - your code begins here.'); +// console.log('This is the JavaScript entry file - your code begins here.'); +// export let allData; +import { getAllData } from './api-calls.js'; +import {load} from './dom-updates.js'; +load() + diff --git a/src/user.js b/src/user.js new file mode 100644 index 000000000..ffabd1794 --- /dev/null +++ b/src/user.js @@ -0,0 +1,34 @@ +function getAllCustomerRoomBookings(customer, bookings, rooms) { + const customerBookings = bookings.filter((booking) => booking.userID === customer.id); + const roomsBooked = customerBookings.map((booking) => { + const room = rooms.find((room) => room.number === booking.roomNumber); + return { + roomType: room.roomType, + numBeds: room.numBeds, + bedSize: room.bedSize, + dateBooked: booking.date, + costPerNight: room.costPerNight, + id: booking.id, + roomNumber: room.number + }; + }); + if (roomsBooked.length === 0) { + return "You currently have no bookings"; + } else { + return roomsBooked; + } +} + +function getTotalCostForAllBookings(customerBookings) { + if (customerBookings === "You currently have no bookings") { + return 0; + } else { + const totalSpent = customerBookings.reduce((total, booking) => { + total += booking.costPerNight; + return total; + }, 0); + return parseFloat(totalSpent.toFixed(2)); + } +} + +export { getAllCustomerRoomBookings, getTotalCostForAllBookings }; diff --git a/test/booking-test.js b/test/booking-test.js new file mode 100644 index 000000000..426326c8b --- /dev/null +++ b/test/booking-test.js @@ -0,0 +1,37 @@ +import chai from 'chai'; +const expect = chai.expect; + +import {bookings, rooms, bookedRoomsForSadPath} from './mock-data'; +import {getAvailableRooms, filterAvailableRoomsByType} from '../src/booking.js' + +describe('Get available bookings', function() { + it('Should return all bookings available for a given date', function() { + const date = "2022/01/10"; + const availableRooms = getAvailableRooms(bookings, rooms, date); + expect(availableRooms).to.deep.equal([rooms[0], rooms[1]]) + }) + it('Should notify a customer if no rooms are available on a given date', function() { + const date = "2022/01/10"; + const availableRooms = getAvailableRooms(bookedRoomsForSadPath, rooms, date); + expect(availableRooms).to.equal('We apologize, but unfortunately there are no rooms for your selected date') + }) +}) + +describe('Filter rooms by their room type', function() { + it('Should return a list of rooms that fit a given room type and that are available on the date chosen', function() { + const date = "2022/01/10"; + const availableRooms = getAvailableRooms(bookings, rooms, date); + const type = 'suite' + const roomsByType = filterAvailableRoomsByType(availableRooms, type) + expect(roomsByType).to.deep.equal([rooms[0], rooms[1]]) + }) + it('Should notify user if there are no rooms by that type available', function() { + const date = "2022/01/10"; + const availableRooms = getAvailableRooms(bookings, rooms, date); + const type = 'single room'; + const roomsByType = filterAvailableRoomsByType(availableRooms, type); + expect(roomsByType).to.equal('We apologize, but unfortunately there are no rooms by that type available') + }) +}) + + diff --git a/test/mock-data.js b/test/mock-data.js new file mode 100644 index 000000000..c502954b7 --- /dev/null +++ b/test/mock-data.js @@ -0,0 +1,130 @@ +export const bookings = [ + { + id: "aaa", + userID: 1, + date: "2022/04/22", + roomNumber: 10, + }, + { + id: "bbb", + userID: 2, + date: "2022/01/24", + roomNumber: 11, + }, + { + id: "ccc", + userID: 3, + date: "2022/01/10", + roomNumber: 12, + }, + { + id: "ddd", + userID: 3, + date: "2022/01/10", + roomNumber: 13, + }, + { + id: "eee", + userID: 1, + date: "2022/01/10", + roomNumber: 14, + }, +]; + +export const rooms = [ + { + number: 10, + roomType: "residential suite", + bidet: true, + bedSize: "queen", + numBeds: 1, + costPerNight: 358.4, + }, + { + number: 11, + roomType: "suite", + bidet: false, + bedSize: "full", + numBeds: 2, + costPerNight: 477.38, + }, + { + number: 12, + roomType: "single room", + bidet: false, + bedSize: "king", + numBeds: 1, + costPerNight: 491.14, + }, + { + number: 13, + roomType: "single room", + bidet: false, + bedSize: "queen", + numBeds: 1, + costPerNight: 429.44, + }, + { + number: 14, + roomType: "suite", + bidet: true, + bedSize: "queen", + numBeds: 2, + costPerNight: 340.17, + }, +]; + +export const customers = [ + { + id: 1, + name: "Leatha Ullrich", + }, + { + id: 2, + name: "Rocio Schuster", + }, + { + id: 3, + name: "Kelvin Schiller", + }, + { + id: 12, + name: "Rocio Schuster", + }, +]; + +export const userBookings = [ + { + roomType: "residential suite", + numBeds: 1, + bedSize: "queen", + dateBooked: "2022/04/22", + costPerNight: 358.4, + id: 'aaa', + roomNumber: 10 + }, + { + roomType: "suite", + numBeds: 2, + bedSize: "queen", + dateBooked: "2022/01/10", + costPerNight: 340.17, + id: 'eee', + roomNumber: 14 + }, +]; + +export const bookedRoomsForSadPath =[ + { + id: "ccc", + userID: 3, + date: "2022/01/10", + roomNumber: 12, + }, + { + id: "ddd", + userID: 3, + date: "2022/01/10", + roomNumber: 13, + } +] diff --git a/test/sample-test.js b/test/sample-test.js index e756b0fb3..85d267ed6 100644 --- a/test/sample-test.js +++ b/test/sample-test.js @@ -1,8 +1,8 @@ -import chai from 'chai'; -const expect = chai.expect; +// import chai from 'chai'; +// const expect = chai.expect; -describe('See if the tests are running', function() { - it('should return true', function() { - expect(true).to.equal(true); - }); -}); +// describe('See if the tests are running', function() { +// it('should return true', function() { +// expect(true).to.equal(true); +// }); +// }); diff --git a/test/user-test.js b/test/user-test.js new file mode 100644 index 000000000..fe43fe638 --- /dev/null +++ b/test/user-test.js @@ -0,0 +1,34 @@ +import chai from 'chai'; +const expect = chai.expect; + +import {bookings, rooms, customers, userBookings} from './mock-data'; +import {getAllCustomerRoomBookings, getTotalCostForAllBookings} from '../src/user'; + +describe('Show all customer bookings', function() { + it('should show all of a customers bookings, past and future', function() { + const customer = customers[0]; + const allBookings = getAllCustomerRoomBookings(customer, bookings, rooms); + expect(allBookings).to.deep.equal([userBookings[0], userBookings[1]]) + }) + it('Should notify a customer if they have no bookings', function() { + const customer = customers[3]; + const allBookings = getAllCustomerRoomBookings(customer, bookings, rooms); + expect(allBookings).to.deep.equal('You currently have no bookings') + }); +}); + +describe('Show total costs of bookings', function() { + it('Should show the total amount a user has spent on bookings', function(){ + const customer = customers[2]; + const allRoomBookings = getAllCustomerRoomBookings(customer, bookings, rooms); + const totalSpent = getTotalCostForAllBookings(allRoomBookings); + expect(totalSpent).to.equal(920.58) + }) + it('Should show zero if a customer has no bookings', function() { + const customer = customers[3]; + const allRoomBookings = getAllCustomerRoomBookings(customer, bookings, rooms); + const totalSpent = getTotalCostForAllBookings(allRoomBookings); + expect(totalSpent).to.equal(0) + }) +}) +