2023-01-01 20:13:11 +01:00
|
|
|
// this file should contain a class that gets info from /api/showings/{id}
|
|
|
|
// and renders the seat chooser
|
|
|
|
// there may be multiple seats selected at once
|
|
|
|
// the input is a matrix with null OR a seat object
|
|
|
|
|
|
|
|
class Seat {
|
2023-01-02 00:51:48 +01:00
|
|
|
constructor(id, row, col, linked, reserved, type) {
|
2023-01-01 20:13:11 +01:00
|
|
|
this.id = id; // database id
|
|
|
|
this.row = row;
|
|
|
|
this.col = col;
|
|
|
|
this.linked = linked; // linked seat
|
|
|
|
this.reserved = reserved;
|
2023-01-02 00:51:48 +01:00
|
|
|
this.type = type; // seat type
|
2023-01-01 20:13:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SeatChooser {
|
2023-01-02 00:51:48 +01:00
|
|
|
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 = `<h2>Loading...</h2><span id="seat-chooser-status-message"></span>`;
|
|
|
|
status.id = 'seat-chooser-status';
|
|
|
|
status.style.textAlign = 'center';
|
|
|
|
this.container.appendChild(status);
|
2023-01-01 20:13:11 +01:00
|
|
|
}
|
2023-02-02 08:17:38 +01:00
|
|
|
// set the message if it exists
|
|
|
|
if (this.container.querySelector('#seat-chooser-status-message'))
|
|
|
|
this.container.querySelector('#seat-chooser-status-message').innerText = msg;
|
2023-01-02 00:51:48 +01:00
|
|
|
}
|
|
|
|
|
2023-02-02 08:17:38 +01:00
|
|
|
constructor(showingId, container, seatClickCallback = (sc, s) => {}, submitCallback = null) {
|
2023-01-01 20:13:11 +01:00
|
|
|
this.showingId = showingId; // id of the showing
|
|
|
|
this.container = container; // the container to render the seat chooser in
|
2023-01-02 00:51:48 +01:00
|
|
|
|
|
|
|
this.setLoadingStatus("Initializing...");
|
2023-01-01 20:13:11 +01:00
|
|
|
|
2023-02-02 08:17:38 +01:00
|
|
|
this.submitCallback = submitCallback; // callback function to call when the user submits the form
|
|
|
|
this.seatClickCallback = seatClickCallback; // callback function to call when the user clicks on a seat
|
2023-01-01 20:13:11 +01:00
|
|
|
|
2023-02-02 08:17:38 +01:00
|
|
|
this.seats = []; // array of nulls and Seat objects
|
2023-01-01 20:13:11 +01:00
|
|
|
this.selectedSeats = []; // array of Seat objects
|
|
|
|
|
|
|
|
this.init();
|
|
|
|
}
|
|
|
|
|
|
|
|
async init() {
|
2023-01-02 00:51:48 +01:00
|
|
|
this.setLoadingStatus("Fetching showing data...");
|
2023-01-01 20:13:11 +01:00
|
|
|
let showingJson = await (await fetch(`/api/showings/${this.showingId}`)).json();
|
2023-01-02 00:51:48 +01:00
|
|
|
this.setLoadingStatus("Fetching seats...");
|
2023-01-01 20:13:11 +01:00
|
|
|
let roomMatrix = await (await fetch(`/api/showings/${this.showingId}/seatMatrix`)).json();
|
|
|
|
// Matrix<json | null> -> Matrix<Seat | null>
|
2023-01-02 00:51:48 +01:00
|
|
|
this.setLoadingStatus("Processing seats...");
|
2023-01-01 20:13:11 +01:00
|
|
|
console.log("Matrix<json | null> -> Matrix<Seat | null>");
|
|
|
|
this.seats = roomMatrix.map((row, rowIndex) => {
|
|
|
|
return row.map((seat, colIndex) => {
|
|
|
|
if (seat === null) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-01-02 00:51:48 +01:00
|
|
|
this.setLoadingStatus("Processing seats... " + Math.round((rowIndex * row.length + colIndex) / (row.length * row.length) * 100) + "%");
|
2023-01-01 20:13:11 +01:00
|
|
|
return new Seat(
|
|
|
|
seat['seat_id'],
|
|
|
|
seat['seat_row'],
|
|
|
|
seat['seat_column'],
|
|
|
|
seat['seat_linked_id'],
|
2023-01-02 00:51:48 +01:00
|
|
|
seat['reserved'],
|
|
|
|
seat['seat_type']
|
2023-01-01 20:13:11 +01:00
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
console.log(this.seats);
|
2023-01-02 00:51:48 +01:00
|
|
|
await this.fixCoronaSeating();
|
2023-01-01 20:13:11 +01:00
|
|
|
this.render();
|
|
|
|
}
|
|
|
|
|
2023-01-02 00:51:48 +01:00
|
|
|
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';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:13:11 +01:00
|
|
|
render() {
|
2023-01-02 00:51:48 +01:00
|
|
|
this.setLoadingStatus("Rendering...");
|
2023-01-01 20:13:11 +01:00
|
|
|
let linkedCache = false;
|
|
|
|
// render the seat chooser
|
|
|
|
// this -> row -> seat
|
2023-01-02 00:51:48 +01:00
|
|
|
this.container.innerHTML = '';
|
2023-01-01 20:13:11 +01:00
|
|
|
this.seats.forEach((row, rowIndex) => {
|
|
|
|
let rowDiv = document.createElement('div');
|
|
|
|
rowDiv.classList.add('row');
|
|
|
|
row.forEach((seat, colIndex) => {
|
|
|
|
let seatDiv = document.createElement('div');
|
|
|
|
if (seat) {
|
|
|
|
seatDiv.classList.add('seat');
|
2023-01-02 00:51:48 +01:00
|
|
|
if (seat.reserved) {
|
|
|
|
seatDiv.classList.add('reserved');
|
|
|
|
seatDiv.title = 'This seat is reserved';
|
|
|
|
}
|
2023-01-01 20:13:11 +01:00
|
|
|
|
|
|
|
if (seat.linked) {
|
|
|
|
// if cache is false, then this is the first seat in the linked pair
|
|
|
|
if (!linkedCache) {
|
|
|
|
seatDiv.classList.add('linked-left');
|
|
|
|
linkedCache = true;
|
|
|
|
} else {
|
|
|
|
seatDiv.classList.add('linked-right');
|
|
|
|
linkedCache = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
linkedCache = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
seatDiv.innerText = `${seat.row}-${seat.col}`;
|
|
|
|
|
2023-01-02 00:51:48 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-01 20:13:11 +01:00
|
|
|
seatDiv.dataset.id = seat.id;
|
|
|
|
seatDiv.dataset.row = seat.row;
|
|
|
|
seatDiv.dataset.col = seat.col;
|
|
|
|
|
|
|
|
seatDiv.addEventListener('click', (e) => {
|
|
|
|
this.toggleSeat(seat, e);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
seatDiv.classList.add('no-seat');
|
|
|
|
}
|
|
|
|
rowDiv.appendChild(seatDiv);
|
|
|
|
});
|
|
|
|
this.container.appendChild(rowDiv);
|
|
|
|
});
|
2023-01-02 00:51:48 +01:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2023-02-02 08:17:38 +01:00
|
|
|
if (this.submitCallback !== null) {
|
|
|
|
// 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);
|
|
|
|
}
|
2023-01-02 00:51:48 +01:00
|
|
|
this.container.appendChild(buttonContainer);
|
2023-01-01 20:13:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
toggleSeat(seat, e) {
|
|
|
|
// toggle the seat
|
|
|
|
// because of linked seats, we need to toggle both seats
|
2023-01-02 00:51:48 +01:00
|
|
|
if (seat.reserved || seat.type == 'not_available') return;
|
2023-01-01 20:13:11 +01:00
|
|
|
e.target.classList.toggle('selected');
|
|
|
|
if (seat.linked) {
|
|
|
|
// beware that you can click on the left or right seat
|
|
|
|
// the left seat has class linked-left and the right seat has class linked-right
|
|
|
|
let leftbool = e.target.classList.contains('linked-left');
|
|
|
|
let linkedSeat = this.seats[seat.row - 1][leftbool ? seat.col - 1 : seat.col + 1];
|
|
|
|
if (leftbool) e.target.nextElementSibling.classList.toggle('selected');
|
|
|
|
else e.target.previousElementSibling.classList.toggle('selected');
|
|
|
|
if (this.selectedSeats.includes(linkedSeat)) {
|
|
|
|
this.selectedSeats.splice(this.selectedSeats.indexOf(linkedSeat), 1);
|
|
|
|
this.selectedSeats.splice(this.selectedSeats.indexOf(seat), 1);
|
|
|
|
} else {
|
|
|
|
this.selectedSeats.push(linkedSeat);
|
|
|
|
this.selectedSeats.push(seat);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (this.selectedSeats.includes(seat)) {
|
|
|
|
this.selectedSeats.splice(this.selectedSeats.indexOf(seat), 1);
|
|
|
|
} else {
|
|
|
|
this.selectedSeats.push(seat);
|
|
|
|
}
|
|
|
|
}
|
2023-02-02 08:17:38 +01:00
|
|
|
this.seatClickCallback(this, seat, this.selectedSeats.includes(seat));
|
2023-01-01 20:13:11 +01:00
|
|
|
}
|
2023-01-02 00:51:48 +01:00
|
|
|
|
|
|
|
clearSelection() {
|
|
|
|
this.selectedSeats.forEach(seat => {
|
|
|
|
let seatDiv = this.container.querySelector(`[data-id="${seat.id}"]`);
|
|
|
|
seatDiv.classList.remove('selected');
|
|
|
|
});
|
|
|
|
this.selectedSeats = [];
|
|
|
|
}
|
2023-01-01 20:13:11 +01:00
|
|
|
}
|