diff --git a/app/Classes.php b/app/Classes.php index 7b10e14..b344981 100644 --- a/app/Classes.php +++ b/app/Classes.php @@ -7,6 +7,6 @@ use Illuminate\Database\Eloquent\Model; class Classes extends Model { protected $fillable = [ - 'subject_id', 'date' + 'subject_id', 'date', 'classes_code' ]; } diff --git a/app/Http/Controllers/User/UserClassesController.php b/app/Http/Controllers/User/UserClassesController.php index 682b30f..b0d88fd 100644 --- a/app/Http/Controllers/User/UserClassesController.php +++ b/app/Http/Controllers/User/UserClassesController.php @@ -52,31 +52,41 @@ class UserClassesController extends Controller public function start_classes($classes_id) { - $classes_code = generateRandomString(10); - session([ - 'CLASSES_CODE' => $classes_code, - 'CLASSES_ID' => $classes_id - ]); $classes = Classes::find($classes_id); - return view('user.user_classes_start', ['verified' => false, 'classes_code' => $classes_code, 'classes' => $classes]); + $classes_code = $classes->classes_code; + if(!$classes_code){ + $classes_code = generateRandomString(10); + $classes->classes_code = $classes_code; + $classes->save(); + } + return view('map.start_map', ['classes_code' => $classes_code, 'classes' => $classes]); } public function start_classes_verified(Request $request) { + $student_id_number = $request->input('student_id_number'); + $student_name = $request->input('student_name'); + $student_surname = $request->input('student_surname'); $classes = Classes::find($request->get('classes_id')); - $room = Room::find(Subject::find($classes->subject_id)->room_id)->name; + $attendances = Attendance::where('classes_id', $classes->id)->get(); + $seat_numbers = $attendances->pluck('seat_number')->toArray(); + return view('map.seat_map', ['student_name' => $student_name, 'student_surname' => $student_surname, 'student_id_number' => $student_id_number, 'classes_id' => $classes->id, 'seat_numbers' => $seat_numbers]); + } + + public function save_classes_data(Request $request) + { + $classes_id = $request->input('classes_id'); $student_id_number = $request->input('student_id_number'); $student_name = $request->input('student_name'); $student_surname = $request->input('student_surname'); $seat_number = $request->input('seat_number'); Attendance::create([ - 'classes_id' => $classes->id, + 'classes_id' => $classes_id, 'student_id_number' => $student_id_number, 'student_name' => $student_name, 'student_surname' => $student_surname, 'seat_number' => $seat_number, ]); - $attendances = Attendance::where('classes_id', $classes->id)->get(); - return view('user.user_classes_start', ['verified' => true, 'room' => $room, 'attendances' => $attendances]); + return view('map.summary_map', ['student_name' => $student_name, 'student_surname' => $student_surname, 'seat_number' => $seat_number, 'student_id_number' => $student_id_number, 'classes_id' => $classes_id]); } } diff --git a/app/Http/Middleware/CheckClassesCode.php b/app/Http/Middleware/CheckClassesCode.php index 58cc185..e66faa2 100644 --- a/app/Http/Middleware/CheckClassesCode.php +++ b/app/Http/Middleware/CheckClassesCode.php @@ -20,19 +20,13 @@ class CheckClassesCode public function handle($request, Closure $next) { $classes_code_from_request = $request->input('classes_code'); - $classes_code_from_session = session()->get('CLASSES_CODE'); - $classes_id = session()->get('CLASSES_ID'); - $classes = Classes::find($classes_id); + $classes = Classes::where('classes_code', $classes_code_from_request)->first(); if(!$classes) { return redirect('home'); } else { - if ($classes_code_from_request != $classes_code_from_session) { + $user_id = Subject::where('id', $classes->subject_id)->first()->user_id; + if (!$user_id || $user_id != Auth::id()) { return redirect('home'); - } else { - $user_id = Subject::where('id', $classes->subject_id)->first()->user_id; - if (!$user_id || $user_id != Auth::id()) { - return redirect('home'); - } } } $request->attributes->add(['classes_id' => $classes->id]); diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index e7b692f..8c82bf5 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -19,6 +19,7 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - 'user/classes/start' + 'user/classes/start', + 'user/classes/save' ]; } diff --git a/database/migrations/2019_11_26_232020_create_classes_table.php b/database/migrations/2019_11_26_232020_create_classes_table.php index 157c895..7f186fc 100644 --- a/database/migrations/2019_11_26_232020_create_classes_table.php +++ b/database/migrations/2019_11_26_232020_create_classes_table.php @@ -17,6 +17,7 @@ class CreateClassesTable extends Migration $table->bigIncrements('id')->unique(); $table->integer('subject_id'); $table->date('date'); + $table->string('classes_code')->nullable(); $table->timestamps(); }); } diff --git a/public/css/map/jquery.seat-charts.css b/public/css/map/jquery.seat-charts.css new file mode 100644 index 0000000..15a0f4c --- /dev/null +++ b/public/css/map/jquery.seat-charts.css @@ -0,0 +1,71 @@ +div.seatCharts-container { + /*min-width: 700px;*/ +} +div.seatCharts-cell { + + height: 16px; + width: 16px; + margin: 3px; + float: left; + text-align: center; + outline: none; + font-size: 13px; + line-height:16px; + color: blue; + +} +div.seatCharts-seat { + background-color: green; + color: white; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + cursor: default; +} +div.seatCharts-seat:focus { + border: none; +} +/* +.seatCharts-seat:focus { + outline: none; +} +*/ + +div.seatCharts-space { + background-color: white; +} +div.seatCharts-row { + height: 50px; +} + +div.seatCharts-row:after { + clear: both; +} + +div.seatCharts-seat.selected { + background-color: aqua; +} + +div.seatCharts-seat.focused { + background-color: #6db131; +} + +div.seatCharts-seat.available { + background-color: green; +} + +div.seatCharts-seat.unavailable { + background-color: red; + cursor: not-allowed; +} + +ul.seatCharts-legendList { + list-style: none; +} +li.seatCharts-legendItem { + margin-top: 10px; + line-height: 2; +} +li.seatCharts-legendItem div { + cursor: default; +} diff --git a/public/css/map/koncowastrona.css b/public/css/map/koncowastrona.css new file mode 100644 index 0000000..6c68a36 --- /dev/null +++ b/public/css/map/koncowastrona.css @@ -0,0 +1,55 @@ +body { + font-family: monospace, sans-serif; +} + +.wrapper { + text-align: center; + margin: 100px; + background-color: rgba(159, 183, 218, 0.856); + border-radius: 10px; + padding: 50px 0; + +} + +.wrapper h2 { + font-size: 38px; + padding: 15px 0; + +} + +.wrapper h3 { + font-size: 22px; +} + +#selected-seats { + font-size: 30px; + text-shadow: 1px 1px 1px rgb(160, 160, 160); + margin-top: -15px; + margin-bottom: 100px; +} + +button { + margin: auto 0; + font-size: 18px; + background-color: #5d7cd3; + border-radius: 2px; + border: 0; + min-width: 250px; + padding: 25px 60px; + text-align: center; + box-shadow:0px 4px 0px #1e3572; + font-family: monospace; + float: right; +} + +button:hover { + box-shadow: 0 0 rgb(103, 88, 184); + background-color: #3654c9; + cursor: pointer; +} + +button:active { + top: 4px; + box-shadow: 0 0 #b85a5b; + background-color: #3654ff; +} diff --git a/public/css/map/przylozlegitke.css b/public/css/map/przylozlegitke.css new file mode 100644 index 0000000..176b025 --- /dev/null +++ b/public/css/map/przylozlegitke.css @@ -0,0 +1,96 @@ +body { + background: rgb(210, 218, 231); +} + +.wrapper { + margin: 0 auto; + display: inline-block; + background-color: rgba(159, 183, 218, 0.856); + border-radius: 10px; + width: 70%; + height:60vh; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + +} + +.czytnik { + display: inline-block; + text-align: center; + position: absolute; + left: 50%; + bottom: -20%; + transform: translate(-50%, -10%); + background-color: rgba(139, 152, 172, 0.856); + padding: 30px 80px; + font-family: monospace; + font-size: 30px; +} + +.czytnik:hover { + box-shadow: 0 0 rgb(103, 88, 184); + background-color: #3654c9; + cursor: progress; +} + +.code-p { + text-align: center; +} + +.main-text { + + font-family: "Oswald", Tahoma, sans-serif; + font-weight: bold; + text-transform: uppercase; + letter-spacing: .15em; + display: inline-block; + text-shadow: 0 0 80px rgba(255,255,255,.5); + color: #1C1C1C; + text-align: center; + line-height: 15vh; + font-size: 11vh; + height: 10%; +} + +@media (max-width: 1300px) { + .main-text { + font-size: 7.5vh; + width: 100%; + } + + } + + @media (max-width: 900px) { + .wrapper { + height: 40%; + } + .main-text { + font-size: 3.3em; + line-height: 10vh; + width: 100%; + } + + .czytnik { + bottom: -30%; + transform: translate(-50%, -10%); + padding: 30px 50px; + font-size: 25px; + } + + } + + @media (max-width: 650px) { + + .wrapper { + height: 30%; + } + + .main-text { + font-size: 2.1em; + line-height: 6vh; + width: 100%; + padding: 20px 0; + } + } diff --git a/public/css/map/seatchart.css b/public/css/map/seatchart.css new file mode 100644 index 0000000..92085ff --- /dev/null +++ b/public/css/map/seatchart.css @@ -0,0 +1,153 @@ +body { + font-family: 'Lato', sans-serif; + font-style: #b71a4c; +} +a { + color: #b71a4c; +} +.front-indicator { + width: 90%; + margin: 5px 32px 5px 32px; + background-color: #f6f6f6; + color: #adadad; + text-align: center; + padding: 15px; + border-radius: 5px; +} + +.container { + margin: 0 auto; + text-align: left; +} +.seat-stat-wrapper { + display: inline; +} +#legend { + width: 49%; + margin: 0 auto; + float: left; + font-family: monospace; + font-size: 16px; +} +.booking-details { + display: inline-block; + text-align: right; + font-size: 12px; + width: 49%; + margin: 0 auto; + +} +@media screen and (max-width: 550px) { + #legend { + width: 100%; + } + .booking-details { + width: 100%; + text-align: left; + } +} +.booking-details h2 { + margin: 25px 0 20px 0; + font-size: 35px; + color: #333333; + font-family: monospace; + letter-spacing: 0.15em; + font-weight: bold; +} +.booking-details h3 { + margin: 5px 5px 0 0; + font-size: 18px; + color: #333333; + font-family: monospace; + font-weight: bold; +} +div.seatCharts-cell { + color: #182C4E; + height: 90px; + width: 90px; + line-height: 90px; + +} +div.seatCharts-seat { + color: #FFFFFF; + cursor: pointer; + +} +div.seatCharts-row { + height: 100px; + width: 90%; + display: flex; + /*align-items: center;*/ + justify-content: center; +} +div.seatCharts-seat.available { + background-color: #a8b9bd; + +} +div.seatCharts-seat.available.student-class { + background-color: #a8b9bd; +} +div.seatCharts-seat.focused { + background-color: #758184; +} +div.seatCharts-seat.selected { + background-color: rgb(216, 196, 230); +} +div.seatCharts-seat.unavailable { + background-color: #caaa41; +} + +div.seatCharts-container { + width: 80%; + padding: 20px 0; + margin: 0 auto; + text-align: center; + +} +div.seatCharts-legend { + display: inline-block; +} + +div.seatCharts-legend li { + text-align: left; +} + +ul.seatCharts-legendList { + padding-left: 0px; +} +span.seatCharts-legendDescription { + margin-left: 5px; + line-height: 100px; +} + +.checkout-button { + margin: 10px 0; + font-size: 14px; + background-color: #5d7cd3; + border-radius: 2px; + border: 0; + padding: 15px 40px; + display: inline-block; + text-align: center; + box-shadow:0px 4px 0px #1e3572; + font-family: monospace; +} + +.checkout-button:hover { + box-shadow: 0 0 rgb(103, 88, 184); + background-color: #3654c9; + cursor: pointer; +} + +.checkout-button:active { + top: 4px; + box-shadow: 0 0 #b85a5b; + background-color: #3654ff; +} + +#selected-seats { + list-style-type: none; + font-size: 14px; + margin-left: 0; + padding-left: 0; +} diff --git a/public/js/map/jquery.seat-charts.js b/public/js/map/jquery.seat-charts.js new file mode 100644 index 0000000..63ef61a --- /dev/null +++ b/public/js/map/jquery.seat-charts.js @@ -0,0 +1,627 @@ +/*! + * jQuery-Seat-Charts v1.1.5 + * https://github.com/mateuszmarkowski/jQuery-Seat-Charts + * + * Copyright 2013, 2016 Mateusz Markowski + * Released under the MIT license + */ + +(function($) { + + //'use strict'; + + $.fn.seatCharts = function (setup) { + + //if there's seatCharts object associated with the current element, return it + if (this.data('seatCharts')) { + return this.data('seatCharts'); + } + + var fn = this, + seats = {}, + seatIds = [], + legend, + settings = { + animate : false, //requires jQuery UI + naming : { + top : true, + left : true, + getId : function(character, row, column) { + return row + '_' + column; + }, + getLabel : function (character, row, column) { + return column; + } + + }, + legend : { + node : null, + items : [] + }, + click : function() { + + if (this.status() == 'available') { + return 'selected'; + } else if (this.status() == 'selected') { + return 'available'; + } else { + return this.style(); + } + + }, + focus : function() { + + if (this.status() == 'available') { + return 'focused'; + } else { + return this.style(); + } + }, + blur : function() { + return this.status(); + }, + seats : {} + + }, + //seat will be basically a seat object which we'll when generating the map + seat = (function(seatCharts, seatChartsSettings) { + return function (setup) { + var fn = this; + + fn.settings = $.extend({ + status : 'available', //available, unavailable, selected + style : 'available', + //make sure there's an empty hash if user doesn't pass anything + data : seatChartsSettings.seats[setup.character] || {} + //anything goes here? + }, setup); + + fn.settings.$node = $('
'); + + fn.settings.$node + .attr({ + id : fn.settings.id, + role : 'checkbox', + 'aria-checked' : false, + focusable : true, + tabIndex : -1 //manual focus + }) + .text(fn.settings.label) + .addClass(['seatCharts-seat', 'seatCharts-cell', 'available'].concat( + //let's merge custom user defined classes with standard JSC ones + fn.settings.classes, + typeof seatChartsSettings.seats[fn.settings.character] == "undefined" ? + [] : seatChartsSettings.seats[fn.settings.character].classes + ).join(' ')); + + //basically a wrapper function + fn.data = function() { + return fn.settings.data; + }; + + fn.char = function() { + return fn.settings.character; + }; + + fn.node = function() { + return fn.settings.$node; + }; + + /* + * Can either set or return status depending on arguments. + * + * If there's no argument, it will return the current style. + * + * If you pass an argument, it will update seat's style + */ + fn.style = function() { + + return arguments.length == 1 ? + (function(newStyle) { + var oldStyle = fn.settings.style; + + //if nothing changes, do nothing + if (newStyle == oldStyle) { + return oldStyle; + } + + //focused is a special style which is not associated with status + fn.settings.status = newStyle != 'focused' ? newStyle : fn.settings.status; + fn.settings.$node + .attr('aria-checked', newStyle == 'selected'); + + //if user wants to animate status changes, let him do this + seatChartsSettings.animate ? + fn.settings.$node.switchClass(oldStyle, newStyle, 200) : + fn.settings.$node.removeClass(oldStyle).addClass(newStyle); + + return fn.settings.style = newStyle; + })(arguments[0]) : fn.settings.style; + }; + + //either set or retrieve + fn.status = function() { + + return fn.settings.status = arguments.length == 1 ? + fn.style(arguments[0]) : fn.settings.status; + }; + + //using immediate function to convienietly get shortcut variables + (function(seatSettings, character, seat) { + //attach event handlers + $.each(['click', 'focus', 'blur'], function(index, callback) { + + //we want to be able to call the functions for each seat object + fn[callback] = function() { + if (callback == 'focus') { + //if there's already a focused element, we have to remove focus from it first + if (seatCharts.attr('aria-activedescendant') !== undefined) { + seats[seatCharts.attr('aria-activedescendant')].blur(); + } + seatCharts.attr('aria-activedescendant', seat.settings.id); + seat.node().focus(); + } + + /* + * User can pass his own callback function, so we have to first check if it exists + * and if not, use our default callback. + * + * Each callback function is executed in the current seat context. + */ + return fn.style(typeof seatSettings[character][callback] === 'function' ? + seatSettings[character][callback].apply(seat) : seatChartsSettings[callback].apply(seat)); + }; + + }); + //the below will become seatSettings, character, seat thanks to the immediate function + })(seatChartsSettings.seats, fn.settings.character, fn); + + fn.node() + //the first three mouse events are simple + .on('click', fn.click) + .on('mouseenter', fn.focus) + .on('mouseleave', fn.blur) + + //keydown requires quite a lot of logic, because we have to know where to move the focus + .on('keydown', (function(seat, $seat) { + + return function (e) { + + var $newSeat; + + //everything depends on the pressed key + switch (e.which) { + //spacebar will just trigger the same event mouse click does + case 32: + e.preventDefault(); + seat.click(); + break; + //UP & DOWN + case 40: + case 38: + e.preventDefault(); + + /* + * This is a recursive, immediate function which searches for the first "focusable" row. + * + * We're using immediate function because we want a convenient access to some DOM elements + * We're using recursion because sometimes we may hit an empty space rather than a seat. + * + */ + $newSeat = (function findAvailable($rows, $seats, $currentRow) { + var $newRow; + + //let's determine which row should we move to + + if (!$rows.index($currentRow) && e.which == 38) { + //if this is the first row and user has pressed up arrow, move to the last row + $newRow = $rows.last(); + } else if ($rows.index($currentRow) == $rows.length-1 && e.which == 40) { + //if this is the last row and user has pressed down arrow, move to the first row + $newRow = $rows.first(); + } else { + //using eq to get an element at the desired index position + $newRow = $rows.eq( + //if up arrow, then decrement the index, if down increment it + $rows.index($currentRow) + (e.which == 38 ? (-1) : (+1)) + ); + } + + //now that we know the row, let's get the seat using the current column position + $newSeat = $newRow.find('.seatCharts-seat,.seatCharts-space').eq($seats.index($seat)); + + //if the seat we found is a space, keep looking further + return $newSeat.hasClass('seatCharts-space') ? + findAvailable($rows, $seats, $newRow) : $newSeat; + + })($seat + //get a reference to the parent container and then select all rows but the header + .parents('.seatCharts-container') + .find('.seatCharts-row:not(.seatCharts-header)'), + $seat + //get a reference to the parent row and then find all seat cells (both seats & spaces) + .parents('.seatCharts-row:first') + .find('.seatCharts-seat,.seatCharts-space'), + //get a reference to the current row + $seat.parents('.seatCharts-row:not(.seatCharts-header)') + ); + + //we couldn't determine the new seat, so we better give up + if (!$newSeat.length) { + return; + } + + //remove focus from the old seat and put it on the new one + seat.blur(); + seats[$newSeat.attr('id')].focus(); + $newSeat.focus(); + + //update our "aria" reference with the new seat id + seatCharts.attr('aria-activedescendant', $newSeat.attr('id')); + + break; + //LEFT & RIGHT + case 37: + case 39: + e.preventDefault(); + /* + * The logic here is slightly different from the one for up/down arrows. + * User will be able to browse the whole map using just left/right arrow, because + * it will move to the next row when we reach the right/left-most seat. + */ + $newSeat = (function($seats) { + + if (!$seats.index($seat) && e.which == 37) { + //user has pressed left arrow and we're currently on the left-most seat + return $seats.last(); + } else if ($seats.index($seat) == $seats.length -1 && e.which == 39) { + //user has pressed right arrow and we're currently on the right-most seat + return $seats.first(); + } else { + //simply move one seat left or right depending on the key + return $seats.eq($seats.index($seat) + (e.which == 37 ? (-1) : (+1))); + } + + })($seat + .parents('.seatCharts-container:first') + .find('.seatCharts-seat:not(.seatCharts-space)')); + + if (!$newSeat.length) { + return; + } + + //handle focus + seat.blur(); + seats[$newSeat.attr('id')].focus(); + $newSeat.focus(); + + //update our "aria" reference with the new seat id + seatCharts.attr('aria-activedescendant', $newSeat.attr('id')); + break; + default: + break; + + } + }; + + })(fn, fn.node())); + //.appendTo(seatCharts.find('.' + row)); + + } + })(fn, settings); + + fn.addClass('seatCharts-container'); + + //true -> deep copy! + $.extend(true, settings, setup); + + //Generate default row ids unless user passed his own + settings.naming.rows = settings.naming.rows || (function(length) { + var rows = []; + for (var i = 1; i <= length; i++) { + rows.push(i); + } + return rows; + })(settings.map.length); + + //Generate default column ids unless user passed his own + settings.naming.columns = settings.naming.columns || (function(length) { + var columns = []; + for (var i = 1; i <= length; i++) { + columns.push(i); + } + return columns; + })(settings.map[0].split('').length); + + if (settings.naming.top) { + var $headerRow = $('') + .addClass('seatCharts-row seatCharts-header'); + + if (settings.naming.left) { + $headerRow.append($('').addClass('seatCharts-cell')); + } + + + $.each(settings.naming.columns, function(index, value) { + $headerRow.append( + $('') + .addClass('seatCharts-cell') + .text(value) + ); + }); + } + + fn.append($headerRow); + + //do this for each map row + $.each(settings.map, function(row, characters) { + + var $row = $('').addClass('seatCharts-row'); + + if (settings.naming.left) { + $row.append( + $('') + .addClass('seatCharts-cell seatCharts-space') + .text(settings.naming.rows[row]) + ); + } + + /* + * Do this for each seat (letter) + * + * Now users will be able to pass custom ID and label which overwrite the one that seat would be assigned by getId and + * getLabel + * + * New format is like this: + * a[ID,label]a[ID]aaaaa + * + * So you can overwrite the ID or label (or both) even for just one seat. + * Basically ID should be first, so if you want to overwrite just label write it as follows: + * a[,LABEL] + * + * Allowed characters in IDs areL 0-9, a-z, A-Z, _ + * Allowed characters in labels are: 0-9, a-z, A-Z, _, ' ' (space) + * + */ + + $.each(characters.match(/[a-z_]{1}(\[[0-9a-z_]{0,}(,[0-9a-z_ ]+)?\])?/gi), function (column, characterParams) { + var matches = characterParams.match(/([a-z_]{1})(\[([0-9a-z_ ,]+)\])?/i), + //no matter if user specifies [] params, the character should be in the second element + character = matches[1], + //check if user has passed some additional params to override id or label + params = typeof matches[3] !== 'undefined' ? matches[3].split(',') : [], + //id param should be first + overrideId = params.length ? params[0] : null, + //label param should be second + overrideLabel = params.length === 2 ? params[1] : null; + + $row.append(character != '_' ? + //if the character is not an underscore (empty space) + (function(naming) { + + //so users don't have to specify empty objects + settings.seats[character] = character in settings.seats ? settings.seats[character] : {}; + + var id = overrideId ? overrideId : naming.getId(character, naming.rows[row], naming.columns[column]); + seats[id] = new seat({ + id : id, + label : overrideLabel ? + overrideLabel : naming.getLabel(character, naming.rows[row], naming.columns[column]), + row : row, + column : column, + character : character + }); + + seatIds.push(id); + return seats[id].node(); + + })(settings.naming) : + //this is just an empty space (_) + $('').addClass('seatCharts-cell seatCharts-space') + ); + }); + + fn.append($row); + }); + + //if there're any legend items to be rendered + settings.legend.items.length ? (function(legend) { + //either use user-defined container or create our own and insert it right after the seat chart div + var $container = (legend.node || $('').insertAfter(fn)) + .addClass('seatCharts-legend'); + + var $ul = $('Kod: {{ $classes_code }}
+