diff --git a/app/Models/Room.php b/app/Models/Room.php index 207355f..f5e628b 100644 --- a/app/Models/Room.php +++ b/app/Models/Room.php @@ -47,15 +47,9 @@ public function seatMatrix($showing_id = null) { $seats = $this->seats; $matrix = []; - // first, create an empty matrix - for ($i = 0; $i < $this->room_rows-1; $i++) { - $matrix[$i] = []; - for ($j = 0; $j < $this->room_columns-1; $j++) { - $matrix[$i][$j] = null; - } - } // then, fill it with the seats foreach ($seats as $seat) { + if (!isset($matrix[$seat->seat_row - 1])) $matrix[$seat->seat_row - 1] = []; $matrix[$seat->seat_row-1][$seat->seat_column-1] = [ 'seat_id' => $seat->seat_id, 'seat_row' => $seat->seat_row, @@ -65,7 +59,24 @@ public function seatMatrix($showing_id = null) 'reserved' => $showing_id ? $seat->isReserved($showing_id) : false, ]; } - return $matrix; + return fix_matrix($matrix, $this->room_columns-1, $this->room_rows-1); } } + +function fix_matrix($m, $h, $w) { + $_nm = []; + // write the matrix into _nm and fill the empty spaces with null + for ($i = 0; $i <= $h; $i++) { + $_nm[$i] = []; + for ($j = 0; $j <= $w; $j++) { + if (isset($m[$i][$j])) { + $_nm[$i][$j] = $m[$i][$j]; + } else { + $_nm[$i][$j] = null; + } + } + } + + return $_nm; +} diff --git a/public/css/seat-chooser.css b/public/css/seat-chooser.css index efa2923..860d90b 100644 --- a/public/css/seat-chooser.css +++ b/public/css/seat-chooser.css @@ -14,6 +14,12 @@ #seat-chooser { justify-content: center; width: 100%; height: 100%; + + --seat-width: 2.5rem; + --seat-height: var(--seat-width); + --seat-gap: 0.5rem; + --seat-border-size: 0.1rem; + --seat-border-radius: 0.5rem; } #seat-chooser .row { @@ -29,12 +35,12 @@ #seat-chooser .row .seat { display: flex; align-items: center; justify-content: center; - width: 50px; - height: 50px; - margin: 5px; - border: 1px solid #000; - border-radius: 5px; - background-color: #fff; + width: var(--seat-width); + height: var(--seat-height); + margin: var(--seat-gap); + border: var(--seat-border-size) solid var(--primary-color); + border-radius: var(--seat-border-radius); + background-color: var(--second-bg); cursor: pointer; } @@ -50,22 +56,39 @@ #seat-chooser .row .seat.linked-right { border-left: 0; } +#seat-chooser .row .seat.loveseat { + background-color: #ff3fe5; +} + #seat-chooser .row .no-seat { /* this seat DOES NOT EXISTS it's a filler for layout*/ border: none; background-color: transparent; cursor: default; - width: 50px; - height: 50px; - margin: 5px; + + width: var(--seat-width); + height: var(--seat-height); + margin: calc(var(--seat-border-size) + var(--seat-gap)); } #seat-chooser .row .seat.selected { - background-color: #55f; + border: calc(var(--seat-gap) - 0.1rem) solid var(--primary-color); + margin: 0.1rem; + filter: brightness(1.2); } #seat-chooser .row .seat.reserved { - background-color: #f00; + background-color: var(--highlight-bg); + cursor: not-allowed; +} + +#seat-chooser .row .seat.disabled { + background-color: var(--default-bg); + cursor: not-allowed; +} + +#seat-chooser .row .seat.wheelchair { + background-color: #3498db; } #seat-chooser .row .seat:hover { diff --git a/public/js/seat-chooser.js b/public/js/seat-chooser.js index 44c5d3c..4bae2c5 100644 --- a/public/js/seat-chooser.js +++ b/public/js/seat-chooser.js @@ -4,22 +4,38 @@ // the input is a matrix with null OR a seat object class Seat { - constructor(id, row, col, linked, reserved) { + constructor(id, row, col, linked, reserved, type) { this.id = id; // database id this.row = row; this.col = col; this.linked = linked; // linked seat this.reserved = reserved; + this.type = type; // seat type } } class SeatChooser { - constructor(showingId, container) { - if (container.innerText === '') { - container.innerText = 'Loading...'; + setLoadingStatus(msg) { + // if no status element exists, create one + if (!this.container.querySelector('#seat-chooser-status')) { + let status = document.createElement('div'); + // h2 for "Loading..." + // span for message + status.innerHTML = `

Loading...

`; + status.id = 'seat-chooser-status'; + status.style.textAlign = 'center'; + this.container.appendChild(status); } + // set the message + this.container.querySelector('#seat-chooser-status-message').innerText = msg; + } + + constructor(showingId, container, submitCallback) { this.showingId = showingId; // id of the showing this.container = container; // the container to render the seat chooser in + this.submitCallback = submitCallback; // callback function to call when the user submits the form + + this.setLoadingStatus("Initializing..."); this.seats = []; // array of nulls and Seat objects @@ -29,34 +45,55 @@ class SeatChooser { } async init() { + this.setLoadingStatus("Fetching showing data..."); let showingJson = await (await fetch(`/api/showings/${this.showingId}`)).json(); + this.setLoadingStatus("Fetching seats..."); let roomMatrix = await (await fetch(`/api/showings/${this.showingId}/seatMatrix`)).json(); // Matrix -> Matrix + this.setLoadingStatus("Processing seats..."); console.log("Matrix -> Matrix"); this.seats = roomMatrix.map((row, rowIndex) => { return row.map((seat, colIndex) => { if (seat === null) { return null; } + this.setLoadingStatus("Processing seats... " + Math.round((rowIndex * row.length + colIndex) / (row.length * row.length) * 100) + "%"); return new Seat( seat['seat_id'], seat['seat_row'], seat['seat_column'], seat['seat_linked_id'], - seat['reserved'] + seat['reserved'], + seat['seat_type'] ); }); }); console.log(this.seats); + await this.fixCoronaSeating(); this.render(); } + async fixCoronaSeating() { + // the seat left or right of a set of reserved seats are not available + this.seats.forEach((row, rowIndex) => { + row.forEach((seat, colIndex) => { + if (seat === null) return; + if (seat.reserved) { + if (row[colIndex - 1] && !row[colIndex - 1].reserved) + row[colIndex - 1].type = 'not_available'; + if (row[colIndex + 1] && !row[colIndex + 1].reserved) + row[colIndex + 1].type = 'not_available'; + } + }); + }); + } + render() { - console.log("rendering"); + this.setLoadingStatus("Rendering..."); let linkedCache = false; // render the seat chooser - this.container.innerHTML = ''; // this -> row -> seat + this.container.innerHTML = ''; this.seats.forEach((row, rowIndex) => { let rowDiv = document.createElement('div'); rowDiv.classList.add('row'); @@ -64,7 +101,10 @@ class SeatChooser { let seatDiv = document.createElement('div'); if (seat) { seatDiv.classList.add('seat'); - seatDiv.classList.add(seat.reserved ? 'reserved' : 'available'); + if (seat.reserved) { + seatDiv.classList.add('reserved'); + seatDiv.title = 'This seat is reserved'; + } if (seat.linked) { // if cache is false, then this is the first seat in the linked pair @@ -81,6 +121,27 @@ class SeatChooser { seatDiv.innerText = `${seat.row}-${seat.col}`; + switch (seat.type) { + case 'not_available': + seatDiv.title = 'This seat is not available'; + seatDiv.classList.add('disabled'); + break; + + case 'wheelchair': + seatDiv.title = 'This seat is wheelchair accessible'; + seatDiv.classList.add('wheelchair'); + break; + + case 'loveseat': + seatDiv.title = 'This seat is a loveseat'; + seatDiv.classList.add('loveseat'); + break; + + case 'standard': + seatDiv.title = 'This is a standard seat'; + break; + } + seatDiv.dataset.id = seat.id; seatDiv.dataset.row = seat.row; seatDiv.dataset.col = seat.col; @@ -95,12 +156,37 @@ class SeatChooser { }); this.container.appendChild(rowDiv); }); + + // button container + let buttonContainer = document.createElement('div'); + buttonContainer.style.display = 'flex'; + + // clear button + let clearButton = document.createElement('button'); + clearButton.classList.add('button'); + clearButton.style.margin = '1rem'; + clearButton.innerText = 'Clear'; + clearButton.addEventListener('click', () => { + this.clearSelection(); + }); + buttonContainer.appendChild(clearButton); + + // submit button + let submitButton = document.createElement('button'); + submitButton.classList.add('button'); + submitButton.style.margin = '1rem'; + submitButton.innerText = 'Submit'; + submitButton.addEventListener('click', () => { + this.submitCallback(this); + }); + buttonContainer.appendChild(submitButton); + this.container.appendChild(buttonContainer); } toggleSeat(seat, e) { // toggle the seat // because of linked seats, we need to toggle both seats - if (seat.reserved) return; + if (seat.reserved || seat.type == 'not_available') return; e.target.classList.toggle('selected'); if (seat.linked) { // beware that you can click on the left or right seat @@ -124,6 +210,30 @@ class SeatChooser { } } } + + clearSelection() { + this.selectedSeats.forEach(seat => { + let seatDiv = this.container.querySelector(`[data-id="${seat.id}"]`); + seatDiv.classList.remove('selected'); + }); + this.selectedSeats = []; + } } -let seatChooser = new SeatChooser(1, document.getElementById('seat-chooser')); +let seatChooser = new SeatChooser(1, document.getElementById('seat-chooser'), (sc) => { + // gray overlay and show selected seats + let overlay = document.createElement('div'); + overlay.style.position = 'fixed'; + overlay.style.top = '0'; + overlay.style.left = '0'; + overlay.style.width = '100vw'; + overlay.style.height = '100vh'; + overlay.style.backgroundColor = 'rgba(0,0,0,0.5)'; + overlay.style.display = 'flex'; + overlay.style.justifyContent = 'center'; + overlay.innerText = 'You selected the following seats: ' + sc.selectedSeats.map(seat => `${seat.row}-${seat.col}`).join(', '); + overlay.addEventListener('click', () => { + overlay.remove(); + }); + sc.container.appendChild(overlay); +}); diff --git a/resources/views/components/seat-chooser.blade.php b/resources/views/components/seat-chooser.blade.php index 4133fe4..da51a17 100644 --- a/resources/views/components/seat-chooser.blade.php +++ b/resources/views/components/seat-chooser.blade.php @@ -1,6 +1,7 @@ @extends('layout') @push('head') + @endpush