Scobda nomination ensembles #106
|
|
@ -24,7 +24,8 @@ class AuditionSettings extends Controller
|
||||||
'organizerName' => ['required'],
|
'organizerName' => ['required'],
|
||||||
'organizerEmail' => ['required', 'email'],
|
'organizerEmail' => ['required', 'email'],
|
||||||
'registrationCode' => ['required'],
|
'registrationCode' => ['required'],
|
||||||
'fee_structure' => ['required', 'in:oneFeePerEntry,oneFeePerStudent'], // Options should align with the boot method of InvoiceDataServiceProvider
|
'fee_structure' => ['required', 'in:oneFeePerEntry,oneFeePerStudent'],
|
||||||
|
// Options should align with the boot method of InvoiceDataServiceProvider
|
||||||
'late_fee' => ['nullable', 'numeric', 'min:0'],
|
'late_fee' => ['nullable', 'numeric', 'min:0'],
|
||||||
'school_fee' => ['nullable', 'numeric', 'min:0'],
|
'school_fee' => ['nullable', 'numeric', 'min:0'],
|
||||||
'payment_address' => ['required'],
|
'payment_address' => ['required'],
|
||||||
|
|
@ -32,6 +33,8 @@ class AuditionSettings extends Controller
|
||||||
'payment_state' => ['required', 'max:2'],
|
'payment_state' => ['required', 'max:2'],
|
||||||
'payment_zip' => ['required', 'min:5'],
|
'payment_zip' => ['required', 'min:5'],
|
||||||
'advanceTo' => ['nullable'],
|
'advanceTo' => ['nullable'],
|
||||||
|
'nomination_ensemble_rules' => ['required', 'in:disabled,scobda'],
|
||||||
|
// Options should align with the boot method of NominationEnsembleServiceProvider
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Olympic Scoring Switch
|
// Olympic Scoring Switch
|
||||||
|
|
@ -43,6 +46,9 @@ class AuditionSettings extends Controller
|
||||||
// Enable Invoicing Switch
|
// Enable Invoicing Switch
|
||||||
$validData['invoicing_enabled'] = $request->get('invoicing_enabled') == '1';
|
$validData['invoicing_enabled'] = $request->get('invoicing_enabled') == '1';
|
||||||
|
|
||||||
|
// Enable collect shirt size switch
|
||||||
|
$validData['student_data_collect_shirt_size'] = $request->get('student_data_collect_shirt_size') == '1';
|
||||||
|
|
||||||
// Store currency values as cents
|
// Store currency values as cents
|
||||||
$validData['late_fee'] = $validData['late_fee'] * 100;
|
$validData['late_fee'] = $validData['late_fee'] * 100;
|
||||||
$validData['school_fee'] = $validData['school_fee'] * 100;
|
$validData['school_fee'] = $validData['school_fee'] * 100;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use App\Models\Audition;
|
||||||
use App\Models\AuditLogEntry;
|
use App\Models\AuditLogEntry;
|
||||||
use App\Models\Entry;
|
use App\Models\Entry;
|
||||||
use App\Models\Event;
|
use App\Models\Event;
|
||||||
|
use App\Models\NominationEnsemble;
|
||||||
use App\Models\School;
|
use App\Models\School;
|
||||||
use App\Models\Student;
|
use App\Models\Student;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
@ -14,6 +15,8 @@ use Illuminate\Support\Facades\Auth;
|
||||||
use function abort;
|
use function abort;
|
||||||
use function auth;
|
use function auth;
|
||||||
use function compact;
|
use function compact;
|
||||||
|
use function max;
|
||||||
|
use function min;
|
||||||
use function request;
|
use function request;
|
||||||
use function to_route;
|
use function to_route;
|
||||||
use function view;
|
use function view;
|
||||||
|
|
@ -54,8 +57,8 @@ class StudentController extends Controller
|
||||||
if (! Auth::user()->is_admin) {
|
if (! Auth::user()->is_admin) {
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
$minGrade = Audition::min('minimum_grade');
|
$minGrade = min(Audition::min('minimum_grade'), NominationEnsemble::min('minimum_grade'));
|
||||||
$maxGrade = Audition::max('maximum_grade');
|
$maxGrade = max(Audition::max('maximum_grade'), NominationEnsemble::max('maximum_grade'));
|
||||||
$schools = School::orderBy('name')->get();
|
$schools = School::orderBy('name')->get();
|
||||||
|
|
||||||
return view('admin.students.create', ['schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]);
|
return view('admin.students.create', ['schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]);
|
||||||
|
|
@ -105,8 +108,8 @@ class StudentController extends Controller
|
||||||
if (! Auth::user()->is_admin) {
|
if (! Auth::user()->is_admin) {
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
$minGrade = Audition::min('minimum_grade');
|
$minGrade = min(Audition::min('minimum_grade'), NominationEnsemble::min('minimum_grade'));
|
||||||
$maxGrade = Audition::max('maximum_grade');
|
$maxGrade = max(Audition::max('maximum_grade'), NominationEnsemble::max('maximum_grade'));
|
||||||
$schools = School::orderBy('name')->get();
|
$schools = School::orderBy('name')->get();
|
||||||
$student->loadCount('entries');
|
$student->loadCount('entries');
|
||||||
$entries = $student->entries;
|
$entries = $student->entries;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NominationEnsembles;
|
||||||
|
|
||||||
|
use App\Models\NominationEnsembleEntry;
|
||||||
|
|
||||||
|
interface NominationAdminController
|
||||||
|
{
|
||||||
|
public function index();
|
||||||
|
|
||||||
|
public function show(NominationEnsembleEntry $entry);
|
||||||
|
|
||||||
|
public function create();
|
||||||
|
|
||||||
|
public function store();
|
||||||
|
|
||||||
|
public function edit(NominationEnsembleEntry $entry);
|
||||||
|
|
||||||
|
public function update(NominationEnsembleEntry $entry);
|
||||||
|
|
||||||
|
public function destroy(NominationEnsembleEntry $entry);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NominationEnsembles;
|
||||||
|
|
||||||
|
use App\Models\NominationEnsemble;
|
||||||
|
|
||||||
|
interface NominationEnsembleController
|
||||||
|
{
|
||||||
|
public function index();
|
||||||
|
|
||||||
|
public function show(NominationEnsemble $ensemble);
|
||||||
|
|
||||||
|
public function create();
|
||||||
|
|
||||||
|
public function store();
|
||||||
|
|
||||||
|
public function edit(NominationEnsemble $ensemble);
|
||||||
|
|
||||||
|
public function update(NominationEnsemble $ensemble);
|
||||||
|
|
||||||
|
public function destroy(NominationEnsemble $ensemble);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NominationEnsembles;
|
||||||
|
|
||||||
|
use App\Models\NominationEnsembleEntry;
|
||||||
|
|
||||||
|
interface NominationEnsembleEntryController
|
||||||
|
{
|
||||||
|
public function index();
|
||||||
|
|
||||||
|
public function show(NominationEnsembleEntry $entry);
|
||||||
|
|
||||||
|
public function create();
|
||||||
|
|
||||||
|
public function store();
|
||||||
|
|
||||||
|
public function edit(NominationEnsembleEntry $entry);
|
||||||
|
|
||||||
|
public function update(NominationEnsembleEntry $entry);
|
||||||
|
|
||||||
|
public function destroy(NominationEnsembleEntry $entry);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NominationEnsembles;
|
||||||
|
|
||||||
|
use App\Models\NominationEnsemble;
|
||||||
|
|
||||||
|
interface NominationSeatingController
|
||||||
|
{
|
||||||
|
public function index();
|
||||||
|
|
||||||
|
public function show(NominationEnsemble $ensemble);
|
||||||
|
|
||||||
|
public function seat(NominationEnsemble $ensemble);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NominationEnsembles;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\NominationEnsembleEntry;
|
||||||
|
|
||||||
|
class ScobdaNominationAdminController extends Controller implements NominationAdminController
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$nominations = NominationEnsembleEntry::with('student')->with('ensemble')->get();
|
||||||
|
|
||||||
|
return view('nomination_ensembles.scobda.admin.index', compact('nominations'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(NominationEnsembleEntry $entry)
|
||||||
|
{
|
||||||
|
// TODO: Implement show() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
// TODO: Implement create() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
// TODO: Implement store() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(NominationEnsembleEntry $entry)
|
||||||
|
{
|
||||||
|
// TODO: Implement edit() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(NominationEnsembleEntry $entry)
|
||||||
|
{
|
||||||
|
// TODO: Implement update() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(NominationEnsembleEntry $entry)
|
||||||
|
{
|
||||||
|
// TODO: Implement destroy() method.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NominationEnsembles;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\NominationEnsemble;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
use function redirect;
|
||||||
|
|
||||||
|
class ScobdaNominationEnsembleController extends Controller implements NominationEnsembleController
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$ensembles = NominationEnsemble::all();
|
||||||
|
|
||||||
|
return view('nomination_ensembles.scobda.admin.ensembles.index', compact('ensembles'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(NominationEnsemble $ensemble)
|
||||||
|
{
|
||||||
|
// TODO: Implement show() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
// TODO: Implement create() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
//dd(request()->all());
|
||||||
|
$validated = request()->validate([
|
||||||
|
'ensemble_name' => 'required|unique:nomination_ensembles,name',
|
||||||
|
'entry_deadline' => 'required|date',
|
||||||
|
'min_grade' => 'required|numeric|min:0',
|
||||||
|
'max_grade' => 'required|numeric|gte:min_grade',
|
||||||
|
'max_nominations' => 'required|numeric|min:1',
|
||||||
|
'target_size' => 'required|numeric|min:1',
|
||||||
|
'rounding_direction' => 'required|in:up,down',
|
||||||
|
'instrument_list' => 'required|string',
|
||||||
|
], [
|
||||||
|
'maximum_grade.gte' => 'The maximum grade must be greater than the minimum grade.',
|
||||||
|
'rounding_direction.in' => 'The rounding direction must be either "up" or "down".',
|
||||||
|
]);
|
||||||
|
$instrument_list = preg_replace('/\s*,\s*/', ',', $validated['instrument_list']);
|
||||||
|
$instrument_array = explode(',', $instrument_list);
|
||||||
|
|
||||||
|
$ensemble = new NominationEnsemble();
|
||||||
|
$ensemble->name = $validated['ensemble_name'];
|
||||||
|
$ensemble->entry_deadline = $validated['entry_deadline'];
|
||||||
|
$ensemble->minimum_grade = $validated['min_grade'];
|
||||||
|
$ensemble->maximum_grade = $validated['max_grade'];
|
||||||
|
$data = [];
|
||||||
|
$data['max_nominations'] = $validated['max_nominations'];
|
||||||
|
$data['target_size'] = $validated['target_size'];
|
||||||
|
$data['instruments'] = $instrument_array;
|
||||||
|
$data['rounding_direction'] = $validated['rounding_direction'];
|
||||||
|
$ensemble->data = $data;
|
||||||
|
$ensemble->save();
|
||||||
|
|
||||||
|
return redirect()->route('nomination.admin.ensemble.index')->with('success', 'Nomination Ensemble has been created.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(NominationEnsemble $ensemble)
|
||||||
|
{
|
||||||
|
// TODO: Implement edit() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(NominationEnsemble $ensemble)
|
||||||
|
{
|
||||||
|
$validated = request()->validate([
|
||||||
|
'ensemble_name' => [
|
||||||
|
'required',
|
||||||
|
Rule::unique('nomination_ensembles', 'name')->ignore($ensemble->id),
|
||||||
|
],
|
||||||
|
'entry_deadline' => 'required|date',
|
||||||
|
'min_grade' => 'required|numeric|min:0',
|
||||||
|
'max_grade' => 'required|numeric|gte:min_grade',
|
||||||
|
'max_nominations' => 'required|numeric|min:1',
|
||||||
|
'target_size' => 'required|numeric|min:1',
|
||||||
|
'rounding_direction' => 'required|in:up,down',
|
||||||
|
'instrument_list' => 'required|string',
|
||||||
|
], [
|
||||||
|
'maximum_grade.gte' => 'The maximum grade must be greater than the minimum grade.',
|
||||||
|
'rounding_direction.in' => 'The rounding direction must be either "up" or "down".',
|
||||||
|
]);
|
||||||
|
$instrument_list = preg_replace('/\s*,\s*/', ',', $validated['instrument_list']);
|
||||||
|
$instrument_array = explode(',', $instrument_list);
|
||||||
|
|
||||||
|
$ensemble->name = $validated['ensemble_name'];
|
||||||
|
$ensemble->entry_deadline = $validated['entry_deadline'];
|
||||||
|
$ensemble->minimum_grade = $validated['min_grade'];
|
||||||
|
$ensemble->maximum_grade = $validated['max_grade'];
|
||||||
|
$data = [];
|
||||||
|
$data['max_nominations'] = $validated['max_nominations'];
|
||||||
|
$data['target_size'] = $validated['target_size'];
|
||||||
|
$data['instruments'] = $instrument_array;
|
||||||
|
$data['rounding_direction'] = $validated['rounding_direction'];
|
||||||
|
$ensemble->data = $data;
|
||||||
|
$ensemble->save();
|
||||||
|
|
||||||
|
return redirect()->route('nomination.admin.ensemble.index')->with('success', 'Nomination Ensemble has been modified.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(NominationEnsemble $ensemble)
|
||||||
|
{
|
||||||
|
$ensemble->delete();
|
||||||
|
|
||||||
|
// TODO: Delete associated nomionations.
|
||||||
|
return redirect()->route('nomination.admin.ensemble.index')->with('success', 'Nomination Ensemble has been deleted.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NominationEnsembles;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\NominationEnsemble;
|
||||||
|
use App\Models\NominationEnsembleEntry;
|
||||||
|
use App\Models\School;
|
||||||
|
use App\Models\Student;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class ScobdaNominationEnsembleEntryController extends Controller implements NominationEnsembleEntryController
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
// Get current date for checking deadlines
|
||||||
|
$currentDate = Carbon::now('America/Chicago');
|
||||||
|
$currentDate = $currentDate->format('Y-m-d');
|
||||||
|
|
||||||
|
$ensembles = NominationEnsemble::all();
|
||||||
|
// populate an array with each ensemble id as a key. Each item will be a collection of students available to be nominated
|
||||||
|
$availableStudents = [];
|
||||||
|
// populate an array with each ensemble id as a key. Each item will be a collection of available instruments
|
||||||
|
$availableInstruments = [];
|
||||||
|
// populate an array with each ensemble id as a key. Each item will be a collection of nominationEntries already made
|
||||||
|
$nominatedStudents = [];
|
||||||
|
// an array of bool values with each ensemble id as a key. It will be true if additional nominations are available
|
||||||
|
$nominationsAvailable = [];
|
||||||
|
foreach ($ensembles as $ensemble) {
|
||||||
|
// Gather a collection of students who may be nominated for this ensemble
|
||||||
|
$availableStudents[$ensemble->id] = Student::where('grade', '<=', $ensemble->maximum_grade)
|
||||||
|
->where('grade', '>=', $ensemble->minimum_grade)
|
||||||
|
->where('school_id', auth()->user()->school_id)
|
||||||
|
->orderBy('last_name')
|
||||||
|
->orderBy('first_name')
|
||||||
|
->get();
|
||||||
|
$availableInstruments[$ensemble->id] = $ensemble->data['instruments'];
|
||||||
|
$nominatedStudents[$ensemble->id] = $this->collapseNominations(auth()->user()->school, $ensemble,
|
||||||
|
'nominations');
|
||||||
|
|
||||||
|
$nominatedStudentIds = [];
|
||||||
|
|
||||||
|
// Removed students already nominated from available students
|
||||||
|
foreach ($nominatedStudents[$ensemble->id] as $nominatedStudent) {
|
||||||
|
$nominatedStudentIds[] = $nominatedStudent->student_id;
|
||||||
|
}
|
||||||
|
$availableStudents[$ensemble->id] = $availableStudents[$ensemble->id]->reject(function ($student) use (
|
||||||
|
$nominatedStudentIds
|
||||||
|
) {
|
||||||
|
return in_array($student->id, $nominatedStudentIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
$nominationsAvailable[$ensemble->id] = $ensemble->data['max_nominations'] > count($nominatedStudents[$ensemble->id]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('nomination_ensembles.scobda.entries.index',
|
||||||
|
compact('ensembles', 'availableStudents', 'availableInstruments', 'nominatedStudents',
|
||||||
|
'nominationsAvailable', 'currentDate'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(NominationEnsembleEntry $entry)
|
||||||
|
{
|
||||||
|
// TODO: Implement show() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
// TODO: Implement create() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
$validData = request()->validate([
|
||||||
|
'ensemble' => [
|
||||||
|
'required',
|
||||||
|
'exists:App\Models\NominationEnsemble,id',
|
||||||
|
],
|
||||||
|
'new_student' => [
|
||||||
|
'required',
|
||||||
|
'exists:App\Models\Student,id',
|
||||||
|
],
|
||||||
|
'new_instrument' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (NominationEnsembleEntry::where('student_id', $validData['new_student'])
|
||||||
|
->where('nomination_ensemble_id', $validData['ensemble'])
|
||||||
|
->count() > 0) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'Student already nominated for that ensemble');
|
||||||
|
}
|
||||||
|
|
||||||
|
$proposedEnsemble = NominationEnsemble::find($validData['ensemble']);
|
||||||
|
|
||||||
|
$currentDate = Carbon::now('America/Chicago');
|
||||||
|
$currentDate = $currentDate->format('Y-m-d');
|
||||||
|
if ($proposedEnsemble->entry_deadline < $currentDate) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'The nomination deadline for that ensemble has passed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! in_array($validData['new_instrument'], $proposedEnsemble->data['instruments'])) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'Invalid Instrument specified');
|
||||||
|
}
|
||||||
|
|
||||||
|
$student = Student::find($validData['new_student']);
|
||||||
|
if (auth()->user()->school_id !== $student->school_id) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'You may only nominate students from your school');
|
||||||
|
}
|
||||||
|
$nextRank = $this->collapseNominations($student->school, $proposedEnsemble, 'next');
|
||||||
|
if ($nextRank > $proposedEnsemble->data['max_nominations']) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'You have already used all of your nominations');
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry = new NominationEnsembleEntry();
|
||||||
|
$entry->student_id = $validData['new_student'];
|
||||||
|
$entry->nomination_ensemble_id = $validData['ensemble'];
|
||||||
|
$data = [];
|
||||||
|
$data['rank'] = $nextRank;
|
||||||
|
$data['instrument'] = $validData['new_instrument'];
|
||||||
|
$entry->data = $data;
|
||||||
|
$entry->save();
|
||||||
|
|
||||||
|
return redirect()->route('nomination.entry.index')->with('success',
|
||||||
|
'Nomination Recorded');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(NominationEnsembleEntry $entry)
|
||||||
|
{
|
||||||
|
// TODO: Implement edit() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(NominationEnsembleEntry $entry)
|
||||||
|
{
|
||||||
|
// TODO: Implement update() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(NominationEnsembleEntry $entry)
|
||||||
|
{
|
||||||
|
if ($entry->student->school_id !== auth()->user()->school_id) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'You may only delete nominations from your school');
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentDate = Carbon::now('America/Chicago');
|
||||||
|
$currentDate = $currentDate->format('Y-m-d');
|
||||||
|
if ($entry->ensemble->entry_deadline < $currentDate) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'You cannot delete nominations after the deadline');
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry->delete();
|
||||||
|
|
||||||
|
return redirect()->route('nomination.entry.index')->with('success', 'Nomination Deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a school and nomination ensemble, consolidate the rank valuek
|
||||||
|
*
|
||||||
|
* if returnType is next, the next available rank will be returned
|
||||||
|
* if returnType is nominations, a collection of nominations will be returned
|
||||||
|
*
|
||||||
|
* @return int|array
|
||||||
|
*
|
||||||
|
* @var returnType = next|nominations
|
||||||
|
*/
|
||||||
|
private function collapseNominations(School $school, NominationEnsemble $ensemble, $returnType = 'next')
|
||||||
|
{
|
||||||
|
$nominations = $school->nominations()->get()->where('nomination_ensemble_id',
|
||||||
|
$ensemble->id)->sortBy('data.rank');
|
||||||
|
$n = 1;
|
||||||
|
foreach ($nominations as $nomination) {
|
||||||
|
$nomination->update(['data->rank' => $n]);
|
||||||
|
$n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($returnType == 'next') {
|
||||||
|
return $n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $nominations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function move()
|
||||||
|
{
|
||||||
|
|
||||||
|
$validData = request()->validate([
|
||||||
|
'direction' => 'required|in:up,down',
|
||||||
|
'nominationId' => 'required|exists:App\Models\NominationEnsembleEntry,id',
|
||||||
|
]);
|
||||||
|
$direction = $validData['direction'];
|
||||||
|
$nomination = NominationEnsembleEntry::findOrFail($validData['nominationId']);
|
||||||
|
|
||||||
|
// Verify the entry deadline for the ensemble has not passed
|
||||||
|
$currentDate = Carbon::now('America/Chicago');
|
||||||
|
$currentDate = $currentDate->format('Y-m-d');
|
||||||
|
if ($nomination->ensemble->entry_deadline < $currentDate) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'The entry deadline for that nomination ensemble has passed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the student being moved is from the users school
|
||||||
|
if (auth()->user()->school_id !== $nomination->student_id) {
|
||||||
|
return redirect()->route('nomination.entry.index')->with('error',
|
||||||
|
'You cannot modify nominations of another school');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $nomination->data;
|
||||||
|
if ($validData['direction'] == 'up') {
|
||||||
|
$data['rank'] = $nomination->data['rank'] - 1.5;
|
||||||
|
}
|
||||||
|
if ($validData['direction'] == 'down') {
|
||||||
|
$data['rank'] = $nomination->data['rank'] + 1.5;
|
||||||
|
}
|
||||||
|
$nomination->update(['data' => $data]);
|
||||||
|
$this->collapseNominations($nomination->student->school, $nomination->ensemble, 'next');
|
||||||
|
|
||||||
|
return redirect()->route('nomination.entry.index')->with('success', 'Nomination Moved');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NominationEnsembles;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\NominationEnsemble;
|
||||||
|
use App\Models\NominationEnsembleEntry;
|
||||||
|
|
||||||
|
use function redirect;
|
||||||
|
|
||||||
|
class ScobdaNominationSeatingController extends Controller implements NominationSeatingController
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$ensembles = NominationEnsemble::all();
|
||||||
|
$ensemble = null;
|
||||||
|
|
||||||
|
return view('nomination_ensembles.scobda.admin.seating.index', compact('ensembles', 'ensemble'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(NominationEnsemble $ensemble)
|
||||||
|
{
|
||||||
|
$ensembles = NominationEnsemble::all();
|
||||||
|
$acceptedNominations = NominationEnsembleEntry::where('nomination_ensemble_id', $ensemble->id)
|
||||||
|
->where('data->accepted', true)
|
||||||
|
->orderByRaw('CAST(data->"$.rank" AS UNSIGNED)')
|
||||||
|
->get();
|
||||||
|
$acceptedNominations = $acceptedNominations->groupBy(function ($item) {
|
||||||
|
return $item->data['instrument'];
|
||||||
|
});
|
||||||
|
|
||||||
|
return view('nomination_ensembles.scobda.admin.seating.index',
|
||||||
|
compact('ensembles', 'ensemble', 'acceptedNominations'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function seat(NominationEnsemble $ensemble)
|
||||||
|
{
|
||||||
|
$nominations = NominationEnsembleEntry::where('nomination_ensemble_id',
|
||||||
|
$ensemble->id)->orderByRaw('CAST(data->"$.rank" AS UNSIGNED)')->inRandomOrder()->get();
|
||||||
|
$rankGroupedNominations = $nominations->groupBy(function ($entry) {
|
||||||
|
return $entry->data['rank'];
|
||||||
|
});
|
||||||
|
|
||||||
|
$validData = request()->validate([
|
||||||
|
'action' => ['required', 'in:seat,clear'],
|
||||||
|
]);
|
||||||
|
$action = $validData['action'];
|
||||||
|
|
||||||
|
if ($action == 'clear') {
|
||||||
|
foreach ($nominations as $nomination) {
|
||||||
|
$data = $nomination->data;
|
||||||
|
unset($data['accepted']);
|
||||||
|
$nomination->update(['data' => $data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $ensemble->data;
|
||||||
|
$data['seated'] = false;
|
||||||
|
$ensemble->data = $data;
|
||||||
|
$ensemble->update();
|
||||||
|
|
||||||
|
return redirect()->route('nomination.admin.seating.show',
|
||||||
|
['ensemble' => $ensemble])->with('Seating Cleared');
|
||||||
|
}
|
||||||
|
|
||||||
|
$acceptedNominations = collect();
|
||||||
|
$rankOn = 1;
|
||||||
|
// Collect students to add to the ensemble
|
||||||
|
while ($rankOn <= $ensemble->data['max_nominations'] && $rankGroupedNominations->has($rankOn)) {
|
||||||
|
// If were at or over the target size of the ensemble, stop adding people
|
||||||
|
if ($acceptedNominations->count() >= $ensemble->data['target_size']) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Add people of the current rank to the ensemble
|
||||||
|
foreach ($rankGroupedNominations[$rankOn] as $nomination) {
|
||||||
|
$acceptedNominations->push($nomination);
|
||||||
|
}
|
||||||
|
$rankOn++;
|
||||||
|
|
||||||
|
// If we want to round down the ensemble size, quit adding people if hte next rank will exceed the target
|
||||||
|
if (
|
||||||
|
$rankGroupedNominations->has($rankOn) &&
|
||||||
|
$acceptedNominations->count() + $rankGroupedNominations[$rankOn]->count() >= $ensemble->data['target_size'] &&
|
||||||
|
$ensemble->data['rounding_direction'] === 'down'
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($acceptedNominations as $nomination) {
|
||||||
|
$data = $nomination->data;
|
||||||
|
$data['accepted'] = true;
|
||||||
|
$nomination->update(['data' => $data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $ensemble->data;
|
||||||
|
$data['seated'] = true;
|
||||||
|
$ensemble->data = $data;
|
||||||
|
$ensemble->update();
|
||||||
|
|
||||||
|
return redirect()->route('nomination.admin.seating.show', ['ensemble' => $ensemble])->with('Seating Complete');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,10 @@ class StudentController extends Controller
|
||||||
$students = Auth::user()->students()->withCount('entries')->get();
|
$students = Auth::user()->students()->withCount('entries')->get();
|
||||||
$auditions = Audition::all();
|
$auditions = Audition::all();
|
||||||
|
|
||||||
return view('students.index', ['students' => $students, 'auditions' => $auditions]);
|
$shirtSizes = Student::$shirtSizes;
|
||||||
|
|
||||||
|
return view('students.index',
|
||||||
|
['students' => $students, 'auditions' => $auditions, 'shirtSizes' => $shirtSizes]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -51,6 +54,14 @@ class StudentController extends Controller
|
||||||
new UniqueFullNameAtSchool(request('first_name'), request('last_name'), Auth::user()->school_id),
|
new UniqueFullNameAtSchool(request('first_name'), request('last_name'), Auth::user()->school_id),
|
||||||
],
|
],
|
||||||
'grade' => ['required', 'integer'],
|
'grade' => ['required', 'integer'],
|
||||||
|
'shirt_size' => [
|
||||||
|
'nullable',
|
||||||
|
function ($attribute, $value, $fail) {
|
||||||
|
if (! array_key_exists($value, Student::$shirtSizes)) {
|
||||||
|
$fail("The selected $attribute is invalid.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$student = Student::create([
|
$student = Student::create([
|
||||||
|
|
@ -59,6 +70,9 @@ class StudentController extends Controller
|
||||||
'grade' => request('grade'),
|
'grade' => request('grade'),
|
||||||
'school_id' => Auth::user()->school_id,
|
'school_id' => Auth::user()->school_id,
|
||||||
]);
|
]);
|
||||||
|
if (request('shirt_size') !== 'none') {
|
||||||
|
$student->update(['optional_data->shirt_size' => $request['shirt_size']]);
|
||||||
|
}
|
||||||
$message = 'Created student #'.$student->id.' - '.$student->full_name().'<br>Grade: '.$student->grade.'<br>School: '.$student->school->name;
|
$message = 'Created student #'.$student->id.' - '.$student->full_name().'<br>Grade: '.$student->grade.'<br>School: '.$student->school->name;
|
||||||
AuditLogEntry::create([
|
AuditLogEntry::create([
|
||||||
'user' => auth()->user()->email,
|
'user' => auth()->user()->email,
|
||||||
|
|
@ -90,7 +104,9 @@ class StudentController extends Controller
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('students.edit', ['student' => $student]);
|
$shirtSizes = Student::$shirtSizes;
|
||||||
|
|
||||||
|
return view('students.edit', ['student' => $student, 'shirtSizes' => $shirtSizes]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -106,6 +122,14 @@ class StudentController extends Controller
|
||||||
'first_name' => ['required'],
|
'first_name' => ['required'],
|
||||||
'last_name' => ['required'],
|
'last_name' => ['required'],
|
||||||
'grade' => ['required', 'integer'],
|
'grade' => ['required', 'integer'],
|
||||||
|
'shirt_size' => [
|
||||||
|
'nullable',
|
||||||
|
function ($attribute, $value, $fail) {
|
||||||
|
if (! array_key_exists($value, Student::$shirtSizes)) {
|
||||||
|
$fail("The selected $attribute is invalid.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (Student::where('first_name', request('first_name'))
|
if (Student::where('first_name', request('first_name'))
|
||||||
|
|
@ -122,6 +146,9 @@ class StudentController extends Controller
|
||||||
'last_name' => request('last_name'),
|
'last_name' => request('last_name'),
|
||||||
'grade' => request('grade'),
|
'grade' => request('grade'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$student->update(['optional_data->shirt_size' => $request['shirt_size']]);
|
||||||
|
|
||||||
$message = 'Updated student #'.$student->id.'<br>Name: '.$student->full_name().'<br>Grade: '.$student->grade.'<br>School: '.$student->school->name;
|
$message = 'Updated student #'.$student->id.'<br>Name: '.$student->full_name().'<br>Grade: '.$student->grade.'<br>School: '.$student->school->name;
|
||||||
AuditLogEntry::create([
|
AuditLogEntry::create([
|
||||||
'user' => auth()->user()->email,
|
'user' => auth()->user()->email,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class NominationEnsemble extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function entries(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(NominationEnsembleEntry::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class NominationEnsembleEntry extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ensemble(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(NominationEnsemble::class, 'nomination_ensemble_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function student(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Student::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,4 +51,15 @@ class School extends Model
|
||||||
'id',
|
'id',
|
||||||
'id');
|
'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function nominations(): HasManyThrough
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(
|
||||||
|
NominationEnsembleEntry::class,
|
||||||
|
Student::class,
|
||||||
|
'school_id',
|
||||||
|
'student_id',
|
||||||
|
'id',
|
||||||
|
'id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,34 @@ class Student extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
|
public static $shirtSizes = [
|
||||||
|
'none' => '---',
|
||||||
|
'YS' => 'Youth Small',
|
||||||
|
'YM' => 'Youth Medium',
|
||||||
|
'YL' => 'Youth Large',
|
||||||
|
'YXL' => 'Youth Extra Large',
|
||||||
|
'S' => 'Small',
|
||||||
|
'M' => 'Medium',
|
||||||
|
'L' => 'Large',
|
||||||
|
'XL' => 'Extra Large',
|
||||||
|
'2XL' => '2XL',
|
||||||
|
'3XL' => '3XL',
|
||||||
|
];
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'optional_data' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nominations(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(NominationEnsembleEntry::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function school(): BelongsTo
|
public function school(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(School::class);
|
return $this->belongsTo(School::class);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,14 @@ use App\Actions\Tabulation\CalculateEntryScore;
|
||||||
use App\Actions\Tabulation\CalculateScoreSheetTotal;
|
use App\Actions\Tabulation\CalculateScoreSheetTotal;
|
||||||
use App\Actions\Tabulation\CalculateScoreSheetTotalDivideByTotalWeights;
|
use App\Actions\Tabulation\CalculateScoreSheetTotalDivideByTotalWeights;
|
||||||
use App\Actions\Tabulation\CalculateScoreSheetTotalDivideByWeightedPossible;
|
use App\Actions\Tabulation\CalculateScoreSheetTotalDivideByWeightedPossible;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\NominationAdminController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\NominationEnsembleController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\NominationEnsembleEntryController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\NominationSeatingController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\ScobdaNominationAdminController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\ScobdaNominationEnsembleController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\ScobdaNominationEnsembleEntryController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\ScobdaNominationSeatingController;
|
||||||
use App\Models\Audition;
|
use App\Models\Audition;
|
||||||
use App\Models\Entry;
|
use App\Models\Entry;
|
||||||
use App\Models\Room;
|
use App\Models\Room;
|
||||||
|
|
@ -62,6 +70,11 @@ class AppServiceProvider extends ServiceProvider
|
||||||
$this->app->singleton(UpdateEntry::class, UpdateEntry::class);
|
$this->app->singleton(UpdateEntry::class, UpdateEntry::class);
|
||||||
$this->app->singleton(SetHeadDirector::class, SetHeadDirector::class);
|
$this->app->singleton(SetHeadDirector::class, SetHeadDirector::class);
|
||||||
|
|
||||||
|
// Nomination Ensemble
|
||||||
|
$this->app->bind(NominationEnsembleController::class, ScobdaNominationEnsembleController::class);
|
||||||
|
$this->app->bind(NominationEnsembleEntryController::class, ScobdaNominationEnsembleEntryController::class);
|
||||||
|
$this->app->bind(NominationAdminController::class, ScobdaNominationAdminController::class);
|
||||||
|
$this->app->bind(NominationSeatingController::class, ScobdaNominationSeatingController::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\NominationEnsembleEntry;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class NominationEnsembleEntryFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = NominationEnsembleEntry::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'student_id' => $this->faker->randomNumber(),
|
||||||
|
'nomination_ensemble_id' => $this->faker->randomNumber(),
|
||||||
|
'data' => $this->faker->words(),
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
'updated_at' => Carbon::now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Check if invoicing_enabled setting exists
|
||||||
|
$exists = DB::table('site_settings')
|
||||||
|
->where('setting_key', 'student_data_collect_shirt_size')
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
// If it doesn't insert the new row
|
||||||
|
if (! $exists) {
|
||||||
|
DB::table('site_settings')->insert([
|
||||||
|
'setting_key' => 'student_data_collect_shirt_size',
|
||||||
|
'setting_value' => '0',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('students', function (Blueprint $table) {
|
||||||
|
$table->json('optional_data')->nullable()->after('grade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('students', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('optional_data');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('nomination_ensembles', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->date('entry_deadline');
|
||||||
|
$table->integer('minimum_grade');
|
||||||
|
$table->integer('maximum_grade');
|
||||||
|
$table->json('data')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('nomination_ensembles');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('nomination_ensembles', function (Blueprint $table) {
|
||||||
|
$table->unique('name');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('nomination_ensembles', function (Blueprint $table) {
|
||||||
|
$table->dropUnique(['name']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Check if invoicing_enabled setting exists
|
||||||
|
$exists = DB::table('site_settings')
|
||||||
|
->where('setting_key', 'nomination_ensemble_rules')
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
// If it doesn't insert the new row
|
||||||
|
if (! $exists) {
|
||||||
|
DB::table('site_settings')->insert([
|
||||||
|
'setting_key' => 'nomination_ensemble_rules',
|
||||||
|
'setting_value' => 'disabled',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\NominationEnsemble;
|
||||||
|
use App\Models\Student;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('nomination_ensemble_entries', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignIdFor(Student::class)->constrained()->cascadeOnUpdate()->restrictOnDelete();
|
||||||
|
$table->foreignIdFor(NominationEnsemble::class)->constrained()->cascadeOnUpdate()->restrictOnDelete();
|
||||||
|
$table->json('data');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('nomination_ensemble_entries');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\NominationEnsemble;
|
||||||
|
use App\Models\NominationEnsembleEntry;
|
||||||
|
use App\Models\School;
|
||||||
|
use App\Models\Student;
|
||||||
|
use Faker\Factory as Faker;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class ScobdaNominationEnsembleAndEntrySeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
// Clear existing nomination ensembles and nominations
|
||||||
|
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
|
||||||
|
DB::table('nomination_ensemble_entries')->truncate();
|
||||||
|
DB::table('nomination_ensembles')->truncate();
|
||||||
|
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
||||||
|
|
||||||
|
// Create First Year Ensemble
|
||||||
|
$ensemble = new NominationEnsemble();
|
||||||
|
$ensemble->name = 'First Year Band';
|
||||||
|
$ensemble->entry_deadline = '2028-01-01';
|
||||||
|
$ensemble->minimum_grade = 5;
|
||||||
|
$ensemble->maximum_grade = 8;
|
||||||
|
$instruments = [
|
||||||
|
'Flute',
|
||||||
|
'Oboe',
|
||||||
|
'Bassoon',
|
||||||
|
'Clarinet',
|
||||||
|
'Bass Clarinet',
|
||||||
|
'Contra Clarinet',
|
||||||
|
'Alto Sax',
|
||||||
|
'Tenor Sax',
|
||||||
|
'Bari Sax',
|
||||||
|
'Trumpet',
|
||||||
|
'Horn',
|
||||||
|
'Trombone',
|
||||||
|
'Euphonium',
|
||||||
|
'Tuba',
|
||||||
|
'String Bass',
|
||||||
|
'Percussion',
|
||||||
|
];
|
||||||
|
$data = [
|
||||||
|
'instruments' => $instruments,
|
||||||
|
'target_size' => 100,
|
||||||
|
'max_nominations' => 10,
|
||||||
|
'rounding_direction' => 'up',
|
||||||
|
];
|
||||||
|
$ensemble->data = $data;
|
||||||
|
$ensemble->save();
|
||||||
|
|
||||||
|
// Fill the nominations table
|
||||||
|
$faker = Faker::create();
|
||||||
|
$schools = School::all();
|
||||||
|
foreach ($schools as $school) {
|
||||||
|
$students = Student::factory()->count(10)->create(['school_id' => $school->id, 'grade' => 5]);
|
||||||
|
$n = 1;
|
||||||
|
foreach ($students as $student) {
|
||||||
|
$nomData = [
|
||||||
|
'rank' => $n,
|
||||||
|
'instrument' => $faker->randomElement($instruments),
|
||||||
|
];
|
||||||
|
NominationEnsembleEntry::create([
|
||||||
|
'student_id' => $student->id,
|
||||||
|
'nomination_ensemble_id' => $ensemble->id,
|
||||||
|
'data' => $nomData,
|
||||||
|
]);
|
||||||
|
$n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,28 @@
|
||||||
<x-form.body-grid columns="12" class="m-3">
|
<x-form.body-grid columns="12" class="m-3">
|
||||||
<x-form.field label_text="Registration Code" name="registrationCode" colspan="3" :value="auditionSetting('registrationCode')"/>
|
<x-form.field label_text="Registration Code" name="registrationCode" colspan="3" :value="auditionSetting('registrationCode')"/>
|
||||||
<x-form.field label_text="Next Event Name" name="advanceTo" colspan="4" :value="auditionSetting('advanceTo')"/>
|
<x-form.field label_text="Next Event Name" name="advanceTo" colspan="4" :value="auditionSetting('advanceTo')"/>
|
||||||
|
<x-form.select name="nomination_ensemble_rules" colspan="4">
|
||||||
|
<x-slot:label>Nomination Ensemble Rules</x-slot:label>
|
||||||
|
{{-- Values should be one of the options in the boot method NominationEnsembleServiceProvider --}}
|
||||||
|
<option value="disabled" {{ auditionSetting('nomination_ensemble_rules') === 'disabled' ? 'selected':'' }}>
|
||||||
|
No Nomination Ensembles
|
||||||
|
</option>
|
||||||
|
<option value="scobda" {{ auditionSetting('nomination_ensemble_rules') === 'scobda' ? 'selected':'' }}>
|
||||||
|
SCOBDA Rules
|
||||||
|
</option>
|
||||||
|
</x-form.select>
|
||||||
|
</x-form.body-grid>
|
||||||
|
</x-layout.page-section>
|
||||||
|
|
||||||
|
<x-layout.page-section>
|
||||||
|
<x-slot:section_name>Optional Student Data</x-slot:section_name>
|
||||||
|
<x-form.body-grid columns 12 class="m-3">
|
||||||
|
<div class="col-span-6 flex space-x-3">
|
||||||
|
<x-form.toggle-checkbox
|
||||||
|
checked="{{ auditionSetting('student_data_collect_shirt_size') }}"
|
||||||
|
name="student_data_collect_shirt_size"/>
|
||||||
|
<span>Collect Student Shirt Size</span>
|
||||||
|
</div>
|
||||||
</x-form.body-grid>
|
</x-form.body-grid>
|
||||||
</x-layout.page-section>
|
</x-layout.page-section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19V5m0 14-4-4m4 4 4-4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 296 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" {{ $attributes }}>
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 478 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v13m0-13 4 4m-4-4-4 4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 297 B |
|
|
@ -25,6 +25,9 @@
|
||||||
<a href="{{route('admin.schools.index')}}" class="block p-2 hover:text-indigo-600">Schools</a>
|
<a href="{{route('admin.schools.index')}}" class="block p-2 hover:text-indigo-600">Schools</a>
|
||||||
<a href="{{route('admin.students.index')}}" class="block p-2 hover:text-indigo-600">Students</a>
|
<a href="{{route('admin.students.index')}}" class="block p-2 hover:text-indigo-600">Students</a>
|
||||||
<a href="{{route('admin.entries.index')}}" class="block p-2 hover:text-indigo-600">Entries</a>
|
<a href="{{route('admin.entries.index')}}" class="block p-2 hover:text-indigo-600">Entries</a>
|
||||||
|
@if(auditionSetting('nomination_ensemble_rules') !== 'disabled')
|
||||||
|
<x-layout.navbar.menus.menu-item :href="route('nomination.admin.index')">Nominations</x-layout.navbar.menus.menu-item>
|
||||||
|
@endif
|
||||||
<a href="{{route('admin.view_logs')}}" class="block p-2 hover:text-indigo-600">View Logs</a>
|
<a href="{{route('admin.view_logs')}}" class="block p-2 hover:text-indigo-600">View Logs</a>
|
||||||
<a href="{{route('admin.export_results')}}" class="block p-2 hover:text-indigo-600">Export Results</a>
|
<a href="{{route('admin.export_results')}}" class="block p-2 hover:text-indigo-600">Export Results</a>
|
||||||
<a href="{{route('admin.export_entries')}}" class="block p-2 hover:text-indigo-600">Export Entries</a>
|
<a href="{{route('admin.export_entries')}}" class="block p-2 hover:text-indigo-600">Export Entries</a>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@
|
||||||
@if(Auth::user()->school_id)
|
@if(Auth::user()->school_id)
|
||||||
<a href="{{route('students.index')}}" class="block p-2 hover:text-indigo-600">My Students</a>
|
<a href="{{route('students.index')}}" class="block p-2 hover:text-indigo-600">My Students</a>
|
||||||
<a href="{{route('entries.index')}}" class="block p-2 hover:text-indigo-600">My Entries</a>
|
<a href="{{route('entries.index')}}" class="block p-2 hover:text-indigo-600">My Entries</a>
|
||||||
|
@if(auditionSetting('nomination_ensemble_rules') !== 'disabled')
|
||||||
|
<x-layout.navbar.menus.menu-item :href="route('nomination.entry.index')">My Nominations</x-layout.navbar.menus.menu-item>
|
||||||
|
@endif
|
||||||
<a href="{{route('doubler_request.index')}}" class="block p-2 hover:text-indigo-600">My Doubler Requests</a>
|
<a href="{{route('doubler_request.index')}}" class="block p-2 hover:text-indigo-600">My Doubler Requests</a>
|
||||||
<a href="{{route('my_school')}}" class="block p-2 hover:text-indigo-600">My School</a>
|
<a href="{{route('my_school')}}" class="block p-2 hover:text-indigo-600">My School</a>
|
||||||
@if(auditionSetting('invoicing_enabled'))
|
@if(auditionSetting('invoicing_enabled'))
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@
|
||||||
<x-layout.navbar.menus.menu-item :href="route('admin.cards.index')">Print Cards</x-layout.navbar.menus.menu-item>
|
<x-layout.navbar.menus.menu-item :href="route('admin.cards.index')">Print Cards</x-layout.navbar.menus.menu-item>
|
||||||
<x-layout.navbar.menus.menu-item :href="route('admin.signInSheets.index')">Print Sign-In Sheets</x-layout.navbar.menus.menu-item>
|
<x-layout.navbar.menus.menu-item :href="route('admin.signInSheets.index')">Print Sign-In Sheets</x-layout.navbar.menus.menu-item>
|
||||||
<x-layout.navbar.menus.menu-item :href="route('admin.print_room_assignment_report')">Print Room and Judge Assignments</x-layout.navbar.menus.menu-item>
|
<x-layout.navbar.menus.menu-item :href="route('admin.print_room_assignment_report')">Print Room and Judge Assignments</x-layout.navbar.menus.menu-item>
|
||||||
|
@if(auditionSetting('nomination_ensemble_rules') !== 'disabled')
|
||||||
|
<x-layout.navbar.menus.menu-item :href="route('nomination.admin.ensemble.index')">Nomination Ensemble Setup</x-layout.navbar.menus.menu-item>
|
||||||
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@
|
||||||
@if(auditionSetting('advanceTo'))
|
@if(auditionSetting('advanceTo'))
|
||||||
<a href="{{ route('advancement.status') }}" class="block p-2 hover:text-indigo-600">{{ auditionSetting('advanceTo') }} Status</a>
|
<a href="{{ route('advancement.status') }}" class="block p-2 hover:text-indigo-600">{{ auditionSetting('advanceTo') }} Status</a>
|
||||||
@endif
|
@endif
|
||||||
|
@if(auditionSetting('nomination_ensemble_rules') !== 'disabled')
|
||||||
|
<x-layout.navbar.menus.menu-item :href="route('nomination.admin.seating.index')">Nomination Ensemble Seating</x-layout.navbar.menus.menu-item>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<x-layout.app>
|
||||||
|
<x-slot:page_title>Nomination Ensembles</x-slot:page_title>
|
||||||
|
|
||||||
|
<x-layout.page-section-container>
|
||||||
|
<x-layout.page-section>
|
||||||
|
<x-slot:section_name>Add Nomination Ensemble</x-slot:section_name>
|
||||||
|
<x-form.form method="POST" action="{{ route('nomination.admin.ensemble.store') }}" class="mb-6 mt-3">
|
||||||
|
<x-form.body-grid columns="9" class="max-w-full">
|
||||||
|
<x-form.field name="ensemble_name" label_text="Ensemble Name" colspan="3" autofocus />
|
||||||
|
<x-form.field name="entry_deadline" label_text="Entry Deadline" type="date" colspan="2"/>
|
||||||
|
<x-form.field name="min_grade" label_text="Minimum Grade" type="number" colspan="2"/>
|
||||||
|
<x-form.field name="max_grade" label_text="Maximum Grade" type="number" colspan="2"/>
|
||||||
|
<x-form.field name="max_nominations" label_text="Maximum Nominations per School" type="number" colspan="3"/>
|
||||||
|
<x-form.field name="target_size" label_text="Target Ensemble Size" type="number" colspan="3"/>
|
||||||
|
<x-form.select name="rounding_direction" colspan="3">
|
||||||
|
<x-slot:label>Round</x-slot:label>
|
||||||
|
<option value="up">Up</option>
|
||||||
|
<option value="down">Down</option>
|
||||||
|
</x-form.select>
|
||||||
|
<x-form.textarea name="instrument_list" colspan="9">
|
||||||
|
<x-slot:label>Instrument List (comma separated)</x-slot:label>
|
||||||
|
</x-form.textarea>
|
||||||
|
</x-form.body-grid>
|
||||||
|
<x-form.footer submit-button-text="Create Ensemble"/>
|
||||||
|
</x-form.form>
|
||||||
|
</x-layout.page-section>
|
||||||
|
|
||||||
|
<x-layout.page-section>
|
||||||
|
<x-slot:section_name>Nomination Ensembles</x-slot:section_name>
|
||||||
|
<div class="p-4">
|
||||||
|
@foreach($ensembles as $ensemble)
|
||||||
|
<x-card.card class="m-3" x-data="{ editable: false }" >
|
||||||
|
<x-card.heading>
|
||||||
|
{{ $ensemble->name }}
|
||||||
|
<x-slot:right_side class="flex">
|
||||||
|
<x-icons.pencil @click="editable = true" x-show="!editable"/>
|
||||||
|
<x-delete-resource-modal
|
||||||
|
title="Delete Nomination Ensemble {{$ensemble->name}}"
|
||||||
|
method="DELETE"
|
||||||
|
action="{{ route('nomination.admin.ensemble.destroy',[$ensemble]) }}"
|
||||||
|
>
|
||||||
|
Are you sure you want to delete this nomination ensemble?
|
||||||
|
</x-delete-resource-modal>
|
||||||
|
</x-slot:right_side>
|
||||||
|
</x-card.heading>
|
||||||
|
<x-form.form method="POST" action="{{ route('nomination.admin.ensemble.update',[$ensemble]) }}" class="mb-6 mt-3">
|
||||||
|
@method('PATCH')
|
||||||
|
<x-form.body-grid columns="9" class="max-w-full">
|
||||||
|
<x-form.field name="ensemble_name" label_text="Ensemble Name" colspan="3" value="{{ $ensemble->name }}" x-bind:readonly="!editable" />
|
||||||
|
<x-form.field name="entry_deadline" label_text="Entry Deadline" type="date" colspan="2" value="{{ $ensemble->entry_deadline }}" x-bind:readonly="!editable" />
|
||||||
|
<x-form.field name="min_grade" label_text="Minimum Grade" type="number" colspan="2" value="{{ $ensemble->minimum_grade }}" x-bind:readonly="!editable" />
|
||||||
|
<x-form.field name="max_grade" label_text="Maximum Grade" type="number" colspan="2" value="{{ $ensemble->maximum_grade }}" x-bind:readonly="!editable" />
|
||||||
|
<x-form.field name="max_nominations" label_text="Maximum Nominations per School" type="number" colspan="3" value="{{ $ensemble->data['max_nominations'] }}" x-bind:readonly="!editable" />
|
||||||
|
<x-form.field name="target_size" label_text="Target Ensemble Size" type="number" colspan="3" value="{{ $ensemble->data['target_size'] }}" x-bind:readonly="!editable" />
|
||||||
|
<x-form.select name="rounding_direction" colspan="3" x-bind:disabled="!editable" >
|
||||||
|
<x-slot:label>Round</x-slot:label>
|
||||||
|
<option value="up" @if(($ensemble->data['rounding_direction'] ?? null) == 'up') selected @endif()>Up</option>
|
||||||
|
<option value="down" @if(($ensemble->data['rounding_direction'] ?? null) == 'down') selected @endif()>Down</option>
|
||||||
|
</x-form.select>
|
||||||
|
<x-form.textarea name="instrument_list" colspan="9" x-bind:readonly="!editable" >
|
||||||
|
<x-slot:label>Instrument List (comma separated)</x-slot:label>
|
||||||
|
{{ implode(', ',$ensemble->data['instruments']) }}
|
||||||
|
</x-form.textarea>
|
||||||
|
</x-form.body-grid>
|
||||||
|
<x-form.footer submit-button-text="Edit Ensemble" x-show="editable" x-cloak/>
|
||||||
|
</x-form.form>
|
||||||
|
</x-card.card>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</x-layout.page-section>
|
||||||
|
</x-layout.page-section-container>
|
||||||
|
|
||||||
|
</x-layout.app>
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<x-layout.app>
|
||||||
|
<script>
|
||||||
|
function sortTable(n) {
|
||||||
|
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
|
||||||
|
table = document.getElementById("nominationTable");
|
||||||
|
switching = true;
|
||||||
|
// Set the sorting direction to ascending:
|
||||||
|
dir = "asc";
|
||||||
|
/* Make a loop that will continue until
|
||||||
|
no switching has been done: */
|
||||||
|
while (switching) {
|
||||||
|
// Start by saying: no switching is done:
|
||||||
|
switching = false;
|
||||||
|
rows = table.rows;
|
||||||
|
/* Loop through all table rows (except the
|
||||||
|
first, which contains table headers): */
|
||||||
|
for (i = 1; i < (rows.length - 1); i++) {
|
||||||
|
// Start by saying there should be no switching:
|
||||||
|
shouldSwitch = false;
|
||||||
|
/* Get the two elements you want to compare,
|
||||||
|
one from current row and one from the next: */
|
||||||
|
x = rows[i].getElementsByTagName("TD")[n];
|
||||||
|
y = rows[i + 1].getElementsByTagName("TD")[n];
|
||||||
|
/* Check if the two rows should switch place,
|
||||||
|
based on the direction, asc or desc: */
|
||||||
|
if (dir == "asc") {
|
||||||
|
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
|
||||||
|
// If so, mark as a switch and break the loop:
|
||||||
|
shouldSwitch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (dir == "desc") {
|
||||||
|
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
|
||||||
|
// If so, mark as a switch and break the loop:
|
||||||
|
shouldSwitch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldSwitch) {
|
||||||
|
/* If a switch has been marked, make the switch
|
||||||
|
and mark that a switch has been done: */
|
||||||
|
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
|
||||||
|
switching = true;
|
||||||
|
// Each time a switch is done, increase this count by 1:
|
||||||
|
switchcount ++;
|
||||||
|
} else {
|
||||||
|
/* If no switching has been done AND the direction is "asc",
|
||||||
|
set the direction to "desc" and run the while loop again. */
|
||||||
|
if (switchcount == 0 && dir == "asc") {
|
||||||
|
dir = "desc";
|
||||||
|
switching = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<x-slot:page_title>Nomination Administration</x-slot:page_title>
|
||||||
|
|
||||||
|
<x-table.table id="nominationTable">
|
||||||
|
<x-slot:title>Nominations</x-slot:title>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<x-table.th onclick="sortTable(0)">Name</x-table.th>
|
||||||
|
<x-table.th onclick="sortTable(1)">School</x-table.th>
|
||||||
|
<x-table.th onclick="sortTable(2)">Nomination</x-table.th>
|
||||||
|
<x-table.th onclick="sortTable(3)">Rank</x-table.th>
|
||||||
|
<x-table.th onclick="sortTable(4)">Instrument</x-table.th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<x-table.body>
|
||||||
|
@foreach($nominations as $nomination)
|
||||||
|
<tr>
|
||||||
|
<x-table.td>{{ $nomination->student->full_name('lf') }}</x-table.td>
|
||||||
|
<x-table.td>{{ $nomination->student->school->name }}</x-table.td>
|
||||||
|
<x-table.td>{{ $nomination->ensemble->name }}</x-table.td>
|
||||||
|
<x-table.td>{{ $nomination->data['rank'] }}</x-table.td>
|
||||||
|
<x-table.td>{{ $nomination->data['instrument'] }}</x-table.td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</x-table.body>
|
||||||
|
</x-table.table>
|
||||||
|
|
||||||
|
</x-layout.app>
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<x-layout.app>
|
||||||
|
<x-slot:page_title>Nomination Ensemble Seating</x-slot:page_title>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-5 gap-3">
|
||||||
|
|
||||||
|
<div id="left-ensemble-list">
|
||||||
|
<nav class="flex flex-1 flex-col" aria-label="Sidebar Ensemble List">
|
||||||
|
<p class="text-md/6 font-semibold text-gray-800 mb-3">Select Ensemble</p>
|
||||||
|
<ul role="list" class="-mx2 space-y-1">
|
||||||
|
@foreach($ensembles as $menuItem)
|
||||||
|
<a href="{{ route('nomination.admin.seating.show',[$menuItem->id]) }}"
|
||||||
|
class="group flex gap-x-3 rounded-md p-2 pl-3 text-sm/6 font-semibold text-gray-700 hover:bg-gray-50 hover:text-indigo-600">
|
||||||
|
{{ $menuItem->name }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="seating-pane" class="col-span-4">
|
||||||
|
@if($ensemble)
|
||||||
|
<x-card.card>
|
||||||
|
<x-card.heading>
|
||||||
|
{{ $ensemble->name }}
|
||||||
|
<x-slot:right_side class="flex">
|
||||||
|
@if($ensemble->data['seated'] ?? false)
|
||||||
|
<x-form.form method="POST"
|
||||||
|
action="{{ route('nomination.admin.seating.seat',[$ensemble]) }}">
|
||||||
|
<input type="hidden" name="action" value="clear">
|
||||||
|
<x-form.button>Clear Seats</x-form.button>
|
||||||
|
</x-form.form>
|
||||||
|
@else
|
||||||
|
<x-form.form method="POST"
|
||||||
|
action="{{ route('nomination.admin.seating.seat',[$ensemble]) }}">
|
||||||
|
<input type="hidden" name="action" value="seat">
|
||||||
|
<x-form.button>Seat Ensemble</x-form.button>
|
||||||
|
</x-form.form>
|
||||||
|
@endif
|
||||||
|
</x-slot:right_side>
|
||||||
|
</x-card.heading>
|
||||||
|
<x-table.table>
|
||||||
|
@foreach($ensemble->data['instruments'] as $instrument)
|
||||||
|
@php($seatOn = 1)
|
||||||
|
@continue(! $acceptedNominations->has($instrument))
|
||||||
|
<tr class="border-t-2 border b-2">
|
||||||
|
<x-table.th>{{ $instrument }}</x-table.th>
|
||||||
|
<x-table.th>Student Name</x-table.th>
|
||||||
|
<x-table.th>School (Nom Rank)</x-table.th>
|
||||||
|
</tr>
|
||||||
|
@foreach($acceptedNominations[$instrument] as $nom)
|
||||||
|
<tr>
|
||||||
|
<x-table.td>{{ $seatOn }}</x-table.td>
|
||||||
|
<x-table.td>{{ $nom->student->full_name() }}</x-table.td>
|
||||||
|
<x-table.td>{{ $nom->student->school->name }} ({{ $nom->data['rank'] }})
|
||||||
|
</x-table.td>
|
||||||
|
</tr>
|
||||||
|
@php($seatOn++)
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</x-table.table>
|
||||||
|
</x-card.card>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</x-layout.app>
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
@php($n=1)
|
||||||
|
|
||||||
|
<x-layout.app>
|
||||||
|
<x-slot:page_title>Nomination Entries</x-slot:page_title>
|
||||||
|
|
||||||
|
<x-layout.page-section-container>
|
||||||
|
|
||||||
|
@foreach($ensembles as $ensemble)
|
||||||
|
<x-layout.page-section>
|
||||||
|
<x-slot:section_name>{{ $ensemble->name }}</x-slot:section_name>
|
||||||
|
<x-slot:section_description>
|
||||||
|
{{ $ensemble->data['max_nominations'] }} nominations accepted<br>
|
||||||
|
Entry Deadline {{ \Carbon\Carbon::parse($ensemble->entry_deadline)->format('M j, Y') }}
|
||||||
|
</x-slot:section_description>
|
||||||
|
<x-table.table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<x-table.th>Rank</x-table.th>
|
||||||
|
<x-table.th>Student</x-table.th>
|
||||||
|
<x-table.th>Instrument</x-table.th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<x-table.body>
|
||||||
|
{{-- List existing nominations--}}
|
||||||
|
@foreach($nominatedStudents[$ensemble->id] as $nomination)
|
||||||
|
<tr>
|
||||||
|
<x-table.td>{{ $nomination->data['rank'] }}</x-table.td>
|
||||||
|
<x-table.td>{{ $nomination->student->full_name() }}</x-table.td>
|
||||||
|
<x-table.td>{{ $nomination->data['instrument'] }}</x-table.td>
|
||||||
|
@if($currentDate <= $ensemble->entry_deadline)
|
||||||
|
<x-table.td class="flex">
|
||||||
|
<x-delete-resource-modal
|
||||||
|
title="Delete Nomination"
|
||||||
|
method="DELETE"
|
||||||
|
action="{{ route('nomination.entry.destroy', [$nomination]) }}">
|
||||||
|
Confirm you wish to delete the nomination
|
||||||
|
of {{ $nomination->student->full_name() }}<br>
|
||||||
|
for the {{ $ensemble->name }} ensemble.
|
||||||
|
</x-delete-resource-modal>
|
||||||
|
<form method="POST" action="{{ route('nomination.entry.move') }}">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="direction" value="up">
|
||||||
|
<input type="hidden" name="nominationId" value="{{ $nomination->id }}">
|
||||||
|
<button class="ml-3" type="submit">
|
||||||
|
<x-icons.up-arrow/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form method="POST" action="{{ route('nomination.entry.move') }}">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="direction" value="down">
|
||||||
|
<input type="hidden" name="nominationId" value="{{ $nomination->id }}">
|
||||||
|
<button class="ml-3" type="submit">
|
||||||
|
<x-icons.down-arrow/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</x-table.td>
|
||||||
|
@endif
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
{{-- LINE TO ADD A NOMINATION--}}
|
||||||
|
@if($currentDate <= $ensemble->entry_deadline && $nominationsAvailable[$ensemble->id] && $availableStudents[$ensemble->id]->count() > 0)
|
||||||
|
<tr>
|
||||||
|
<x-form.form method="POST" action="{{ route('nomination.entry.store') }}">
|
||||||
|
<input type="hidden" name="ensemble" value="{{ $ensemble->id }}"/>
|
||||||
|
<x-table.th>NEW</x-table.th>
|
||||||
|
<x-table.td>
|
||||||
|
<x-form.select name="new_student">
|
||||||
|
@foreach($availableStudents[$ensemble->id] as $student)
|
||||||
|
<option value="{{$student->id}}">{{ $student->full_name() }}
|
||||||
|
(Grade {{ $student->grade }})
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</x-form.select>
|
||||||
|
</x-table.td>
|
||||||
|
|
||||||
|
<x-table.td>
|
||||||
|
<x-form.select name="new_instrument">
|
||||||
|
@foreach($availableInstruments[$ensemble->id] as $instrument)
|
||||||
|
<option value="{{$instrument}}">{{$instrument}}</option>
|
||||||
|
@endforeach
|
||||||
|
</x-form.select>
|
||||||
|
</x-table.td>
|
||||||
|
|
||||||
|
<x-table.td>
|
||||||
|
<x-form.button class="bg-green-800">Add</x-form.button>
|
||||||
|
</x-table.td>
|
||||||
|
</x-form.form>
|
||||||
|
</tr>
|
||||||
|
@endif
|
||||||
|
</x-table.body>
|
||||||
|
</x-table.table>
|
||||||
|
|
||||||
|
</x-layout.page-section>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</x-layout.page-section-container>
|
||||||
|
|
||||||
|
</x-layout.app>
|
||||||
|
|
@ -7,7 +7,15 @@
|
||||||
<x-form.field name="first_name" label_text="First Name" type="text" value="{{ $student->first_name }}"/>
|
<x-form.field name="first_name" label_text="First Name" type="text" value="{{ $student->first_name }}"/>
|
||||||
<x-form.field name="last_name" label_text="Last Name" type="text" value="{{ $student->last_name }}"/>
|
<x-form.field name="last_name" label_text="Last Name" type="text" value="{{ $student->last_name }}"/>
|
||||||
<x-form.field name="grade" label_text="Grade" type="number" class="mb-3" value="{{ $student->grade }}"/>
|
<x-form.field name="grade" label_text="Grade" type="number" class="mb-3" value="{{ $student->grade }}"/>
|
||||||
<x-form.footer submit-button-text="Save Changes" />
|
@if(auditionSetting('student_data_collect_shirt_size'))
|
||||||
|
<x-form.select name="shirt_size" colspan="2">
|
||||||
|
<x-slot:label>Shirt Size</x-slot:label>
|
||||||
|
@foreach($shirtSizes as $abbreviation => $name)
|
||||||
|
<option value="{{ $abbreviation }}" @if($abbreviation === ($student->optional_data['shirt_size'] ?? null) ) SELECTED @endif>{{ $name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</x-form.select>
|
||||||
|
@endif
|
||||||
|
<x-form.footer submit-button-text="Save Changes"/>
|
||||||
</x-form.form>
|
</x-form.form>
|
||||||
</x-card.card>
|
</x-card.card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@php use App\Models\Audition;use Illuminate\Support\Facades\Auth; @endphp
|
@php use App\Models\Audition;use App\Models\NominationEnsemble;use Illuminate\Support\Facades\Auth; @endphp
|
||||||
|
|
||||||
<x-layout.app>
|
<x-layout.app>
|
||||||
<x-slot:page_title>Students</x-slot:page_title>
|
<x-slot:page_title>Students</x-slot:page_title>
|
||||||
|
|
@ -8,19 +8,29 @@
|
||||||
<x-slot:section_name>Add Student</x-slot:section_name>
|
<x-slot:section_name>Add Student</x-slot:section_name>
|
||||||
<x-form.form method="POST" action="{{ route('students.store') }}" class="mb-6 mt-3">
|
<x-form.form method="POST" action="{{ route('students.store') }}" class="mb-6 mt-3">
|
||||||
<x-form.body-grid columns="8" class="max-w-full">
|
<x-form.body-grid columns="8" class="max-w-full">
|
||||||
<x-form.field name="first_name" label_text="First Name" colspan="3" autofocus />
|
<x-form.field name="first_name" label_text="First Name" colspan="3" autofocus/>
|
||||||
<x-form.field name="last_name" label_text="Last Name" colspan="3"/>
|
<x-form.field name="last_name" label_text="Last Name" colspan="3"/>
|
||||||
{{-- <x-form.field name="grade" label_text="Grade" colspan="1" />--}}
|
|
||||||
|
|
||||||
<x-form.select name="grade">
|
<x-form.select name="grade">
|
||||||
<x-slot:label>Grade</x-slot:label>
|
<x-slot:label>Grade</x-slot:label>
|
||||||
@php($n = Audition::min('minimum_grade'))
|
@php($n = min(Audition::min('minimum_grade'),NominationEnsemble::min('minimum_grade')))
|
||||||
@php($maxGrade = Audition::max('maximum_grade'))
|
@php($maxGrade = max(Audition::max('maximum_grade'), NominationEnsemble::max('maximum_grade')))
|
||||||
@while($n <= $maxGrade)
|
@while($n <= $maxGrade)
|
||||||
<option value="{{ $n }}">{{ $n }}</option>
|
<option value="{{ $n }}">{{ $n }}</option>
|
||||||
@php($n++);
|
@php($n++);
|
||||||
@endwhile
|
@endwhile
|
||||||
</x-form.select>
|
</x-form.select>
|
||||||
|
|
||||||
|
@if(auditionSetting('student_data_collect_shirt_size'))
|
||||||
|
<x-form.select name="shirt_size" colspan="2">
|
||||||
|
<x-slot:label>Shirt Size</x-slot:label>
|
||||||
|
@foreach($shirtSizes as $abbreviation => $name)
|
||||||
|
<option value="{{ $abbreviation }}">{{ $name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</x-form.select>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
<x-form.button class="mt-6">Save</x-form.button>
|
<x-form.button class="mt-6">Save</x-form.button>
|
||||||
</x-form.body-grid>
|
</x-form.body-grid>
|
||||||
</x-form.form>
|
</x-form.form>
|
||||||
|
|
@ -35,21 +45,28 @@
|
||||||
<tr>
|
<tr>
|
||||||
<x-table.th first>Name</x-table.th>
|
<x-table.th first>Name</x-table.th>
|
||||||
<x-table.th>Grade</x-table.th>
|
<x-table.th>Grade</x-table.th>
|
||||||
|
@if(auditionSetting('student_data_collect_shirt_size'))
|
||||||
|
<x-table.th>Shirt</x-table.th>
|
||||||
|
@endif
|
||||||
<x-table.th class="hidden md:table-cell">Entries</x-table.th>
|
<x-table.th class="hidden md:table-cell">Entries</x-table.th>
|
||||||
<x-table.th spacer_only>
|
<x-table.th spacer_only>
|
||||||
<span class="sr-only">Edit</span>
|
<span class="sr-only">Edit</span>
|
||||||
</x-table.th>
|
</x-table.th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<x-table.body >
|
<x-table.body>
|
||||||
@foreach($students as $student)
|
@foreach($students as $student)
|
||||||
<tr>
|
<tr>
|
||||||
<x-table.td first>{{ $student->full_name(true) }}</x-table.td>
|
<x-table.td first>{{ $student->full_name(true) }}</x-table.td>
|
||||||
<x-table.td>{{ $student->grade }}</x-table.td>
|
<x-table.td>{{ $student->grade }}</x-table.td>
|
||||||
|
@if(auditionSetting('student_data_collect_shirt_size'))
|
||||||
|
<x-table.th>{{ $student->optional_data['shirt_size'] ?? '' }}</x-table.th>
|
||||||
|
@endif
|
||||||
<x-table.td class="hidden md:table-cell">{{ $student->entries_count }}</x-table.td>
|
<x-table.td class="hidden md:table-cell">{{ $student->entries_count }}</x-table.td>
|
||||||
<x-table.td for_button>
|
<x-table.td for_button>
|
||||||
@if( $student->entries_count === 0)
|
@if( $student->entries_count === 0)
|
||||||
<form method="POST" action="{{ route('students.destroy',$student) }}" class="inline">
|
<form method="POST" action="{{ route('students.destroy',$student) }}"
|
||||||
|
class="inline">
|
||||||
@csrf
|
@csrf
|
||||||
@method('DELETE')
|
@method('DELETE')
|
||||||
<x-table.button
|
<x-table.button
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\NominationEnsembles\NominationAdminController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\NominationEnsembleController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\NominationEnsembleEntryController;
|
||||||
|
use App\Http\Controllers\NominationEnsembles\NominationSeatingController;
|
||||||
|
use App\Http\Middleware\CheckIfAdmin;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('nomination/admin/')->group(function () {
|
||||||
|
Route::prefix('ensemble/')->controller(NominationEnsembleController::class)->group(function () {
|
||||||
|
Route::get('/', 'index')->name('nomination.admin.ensemble.index');
|
||||||
|
Route::post('/', 'store')->name('nomination.admin.ensemble.store');
|
||||||
|
Route::patch('/{ensemble}', 'update')->name('nomination.admin.ensemble.update');
|
||||||
|
Route::delete('/{ensemble}', 'destroy')->name('nomination.admin.ensemble.destroy');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::prefix('nominations/')->controller(NominationAdminController::class)->group(function () {
|
||||||
|
Route::get('/', 'index')->name('nomination.admin.index');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::prefix('seating/')->controller(NominationSeatingController::class)->group(function () {
|
||||||
|
Route::get('/', 'index')->name('nomination.admin.seating.index');
|
||||||
|
Route::get('/{ensemble}', 'show')->name('nomination.admin.seating.show');
|
||||||
|
Route::post('/{ensemble}', 'seat')->name('nomination.admin.seating.seat');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware(['auth', 'verified'])->prefix('nominations/')->group(function () {
|
||||||
|
Route::controller(NominationEnsembleEntryController::class)->group(function () {
|
||||||
|
Route::get('/', 'index')->name('nomination.entry.index');
|
||||||
|
Route::post('/', 'store')->name('nomination.entry.store');
|
||||||
|
Route::delete('/{entry}', 'destroy')->name('nomination.entry.destroy');
|
||||||
|
Route::post('/move', 'move')->name('nomination.entry.move');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -10,6 +10,7 @@ require __DIR__.'/admin.php';
|
||||||
require __DIR__.'/judging.php';
|
require __DIR__.'/judging.php';
|
||||||
require __DIR__.'/tabulation.php';
|
require __DIR__.'/tabulation.php';
|
||||||
require __DIR__.'/user.php';
|
require __DIR__.'/user.php';
|
||||||
|
require __DIR__.'/nominationEnsemble.php';
|
||||||
|
|
||||||
Route::get('/test', [TestController::class, 'flashTest'])->middleware('auth', 'verified');
|
Route::get('/test', [TestController::class, 'flashTest'])->middleware('auth', 'verified');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue