tweaks for seat-chooser

This commit is contained in:
Didier Slof 2023-01-02 00:51:48 +01:00
parent b0cc5b5278
commit 2451ab45cb
Signed by: didier
GPG key ID: 01E71F18AA4398E5
4 changed files with 174 additions and 29 deletions

View file

@ -47,15 +47,9 @@ public function seatMatrix($showing_id = null)
{ {
$seats = $this->seats; $seats = $this->seats;
$matrix = []; $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 // then, fill it with the seats
foreach ($seats as $seat) { 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] = [ $matrix[$seat->seat_row-1][$seat->seat_column-1] = [
'seat_id' => $seat->seat_id, 'seat_id' => $seat->seat_id,
'seat_row' => $seat->seat_row, 'seat_row' => $seat->seat_row,
@ -65,7 +59,24 @@ public function seatMatrix($showing_id = null)
'reserved' => $showing_id ? $seat->isReserved($showing_id) : false, '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;
}

View file

@ -14,6 +14,12 @@ #seat-chooser {
justify-content: center; justify-content: center;
width: 100%; width: 100%;
height: 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 { #seat-chooser .row {
@ -29,12 +35,12 @@ #seat-chooser .row .seat {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 50px; width: var(--seat-width);
height: 50px; height: var(--seat-height);
margin: 5px; margin: var(--seat-gap);
border: 1px solid #000; border: var(--seat-border-size) solid var(--primary-color);
border-radius: 5px; border-radius: var(--seat-border-radius);
background-color: #fff; background-color: var(--second-bg);
cursor: pointer; cursor: pointer;
} }
@ -50,22 +56,39 @@ #seat-chooser .row .seat.linked-right {
border-left: 0; border-left: 0;
} }
#seat-chooser .row .seat.loveseat {
background-color: #ff3fe5;
}
#seat-chooser .row .no-seat { #seat-chooser .row .no-seat {
/* this seat DOES NOT EXISTS it's a filler for layout*/ /* this seat DOES NOT EXISTS it's a filler for layout*/
border: none; border: none;
background-color: transparent; background-color: transparent;
cursor: default; cursor: default;
width: 50px;
height: 50px; width: var(--seat-width);
margin: 5px; height: var(--seat-height);
margin: calc(var(--seat-border-size) + var(--seat-gap));
} }
#seat-chooser .row .seat.selected { #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 { #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 { #seat-chooser .row .seat:hover {

View file

@ -4,22 +4,38 @@
// the input is a matrix with null OR a seat object // the input is a matrix with null OR a seat object
class Seat { class Seat {
constructor(id, row, col, linked, reserved) { constructor(id, row, col, linked, reserved, type) {
this.id = id; // database id this.id = id; // database id
this.row = row; this.row = row;
this.col = col; this.col = col;
this.linked = linked; // linked seat this.linked = linked; // linked seat
this.reserved = reserved; this.reserved = reserved;
this.type = type; // seat type
} }
} }
class SeatChooser { class SeatChooser {
constructor(showingId, container) { setLoadingStatus(msg) {
if (container.innerText === '') { // if no status element exists, create one
container.innerText = 'Loading...'; if (!this.container.querySelector('#seat-chooser-status')) {
let status = document.createElement('div');
// h2 for "Loading..."
// span for message
status.innerHTML = `<h2>Loading...</h2><span id="seat-chooser-status-message"></span>`;
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.showingId = showingId; // id of the showing
this.container = container; // the container to render the seat chooser in 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 this.seats = []; // array of nulls and Seat objects
@ -29,34 +45,55 @@ class SeatChooser {
} }
async init() { async init() {
this.setLoadingStatus("Fetching showing data...");
let showingJson = await (await fetch(`/api/showings/${this.showingId}`)).json(); 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(); let roomMatrix = await (await fetch(`/api/showings/${this.showingId}/seatMatrix`)).json();
// Matrix<json | null> -> Matrix<Seat | null> // Matrix<json | null> -> Matrix<Seat | null>
this.setLoadingStatus("Processing seats...");
console.log("Matrix<json | null> -> Matrix<Seat | null>"); console.log("Matrix<json | null> -> Matrix<Seat | null>");
this.seats = roomMatrix.map((row, rowIndex) => { this.seats = roomMatrix.map((row, rowIndex) => {
return row.map((seat, colIndex) => { return row.map((seat, colIndex) => {
if (seat === null) { if (seat === null) {
return null; return null;
} }
this.setLoadingStatus("Processing seats... " + Math.round((rowIndex * row.length + colIndex) / (row.length * row.length) * 100) + "%");
return new Seat( return new Seat(
seat['seat_id'], seat['seat_id'],
seat['seat_row'], seat['seat_row'],
seat['seat_column'], seat['seat_column'],
seat['seat_linked_id'], seat['seat_linked_id'],
seat['reserved'] seat['reserved'],
seat['seat_type']
); );
}); });
}); });
console.log(this.seats); console.log(this.seats);
await this.fixCoronaSeating();
this.render(); 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() { render() {
console.log("rendering"); this.setLoadingStatus("Rendering...");
let linkedCache = false; let linkedCache = false;
// render the seat chooser // render the seat chooser
this.container.innerHTML = '';
// this -> row -> seat // this -> row -> seat
this.container.innerHTML = '';
this.seats.forEach((row, rowIndex) => { this.seats.forEach((row, rowIndex) => {
let rowDiv = document.createElement('div'); let rowDiv = document.createElement('div');
rowDiv.classList.add('row'); rowDiv.classList.add('row');
@ -64,7 +101,10 @@ class SeatChooser {
let seatDiv = document.createElement('div'); let seatDiv = document.createElement('div');
if (seat) { if (seat) {
seatDiv.classList.add('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 (seat.linked) {
// if cache is false, then this is the first seat in the linked pair // 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}`; 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.id = seat.id;
seatDiv.dataset.row = seat.row; seatDiv.dataset.row = seat.row;
seatDiv.dataset.col = seat.col; seatDiv.dataset.col = seat.col;
@ -95,12 +156,37 @@ class SeatChooser {
}); });
this.container.appendChild(rowDiv); 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) { toggleSeat(seat, e) {
// toggle the seat // toggle the seat
// because of linked seats, we need to toggle both seats // 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'); e.target.classList.toggle('selected');
if (seat.linked) { if (seat.linked) {
// beware that you can click on the left or right seat // 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);
});

View file

@ -1,6 +1,7 @@
@extends('layout') @extends('layout')
@push('head') @push('head')
<link rel="stylesheet" href="{{ asset('css/generic.css') }}">
<link rel="stylesheet" href="{{ asset('css/seat-chooser.css') }}"> <link rel="stylesheet" href="{{ asset('css/seat-chooser.css') }}">
<script src="{{ asset('js/seat-chooser.js') }}" defer></script> <script src="{{ asset('js/seat-chooser.js') }}" defer></script>
@endpush @endpush