mega-commit: migrations, controllers, models, etc.

This commit is contained in:
Didier Slof 2022-12-08 09:30:07 +01:00
parent 9732135e90
commit 2c6745e812
Signed by: didier
GPG key ID: 01E71F18AA4398E5
70 changed files with 2124 additions and 400 deletions

View file

@ -4,6 +4,6 @@ php:
- no_unused_imports
finder:
not-name:
- index.php
- index.blade.php
js: true
css: true

View file

@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers\Managing;
class CinemaController extends \App\Http\Controllers\Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('atleast:employee');
}
public function showAllCinemas()
{
return view('manage.cinemas.index', ['title' => "Manage Cinemas", 'cinemas' => \App\Models\Cinema::all()]);
}
public function edit($id)
{
$c = \App\Models\Cinema::findOrfail($id);
return view('manage.cinemas.cinema', ['title' => "Manage Cinema", 'cinema' => $c, 'rooms' => $c->rooms]);
}
public function createCinema()
{
return view('manage.cinemas.create', ['title' => "Create Cinema"]);
}
public function store()
{
$cinema = new \App\Models\Cinema();
$cinema->cinema_name = request('cinema_name');
$cinema->address_id = request('address_id');
$cinema->save();
return redirect()->route('manage.cinemas');
}
public function show($id)
{
return view('main.cinemas.cinema', ['title' => "Edit Cinema", 'cinema' => \App\Models\Cinema::findOrfail($id)]);
}
public function update($id)
{
$cinema = \App\Models\Cinema::findOrfail($id);
$cinema->cinema_name = request('cinema_name');
$cinema->address_id = request('address_id');
$cinema->save();
return redirect()->route('manage.cinemas');
}
public function destroy($id)
{
$cinema = \App\Models\Cinema::findOrfail($id);
$cinema->delete();
return redirect()->route('manage.cinemas');
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Managing;
use App\Http\Controllers\Controller;
class GenreController extends Controller
{
public function __construct() {
$this->middleware('auth');
$this->middleware('atleast:employee');
}
public function showAllGenres() {
return view('manage.genres.index', ['title' => "Manage Genres", 'genres' => \App\Models\Genre::all()]);
}
public function edit($id) {
$g = \App\Models\Genre::findOrfail($id);
return view('manage.genres.genre', ['title' => "Manage Genre", 'genre' => $g, 'movies' => $g->movies]);
}
public function createGenre() {
return view('manage.genres.create', ['title' => "Create Genre"]);
}
public function store() {
$genre = new \App\Models\Genre();
$genre->genre_name = request('genre_name');
$genre->save();
return redirect()->route('manage.genres');
}
public function show($id) {
return view('main.genres.genre', ['title' => "Edit Genre", 'genre' => \App\Models\Genre::findOrfail($id)]);
}
public function update($id) {
$genre = \App\Models\Genre::findOrfail($id);
$genre->genre_name = request('genre_name');
$genre->save();
return redirect()->route('manage.genres');
}
public function destroy($id) {
$genre = \App\Models\Genre::findOrfail($id);
$genre->delete();
return redirect()->route('manage.genres');
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Managing;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
class MovieController extends Controller
{
public function __construct() {
$this->middleware('auth');
$this->middleware('atleast:employee');
}
public function showAllMovies() {
// has permission READ_MOVIES ?
if(!Auth::user()->allowedTo('READ_MOVIES')) {
abort(403);
}
return view('manage.movies.index', ['title' => "Manage Movies", 'movies' => \App\Models\Movie::all()]);
}
public function edit($id) {
$m = \App\Models\Movie::findOrfail($id);
return view('manage.movies.movie', ['title' => "Manage Movie", 'movie' => $m, 'showings' => $m->showings]);
}
public function createMovie() {
return view('manage.movies.create', ['title' => "Create Movie", 'genres' => \App\Models\Genre::all()]);
}
public function store() {
$movie = new \App\Models\Movie();
$movie->movie_name = request('movie_name');
$movie->movie_description = request('movie_description');
$movie->movie_year = request('movie_year');
$movie->movie_image = request('movie_image');
// $movie->user_id = auth()->user()->user_id;
$movie->genre_id = request('genre_id');
$movie->save();
return redirect()->route('manage.movies');
}
public function show($id) {
return view('main.movies.movie', ['title' => "Edit Movie", 'movie' => \App\Models\Movie::findOrfail($id)]);
}
public function update($id) {
$movie = \App\Models\Movie::findOrfail($id);
$movie->movie_name = request('movie_name');
$movie->movie_description = request('movie_description');
$movie->movie_year = request('movie_year');
$movie->movie_image = request('movie_image');
// $movie->user_id = auth()->user()->user_id;
$movie->genre_id = request('genre_id') !== null ? request('genre_id') : $movie->genre_id;
$movie->save();
return redirect()->route('manage.movies');
}
public function destroy($id) {
$movie = \App\Models\Movie::findOrfail($id);
$movie->delete();
return redirect()->route('manage.movies');
}
}

View file

@ -23,6 +23,6 @@ public function __construct()
*/
public function index()
{
return view('dash');
return view('dash', ['title'=>"Dashboard", 'user'=>auth()->user()]);
}
}

View file

@ -54,6 +54,7 @@ class Kernel extends HttpKernel
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'atleast' => \App\Http\Middleware\AtleastRole::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,

View file

@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Closure;
class AtleastRole
{
public function handle($request, Closure $next, $role)
{
if (!auth()->user()->atleast($role)) {
return redirect()->route('home');
}
return $next($request);
}
}

View file

@ -4,7 +4,7 @@
use Illuminate\Database\Eloquent\Model;
class MovieGenre extends Model
class Genre extends Model
{
protected $table = 'genres';
protected $primaryKey = 'genre_id';
@ -13,9 +13,14 @@ class MovieGenre extends Model
'genre_name',
];
public function findGenreByName($genre_name)
{
return Genre::where('genre_name', $genre_name)->first();
}
public function movies()
{
return $this->belongsToMany(Movie::class, 'movie_genres', 'genre_id', 'movie_id');
return $this->hasMany('App\Models\Movie', 'genre_id');
}
}

View file

@ -24,9 +24,14 @@ class Movie extends Model
'updated_at',
];
public static function findOrfail($id)
{
return Movie::where('movie_id', $id)->firstOrFail();
}
public function genre()
{
return $this->belongsTo(MovieGenre::class, 'genre_id', 'genre_id');
return $this->belongsTo(Genre::class, 'genre_id', 'genre_id');
}
public function showings()

View file

@ -17,11 +17,9 @@ class Permission extends Model
'updated_at',
];
// Permissions are linked to users by the user_permissions table
// User permissions are linked to users by the user_id and are valid per date
public function users()
{
return $this->belongsToMany(User::class, 'user_permissions', 'permission_id', 'user_id');
return $this->belongsToMany('App\Models\User', 'user_permissions', 'permission_id', 'user_id');
}
}

View file

@ -15,6 +15,7 @@ class Room extends Model
'room_rows',
'room_columns',
'user_id', // who added the room?
'cinema_id',
];
protected $hidden = [
@ -22,10 +23,24 @@ class Room extends Model
'updated_at',
];
public static function find(int $room_id)
{
return self::where('room_id', $room_id)->first();
}
public function showings()
{
return $this->hasMany(Showing::class, 'room_id', 'room_id');
}
public function seats()
{
return $this->hasMany(Seat::class, 'room_id', 'room_id');
}
public function cinema()
{
return $this->belongsTo(Cinema::class, 'cinema_id', 'cinema_id');
}
}

View file

@ -32,5 +32,23 @@ 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');
}
// 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)
{
$tickets = $this->tickets()->where('showing_id', $showing_id)->get();
foreach ($tickets as $ticket) {
if ($ticket->order->order_status == 'pending' || $ticket->order->order_status == 'paid') {
return true;
}
}
return false;
}
}

View file

@ -16,7 +16,6 @@ class Showing extends Model
protected $fillable = [
'movie_id',
'showing_start',
'showing_end',
'room_id', // which room is showing the movie?
'user_id', // who added the showing?
];
@ -26,6 +25,16 @@ class Showing extends Model
'updated_at',
];
public static function findOrfail($id)
{
return Showing::where('showing_id', $id)->firstOrFail();
}
public function room()
{
return $this->belongsTo(Room::class, 'room_id', 'room_id');
}
public function movie()
{
return $this->belongsTo(Movie::class, 'movie_id', 'movie_id');

View file

@ -32,6 +32,4 @@ public function showing()
return $this->belongsTo(Showing::class, 'showing_id', 'showing_id');
}
}

View file

@ -14,10 +14,6 @@ class User extends Authenticatable
protected $primaryKey = 'user_id';
// User can be a customer or an employee
// If customer, then there'll be a customer_id in the users table
// If employee, then there'll be an employee_id in the users table
/**
* The attributes that are mass assignable.
*
@ -26,10 +22,8 @@ class User extends Authenticatable
protected $fillable = [
'name',
'email',
'password',
'customer_id', // if this is filled, then this user is a customer
'employee_id', // if this is filled, then this user is an employee
// if both are filled, then this user is both a customer and an employee
'role', // default, employee, manage
'password'
];
/**
@ -53,5 +47,43 @@ class User extends Authenticatable
'email_verified_at' => 'datetime',
];
public function permissions()
{
// get permissions for this user
// note: permissions are linked to users by the user_permissions table
// the permissions are in the permissions table
// only return valid permissions
return $this->belongsToMany('App\Models\Permission', 'user_permissions', 'user_id', 'permission_id')->where(function ($query) {
$query->where('user_permission_start', '<=', now())->where(function ($query) {
$query->where('user_permission_end', '>=', now())->orWhereNull('user_permission_end');
});
});
}
public function allowedTo($permission): bool
{
if($this->role === 'manage') {
return true;
}
if ($this->permissions()->where('permission_name', $permission)->first()) {
return true;
}
return false;
}
public function atleast($role): bool
{
$hierarchy = [
'default' => 0,
'employee' => 1,
'manage' => 2
];
return $hierarchy[$this->role] >= $hierarchy[$role];
}
public static function find($id)
{
return User::all()->where('user_id', $id)->first();
}
}

View file

@ -17,7 +17,7 @@ class RouteServiceProvider extends ServiceProvider
*
* @var string
*/
public const HOME = '/home';
public const HOME = '/dash';
/**
* Define your route model bindings, pattern filters, and other route configuration.

View file

@ -0,0 +1,51 @@
<?php
namespace App\View\Components;
use App\Models\Room;
use Illuminate\View\Component;
class SeatChooser extends Component
{
public int $room_id;
public int $showing_id;
public Room $room;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct(int $room_id, int $showing_id)
{
$this->room_id = $room_id;
$this->room = Room::find($room_id);
$this->showing_id = $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;
}
}
return $matrix;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.seat-chooser', [
'room' => $this->room,
'seatmatrix' => $this->matrixGenerate(),
'showing_id' => $this->showing_id,
]);
}
}

View file

@ -19,6 +19,7 @@ public function up()
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->enum('role', ['default', 'employee', 'manage'])->default('default');
$table->rememberToken();
$table->timestamps();
});

View file

@ -1,36 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id('failed_job_id');
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('failed_jobs');
}
};

View file

@ -19,8 +19,8 @@ public function up()
$table->addColumn('string', 'address_city', ['length' => 255]);
$table->addColumn('string', 'address_state', ['length' => 255]);
$table->addColumn('string', 'address_zip', ['length' => 255]);
$table->addColumn('string', 'address_country', ['length' => 255]);
$table->foreignId('user_id')->constrained('users', 'user_id');
$table->addColumn('string', 'address_country', ['length' => 255])->default('Netherlands');
$table->addColumn('string', 'address_phone', ['length' => 255])->nullable();
});
}

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('cinemas', function (Blueprint $table) {
$table->id('cinema_id');
$table->timestamps();
$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();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('cinemas');
}
};

View file

@ -17,6 +17,7 @@ public function up()
$table->id('permission_id');
$table->timestamps();
$table->addColumn('string', 'permission_name', ['length' => 255]);
$table->addColumn('string', 'permission_description', ['length' => 511])->nullable();
$table->unique(['permission_name']);
});
}

View file

@ -16,10 +16,11 @@ public function up()
Schema::create('rooms', function (Blueprint $table) {
$table->id('room_id');
$table->timestamps();
$table->addColumn('string', 'room_name', ['length' => 255]);
$table->addColumn('integer', 'room_rows');
$table->addColumn('integer', 'room_columns');
$table->unique(['room_name']);
$table->string('room_name');
$table->integer('room_rows');
$table->integer('room_columns');
$table->foreignId('cinema_id')->constrained('cinemas', 'cinema_id');
$table->foreignId('user_id')->constrained('users', 'user_id'); // who created this room
});
}

View file

@ -17,7 +17,7 @@ public function up()
$table->id('showing_id');
$table->timestamps();
$table->dateTime('showing_start');
$table->dateTime('showing_end');
// no showing end time, that's implied by the movie length and 10 minutes between showings
$table->foreignId('movie_id')->constrained('movies', 'movie_id');
$table->foreignId('room_id')->constrained('rooms', 'room_id');
$table->foreignId('user_id')->constrained('users', 'user_id');

View file

@ -18,8 +18,9 @@ public function up()
$table->timestamps();
$table->foreignId('permission_id')->constrained('permissions', 'permission_id');
$table->foreignId('user_id')->constrained('users', 'user_id');
$table->date('user_permission_start');
$table->date('user_permission_end');
// end can be null to mark indefinitely
$table->date('user_permission_start')->useCurrent();
$table->date('user_permission_end')->nullable();
});
}

View file

@ -17,7 +17,7 @@ public function up()
$table->id('order_id');
$table->timestamps();
$table->addColumn('integer', 'order_number');
$table->addColumn('enum', 'order_status', ['values' => ['pending', 'paid', 'cancelled']]);
$table->enum('order_status', ['pending', 'paid', 'cancelled']);
$table->foreignId('user_id')->constrained('users', 'user_id');
$table->foreignId('billing_address_id')->constrained('addresses', 'address_id');
$table->unique(['order_number', 'user_id']);

View file

@ -0,0 +1,61 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class CinemaSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$cinemas = [
[
'cinema_name' => 'Cinema 1',
'cinema_address' => [
'address_line_1' => '123 Main Street',
'address_line_2' => 'Suite 1',
'address_city' => 'City',
'address_state' => 'State',
'address_zip' => '12345',
'address_phone' => '123-456-7890',
],
],
[
'cinema_name' => 'Cinema 2',
'cinema_address' => [
'address_line_1' => '123 Main Street',
'address_line_2' => 'Suite 2',
'address_city' => 'City',
'address_state' => 'State',
'address_zip' => '12345',
'address_phone' => '123-456-7890',
],
]
];
foreach ($cinemas as $cinema) {
$c = new \App\Models\Cinema();
$c->cinema_name = $cinema['cinema_name'];
$c->user_id = 1;
$a = new \App\Models\Address();
$a->address_street = $cinema['cinema_address']['address_line_1'];
$a->address_city = $cinema['cinema_address']['address_city'];
$a->address_state = $cinema['cinema_address']['address_state'];
$a->address_zip = $cinema['cinema_address']['address_zip'];
$a->address_phone = $cinema['cinema_address']['address_phone'];
$a->save();
$c->address_id = $a->address_id;
$c->save();
}
}
}

View file

@ -14,11 +14,14 @@ class DatabaseSeeder extends Seeder
*/
public function run()
{
// \App\Models\User::factory(10)->create();
// \App\Models\User::factory()->create([
// 'name' => 'Test User',
// 'email' => 'test@example.com',
// ]);
$this->call([
PermissionSeeder::class,
UserSeeder::class,
GenreSeeder::class,
MovieSeeder::class,
CinemaSeeder::class,
RoomSeeder::class,
ShowingSeeder::class,
]);
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class GenreSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('genres')->insert([
[
'genre_name' => 'Action',
],
[
'genre_name' => 'Adventure',
],
[
'genre_name' => 'Animation',
],
[
'genre_name' => 'Comedy',
],
[
'genre_name' => 'Crime',
],
[
'genre_name' => 'Documentary',
],
[
'genre_name' => 'Drama',
],
[
'genre_name' => 'Family',
],
[
'genre_name' => 'Fantasy',
],
[
'genre_name' => 'History',
],
[
'genre_name' => 'Horror',
],
[
'genre_name' => 'Music',
],
[
'genre_name' => 'Mystery',
],
[
'genre_name' => 'Romance',
],
[
'genre_name' => 'Science Fiction',
],
[
'genre_name' => 'TV Movie',
],
[
'genre_name' => 'Thriller',
],
[
'genre_name' => 'War',
],
[
'genre_name' => 'Western',
],
]);
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Database\Seeders;
use App\Models\Genre;
use App\Models\Movie;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class MovieSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$movies = [
[
'movie_name' => 'Alvin and the Chipmunks',
'movie_description' => 'A struggling songwriter named Dave Seville finds success when he comes across a trio of singing chipmunks: mischievous leader Alvin, brainy Simon, and chubby, impressionable Theodore.',
'movie_year' => '2007',
'movie_age_limit' => 6,
'movie_length' => 90,
'movie_image' => 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/vRiB9uvcD0WYp7k2pAeWz9ukpuN.jpg',
'genre_id' => (new \App\Models\Genre)->findGenreByName('Comedy')->genre_id,
],
[
'movie_name' => 'Alvin and the Chipmunks: The Squeakquel',
'movie_description' => 'Pop sensations Alvin, Simon and Theodore end up in the care of Dave Seville\'s twenty-something nephew Toby. The boys must put aside music super stardom to return to school, and are tasked with saving the school\'s music program by winning the $25,000 prize in a battle of the bands. But the Chipmunks unexpectedly meet their match in three singing chipmunks known as The Chipettes - Brittany, Eleanor and Jeanette. Romantic and musical sparks are ignited when the Chipmunks and Chipettes square off.',
'movie_year' => '2009',
'movie_age_limit' => 6,
'movie_length' => 92,
'movie_image' => 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/8mdPqOga5fty15nXmaNcK1fsNMa.jpg',
'genre_id' => (new \App\Models\Genre)->findGenreByName('Comedy')->genre_id,
],
];
foreach ($movies as $movie) {
$m = new Movie();
$m->movie_name = $movie['movie_name'];
$m->movie_description = $movie['movie_description'];
$m->movie_year = $movie['movie_year'];
$m->movie_age_limit = $movie['movie_age_limit'];
$m->movie_length = $movie['movie_length'];
$m->movie_image = $movie['movie_image'];
$m->genre_id = $movie['genre_id'];
$m->save();
}
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class PermissionSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('permissions')->insert([
[
'permission_name' => 'READ_GENRES',
'permission_description' => 'Read Genres',
],
[
'permission_name' => 'CREATE_GENRES',
'permission_description' => 'Create Genres',
],
[
'permission_name' => 'UPDATE_GENRES',
'permission_description' => 'Update Genres',
],
[
'permission_name' => 'DELETE_GENRES',
'permission_description' => 'Delete Genres',
],
[
'permission_name' => 'READ_MOVIES',
'permission_description' => 'Read Movies',
],
[
'permission_name' => 'CREATE_MOVIES',
'permission_description' => 'Create Movies',
],
[
'permission_name' => 'UPDATE_MOVIES',
'permission_description' => 'Update Movies',
],
[
'permission_name' => 'DELETE_MOVIES',
'permission_description' => 'Delete Movies',
],
[
'permission_name' => 'READ_SHOWINGS',
'permission_description' => 'Read Showings',
],
[
'permission_name' => 'CREATE_SHOWINGS',
'permission_description' => 'Create Showings',
],
[
'permission_name' => 'UPDATE_SHOWINGS',
'permission_description' => 'Update Showings',
],
[
'permission_name' => 'DELETE_SHOWINGS',
'permission_description' => 'Delete Showings',
],
[
'permission_name' => 'READ_USERS',
'permission_description' => 'Read Users',
],
[
'permission_name' => 'CREATE_USERS',
'permission_description' => 'Create Users',
],
[
'permission_name' => 'UPDATE_USERS',
'permission_description' => 'Update Users',
],
[
'permission_name' => 'DELETE_USERS',
'permission_description' => 'Delete Users',
],
[
'permission_name' => 'READ_PERMISSIONS',
'permission_description' => 'Read Permissions',
],
[
'permission_name' => 'CREATE_PERMISSIONS',
'permission_description' => 'Create Permissions',
],
[
'permission_name' => 'UPDATE_PERMISSIONS',
'permission_description' => 'Update Permissions',
],
[
'permission_name' => 'DELETE_PERMISSIONS',
'permission_description' => 'Delete Permissions',
],
]);
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class RoomSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
foreach (\App\Models\Cinema::all() as $cinema) {
$rooms = [
[
'room_name' => 'Room 1',
'room_rows' => 10,
'room_columns' => 10,
],
[
'room_name' => 'Room 2',
'room_rows' => 10,
'room_columns' => 10,
],
[
'room_name' => 'Room 3',
'room_rows' => 10,
'room_columns' => 10,
],
];
foreach ($rooms as $room) {
$r = new \App\Models\Room();
$r->room_name = $room['room_name'];
$r->room_rows = $room['room_rows'];
$r->room_columns = $room['room_columns'];
$r->user_id = 1;
$r->cinema_id = $cinema->cinema_id;
$r->save();
for ($row = 1; $row <= $r->room_rows; $row++) {
for ($column = 1; $column <= $r->room_columns; $column++) {
$s = new \App\Models\Seat();
$s->seat_row = $row;
$s->seat_column = $column;
$s->room_id = $r->room_id;
$s->save();
}
}
}
}
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class ShowingSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// $showStart = <current time + 5days>;
$showStart = new \DateTime();
$showStart->add(new \DateInterval('P5D'));
foreach (\App\Models\Room::all() as $room) {
$showings = [
[
'showing_start' => $showStart->format('Y-m-d H:i:s'),
'showing_end' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'),
'movie_id' => 1,
'room_id' => $room->room_id,
],
[
'showing_start' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'),
'showing_end' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'),
'movie_id' => 2,
'room_id' => $room->room_id,
],
];
foreach ($showings as $showing) {
$s = new \App\Models\Showing();
$s->showing_start = $showing['showing_start'];
$s->movie_id = $showing['movie_id'];
$s->room_id = $showing['room_id'];
$s->user_id = 1;
$s->save();
}
}
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$user = new User();
$user->name = 'system';
$user->email = 'sys@local.host';
$user->role = 'manage';
$user->password = Hash::make('system');
$user->save();
}
}

View file

@ -0,0 +1,54 @@
.form {
background: var(--second-bg);
padding: 1rem;
border-radius: 10px;
}
.form .item {
margin-top: 1rem;
margin-right: 1rem;
margin-bottom: 0.2rem;
}
.form .item input {
margin-top: 0.2rem;
width: 100%;
height: 2rem;
background: var(--third-bg);
color: var(--default-text);
border: none;
outline: none;
padding: 0.1rem 0.1rem 0.1rem 1rem;
border-radius: 5px;
}
.form .item input:hover {
color: var(--default-text-invert);
filter: brightness(1.2);
}
.form .item input:focus {
background: var(--second-bg);
filter: brightness(1.2);
}
.form .item input:focus:hover {
color: var(--default-text);
}
.form button {
color: var(--default-text);
background: var(--third-bg);
margin-top: 1rem;
width: 100%;
height: 2rem;
border-radius: 5px;
border: none;
outline: none;
}
.form button:focus,
.form button:hover {
color: var(--default-text-invert);
filter: brightness(1.2);
}

32
public/css/extra.css Normal file
View file

@ -0,0 +1,32 @@
.jumbotron {
position: relative;
margin: 0;
width: 100%;
height: 30vh;
}
.jumbotron img.jumbotron-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
filter: blur(1px) brightness(0.5);
}
.jumbotron .container {
position: relative;
z-index: 1;
display: grid;
place-items: center;
height: 100%;
}
.jumbotron .container h1 {
font-size: 3rem;
font-weight: bold;
margin-bottom: 0;
color: var(--primary-color-text);
}

173
public/css/generic.css Normal file
View file

@ -0,0 +1,173 @@
:root {
--default-text: #000;
--default-text-invert: #fff;
--dim-text: #666;
--primary-color: #f55;
--primary-color-text: #fff;
--secondary-color: #ed0;
--secondary-color-text: #000;
--default-bg: #fff;
--second-bg: #ddd;
--third-bg: #bbb;
--darkest-bg: #222;
--inactive-item-bg: #eee;
--active-item-bg: #ccc;
--highlight-bg: #f55;
--highlight-text: #fff;
}
* {
transition: all .5s;
}
@media (prefers-color-scheme: dark) {
:root {
--default-text: #fff;
--default-text-invert: #000;
--dim-text: #999;
--default-bg: #222;
--second-bg: #777;
--third-bg: #aaa;
--darkest-bg: #111;
--inactive-item-bg: #333;
--active-item-bg: #666;
}
}
html {
color: var(--default-text);
background: var(--default-bg);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
body {
margin: 0;
padding: 0;
}
a {
color: var(--default-text);
text-decoration: none;
}
a:hover {
color: var(--secondary-color);
}
.spread-h {
display: flex;
flex-direction: row;
justify-content: space-between;
}
ul {
list-style: none;
padding: 0;
}
ul li {
margin: 0.5rem 0;
border-bottom: 1px solid var(--second-bg);
}
ul.numbered {
counter-reset: li;
list-style: decimal;
}
ul.numbered li {
counter-increment: li;
}
.card {
background: var(--default-bg);
border: 1px solid var(--second-bg);
border-radius: 0.5rem;
padding: 1rem;
margin: 1rem 0;
}
.clicky-card {
cursor: pointer;
}
.clicky-card:hover .card {
background: var(--darkest-bg);
}
button.button,
a.button {
display: grid;
place-items: center;
align-content: center;
font-weight: bold;
min-height: 2rem;
min-width: 4rem;
background: var(--primary-color);
color: var(--primary-color-text);
padding: 0.5rem 1rem;
border-radius: 0.5rem;
text-decoration: none;
outline: none;
border: none;
cursor: pointer;
}
button.button:hover,
a.button:hover {
background: var(--secondary-color);
color: var(--secondary-color-text);
}
table {
border-collapse: collapse;
width: 100%;
}
table td {
border: 1px solid var(--second-bg);
padding: 0.5rem;
}
table th {
border: 1px solid var(--second-bg);
padding: 0.5rem;
background: var(--second-bg);
}
table tr:nth-child(even) {
background: var(--default-bg);
filter: brightness(0.7);
}
table tr:nth-child(odd) {
background: var(--default-bg);
}
table tr:hover {
background: var(--default-bg);
filter: brightness(1.2);
}
tr {
border: 1px solid var(--second-bg);
}
.hide {
display: none;
}
.op0 {
opacity: 0;
}

77
public/css/main.css Normal file
View file

@ -0,0 +1,77 @@
header {
background: var(--second-bg);
display: grid;
grid-template-columns: 1.5fr 0.5fr;
height: 7vh;
align-items: center;
}
header span {
font-size: 1.5rem;
font-weight: bold;
margin: 0.5rem 1rem;
}
header div {
text-align: right;
display: inline;
}
header div a {
padding: 1.5rem;
}
header div a:hover {
font-size: 1.2rem;
}
main {
margin: 0 auto;
padding: 1rem;
max-width: 800px;
}
/* #movies needs to be a grid with movie poster and show details on hover */
#movies {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 1rem;
}
#movies a {
position: relative;
overflow: hidden;
border-radius: 0.5rem;
}
#movies a img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s;
}
#movies a:hover img {
transform: scale(1.1);
}
#movies a .details {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(0deg, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%);
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
opacity: 0;
transition: opacity 0.5s;
}
#movies a:hover .details {
opacity: 1;
}

170
public/css/manage.css Normal file
View file

@ -0,0 +1,170 @@
header {
position: fixed;
top: 0;
left: 0;
background: var(--second-bg);
display: grid;
grid-template-columns: 1.5fr 0.5fr;
height: 7vh;
align-items: center;
width: 100%;
z-index: 1;
}
header span {
font-size: 1.5rem;
font-weight: bold;
margin: 0.5rem 1rem;
}
header div {
text-align: right;
display: inline;
}
header div a {
padding: 1.5rem;
}
/* side bar needs to be under header*/
#sidebar {
background: var(--darkest-bg);
height: 93vh;
width: 20vw;
position: fixed;
top: 7vh;
left: 0;
z-index: 1;
overflow-x: hidden;
padding-top: 0.25rem;
}
#sidebar a {
background: var(--inactive-item-bg);
padding: 0.5rem 1rem;
text-decoration: none;
color: var(--default-text);
display: block;
margin-top: 0.5rem;
}
#sidebar a:hover {
filter: brightness(1.5);
}
#sidebar a.active {
background: var(--highlight-bg);
color: var(--highlight-text);
}
#sidebar a.child {
padding-left: 2rem;
margin-top: 0;
/* nice visual*/
border-left: 1rem solid var(--second-bg);
}
main {
background: var(--default-bg);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 9vh 1vw 4vh 21vw;
padding: 0;
}
.card {
display: grid;
background: var(--second-bg);
width: 10vw;
place-items: center;
}
.card .big-stat {
font-size: 3rem;
font-weight: bold;
}
#movies {
display: flex;
flex-direction: column;
}
#movies a {
display: flex;
flex-direction: row;
margin: 0.5rem 0;
padding: 0.5rem;
background: var(--second-bg);
border-radius: 0.5rem;
}
#movies a img {
width: 10vw;
height: 16vw;
object-fit: cover;
transition: transform 0.5s;
}
#movies a:hover {
filter: brightness(1.5);
}
#movies a .details {
display: flex;
flex-direction: column;
margin-left: 1rem;
}
/* form */
.form {
display: flex;
flex-direction: column;
margin: 0.5rem 0;
padding: 0.5rem;
background: var(--second-bg);
border-radius: 0.5rem;
}
.form .form-group {
display: flex;
flex-direction: column;
margin: 0.5rem 0;
}
.form .form-group label {
margin: 0.5rem 0;
}
form .form-group button,
form .form-group select,
form .form-group textarea,
form .form-group input {
padding: 0.5rem;
border-radius: 0.5rem;
background: var(--default-bg);
color: var(--default-text);
outline: none;
border: none;
}
form .form-group button:hover,
form .form-group select:hover,
form .form-group textarea:hover,
form .form-group input:hover,
form .form-group button:focus,
form .form-group select:focus,
form .form-group textarea:focus,
form .form-group input:focus {
filter: brightness(1.5);
}
form .form-group button {
background: var(--highlight-bg);
color: var(--highlight-text);
cursor: pointer;
}
form .form-group button:hover {
filter: brightness(1.5);
}

31
public/css/movies.css Normal file
View file

@ -0,0 +1,31 @@
#movies {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
flex-direction: row;
gap: 1rem;
}
/* Show .details on top of image */
#movies a {
position: relative;
}
#movies a .details {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
place-content: center;
align-content: center;
background: var(--second-bg);
opacity: 0;
color: var(--default-text);
transition: all .5s;
}
#movies a:hover .details {
opacity: 0.7;
}

View file

@ -0,0 +1,39 @@
:root {
--spacing: 1rem;
}
.seat-chooser {
padding: var(--spacing);
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;
border-radius: 5px;
overflow: hidden;
cursor: pointer;
width: 32px;
height: 32px;
transition: all 0.3s ease;
}
.seat-chooser .seat.seat-reserved {
background: #f66;
}
.seat-chooser .seat:hover {
background: var(--highlight-bg);
}

View file

@ -1,105 +0,0 @@
:root {
--default-text: #000;
--primary-color: #bb00ff;
--secondary-color: #5eff00;
--default-bg: #fff;
--second-bg: #ddd;
--third-bg: #bbb;
}
* {
transition: all .5s;
}
@media (prefers-color-scheme: dark) {
:root {
--default-text: #fff;
--default-bg: #222;
--second-bg: #777;
--third-bg: #aaa;
}
}
html {
color: var(--default-text);
background: var(--default-bg);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
body {
margin: 0;
padding: 0;
}
a {
color: var(--default-text);
text-decoration: none;
}
a:hover {
color: var(--primary-color);
}
header {
background: var(--second-bg);
display: grid;
grid-template-columns: 1.5fr 0.5fr;
height: 7vh;
align-items: center;
}
header span {
font-size: 1.5rem;
font-weight: bold;
margin: 0.5rem 1rem;
}
header div {
text-align: right;
display: inline;
}
header div a {padding: 1.5rem;}
header div a:hover {
background: var(--third-bg);
padding: 1.5rem 1.5rem 2.5rem 1.5rem;
}
main {
margin: 0 auto;
padding: 1rem;
max-width: 800px;
}
#movies {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
flex-direction: row;
gap: 1rem;
}
/* Show .details on top of image */
#movies a {
position: relative;
}
#movies a .details {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
place-content: center;
align-content: center;
background: var(--second-bg);
opacity: 0;
color: var(--default-text);
transition: all .5s;
}
#movies a:hover .details {
opacity: 0.7;
}

27
public/js/jumbotron.js Normal file
View file

@ -0,0 +1,27 @@
let jumbotron = document.querySelector('.jumbotron');
let topImageHolder = document.querySelector('.jumbotron-image[data-order="1"]');
let bottomImageHolder = document.querySelector('.jumbotron-image[data-order="2"]');
let images = jumbotron.getAttribute('data-images').split(',');
let delay = jumbotron.getAttribute('data-delay');
let imageIndex = 0;
// init
bottomImageHolder.src = images[imageIndex];
imageIndex++;
function loop() {
bottomImageHolder.classList.add('op0');
bottomImageHolder.src = images[imageIndex];
imageIndex++;
topImageHolder.classList.add('op0');
topImageHolder.src = images[imageIndex];
imageIndex++;
bottomImageHolder.classList.remove('op0');
if (imageIndex >= images.length) {
imageIndex = 0;
}
}
setInterval(loop, delay);

View file

@ -1,17 +1,63 @@
@extends('layout')
@extends('main.layout')
@section('head')
@push('head')
<link rel="stylesheet" href="{{ asset('css/auth.css') }}">
@endsection
@endpush
@section('content')
<h2>{{ __('Login') }}</h2>
<div class="form">
<form method="POST" action="{{ route('login') }}">
@csrf
<div class="item">
<label for="email">{{ __('E-Mail Address') }}</label><br/>
<input type="email" name="email" id="email" value="{{ old('email') }}" required autocomplete="email"
autofocus>
<br/>
@error('email')
<span style="color: red">{{ $message }}</span>
<br/>
@enderror
</div>
<div class="item">
<label for="password">{{ __('Password') }}</label><br/>
<input type="password" name="password" id="password" required autocomplete="current-password">
<br/>
@error('password')
<span style="color: red">{{ $message }}</span>
<br/>
@enderror
</div>
<input type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label for="remember">Remember me</label>
<br/>
<button type="submit">Submit</button>
</form>
<br/>
<div class="spread-h">
@if (Route::has('password.request'))
<a href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
@if (Route::has('register'))
<a href="{{ route('register') }}">
{{ __('Register') }}
</a>
@endif
</div>
</div>
@endsection

View file

