fix+add: seats + seat-chooser

This commit is contained in:
Didier Slof 2023-01-01 20:13:11 +01:00
parent 2c6745e812
commit b0cc5b5278
Signed by: didier
GPG key ID: 01E71F18AA4398E5
31 changed files with 808 additions and 115 deletions

View file

@ -8,6 +8,7 @@ public function __construct()
{
$this->middleware('auth');
$this->middleware('atleast:employee');
$this->middleware('permission:manage_cinemas')->only(['create', 'store', 'edit', 'update', 'destroy']);
}
public function showAllCinemas()

View file

@ -10,6 +10,7 @@ class GenreController extends Controller
public function __construct() {
$this->middleware('auth');
$this->middleware('atleast:employee');
$this->middleware('permission:manage_genres')->only(['create', 'store', 'edit', 'update', 'destroy']);
}
public function showAllGenres() {

View file

@ -11,6 +11,7 @@ class MovieController extends Controller
public function __construct() {
$this->middleware('auth');
$this->middleware('atleast:employee');
$this->middleware('permission:manage_movies')->only(['create', 'store', 'edit', 'update', 'destroy']);
}
public function showAllMovies() {

View file

@ -29,5 +29,10 @@ public function user()
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function string()
{
return $this->address_street . ", " . $this->address_city . ", " . $this->address_state . " " . $this->address_zip . ", " . $this->address_country;
}
}

View file

@ -43,4 +43,29 @@ public function cinema()
return $this->belongsTo(Cinema::class, 'cinema_id', 'cinema_id');
}
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) {
$matrix[$seat->seat_row-1][$seat->seat_column-1] = [
'seat_id' => $seat->seat_id,
'seat_row' => $seat->seat_row,
'seat_column' => $seat->seat_column,
'seat_type' => $seat->seat_type,
'seat_linked_id' => $seat->seat_linked_id,
'reserved' => $showing_id ? $seat->isReserved($showing_id) : false,
];
}
return $matrix;
}
}

View file

@ -13,7 +13,8 @@ class Seat extends Model
protected $fillable = [
'seat_row',
'seat_number',
'seat_type', // enum('standard', 'wheelchair', 'loveseat')
'seat_type', // enum('standard', 'wheelchair', 'loveseat', 'not_available')
'seat_linked_id', // if this is a loveseat, this is the other seat
'room_id',
];
@ -27,28 +28,27 @@ public function room()
return $this->belongsTo(Room::class, 'room_id', 'room_id');
}
public function orders()
{
return $this->belongsToMany(Order::class, 'order_seats', 'seat_id', 'order_id');
}
public function tickets()
{
return $this->hasMany(Ticket::class, 'seat_id', 'seat_id');
}
public function linked_seat()
{
return $this->belongsTo(Seat::class, 'seat_linked_id', 'seat_id');
}
// isReserved(int showing_id) method
// Looks at showing / order / ticket if it's reserved
// Returns true if it is reserved, false if it isn't
public function is_reserved(int $showing_id)
public function isReserved(int $showing_id): bool
{
$tickets = $this->tickets()->where('showing_id', $showing_id)->get();
$tickets = $this->tickets->where('showing_id', $showing_id);
foreach ($tickets as $ticket) {
if ($ticket->order->order_status == 'pending' || $ticket->order->order_status == 'paid') {
if ($ticket->showing_id == $showing_id) {
return true;
}
}
return false;
}
}

View file

@ -2,6 +2,7 @@
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
@ -45,4 +46,29 @@ public function nowPlaying()
return $this->where('showing_start', '>=', now())->get();
}
public function end_time() {
$date = new Carbon($this->showing_start);
$date->addMinutes($this->movie->movie_length);
return $date;
}
public function tickets()
{
return $this->hasMany(Ticket::class, 'showing_id', 'showing_id');
}
public function tickets_available() {
$tickets = $this->room->seats()->count();
$tickets_sold = $this->tickets()->count();
return $tickets - $tickets_sold;
}
public function find($id)
{
return Showing::where('showing_id', $id)->first();
}
public function seatMatrix() {
return $this->room->seatMatrix($this->showing_id);
}
}

View file

@ -62,9 +62,6 @@ public function permissions()
public function allowedTo($permission): bool
{
if($this->role === 'manage') {
return true;
}
if ($this->permissions()->where('permission_name', $permission)->first()) {
return true;
}

View file

@ -25,14 +25,20 @@ public function __construct(int $room_id, int $showing_id)
}
public function matrixGenerate() {
$matrix = [];
for ($row = 1; $row <= $this->room->room_rows; $row++) {
$matrix[$row] = [];
for ($column = 1; $column <= $this->room->room_columns; $column++) {
$matrix[$row][$column] = 0;
// returns a matrix of seats
$m = [];
// first make empty matrix
for ($i = 0; $i < $this->room->room_rows-1; $i++) {
$m[$i] = [];
for ($j = 0; $j < $this->room->room_columns-1; $j++) {
$m[$i][$j] = null;
}
}
return $matrix;
$seats = $this->room->seats;
foreach ($seats as $seat) {
$m[$seat->seat_row][$seat->seat_column] = $seat;
}
return $m;
}
/**

View file

@ -13,6 +13,7 @@
"laravel/tinker": "^2.7"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.7",
"fakerphp/faker": "^1.9.1",
"laravel/pint": "^1.0",
"laravel/sail": "^1.0.1",

152
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8c432fe5b25b92e8fcd080943fcdb6a1",
"content-hash": "3ae872d179c399882f9ee7cadecede4c",
"packages": [
{
"name": "brick/math",
@ -5433,6 +5433,90 @@
}
],
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "3372ed65e6d2039d663ed19aa699956f9d346271"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/3372ed65e6d2039d663ed19aa699956f9d346271",
"reference": "3372ed65e6d2039d663ed19aa699956f9d346271",
"shasum": ""
},
"require": {
"illuminate/routing": "^7|^8|^9",
"illuminate/session": "^7|^8|^9",
"illuminate/support": "^7|^8|^9",
"maximebf/debugbar": "^1.17.2",
"php": ">=7.2.5",
"symfony/finder": "^5|^6"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"orchestra/testbench-dusk": "^5|^6|^7",
"phpunit/phpunit": "^8.5|^9.0",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.6-dev"
},
"laravel": {
"providers": [
"Barryvdh\\Debugbar\\ServiceProvider"
],
"aliases": {
"Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
}
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Barryvdh\\Debugbar\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "PHP Debugbar integration for Laravel",
"keywords": [
"debug",
"debugbar",
"laravel",
"profiler",
"webprofiler"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.7.0"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2022-07-11T09:26:42+00:00"
},
{
"name": "doctrine/instantiator",
"version": "1.4.1",
@ -5879,6 +5963,72 @@
},
"time": "2022-11-15T14:36:57+00:00"
},
{
"name": "maximebf/debugbar",
"version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "ba0af68dd4316834701ecb30a00ce9604ced3ee9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/ba0af68dd4316834701ecb30a00ce9604ced3ee9",
"reference": "ba0af68dd4316834701ecb30a00ce9604ced3ee9",
"shasum": ""
},
"require": {
"php": "^7.1|^8",
"psr/log": "^1|^2|^3",
"symfony/var-dumper": "^2.6|^3|^4|^5|^6"
},
"require-dev": {
"phpunit/phpunit": "^7.5.20 || ^9.4.2",
"twig/twig": "^1.38|^2.7|^3.0"
},
"suggest": {
"kriswallsmith/assetic": "The best way to manage assets",
"monolog/monolog": "Log using Monolog",
"predis/predis": "Redis storage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
}
},
"autoload": {
"psr-4": {
"DebugBar\\": "src/DebugBar/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maxime Bouroumeau-Fuseau",
"email": "maxime.bouroumeau@gmail.com",
"homepage": "http://maximebf.com"
},
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Debug bar in the browser for php application",
"homepage": "https://github.com/maximebf/php-debugbar",
"keywords": [
"debug",
"debugbar"
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.18.1"
},
"time": "2022-03-31T14:55:54+00:00"
},
{
"name": "mockery/mockery",
"version": "1.5.1",

View file

@ -19,8 +19,8 @@ public function up()
$table->string('cinema_name');
$table->foreignId('address_id')->constrained('addresses', 'address_id');
$table->foreignId('user_id')->constrained('users', 'user_id'); // who created this cinema
$table->timestamp('cinema_open')->nullable();
$table->timestamp('cinema_close')->nullable();
$table->time('cinema_open');
$table->time('cinema_close');
});
}

View file

@ -18,7 +18,8 @@ public function up()
$table->timestamps();
$table->addColumn('integer', 'seat_row');
$table->addColumn('integer', 'seat_column');
$table->addColumn('enum', 'seat_type', ['values' => ['standard', 'wheelchair', 'loveseat']]);
$table->enum('seat_type', ['standard', 'wheelchair', 'loveseat', 'not_available']);
$table->foreignId('seat_linked_id')->nullable()->constrained('seats', 'seat_id');
$table->foreignId('room_id')->constrained('rooms', 'room_id');
$table->unique(['room_id', 'seat_row', 'seat_column']);
});

View file

@ -25,6 +25,8 @@ public function run()
'address_zip' => '12345',
'address_phone' => '123-456-7890',
],
'cinema_open' => '10:00:00',
'cinema_close' => '22:00:00',
],
[
'cinema_name' => 'Cinema 2',
@ -36,6 +38,8 @@ public function run()
'address_zip' => '12345',
'address_phone' => '123-456-7890',
],
'cinema_open' => '12:00:00',
'cinema_close' => '23:00:00',
]
];
@ -44,6 +48,8 @@ public function run()
$c = new \App\Models\Cinema();
$c->cinema_name = $cinema['cinema_name'];
$c->user_id = 1;
$c->cinema_open = $cinema['cinema_open'];
$c->cinema_close = $cinema['cinema_close'];
$a = new \App\Models\Address();
$a->address_street = $cinema['cinema_address']['address_line_1'];

View file

@ -3,25 +3,16 @@ version: '3.3'
# LARAVEL ENVIRONMENT COMPOSE
services:
redis:
image: redis:alpine
container_name: redis
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: phpmyadmin
restart: always
ports:
- "6379:6379"
memcached:
image: memcached:alpine
container_name: memcached
restart: always
ports:
- "11211:11211"
mailhog:
image: mailhog/mailhog
container_name: mailhog
restart: always
ports:
- "1025:1025"
- "8025:8025"
- "8080:80"
environment:
PMA_HOST: neo.faulty.nl
PMA_PORT: 33061
PMA_USER: laravel
PMA_PASSWORD: laravel
PMA_ARBITRARY: 1
PMA_ABSOLUTE_URI: http://localhost:8080/

View file

@ -171,3 +171,7 @@ .hide {
.op0 {
opacity: 0;
}
.mono {
font-family: monospace;
}

View file

@ -168,3 +168,24 @@ form .form-group button:hover {
filter: brightness(1.5);
}
#showings {
display: flex;
flex-direction: column;
}
#showings .showing {
display: grid;
grid-template-columns: 2fr 1fr 1fr 0.25fr;
gap: 0.5rem;
margin: 0.5rem 0;
padding: 1rem;
background: var(--second-bg);
border-radius: 0.5rem;
}
#showings .showing .actions {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

View file

@ -1,39 +1,95 @@
:root {
--spacing: 1rem;
}
/*
* (C) (C) (C) (C) (C)
(R) S S S S S
(R) S S S S S
(R) S S S S S
(R) S S S S S
...
*/
.seat-chooser {
padding: var(--spacing);
#seat-chooser {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
position: relative;
background: var(--second-bg);
border: 1px solid #ccc;
border-radius: 5px;
overflow: hidden;
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
grid-template-rows: repeat(var(--rows), 1fr);
gap: var(--spacing);
}
.seat-chooser .seat {
position: relative;
background: #5f5;
border: 1px solid #ccc;
#seat-chooser .row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
#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;
overflow: hidden;
background-color: #fff;
cursor: pointer;
width: 32px;
height: 32px;
transition: all 0.3s ease;
}
.seat-chooser .seat.seat-reserved {
background: #f66;
#seat-chooser .row .seat.linked-left {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
}
.seat-chooser .seat:hover {
background: var(--highlight-bg);
#seat-chooser .row .seat.linked-right {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 0;
}
#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;
}
#seat-chooser .row .seat.selected {
background-color: #55f;
}
#seat-chooser .row .seat.reserved {
background-color: #f00;
}
#seat-chooser .row .seat:hover {
filter: brightness(0.8);
}
#load-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
div.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
}

129
public/js/seat-chooser.js Normal file
View file

@ -0,0 +1,129 @@
// 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 {
constructor(id, row, col, linked, reserved) {
this.id = id; // database id
this.row = row;
this.col = col;
this.linked = linked; // linked seat
this.reserved = reserved;
}
}
class SeatChooser {
constructor(showingId, container) {
if (container.innerText === '') {
container.innerText = 'Loading...';
}
this.showingId = showingId; // id of the showing
this.container = container; // the container to render the seat chooser in
this.seats = []; // array of nulls and Seat objects
this.selectedSeats = []; // array of Seat objects
this.init();
}
async init() {
let showingJson = await (await fetch(`/api/showings/${this.showingId}`)).json();
let roomMatrix = await (await fetch(`/api/showings/${this.showingId}/seatMatrix`)).json();
// Matrix<json | null> -> Matrix<Seat | null>
console.log("Matrix<json | null> -> Matrix<Seat | null>");
this.seats = roomMatrix.map((row, rowIndex) => {
return row.map((seat, colIndex) => {
if (seat === null) {
return null;
}
return new Seat(
seat['seat_id'],
seat['seat_row'],
seat['seat_column'],
seat['seat_linked_id'],
seat['reserved']
);
});
});
console.log(this.seats);
this.render();
}
render() {
console.log("rendering");
let linkedCache = false;
// render the seat chooser
this.container.innerHTML = '';
// this -> row -> seat
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');
seatDiv.classList.add(seat.reserved ? 'reserved' : 'available');
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}`;
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);
});
}
toggleSeat(seat, e) {
// toggle the seat
// because of linked seats, we need to toggle both seats
if (seat.reserved) return;
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);
}
}
}
}
let seatChooser = new SeatChooser(1, document.getElementById('seat-chooser'));

View file

@ -0,0 +1,33 @@
@extends('layout')
@push('head')
<link rel="stylesheet" href="{{ asset('css/seat-chooser.css') }}">
<script src="{{ asset('js/seat-chooser.js') }}" defer></script>
@endpush
<div id="load-screen">
<div id="loading">
<div class="spinner"></div>
<div class="loading-text">Loading...</div>
</div>
</div>
<div class="seat-chooser">
@foreach($seatmatrix as $row)
<div class="row">
@foreach($row as $seat)
@if($seat)
<div class="seat @if($seat->isReserved($showing_id)) reserved @endif"
data-seat-id="{{ $seat->seat_id }}"
data-seat-status="{{ $seat->seat_status }}"
data-seat-row="{{ $seat->seat_row }}"
data-seat-column="{{ $seat->seat_column }}">
<div class="seat-number">{{ $seat->seat_row }}-{{ $seat->seat_column }}</div>
</div>
@else
<div class="no-seat"></div>
@endif
@endforeach
</div>
@endforeach
</div>

View file

@ -2,26 +2,7 @@
@push('head')
<link rel="stylesheet" href="{{ asset('css/seat-chooser.css') }}">
<style>
:root {
--rows: {{ $room->room_rows }};
--cols: {{ $room->room_columns }};
}
</style>
<script src="{{ asset('js/seat-chooser.js') }}" defer></script>
@endpush
<div class="seat-chooser">
@foreach($seatmatrix as $row)
<div class="row">
@foreach($row as $seat)
<div class="seat @if($seat->isReserved) reserved @endif"
data-seat-id="{{ $seat->seat_id }}"
data-seat-status="{{ $seat->seat_status }}"
data-seat-row="{{ $seat->seat_row }}"
data-seat-column="{{ $seat->seat_column }}">
<div class="seat-number">{{ $seat->seat_row }}{{ $seat->seat_column }}</div>
</div>
@endforeach
</div>
@endforeach
</div>
<div id="seat-chooser"></div>

View file

@ -0,0 +1,34 @@
@extends('main.layout')
@section('content')
<section>
<h1>{{$cinema->cinema_name}}</h1>
<hr/>
<div id="cinema">
<div class="details">
<span>{{$cinema->address->string()}}</span><br/>
<span>{{$cinema->cinema_open}} - {{$cinema->cinema_close}}</span>
</div>
</div>
</section>
<section>
<h1>Now playing:</h1>
<hr/>
<div id="showings">
@foreach($cinema->showings as $showing)
<a href="/showing/{{$showing->showing_id}}">
<img src="{{$showing->movie->movie_image}}" alt="{{$showing->movie->movie_name}} Poster"
width="200px">
<div class="details">
<span>{{$showing->movie->movie_name}}</span><br/>
<span>{{$showing->showing_date}}</span><br/>
<span>{{$showing->showing_time}}</span>
</div>
</a>
@endforeach
</div>
</section>
@endsection

View file

@ -0,0 +1,30 @@
@extends('main.layout')
@section('content')
<section>
<h1>{{$genre->genre_name}}</h1>
<hr/>
<div id="genre">
<div class="details">
<span>{{$genre->genre_description}}</span><br/>
</div>
</div>
</section>
<section>
<h1>Movies:</h1>
<hr/>
<div id="movies">
@foreach($genre->movies as $movie)
<a href="/movie/{{$movie->movie_id}}">
<img src="{{$movie->movie_image}}" alt="{{$movie->movie_name}} Poster" width="200px">
<div class="details">
<span>{{$movie->movie_name}}</span><br/>
<span>{{$movie->movie_length}} min</span>
</div>
</a>
@endforeach
</div>
</section>
@endsection

View file

@ -1,20 +1,17 @@
@extends('main.layout')
@push('head')
<link rel="stylesheet" href="{{asset('css/movies.css')}}">
@endpush
@section('content')
<section>
<h1>Now playing:</h1>
<hr/>
<div id="movies">
@foreach($showings as $showing)
<a href="/movie/{{$showing->movie->movie_id}}">
<img src="{{$showing->movie->movie_image}}" alt="{{$showing->movie->movie_name}} Poster"
width="200px">
<div class="details">
<span>{{$showing->movie->movie_name}}</span><br/>
<span>{{$showing->movie->movie_length}} min</span>
</div>
</a>
@foreach($movies as $showing)
@endforeach
</section>

View file

@ -0,0 +1,37 @@
@extends('main.layout')
@section('content')
<section>
<h1><img src="{{$showing->movie->movie_image}}" alt="{{$showing->movie->movie_name}} Poster" width="60px">
{{$showing->movie->movie_name}}</h1>
<hr/>
<div id="showing">
<div class="details">
<span>Cinema: <a
href="/cinema/{{$showing->room->cinema->cinema_id}}">{{$showing->room->cinema->cinema_name}}</a></span><br/>
<span>Showing: {{$showing->showing_start}} - {{$showing->end_time()}}</span><br/>
</div>
</div>
</section>
<section>
<h1>Details</h1>
<hr/>
<span>{{$showing->movie->movie_description}}</span><br/>
<hr/>
<span>Runtime: {{$showing->movie->movie_length }} minutes</span><br/>
<span>Year: {{$showing->movie->movie_year}}</span><br/>
<span>Age limit: {{$showing->movie->movie_age_limit}}</span><br/>
<span>Genre: <a
href="/genre/{{$showing->movie->genre->genre_id}}">{{$showing->movie->genre->genre_name}}</a></span><br/>
</section>
<section>
<h1>Tickets:</h1>
<hr/>
<span>Tickets available: {{$showing->tickets_available()}}</span><br/>
<span>Buy tickets?</span>
{{-- todo--}}
</section>
@endsection

View file

@ -1,7 +1,7 @@
{{-- Layout for admins --}}
{{-- Will have a sidebar with links --}}
{{-- Will have a top bar with account and link to main site--}}
{{--@extends('layout')--}}
@extends('layout')
@push('head')
<link rel="stylesheet" href="{{ asset('css/generic.css') }}">
@ -19,9 +19,10 @@
<div id="sidebar"> {{-- Page Aware --}}
<a class="{{ Request::is('manage') ? 'active' : '' }}" href="{{ route('manage') }}">Dashboard</a>
{{-- manage cinemas--}}
<a class="{{ Request::is('manage/cinemas') ? 'active' : '' }}" href="{{ route('manage.cinemas') }}">Cinemas</a>
{{-- <a class="{{ Request::is('manage/cinemas') ? 'active' : '' }}" href="{{ route('manage.cinemas') }}">Cinemas</a>--}}
{{-- <a class="{{ Request::is('manage/cinemas/employees') ? 'active' : '' }} child" href="{{ route('manage.cinemas.employees') }}">Employees</a>--}}
<a class="{{ Request::is('manage/movies') ? 'active' : '' }}" href="{{ route('manage.movies') }}">Movies</a>
<a class="child {{ Request::is('manage/showings') ? 'active' : '' }}" href="{{ route('manage.showings') }}">Showings</a>
<a class="{{ Request::is('manage/genres') ? 'active' : '' }}" href="{{ route('manage.genres') }}">Genres</a>
</div>
<main>

View file

@ -0,0 +1,32 @@
@extends('manage.layout')
@section('content')
<h1>Showing Management</h1>
<div id="showings">
@foreach($showings as $showing)
<div class="showing">
<div class="movie">
<h2>{{ $showing->movie->movie_name }}</h2>
<p>{{ $showing->movie->movie_description }}</p>
<p>{{ $showing->movie->genre->genre_name }}</p>
</div>
<div class="cinema">
<h3>{{ $showing->room->cinema->cinema_name }} - {{ $showing->room->room_name }}</h3>
<p>{{ $showing->room->cinema->address->string() }}</p>
</div>
<div class="time">
<h3>Times</h3>
<span class="mono">
Start: {{ $showing->showing_start }}<br>
Ends : {{ $showing->end_time() }}
</span>
</div>
<div class="actions">
<a class="button" href="{{ route('manage.showing', ['id' => $showing->showing_id]) }}">Edit</a>
</div>
</div>
@endforeach
</div>
<a href="{{ route('manage.showings.create') }}">Create Showing</a>
@endsection

View file

@ -0,0 +1,84 @@
@extends('manage.layout')
@section('content')
<h1>Showing - {{ $showing->showing_start }}</h1>
<form method="POST" action="{{ route('manage.showing', ['id' => $showing->showing_id]) }}" class="form">
@csrf
@method('PUT')
<div class="form-group">
<label for="movie_id">Movie</label>
<select name="movie_id" id="movie_id">
@foreach($movies as $movie)
<option value="{{ $movie->movie_id }}" {{ $movie->movie_id == $showing->movie_id ? 'selected' : '' }}>{{ $movie->movie_name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="showing_start">Showing Start</label>
<input type="datetime-local" name="showing_start" id="showing_start" value="{{ $showing->showing_start }}">
</div>
<div class="form-group">
<label for="room_id">Cinema</label>
<select name="cinema_id" id="cinema_id">
@foreach($cinemas as $cinema)
<option value="{{ $cinema->cinema_id }}" {{ $cinema->cinema_id == $showing->room->cinema_id ? 'selected' : '' }}>{{ $cinema->cinema_name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="room_id">Room</label>
<select name="room_id" id="room_id">
@foreach($showing->room->cinema->rooms as $room)
<option value="{{ $room->room_id }}" {{ $room->room_id == $showing->room_id ? 'selected' : '' }}>{{ $room->room_name }}</option>
@endforeach
</select>
<script>
// select the current room
document.getElementById('cinema_id').addEventListener('change', function() {
var cinema_id = this.value;
var room_id = document.getElementById('room_id');
var rooms = @json($rooms);
var options = '';
for (var i = 0; i < rooms.length; i++) {
if (rooms[i].cinema_id == cinema_id) {
options += '<option value="' + rooms[i].room_id + '">' + rooms[i].room_name + '</option>';
}
}
room_id.innerHTML = options;
});
</script>
</div>
<script>
document.getElementById('cinema_id').addEventListener('change', function() {
var cinema_id = this.value;
var room_id = document.getElementById('room_id');
room_id.innerHTML = '';
fetch('/api/cinemas/' + cinema_id + '/rooms')
.then(function(response) {
return response.json();
})
.then(function(rooms) {
rooms.forEach(function(room) {
var option = document.createElement('option');
option.value = room.room_id;
option.innerHTML = room.room_name;
if (room.room_id == {{ $showing->room_id }}) {
option.selected = true;
}
room_id.appendChild(option);
});
room_id.disabled = false;
});
});
</script>
<div class="form-group">
<button type="submit" class="button">Update Showing</button>
</div>
</form>
<form method="POST" action="{{ route('manage.showing', ['id' => $showing->showing_id]) }}">
@csrf
@method('DELETE')
<button type="submit" class="button">Delete Showing</button>
</form>
@endsection

View file

@ -17,3 +17,28 @@
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
// /api/cinemas/{id}/rooms
Route::get('/cinemas/{id}/rooms', function ($id) {
$cinema = (new App\Models\Cinema)->find($id);
return $cinema->rooms;
});
// /api/showings/{id}
Route::get('/showings/{id}', function ($id) {
$showing = (new App\Models\Showing)->find($id);
return $showing;
});
// /api/rooms/{id}/seatMatrix
Route::get('/rooms/{id}/seatMatrix', function ($id) {
$room = (new App\Models\Room)->find($id);
return $room->seatMatrix();
});
// /api/showings/{id}/seatMatrix
Route::get('/showings/{id}/seatMatrix', function ($id) {
$showing = (new App\Models\Showing)->find($id);
return $showing->seatMatrix();
});

View file

@ -21,25 +21,15 @@
Route::get('/movies', function () {
$s = new \App\Models\Showing();
return view('main.movies', ['title' => "Movies", "showings" => $s->nowPlaying()->unique('movie_id')]);
return view('main.movies.index', ['title' => "Movies", "showings" => $s->nowPlaying()->unique('movie_id')]);
})->name('movies');
Route::get('/movie/{id}', function ($id) {
$m = new \App\Models\Movie();
$movie = $m->find($id);
return view('main.movie', ['title' => $movie->movie_name, "movie" => $movie]);
return view('main.movies.movie', ['title' => $movie->movie_name, "movie" => $movie]);
})->name('movie');
Route::get('/cinemas', function () {
return view('main.cinemas', ['title' => "Cinemas", "cinemas" => \App\Models\Cinema::all()]);
})->name('cinemas');
Route::get('/cinema/{id}', function ($id) {
$c = new \App\Models\Cinema();
$cinema = $c->find($id);
return view('main.cinema', ['title' => $cinema->cinema_name, "cinema" => $cinema]);
})->name('cinema');
Auth::routes();
// account
@ -50,6 +40,19 @@
// CRUD - Create Read Update Delete
// FB - Frontend Backend
// main
Route::get('/cinemas', [App\Http\Controllers\Main\CinemaController::class, 'showAllCinemas'])->name('cinemas');
Route::get('/cinema/{id}', [App\Http\Controllers\Main\CinemaController::class, 'show'])->name('cinema');
Route::get('/showings', [App\Http\Controllers\Main\ShowingController::class, 'showAllShowings'])->name('showings');
Route::get('/showing/{id}', [App\Http\Controllers\Main\ShowingController::class, 'show'])->name('showing');
Route::get('/movies', [App\Http\Controllers\Main\MovieController::class, 'showAllMovies'])->name('movies');
Route::get('/movie/{id}', [App\Http\Controllers\Main\MovieController::class, 'show'])->name('movie');
Route::get('/genres', [App\Http\Controllers\Main\GenreController::class, 'showAllGenres'])->name('genres');
Route::get('/genre/{id}', [App\Http\Controllers\Main\GenreController::class, 'show'])->name('genre');
// Employee Home Page
Route::get('/manage', function () {
if (!auth()->user()->atleast('employee')) {
@ -114,11 +117,26 @@
Route::delete('/manage/cinema/{id}', [\App\Http\Controllers\Managing\CinemaController::class, 'destroy'])->name('manage.cinema');
});
Route::controller(\App\Http\Controllers\Managing\ShowingsController::class)->group(function () {
// /manage/showings - CR showings (FB)
Route::get('/manage/showings', [\App\Http\Controllers\Managing\ShowingsController::class, 'showAllShowings'])->name('manage.showings');
Route::post('/manage/showings', [\App\Http\Controllers\Managing\ShowingsController::class, 'store'])->name('manage.showings');
// /manage/showings/create - C showing (F)
Route::get('/manage/showings/create', [\App\Http\Controllers\Managing\ShowingsController::class, 'createShowing'])->name('manage.showings.create');
// no post, handled by POST /showings
// /manage/showings/{id} - RUD showing (FB)
Route::get('/manage/showing/{id}', [\App\Http\Controllers\Managing\ShowingsController::class, 'edit'])->name('manage.showing');
Route::put('/manage/showing/{id}', [\App\Http\Controllers\Managing\ShowingsController::class, 'update'])->name('manage.showing');
Route::delete('/manage/showing/{id}', [\App\Http\Controllers\Managing\ShowingsController::class, 'destroy'])->name('manage.showing');
});
// /test/comp/{component}
Route::get('/test/comp/{component}', function ($component) {
switch ($component) {
case 'seat-chooser':
$c = new \App\View\Components\SeatChooser(1);
$c = new \App\View\Components\SeatChooser(1, 1);
return $c->render();
default:
return "No component found";