@ -1,77 +1,67 @@
@extends('layout')
@extends('main.layout')
@section('head')
<link rel="stylesheet" href="{{ asset('css/auth.css') }}">
@endsection
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<h2>{{__('Register')}}</h2>
<div class="card-body">
<form method="POST" action="{{ route('register') }}">
<div class="form">
<form method="POST" action="{{route('register')}}">
@csrf
<div class="row mb-3">
<label for="name" class="col-md-4 col-form-label text-md-end">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
<div class="item">
<label for="name">{{__('Name')}}</label><br/>
<input type="text" name="name" id="name" value="{{old('name')}}" required autocomplete="name" autofocus>
<br/>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
<span style="color: red">{{$message}}</span>
<br/>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
<div class="item">
<label for="email">{{__('E-Mail Address')}}</label><br/>
<input type="email" name="email" id="email" value="{{old('email')}}" required autocomplete="email">
<br/>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
<span style="color: red">{{$message}}</span>
<br/>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
<div class="item">
<label for="password">{{__('Password')}}</label><br/>
<input type="password" name="password" id="password" required autocomplete="new-password">
<br/>
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
<span style="color: red">{{$message}}</span>
<br/>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="item">
<label for="password-confirm">{{__('Confirm Password')}}</label><br/>
<input type="password" name="password_confirmation" id="password-confirm" required
autocomplete="new-password">
<br/>
@error('password_confirmation')
<span style="color: red">{{$message}}</span>
<br/>
@enderror
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
<button type="submit">Submit</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -1,7 +1,7 @@
@extends('layout')
@extends('main.layout')
@section('content')
<div class="container">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
@ -18,11 +18,13 @@
{{ __('If you did not receive the email') }},
<form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
@csrf
<button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>.
<button type="submit"
class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>
.
</form>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,27 @@
@extends('layout')
@push('head')
<link rel="stylesheet" href="{{ asset('css/seat-chooser.css') }}">
<style>
:root {
--rows: {{ $room->room_rows }};
--cols: {{ $room->room_columns }};
}
</style>
@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>

View file

@ -1,23 +1,25 @@
@extends('layout')
@extends('main.layout')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Dashboard') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<h1>{{ __('Dashboard') }}</h1>
<hr/>
{{ __('You are logged in!') }}
</div>
</div>
</div>
</div>
</div>
@if(Auth::user()->atleast('employee'))
<a href="/manage" class="button">Management Panel</a>
<hr/>
@endif
<h2>Permissions</h2>
<hr/>
<ul class="numbered">
@foreach(Auth::user()->permissions as $permission)
<li>{{ $permission->permission_name }}</li>
@endforeach
</ul>
@endsection

View file

@ -1,28 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ isset($title) ? $title : 'Page' }} - CineFlex</title>
<link rel="stylesheet" href="{{ asset('css/style.css') }}">
@yield('head')
<title>{{ isset($title) ? $title : 'Page' }} - {{ config('app.name', 'CineFlex') }} Manage</title>
@stack('head')
</head>
<body>
<header>
<span>CineFlex</span>
<div id="links">
<a href="{{ route('home') }}">Home</a>
<a href="{{ route('movies') }}">Movies</a>
@if (Auth::check())
<a href="{{ route('dashboard') }}">Dashboard</a>
<a href="{{ route('logout') }}">Logout</a>
@else
<a href="{{ route('login') }}">Login</a>
@endif
</div>
</header>
<main>
@yield('content')
</main>
<body>
@yield('body')
</body>
</html>

View file

@ -1,80 +0,0 @@
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<!-- Scripts -->
@vite(['resources/css/style.css', 'resources/js/app.js'])
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@guest
@if (Route::has('login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@endif
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
</main>
</div>
</body>
</html>

View file

@ -0,0 +1,16 @@
@extends('main.layout')
@section('content')
<h1>{{ config('app.name', 'CineFlex') }}</h1>
<hr/>
<h2>Cinemas</h2>
<ul>
@foreach($cinemas as $cinema)
<li>
<a href="{{ route('cinema', ['id' => $cinema->id]) }}">
{{ $cinema->name }}
</a>
</li>
@endforeach
</ul>
@endsection

View file

@ -0,0 +1,32 @@
@extends('main.layout')
@push('head')
<link rel="stylesheet" href="{{ asset('css/extra.css') }}">
<script defer src="{{ asset('js/jumbotron.js') }}"></script>
@endpush
@section('content')
{{-- data-images is a , seperated list with all images--}}
<section id="jumbotron" class="jumbotron" data-delay="5000" data-images="img/bar.jpg,img/cinema.jpg,img/lobby.jpg">
<div class="container">
<h1 class="display-4">{{ config('app.name', 'CineFlex') }}</h1>
<p class="lead">Welcome to CineFlex, the best cinema management system in the world!</p>
<p>Here you can see what movies are playing at your local cinema and buy tickets for them.</p>
<a class="button" href="{{ route('cinemas') }}" role="button">Cinemas</a>
</div>
<img class="jumbotron-image" src="" data-order="1">
<img class="jumbotron-image" src="" data-order="2">
</section>
<h2>Cinemas</h2>
<ul>
@foreach($cinemas as $cinema)
<li>
<a href="{{ route('cinema', ['id'=>$cinema->cinema_id]) }}">
{{ $cinema->cinema_name }} - {{ $cinema->address->address_city }}
</a>
</li>
@endforeach
</ul>
@endsection

View file

@ -0,0 +1,26 @@
@extends('layout')
@push('head')
<link rel="stylesheet" href="{{ asset('css/generic.css') }}">
<link rel="stylesheet" href="{{ asset('css/main.css') }}">
@endpush
@section('body')
<header>
<a href="{{ route('home') }}"><span>{{ config('app.name', 'CineFlex') }}</span></a>
<div id="links">
<a href="{{ route('home') }}">Home</a>
<a href="{{ route('movies') }}">Movies</a>
@if (Auth::check())
<a href="{{ route('dash') }}">Dashboard</a>
<a href="{{ route('logout') }}">Logout</a>
@else
<a href="{{ route('login') }}">Login</a>
@endif
</div>
</header>
<main>
@yield('content')
</main>
@endsection

View file

@ -1,4 +1,4 @@
@extends('layout')
@extends('main.layout')
@section('content')
@ -8,7 +8,8 @@
<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">
<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>

View file

@ -0,0 +1,48 @@
@extends('manage.layout')
@section('content')
<h1>Cinemas</h1>
<hr/><br/>
<h2>Stats:</h2>
<div class="spread-h">
<a class="clicky-card" href="{{ route('manage.cinemas') }}">
<div class="card">
<h3>Amount Cinemas</h3>
<span class="big-stat">{{ $cinemas->count() }}</span>
</div>
</a>
<a class="clicky-card" href="{{ route('manage.cinemas.employees') }}">
<div class="card">
<h3>Amount Employees</h3>
<span class="big-stat">{{ $employees->count() }}</span>
</div>
</a>
</div>
<br/>
<h2>Cinemas:</h2>
<div class="spread-h">
@foreach($cinemas as $cinema)
<a class="clicky-card" href="{{ route('manage.cinemas.show', ['id' => $cinema->id]) }}">
<div class="card">
<h3>{{ $cinema->name }}</h3>
<span class="big-stat">{{ $cinema->address->address_city }}</span>
</div>
</a>
@endforeach
</div>
<br/>
<h2>Employees:</h2>
<div class="spread-h">
@foreach($employees as $employee)
<a class="clicky-card" href="{{ route('manage.cinemas.employees.show', ['id' => $employee->id]) }}">
<div class="card">
<h3>{{ $employee->name }}</h3>
<span class="big-stat">{{ $employee->cinema->name }}</span>
</div>
</a>
@endforeach
</div>
<br/>
<a class="btn btn-primary" href="{{ route('manage') }}">Back</a>
@endsection

View file

@ -0,0 +1,19 @@
@extends('manage.layout')
@section('content')
<br/>
<h1>Create Genre</h1>
<hr/>
<br/>
<form action="{{ route('manage.genres') }}" method="post" class="form">
@csrf
<div class="form-group">
<label for="genre_name">Genre Name</label>
<input type="text" name="genre_name" id="genre_name" value="{{ old('genre_name') }}" required>
</div>
<br/>
<div class="form-group">
<button type="submit" class="button">Create Genre</button>
</div>
</form>
@endsection

View file

@ -0,0 +1,20 @@
@extends('manage.layout')
@section('content')
<br/>
<h1>Edit Genre</h1>
<hr/>
<br/>
<form action="{{ route('manage.genre', $genre->genre_id) }}" method="post" class="form">
@csrf
@method('PUT')
<div class="form-group">
<label for="genre_name">Genre Name</label>
<input type="text" name="genre_name" id="genre_name" value="{{ $genre->genre_name }}" required>
</div>
<br/>
<div class="form-group">
<button type="submit" class="button">Update Genre</button>
</div>
</form>
@endsection

View file

@ -0,0 +1,32 @@
@extends('manage.layout')
@section('content')
<br/>
<div class="spread-h">
<h1>Genres</h1>
<a href="{{ route('manage.genres.create') }}" class="button" style="height: 2rem; margin: 0.5rem 0;">Add
Genre</a>
</div>
<hr/>
<br/>
<table>
<thead>
<tr>
<th>Genre Name</th>
<th>Associated Movie Count</th>
</tr>
</thead>
<tbody>
@foreach($genres as $genre)
<tr>
<td>
<a href="{{ route('manage.genre', $genre->genre_id) }}">
{{ $genre->genre_name }}
</a>
</td>
<td>Associated Movies: {{ $genre->movies->count() }}</td>
</tr>
@endforeach
</tbody>
</table>
@endsection

View file

@ -0,0 +1,33 @@
@extends('manage.layout')
@section('content')
<h1>Dashboard</h1>
<hr/><br/>
<h2>Stats:</h2>
<div class="spread-h">
<a class="clicky-card" href="{{ route('manage') }}">
<div class="card">
<h3>Amount Users</h3>
<span class="big-stat">{{ $users->count() }}</span>
</div>
</a>
<a class="clicky-card" href="{{ route('manage.movies') }}">
<div class="card">
<h3>Amount Movies</h3>
<span class="big-stat">{{ $movies->count() }}</span>
</div>
</a>
<a class="clicky-card" href="{{ route('manage.genres') }}">
<div class="card">
<h3>Amount Genres</h3>
<span class="big-stat">{{ $genres->count() }}</span>
</div>
</a>
<a class="clicky-card" href="{{ route('manage') }}">
<div class="card">
<h3>Amount Showings</h3>
<span class="big-stat">{{ $showings->count() }}</span>
</div>
</a>
</div>
@endsection

View file

@ -0,0 +1,31 @@
{{-- Layout for admins --}}
{{-- Will have a sidebar with links --}}
{{-- Will have a top bar with account and link to main site--}}
{{--@extends('layout')--}}
@push('head')
<link rel="stylesheet" href="{{ asset('css/generic.css') }}">
<link rel="stylesheet" href="{{ asset('css/manage.css') }}">
@endpush
@section('body')
<header>
<a href="{{ route('manage') }}"><span>{{ config('app.name', 'CineFlex') }}</span></a>
<div id="links">
<a href="{{ route('home') }}">Main Site</a>
<a href="{{ route('logout') }}">Logout</a>
</div>
</header>
<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/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="{{ Request::is('manage/genres') ? 'active' : '' }}" href="{{ route('manage.genres') }}">Genres</a>
</div>
<main>
@yield('content')
@yield('main')
</main>
@endsection

View file

@ -0,0 +1,64 @@
@extends('manage.layout')
@section('content')
<h1>Add Movie</h1>
<hr/>
<form action="{{ route('manage.movies') }}" method="POST" enctype="multipart/form-data" class="form">
@csrf
<div class="form-group @error('movie_name') error @enderror">
<label for="movie_name">Name:</label>
<input type="text" name="movie_name" id="movie_name" value="{{ old('movie_name') }}">
@error('movie_name')
<span class="error-message">{{ $message }}</span>
@enderror
</div>
<div class="form-group @error('movie_description') error @enderror">
<label for="movie_description">Description:</label>
<textarea name="movie_description" id="movie_description" cols="30"
rows="10">{{ old('movie_description') }}</textarea>
@error('movie_description')
<span class="error-message">{{ $message }}</span>
@enderror
</div>
<div class="form-group @error('movie_image') error @enderror">
<label for="movie_image">Image URL:</label>
<input type="text" name="movie_image" id="movie_image" value="{{ old('movie_image') }}">
@error('movie_image')
<span class="error-message">{{ $message }}</span>
@enderror
</div>
<div class="form-group @error('movie_length') error @enderror">
<label for="movie_length">Length:</label>
<input type="number" name="movie_length" id="movie_length" value="{{ old('movie_length') }}">
@error('movie_length')
<span class="error-message">{{ $message }}</span>
@enderror
</div>
<div class="form-group @error('movie_year') error @enderror">
<label for="movie_year">Year:</label>
<input type="number" name="movie_year" id="movie_year" value="{{ old('movie_year') }}">
@error('movie_year')
<span class="error-message">{{ $message }}</span>
@enderror
</div>
<div class="form-group @error('movie_age_limit') error @enderror">
<label for="movie_age_limit">Age Limit:</label>
<input type="number" name="movie_age_limit" id="movie_age_limit" value="{{ old('movie_age_limit') }}">
@error('movie_age_limit')
<span class="error-message">{{ $message }}</span>
@enderror
</div>
<div class="form-group @error('genre_id') error @enderror">
<label for="genre_id">Genre:</label>
<select name="genre_id" id="genre_id">
@foreach($genres as $genre)
<option value="{{ $genre->genre_id }}">{{ $genre->genre_name }}</option>
@endforeach
</select>
@error('genre_id')
<span class="error-message">{{ $message }}</span>
@enderror
</div>
<button type="submit" class="button">Add Movie</button>
</form>
@endsection

View file

@ -0,0 +1,24 @@
@extends('manage.layout')
@section('content')
<br/>
<div class="spread-h">
<h1>Manage Movies:</h1>
<a href="{{ route('manage.movies.create') }}" class="button" style="height: 2rem; margin: 0.5rem 0;">Add Movie</a>
</div>
<hr/><br/>
<div id="movies">
@foreach($movies as $movie)
<a href="/manage/movie/{{$movie->movie_id}}">
<img src="{{$movie->movie_image}}" alt="{{$movie->movie_name}} Poster" width="200px">
<div class="details">
<h3>{{$movie->movie_name}}</h3><br/>
<span>{{ Str::limit($movie->movie_description, 500) }}</span><br/>
<hr/>
<span>{{$movie->movie_length}} min | {{$movie->movie_year}}</span>
</div>
</a>
@endforeach
</div>
@endsection

View file

@ -0,0 +1,60 @@
@extends('manage.layout')
@section('content')
<br/>
<div class="spread-h">
<h1>Edit Movie: {{ $movie->movie_name }}</h1>
<form action="{{ route('manage.movie', $movie->movie_id) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit" class="button" style="height: 2rem; margin: 0.5rem 0;">Delete Movie</button>
</form>
</div>
<hr/>
{{-- PUT Form with values: Name, Description, Image, Length, Year, Age Limit--}}
<form action="{{ route('manage.movie', $movie->movie_id) }}" method="POST" enctype="multipart/form-data" class="form">
@csrf
@method('PUT')
<div class="form-group">
<label for="movie_name">Name:</label>
<input type="text" name="movie_name" id="movie_name" value="{{ $movie->movie_name }}">
</div>
<div class="form-group">
<label for="movie_description">Description:</label>
<textarea name="movie_description" id="movie_description" cols="30"
rows="10">{{ $movie->movie_description }}</textarea>
</div>
<div class="form-group">
<label for="movie_image">Image URL:</label>
<input type="text" name="movie_image" id="movie_image" value="{{ $movie->movie_image }}">
</div>
<div class="form-group">
<label for="movie_length">Length:</label>
<input type="number" name="movie_length" id="movie_length" value="{{ $movie->movie_length }}">
</div>
<div class="form-group">
<label for="movie_year">Year:</label>
<input type="number" name="movie_year" id="movie_year" value="{{ $movie->movie_year }}">
</div>
<div class="form-group">
<label for="movie_age_limit">Age Limit:</label>
<input type="number" name="movie_age_limit" id="movie_age_limit" value="{{ $movie->movie_age_limit }}">
</div>
<div class="form-group">
<button type="submit" class="button">Update Movie</button>
</div>
</form>
<hr/>
<h2>Movie Showings:</h2>
<ul class="numbered" style="margin-left: 1.2rem;">
@foreach($showings as $showing)
<li>
{{-- TODO: placeholder--}}
<a href="{{ route('manage', $showing->showing_id) }}">
{{ $showing->room->room_name }} - {{ $showing->showing_start }}
</a>
</li>
@endforeach
</ul>
@endsection

View file

@ -1,7 +0,0 @@
@extends('layout')
@section('content')
<h1>{{ config('app.name', 'CineFlex') }}</h1>
<hr/>
<h2>Movies:</h2>
@endsection

View file

@ -15,20 +15,112 @@
*/
Route::get('/', function () {
return view('welcome', ['title'=>"Home"]);
$s = new \App\Models\Showing();
return view('main.home', ['title' => "Home", "showings" => $s->nowPlaying()->unique('movie_id'), "cinemas" => \App\Models\Cinema::all()]);
})->name('home');
Route::get('/movies', function () {
$s = new \App\Models\Showing();
return view('movies', ['title'=>"Movies", "showings"=>$s->nowPlaying()->unique('movie_id')]);
return view('main.movies', ['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('movie', ['title'=>$movie->movie_name, "movie"=>$movie]);
return view('main.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
Route::get('/logout', [App\Http\Controllers\Auth\LoginController::class, 'logout'])->name('logout');
Route::get('/dash', [App\Http\Controllers\UserDashController::class, 'index'])->name('dash');
// FORMAT: {ROUTE} - {C &| R &| U &| D} {what} {F || B}
// CRUD - Create Read Update Delete
// FB - Frontend Backend
// Employee Home Page
Route::get('/manage', function () {
if (!auth()->user()->atleast('employee')) {
return redirect()->route('home');
}
return view('manage.index', [
'title' => "Manage",
'user' => auth()->user(),
'users' => \App\Models\User::all(),
'showings' => \App\Models\Showing::all(),
'movies' => \App\Models\Movie::all(),
'genres' => \App\Models\Genre::all(),
]);
})->name('manage');
Route::controller(\App\Http\Controllers\Managing\MovieController::class)->group(function () {
// /manage/movies - CR movies (FB)
Route::get('/manage/movies', [\App\Http\Controllers\Managing\MovieController::class, 'showAllMovies'])->name('manage.movies');
Route::post('/manage/movies', [\App\Http\Controllers\Managing\MovieController::class, 'store'])->name('manage.movies');
// /manage/movies/create - C movie (F)
Route::get('/manage/movies/create', [\App\Http\Controllers\Managing\MovieController::class, 'createMovie'])->name('manage.movies.create');
// no post, handled by POST /movies
// /manage/movies/{id} - RUD movie (FB)
Route::get('/manage/movie/{id}', [\App\Http\Controllers\Managing\MovieController::class, 'edit'])->name('manage.movie');
Route::put('/manage/movie/{id}', [\App\Http\Controllers\Managing\MovieController::class, 'update'])->name('manage.movie');
Route::delete('/manage/movie/{id}', [\App\Http\Controllers\Managing\MovieController::class, 'destroy'])->name('manage.movie');
});
Route::controller(\App\Http\Controllers\Managing\GenreController::class)->group(function () {
// /manage/genres - CR genres (FB)
Route::get('/manage/genres', [\App\Http\Controllers\Managing\GenreController::class, 'showAllGenres'])->name('manage.genres');
Route::post('/manage/genres', [\App\Http\Controllers\Managing\GenreController::class, 'store'])->name('manage.genres');
// /manage/genres/create - C genre (F)
Route::get('/manage/genres/create', [\App\Http\Controllers\Managing\GenreController::class, 'createGenre'])->name('manage.genres.create');
// no post, handled by POST /genres
// /manage/genres/{id} - RUD genre (FB)
Route::get('/manage/genre/{id}', [\App\Http\Controllers\Managing\GenreController::class, 'edit'])->name('manage.genre');
Route::put('/manage/genre/{id}', [\App\Http\Controllers\Managing\GenreController::class, 'update'])->name('manage.genre');
Route::delete('/manage/genre/{id}', [\App\Http\Controllers\Managing\GenreController::class, 'destroy'])->name('manage.genre');
});
Route::controller(\App\Http\Controllers\Managing\CinemaController::class)->group(function () {
// /manage/cinemas - CR cinemas (FB)
Route::get('/manage/cinemas', [\App\Http\Controllers\Managing\CinemaController::class, 'showAllCinemas'])->name('manage.cinemas');
Route::post('/manage/cinemas', [\App\Http\Controllers\Managing\CinemaController::class, 'store'])->name('manage.cinemas');
// /manage/cinemas/create - C cinema (F)
Route::get('/manage/cinemas/create', [\App\Http\Controllers\Managing\CinemaController::class, 'createCinema'])->name('manage.cinemas.create');
// no post, handled by POST /cinemas
// /manage/cinemas/{id} - RUD cinema (FB)
Route::get('/manage/cinema/{id}', [\App\Http\Controllers\Managing\CinemaController::class, 'edit'])->name('manage.cinema');
Route::put('/manage/cinema/{id}', [\App\Http\Controllers\Managing\CinemaController::class, 'update'])->name('manage.cinema');
Route::delete('/manage/cinema/{id}', [\App\Http\Controllers\Managing\CinemaController::class, 'destroy'])->name('manage.cinema');
});
// /test/comp/{component}
Route::get('/test/comp/{component}', function ($component) {
switch ($component) {
case 'seat-chooser':
$c = new \App\View\Components\SeatChooser(1);
return $c->render();
default:
return "No component found";
}
})->name('test.comp');

View file

@ -4,7 +4,7 @@ import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/style.css', 'resources/js/app.js'],
input: ['resources/css/generic.css', 'resources/js/app.js'],
refresh: true,
}),
],