From a8b8d09390c6dec11946ac917ad99582c66d03e8 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 31 Jan 2025 14:36:29 -0600 Subject: [PATCH 01/25] Add audition setting to optionally collect student shirt sizes --- .../Controllers/Admin/AuditionSettings.php | 3 ++ ...dd_collect_student_tshirt_size_setting.php | 33 +++++++++++++++++++ .../views/admin/audition-settings.blade.php | 12 +++++++ 3 files changed, 48 insertions(+) create mode 100644 database/migrations/2025_01_31_202548_add_collect_student_tshirt_size_setting.php diff --git a/app/Http/Controllers/Admin/AuditionSettings.php b/app/Http/Controllers/Admin/AuditionSettings.php index 07fecd3..bf6eaca 100644 --- a/app/Http/Controllers/Admin/AuditionSettings.php +++ b/app/Http/Controllers/Admin/AuditionSettings.php @@ -43,6 +43,9 @@ class AuditionSettings extends Controller // Enable Invoicing Switch $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 $validData['late_fee'] = $validData['late_fee'] * 100; $validData['school_fee'] = $validData['school_fee'] * 100; diff --git a/database/migrations/2025_01_31_202548_add_collect_student_tshirt_size_setting.php b/database/migrations/2025_01_31_202548_add_collect_student_tshirt_size_setting.php new file mode 100644 index 0000000..14bc1a9 --- /dev/null +++ b/database/migrations/2025_01_31_202548_add_collect_student_tshirt_size_setting.php @@ -0,0 +1,33 @@ +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 + { + // + } +}; diff --git a/resources/views/admin/audition-settings.blade.php b/resources/views/admin/audition-settings.blade.php index d5284c9..9394e84 100644 --- a/resources/views/admin/audition-settings.blade.php +++ b/resources/views/admin/audition-settings.blade.php @@ -22,6 +22,18 @@ + + Optional Student Data + +
+ + Collect Student Shirt Size +
+
+
+ Scoring Settings From 23f71f530571a146f66e76cffa0f6a8a1c6a39a2 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 31 Jan 2025 15:16:59 -0600 Subject: [PATCH 02/25] Add column to students table to store additional data --- ...optional_data_column_to_students_table.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 database/migrations/2025_01_31_204732_add_optional_data_column_to_students_table.php diff --git a/database/migrations/2025_01_31_204732_add_optional_data_column_to_students_table.php b/database/migrations/2025_01_31_204732_add_optional_data_column_to_students_table.php new file mode 100644 index 0000000..f003abb --- /dev/null +++ b/database/migrations/2025_01_31_204732_add_optional_data_column_to_students_table.php @@ -0,0 +1,28 @@ +json('optional_data')->nullable()->after('grade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('students', function (Blueprint $table) { + $table->dropColumn('optional_data'); + }); + } +}; From bb295d8c620504063bb214049e6694ac1438b693 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 1 Feb 2025 10:41:14 -0600 Subject: [PATCH 03/25] Set shirt size when user adds a student. --- app/Http/Controllers/StudentController.php | 4 ++++ app/Models/Student.php | 7 ++++++ resources/views/students/index.blade.php | 26 +++++++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/StudentController.php b/app/Http/Controllers/StudentController.php index 1b24434..32ff1e9 100644 --- a/app/Http/Controllers/StudentController.php +++ b/app/Http/Controllers/StudentController.php @@ -51,6 +51,7 @@ class StudentController extends Controller new UniqueFullNameAtSchool(request('first_name'), request('last_name'), Auth::user()->school_id), ], 'grade' => ['required', 'integer'], + 'shirt_size' => ['nullable'], ]); $student = Student::create([ @@ -59,6 +60,9 @@ class StudentController extends Controller 'grade' => request('grade'), '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().'
Grade: '.$student->grade.'
School: '.$student->school->name; AuditLogEntry::create([ 'user' => auth()->user()->email, diff --git a/app/Models/Student.php b/app/Models/Student.php index dece4dd..67ae150 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -14,6 +14,13 @@ class Student extends Model protected $guarded = []; + protected function casts(): array + { + return [ + 'optional_data' => 'array', + ]; + } + public function school(): BelongsTo { return $this->belongsTo(School::class); diff --git a/resources/views/students/index.blade.php b/resources/views/students/index.blade.php index 090f5f4..a2b1968 100644 --- a/resources/views/students/index.blade.php +++ b/resources/views/students/index.blade.php @@ -10,7 +10,6 @@ - {{-- --}} Grade @@ -21,6 +20,25 @@ @php($n++); @endwhile + + @if(auditionSetting('student_data_collect_shirt_size')) + + Shirt Size + + + + + + + + + + + + + @endif + + Save @@ -35,6 +53,9 @@ Name Grade + @if(auditionSetting('student_data_collect_shirt_size')) + Shirt + @endif Edit @@ -46,6 +67,9 @@ {{ $student->full_name(true) }} {{ $student->grade }} + @if(auditionSetting('student_data_collect_shirt_size')) + sss + @endif @if( $student->entries_count === 0) From a15cadc55103d98e76704569cf3fe92584c72ab2 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 1 Feb 2025 11:50:30 -0600 Subject: [PATCH 04/25] Editing shirt size by users working. --- app/Http/Controllers/StudentController.php | 29 +++++++++++++++++++--- app/Models/Student.php | 14 +++++++++++ resources/views/students/edit.blade.php | 10 +++++++- resources/views/students/index.blade.php | 16 +++--------- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/app/Http/Controllers/StudentController.php b/app/Http/Controllers/StudentController.php index 32ff1e9..52628ae 100644 --- a/app/Http/Controllers/StudentController.php +++ b/app/Http/Controllers/StudentController.php @@ -25,7 +25,10 @@ class StudentController extends Controller $students = Auth::user()->students()->withCount('entries')->get(); $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,7 +54,14 @@ class StudentController extends Controller new UniqueFullNameAtSchool(request('first_name'), request('last_name'), Auth::user()->school_id), ], 'grade' => ['required', 'integer'], - 'shirt_size' => ['nullable'], + 'shirt_size' => [ + 'nullable', + function ($attribute, $value, $fail) { + if (! array_key_exists($value, Student::$shirtSizes)) { + $fail("The selected $attribute is invalid."); + } + }, + ], ]); $student = Student::create([ @@ -94,7 +104,9 @@ class StudentController extends Controller abort(403); } - return view('students.edit', ['student' => $student]); + $shirtSizes = Student::$shirtSizes; + + return view('students.edit', ['student' => $student, 'shirtSizes' => $shirtSizes]); } /** @@ -110,6 +122,14 @@ class StudentController extends Controller 'first_name' => ['required'], 'last_name' => ['required'], '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')) @@ -126,6 +146,9 @@ class StudentController extends Controller 'last_name' => request('last_name'), 'grade' => request('grade'), ]); + + $student->update(['optional_data->shirt_size' => $request['shirt_size']]); + $message = 'Updated student #'.$student->id.'
Name: '.$student->full_name().'
Grade: '.$student->grade.'
School: '.$student->school->name; AuditLogEntry::create([ 'user' => auth()->user()->email, diff --git a/app/Models/Student.php b/app/Models/Student.php index 67ae150..d1e1777 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -12,6 +12,20 @@ class Student extends Model { 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 function casts(): array diff --git a/resources/views/students/edit.blade.php b/resources/views/students/edit.blade.php index 747982a..70a6b5a 100644 --- a/resources/views/students/edit.blade.php +++ b/resources/views/students/edit.blade.php @@ -7,7 +7,15 @@ - + @if(auditionSetting('student_data_collect_shirt_size')) + + Shirt Size + @foreach($shirtSizes as $abbreviation => $name) + + @endforeach + + @endif + diff --git a/resources/views/students/index.blade.php b/resources/views/students/index.blade.php index a2b1968..b4c577a 100644 --- a/resources/views/students/index.blade.php +++ b/resources/views/students/index.blade.php @@ -24,17 +24,9 @@ @if(auditionSetting('student_data_collect_shirt_size')) Shirt Size - - - - - - - - - - - + @foreach($shirtSizes as $abbreviation => $name) + + @endforeach @endif @@ -68,7 +60,7 @@ {{ $student->full_name(true) }} {{ $student->grade }} @if(auditionSetting('student_data_collect_shirt_size')) - sss + {{ $student->optional_data['shirt_size'] ?? '' }} @endif From d52a3a7f71d7bd63252368eb20313834da46b1c3 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 1 Feb 2025 13:38:45 -0600 Subject: [PATCH 05/25] Preliminary work on nomination ensemble model and interface. --- .../NominationEnsembleController.php | 20 ++++++++++++ app/Models/NominationEnsemble.php | 18 +++++++++++ ...4324_create_nomination_ensembles_table.php | 32 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php create mode 100644 app/Models/NominationEnsemble.php create mode 100644 database/migrations/2025_02_01_184324_create_nomination_ensembles_table.php diff --git a/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php b/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php new file mode 100644 index 0000000..9a818a6 --- /dev/null +++ b/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php @@ -0,0 +1,20 @@ + 'array', + ]; + } +} diff --git a/database/migrations/2025_02_01_184324_create_nomination_ensembles_table.php b/database/migrations/2025_02_01_184324_create_nomination_ensembles_table.php new file mode 100644 index 0000000..6f29f1b --- /dev/null +++ b/database/migrations/2025_02_01_184324_create_nomination_ensembles_table.php @@ -0,0 +1,32 @@ +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'); + } +}; From ff8f9afee0c4b6fe4d091ad3871e6f7512f072de Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 1 Feb 2025 14:07:17 -0600 Subject: [PATCH 06/25] Prelim work on nomination ensemble controller --- .../NominationEnsembleController.php | 6 ++- .../ScobdaNominationEnsembleController.php | 44 +++++++++++++++++++ app/Providers/AppServiceProvider.php | 4 +- .../scobda/index.blade.php | 4 ++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php create mode 100644 resources/views/nomination_ensembles/scobda/index.blade.php diff --git a/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php b/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php index 9a818a6..539d8f0 100644 --- a/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php +++ b/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php @@ -2,17 +2,19 @@ namespace App\Http\Controllers\NominationEnsembles; +use App\Models\NominationEnsemble; + interface NominationEnsembleController { public function index(); - public function show(); + public function show(NominationEnsemble $ensemble); public function create(); public function store(); - public function edit(); + public function edit(NominationEnsemble $ensemble); public function update(); diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php new file mode 100644 index 0000000..31ffbef --- /dev/null +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php @@ -0,0 +1,44 @@ +app->singleton(CreateEntry::class, CreateEntry::class); $this->app->singleton(UpdateEntry::class, UpdateEntry::class); $this->app->singleton(SetHeadDirector::class, SetHeadDirector::class); - + $this->app->singleton(NominationEnsembleController::class, ScobdaNominationEnsembleController::class); } /** diff --git a/resources/views/nomination_ensembles/scobda/index.blade.php b/resources/views/nomination_ensembles/scobda/index.blade.php new file mode 100644 index 0000000..11614a8 --- /dev/null +++ b/resources/views/nomination_ensembles/scobda/index.blade.php @@ -0,0 +1,4 @@ + + Nomination Ensembles + + From f7bb1547cc629c45659097809035649aa9099b7c Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 2 Feb 2025 01:07:20 -0600 Subject: [PATCH 07/25] Functional form to create nomination ensemble under scobda rules. --- .../ScobdaNominationEnsembleController.php | 33 ++++++++++++++++++- app/Providers/AppServiceProvider.php | 2 +- .../scobda/index.blade.php | 25 ++++++++++++++ routes/nominationEnsemble.php | 12 +++++++ routes/web.php | 1 + 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 routes/nominationEnsemble.php diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php index 31ffbef..599930f 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php @@ -5,6 +5,8 @@ namespace App\Http\Controllers\NominationEnsembles; use App\Http\Controllers\Controller; use App\Models\NominationEnsemble; +use function redirect; + class ScobdaNominationEnsembleController extends Controller implements NominationEnsembleController { public function index() @@ -24,7 +26,36 @@ class ScobdaNominationEnsembleController extends Controller implements Nominatio public function store() { - // TODO: Implement store() method. + //dd(request()->all()); + $validated = request()->validate([ + 'ensemble_name' => 'required', + '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; + $ensemble->data = $data; + $ensemble->save(); + + return redirect()->route('nomination.admin.ensemble.index')->with('success', 'Nomination Ensemble has been created.'); } public function edit(NominationEnsemble $ensemble) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 479e157..0819dc3 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -63,7 +63,7 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton(CreateEntry::class, CreateEntry::class); $this->app->singleton(UpdateEntry::class, UpdateEntry::class); $this->app->singleton(SetHeadDirector::class, SetHeadDirector::class); - $this->app->singleton(NominationEnsembleController::class, ScobdaNominationEnsembleController::class); + $this->app->bind(NominationEnsembleController::class, ScobdaNominationEnsembleController::class); } /** diff --git a/resources/views/nomination_ensembles/scobda/index.blade.php b/resources/views/nomination_ensembles/scobda/index.blade.php index 11614a8..3550819 100644 --- a/resources/views/nomination_ensembles/scobda/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/index.blade.php @@ -1,4 +1,29 @@ Nomination Ensembles + + + Add Nomination Ensemble + + + + + + + + + + Round + + + + + Instrument List (comma separated) + + + + + + + diff --git a/routes/nominationEnsemble.php b/routes/nominationEnsemble.php new file mode 100644 index 0000000..6c94980 --- /dev/null +++ b/routes/nominationEnsemble.php @@ -0,0 +1,12 @@ +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'); + }); +}); diff --git a/routes/web.php b/routes/web.php index de4abbe..8ca8d03 100644 --- a/routes/web.php +++ b/routes/web.php @@ -10,6 +10,7 @@ require __DIR__.'/admin.php'; require __DIR__.'/judging.php'; require __DIR__.'/tabulation.php'; require __DIR__.'/user.php'; +require __DIR__.'/nominationEnsemble.php'; Route::get('/test', [TestController::class, 'flashTest'])->middleware('auth', 'verified'); From f83ba6e806aa2b5619c2dc23f482257caef50d8d Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 2 Feb 2025 16:56:56 -0600 Subject: [PATCH 08/25] Admin functions to manage nomination ensembles working. --- .../NominationEnsembleController.php | 4 +- .../ScobdaNominationEnsembleController.php | 50 ++++++++++++++++--- ..._to_name_in_nomination_ensembles_table.php | 28 +++++++++++ .../views/components/icons/pencil.blade.php | 3 ++ .../scobda/index.blade.php | 44 ++++++++++++++++ routes/nominationEnsemble.php | 2 + 6 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 database/migrations/2025_02_02_225428_add_unique_constraint_to_name_in_nomination_ensembles_table.php create mode 100644 resources/views/components/icons/pencil.blade.php diff --git a/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php b/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php index 539d8f0..d9c86cd 100644 --- a/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php +++ b/app/Http/Controllers/NominationEnsembles/NominationEnsembleController.php @@ -16,7 +16,7 @@ interface NominationEnsembleController public function edit(NominationEnsemble $ensemble); - public function update(); + public function update(NominationEnsemble $ensemble); - public function destroy(); + public function destroy(NominationEnsemble $ensemble); } diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php index 599930f..ce83fd4 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\NominationEnsembles; use App\Http\Controllers\Controller; use App\Models\NominationEnsemble; +use Illuminate\Validation\Rule; use function redirect; @@ -11,7 +12,9 @@ class ScobdaNominationEnsembleController extends Controller implements Nominatio { public function index() { - return view('nomination_ensembles.scobda.index'); + $ensembles = NominationEnsemble::all(); + + return view('nomination_ensembles.scobda.index', compact('ensembles')); } public function show(NominationEnsemble $ensemble) @@ -28,7 +31,7 @@ class ScobdaNominationEnsembleController extends Controller implements Nominatio { //dd(request()->all()); $validated = request()->validate([ - 'ensemble_name' => 'required', + 'ensemble_name' => 'required|unique:nomination_ensembles,name', 'entry_deadline' => 'required|date', 'min_grade' => 'required|numeric|min:0', 'max_grade' => 'required|numeric|gte:min_grade', @@ -52,6 +55,7 @@ class ScobdaNominationEnsembleController extends Controller implements Nominatio $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(); @@ -63,13 +67,47 @@ class ScobdaNominationEnsembleController extends Controller implements Nominatio // TODO: Implement edit() method. } - public function update() + public function update(NominationEnsemble $ensemble) { - // TODO: Implement update() method. + $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() + public function destroy(NominationEnsemble $ensemble) { - // TODO: Implement destroy() method. + $ensemble->delete(); + + // TODO: Delete associated nomionations. + return redirect()->route('nomination.admin.ensemble.index')->with('success', 'Nomination Ensemble has been deleted.'); } } diff --git a/database/migrations/2025_02_02_225428_add_unique_constraint_to_name_in_nomination_ensembles_table.php b/database/migrations/2025_02_02_225428_add_unique_constraint_to_name_in_nomination_ensembles_table.php new file mode 100644 index 0000000..bd6fc7b --- /dev/null +++ b/database/migrations/2025_02_02_225428_add_unique_constraint_to_name_in_nomination_ensembles_table.php @@ -0,0 +1,28 @@ +unique('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('nomination_ensembles', function (Blueprint $table) { + $table->dropUnique(['name']); + }); + } +}; diff --git a/resources/views/components/icons/pencil.blade.php b/resources/views/components/icons/pencil.blade.php new file mode 100644 index 0000000..e5ca2e7 --- /dev/null +++ b/resources/views/components/icons/pencil.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/nomination_ensembles/scobda/index.blade.php b/resources/views/nomination_ensembles/scobda/index.blade.php index 3550819..cec3d9f 100644 --- a/resources/views/nomination_ensembles/scobda/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/index.blade.php @@ -24,6 +24,50 @@
+ + + Nomination Ensembles +
+ @foreach($ensembles as $ensemble) + + + {{ $ensemble->name }} + + + + Are you sure you want to delete this nomination ensemble? + + + + + @method('PATCH') + + + + + + + + + Round + + + + + Instrument List (comma separated) + {{ implode(', ',$ensemble->data['instruments']) }} + + + + + + @endforeach +
+
diff --git a/routes/nominationEnsemble.php b/routes/nominationEnsemble.php index 6c94980..5f2f8e9 100644 --- a/routes/nominationEnsemble.php +++ b/routes/nominationEnsemble.php @@ -8,5 +8,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('nomination 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'); }); }); From 8055de4778b7d6ff85ad92542d1b78a26f99a222 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 2 Feb 2025 17:13:29 -0600 Subject: [PATCH 09/25] Add setting to enable nomination ensembles and select a ruleset. --- .../Controllers/Admin/AuditionSettings.php | 5 ++- ...setting_to_enable_nomination_ensembles.php | 33 +++++++++++++++++++ .../views/admin/audition-settings.blade.php | 10 ++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2025_02_02_225836_add_setting_to_enable_nomination_ensembles.php diff --git a/app/Http/Controllers/Admin/AuditionSettings.php b/app/Http/Controllers/Admin/AuditionSettings.php index bf6eaca..b02a8be 100644 --- a/app/Http/Controllers/Admin/AuditionSettings.php +++ b/app/Http/Controllers/Admin/AuditionSettings.php @@ -24,7 +24,8 @@ class AuditionSettings extends Controller 'organizerName' => ['required'], 'organizerEmail' => ['required', 'email'], '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'], 'school_fee' => ['nullable', 'numeric', 'min:0'], 'payment_address' => ['required'], @@ -32,6 +33,8 @@ class AuditionSettings extends Controller 'payment_state' => ['required', 'max:2'], 'payment_zip' => ['required', 'min:5'], 'advanceTo' => ['nullable'], + 'nomination_ensemble_rules' => ['required', 'in:disabled,scobda'], + // Options should align with the boot method of NominationEnsembleServiceProvider ]); // Olympic Scoring Switch diff --git a/database/migrations/2025_02_02_225836_add_setting_to_enable_nomination_ensembles.php b/database/migrations/2025_02_02_225836_add_setting_to_enable_nomination_ensembles.php new file mode 100644 index 0000000..ef6583c --- /dev/null +++ b/database/migrations/2025_02_02_225836_add_setting_to_enable_nomination_ensembles.php @@ -0,0 +1,33 @@ +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 + { + + } +}; diff --git a/resources/views/admin/audition-settings.blade.php b/resources/views/admin/audition-settings.blade.php index 9394e84..70a434e 100644 --- a/resources/views/admin/audition-settings.blade.php +++ b/resources/views/admin/audition-settings.blade.php @@ -19,6 +19,16 @@ + + Nomination Ensemble Rules + {{-- Values should be one of the options in the boot method NominationEnsembleServiceProvider --}} + + + From 96a2add6625e4f52b360d915f9a537bb26473f21 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 2 Feb 2025 17:23:06 -0600 Subject: [PATCH 10/25] Initial setup work for making nominations under scobda rules. --- app/Models/NominationEnsembleEntry.php | 11 +++++++ ...eate_nomination_ensemble_entries_table.php | 32 +++++++++++++++++++ routes/nominationEnsemble.php | 4 +++ 3 files changed, 47 insertions(+) create mode 100644 app/Models/NominationEnsembleEntry.php create mode 100644 database/migrations/2025_02_02_231905_create_nomination_ensemble_entries_table.php diff --git a/app/Models/NominationEnsembleEntry.php b/app/Models/NominationEnsembleEntry.php new file mode 100644 index 0000000..1cbf2ce --- /dev/null +++ b/app/Models/NominationEnsembleEntry.php @@ -0,0 +1,11 @@ +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'); + } +}; diff --git a/routes/nominationEnsemble.php b/routes/nominationEnsemble.php index 5f2f8e9..055535e 100644 --- a/routes/nominationEnsemble.php +++ b/routes/nominationEnsemble.php @@ -12,3 +12,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('nomination Route::delete('/{ensemble}', 'destroy')->name('nomination.admin.ensemble.destroy'); }); }); + +Route::middleware(['auth', 'verified'])->prefix('nominations/')->group(function () { + +}); From 6e1c86b72855e45523a8284710c7cd20dff8ff7c Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 2 Feb 2025 20:15:33 -0600 Subject: [PATCH 11/25] Add menubar link to nomination ensemble setup. --- resources/views/components/layout/navbar/menus/setup.blade.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/views/components/layout/navbar/menus/setup.blade.php b/resources/views/components/layout/navbar/menus/setup.blade.php index 892a9d2..807811d 100644 --- a/resources/views/components/layout/navbar/menus/setup.blade.php +++ b/resources/views/components/layout/navbar/menus/setup.blade.php @@ -34,6 +34,9 @@ Print Cards Print Sign-In Sheets Print Room and Judge Assignments + @if(auditionSetting('nomination_ensemble_rules') !== 'disabled') + Nomination Ensemble Setup + @endif From 997a6cf8b134d3ccf0ff38a56bc18d69868d27fb Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 7 Feb 2025 12:30:18 -0600 Subject: [PATCH 12/25] Process for users to add nominations is working. --- .../NominationEnsembleEntryController.php | 22 +++ .../ScobdaNominationEnsembleController.php | 2 +- ...cobdaNominationEnsembleEntryController.php | 155 ++++++++++++++++++ app/Models/NominationEnsemble.php | 6 + app/Models/NominationEnsembleEntry.php | 20 +++ app/Models/School.php | 11 ++ app/Models/Student.php | 5 + app/Providers/AppServiceProvider.php | 5 + .../NominationEnsembleEntryFactory.php | 23 +++ .../layout/navbar/menus/my_audition.blade.php | 3 + .../{ => admin/ensembles}/index.blade.php | 0 .../scobda/entries/index.blade.php | 66 ++++++++ routes/nominationEnsemble.php | 6 +- 13 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 app/Http/Controllers/NominationEnsembles/NominationEnsembleEntryController.php create mode 100644 app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php create mode 100644 database/factories/NominationEnsembleEntryFactory.php rename resources/views/nomination_ensembles/scobda/{ => admin/ensembles}/index.blade.php (100%) create mode 100644 resources/views/nomination_ensembles/scobda/entries/index.blade.php diff --git a/app/Http/Controllers/NominationEnsembles/NominationEnsembleEntryController.php b/app/Http/Controllers/NominationEnsembles/NominationEnsembleEntryController.php new file mode 100644 index 0000000..4e77875 --- /dev/null +++ b/app/Http/Controllers/NominationEnsembles/NominationEnsembleEntryController.php @@ -0,0 +1,22 @@ +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')); + } + + public function show(NominationEnsembleEntry $ensemble) + { + // 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']); + + 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']); + $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 $ensemble) + { + // TODO: Implement edit() method. + } + + public function update(NominationEnsembleEntry $ensemble) + { + // TODO: Implement update() method. + } + + public function destroy(NominationEnsembleEntry $ensemble) + { + // TODO: Implement destroy() method. + } + + /** + * 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) + { + $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; + } +} diff --git a/app/Models/NominationEnsemble.php b/app/Models/NominationEnsemble.php index a26a5de..fec8ef7 100644 --- a/app/Models/NominationEnsemble.php +++ b/app/Models/NominationEnsemble.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; class NominationEnsemble extends Model { @@ -15,4 +16,9 @@ class NominationEnsemble extends Model 'data' => 'array', ]; } + + public function entries(): HasMany + { + return $this->hasMany(NominationEnsembleEntry::class); + } } diff --git a/app/Models/NominationEnsembleEntry.php b/app/Models/NominationEnsembleEntry.php index 1cbf2ce..f6ac68f 100644 --- a/app/Models/NominationEnsembleEntry.php +++ b/app/Models/NominationEnsembleEntry.php @@ -4,8 +4,28 @@ 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', + ]; + } + + protected function ensemble(): BelongsTo + { + return $this->belongsTo(NominationEnsemble::class); + } + + protected function student(): BelongsTo + { + return $this->belongsTo(Student::class); + } } diff --git a/app/Models/School.php b/app/Models/School.php index 93c886e..b359b63 100644 --- a/app/Models/School.php +++ b/app/Models/School.php @@ -51,4 +51,15 @@ class School extends Model 'id', 'id'); } + + public function nominations(): HasManyThrough + { + return $this->hasManyThrough( + NominationEnsembleEntry::class, + Student::class, + 'school_id', + 'student_id', + 'id', + 'id'); + } } diff --git a/app/Models/Student.php b/app/Models/Student.php index d1e1777..925fc29 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -35,6 +35,11 @@ class Student extends Model ]; } + public function nominations(): HasMany + { + return $this->hasMany(NominationEnsembleEntry::class); + } + public function school(): BelongsTo { return $this->belongsTo(School::class); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0819dc3..4f88a11 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -11,7 +11,9 @@ use App\Actions\Tabulation\CalculateScoreSheetTotal; use App\Actions\Tabulation\CalculateScoreSheetTotalDivideByTotalWeights; use App\Actions\Tabulation\CalculateScoreSheetTotalDivideByWeightedPossible; use App\Http\Controllers\NominationEnsembles\NominationEnsembleController; +use App\Http\Controllers\NominationEnsembles\NominationEnsembleEntryController; use App\Http\Controllers\NominationEnsembles\ScobdaNominationEnsembleController; +use App\Http\Controllers\NominationEnsembles\ScobdaNominationEnsembleEntryController; use App\Models\Audition; use App\Models\Entry; use App\Models\Room; @@ -63,7 +65,10 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton(CreateEntry::class, CreateEntry::class); $this->app->singleton(UpdateEntry::class, UpdateEntry::class); $this->app->singleton(SetHeadDirector::class, SetHeadDirector::class); + + // Nomination Ensemble $this->app->bind(NominationEnsembleController::class, ScobdaNominationEnsembleController::class); + $this->app->bind(NominationEnsembleEntryController::class, ScobdaNominationEnsembleEntryController::class); } /** diff --git a/database/factories/NominationEnsembleEntryFactory.php b/database/factories/NominationEnsembleEntryFactory.php new file mode 100644 index 0000000..1f04ba9 --- /dev/null +++ b/database/factories/NominationEnsembleEntryFactory.php @@ -0,0 +1,23 @@ + $this->faker->randomNumber(), + 'nomination_ensemble_id' => $this->faker->randomNumber(), + 'data' => $this->faker->words(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } +} diff --git a/resources/views/components/layout/navbar/menus/my_audition.blade.php b/resources/views/components/layout/navbar/menus/my_audition.blade.php index 00ea2ba..147b5d1 100644 --- a/resources/views/components/layout/navbar/menus/my_audition.blade.php +++ b/resources/views/components/layout/navbar/menus/my_audition.blade.php @@ -29,6 +29,9 @@ @if(Auth::user()->school_id) My Students My Entries + @if(auditionSetting('nomination_ensemble_rules') !== 'disabled') + My Nominations + @endif My Doubler Requests My School @if(auditionSetting('invoicing_enabled')) diff --git a/resources/views/nomination_ensembles/scobda/index.blade.php b/resources/views/nomination_ensembles/scobda/admin/ensembles/index.blade.php similarity index 100% rename from resources/views/nomination_ensembles/scobda/index.blade.php rename to resources/views/nomination_ensembles/scobda/admin/ensembles/index.blade.php diff --git a/resources/views/nomination_ensembles/scobda/entries/index.blade.php b/resources/views/nomination_ensembles/scobda/entries/index.blade.php new file mode 100644 index 0000000..f8fb115 --- /dev/null +++ b/resources/views/nomination_ensembles/scobda/entries/index.blade.php @@ -0,0 +1,66 @@ +@php($n=1) + + Nomination Entries + + + + @foreach($ensembles as $ensemble) + + {{ $ensemble->name }} + {{ $ensemble->data['max_nominations'] }} nominations accepted + + + + Rank + Student + Instrument + + + + @foreach($nominatedStudents[$ensemble->id] as $nomination) + + {{ $nomination->data['rank'] }} + {{ $nomination->student->full_name() }} + {{ $nomination->data['instrument'] }} + + @endforeach + + {{-- LINE TO ADD A NOMINATION--}} + @if($nominationsAvailable[$ensemble->id] && $availableStudents[$ensemble->id]->count() > 0) + + + + NEW + + + @foreach($availableStudents[$ensemble->id] as $student) + + @endforeach + + + + + + @foreach($availableInstruments[$ensemble->id] as $instrument) + + @endforeach + + + + + Add + + + + @endif + + + + + @endforeach + + + + diff --git a/routes/nominationEnsemble.php b/routes/nominationEnsemble.php index 055535e..3d23dae 100644 --- a/routes/nominationEnsemble.php +++ b/routes/nominationEnsemble.php @@ -1,6 +1,7 @@ prefix('nomination }); 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'); + }); }); From bbb0b68a8f35bc4043505d10de4c51867f213732 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 7 Feb 2025 13:54:12 -0600 Subject: [PATCH 13/25] Process for users to delete nominations is working. --- .../NominationEnsembleEntryController.php | 8 +++---- ...cobdaNominationEnsembleEntryController.php | 23 ++++++++++++++----- .../scobda/entries/index.blade.php | 10 ++++++++ routes/nominationEnsemble.php | 1 + 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/NominationEnsembles/NominationEnsembleEntryController.php b/app/Http/Controllers/NominationEnsembles/NominationEnsembleEntryController.php index 4e77875..5d47fa2 100644 --- a/app/Http/Controllers/NominationEnsembles/NominationEnsembleEntryController.php +++ b/app/Http/Controllers/NominationEnsembles/NominationEnsembleEntryController.php @@ -8,15 +8,15 @@ interface NominationEnsembleEntryController { public function index(); - public function show(NominationEnsembleEntry $ensemble); + public function show(NominationEnsembleEntry $entry); public function create(); public function store(); - public function edit(NominationEnsembleEntry $ensemble); + public function edit(NominationEnsembleEntry $entry); - public function update(NominationEnsembleEntry $ensemble); + public function update(NominationEnsembleEntry $entry); - public function destroy(NominationEnsembleEntry $ensemble); + public function destroy(NominationEnsembleEntry $entry); } diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php index 019b28b..33b6f50 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php @@ -50,10 +50,11 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi } return view('nomination_ensembles.scobda.entries.index', - compact('ensembles', 'availableStudents', 'availableInstruments', 'nominatedStudents', 'nominationsAvailable')); + compact('ensembles', 'availableStudents', 'availableInstruments', 'nominatedStudents', + 'nominationsAvailable')); } - public function show(NominationEnsembleEntry $ensemble) + public function show(NominationEnsembleEntry $entry) { // TODO: Implement show() method. } @@ -92,6 +93,10 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi } $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', @@ -111,19 +116,25 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi 'Nomination Recorded'); } - public function edit(NominationEnsembleEntry $ensemble) + public function edit(NominationEnsembleEntry $entry) { // TODO: Implement edit() method. } - public function update(NominationEnsembleEntry $ensemble) + public function update(NominationEnsembleEntry $entry) { // TODO: Implement update() method. } - public function destroy(NominationEnsembleEntry $ensemble) + public function destroy(NominationEnsembleEntry $entry) { - // TODO: Implement destroy() method. + 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'); + } + $entry->delete(); + + return redirect()->route('nomination.entry.index')->with('success', 'Nomination Deleted'); } /** diff --git a/resources/views/nomination_ensembles/scobda/entries/index.blade.php b/resources/views/nomination_ensembles/scobda/entries/index.blade.php index f8fb115..c486156 100644 --- a/resources/views/nomination_ensembles/scobda/entries/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/entries/index.blade.php @@ -17,11 +17,21 @@ +{{-- List existing nominations--}} @foreach($nominatedStudents[$ensemble->id] as $nomination) {{ $nomination->data['rank'] }} {{ $nomination->student->full_name() }} {{ $nomination->data['instrument'] }} + + + Confirm you wish to delete the nomination of {{ $nomination->student->full_name() }}
+ for the {{ $ensemble->name }} ensemble. +
+
@endforeach diff --git a/routes/nominationEnsemble.php b/routes/nominationEnsemble.php index 3d23dae..de35017 100644 --- a/routes/nominationEnsemble.php +++ b/routes/nominationEnsemble.php @@ -18,5 +18,6 @@ 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'); }); }); From a0b4ffe8553f14525e2480840e6d5dd24b925945 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 7 Feb 2025 14:47:44 -0600 Subject: [PATCH 14/25] Implement nomination reordering by users. --- ...cobdaNominationEnsembleEntryController.php | 26 ++++++++++++++++++- app/Models/NominationEnsembleEntry.php | 2 +- .../components/icons/down-arrow.blade.php | 3 +++ .../views/components/icons/up-arrow.blade.php | 3 +++ .../scobda/entries/index.blade.php | 14 +++++++++- routes/nominationEnsemble.php | 1 + 6 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 resources/views/components/icons/down-arrow.blade.php create mode 100644 resources/views/components/icons/up-arrow.blade.php diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php index 33b6f50..83aaf40 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php @@ -147,7 +147,7 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi * * @var returnType = next|nominations */ - private function collapseNominations(School $school, NominationEnsemble $ensemble, $returnType) + private function collapseNominations(School $school, NominationEnsemble $ensemble, $returnType = 'next') { $nominations = $school->nominations()->get()->where('nomination_ensemble_id', $ensemble->id)->sortBy('data.rank'); @@ -163,4 +163,28 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi return $nominations; } + + public function move() + { + + // TODO: Verify the student being moved is from the users school + $validData = request()->validate([ + 'direction' => 'required|in:up,down', + 'nominationId' => 'required|exists:App\Models\NominationEnsembleEntry,id', + ]); + $direction = $validData['direction']; + $nomination = NominationEnsembleEntry::findOrFail($validData['nominationId']); + $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'); + + } } diff --git a/app/Models/NominationEnsembleEntry.php b/app/Models/NominationEnsembleEntry.php index f6ac68f..a5ba469 100644 --- a/app/Models/NominationEnsembleEntry.php +++ b/app/Models/NominationEnsembleEntry.php @@ -21,7 +21,7 @@ class NominationEnsembleEntry extends Model protected function ensemble(): BelongsTo { - return $this->belongsTo(NominationEnsemble::class); + return $this->belongsTo(NominationEnsemble::class, 'nomination_ensemble_id'); } protected function student(): BelongsTo diff --git a/resources/views/components/icons/down-arrow.blade.php b/resources/views/components/icons/down-arrow.blade.php new file mode 100644 index 0000000..820107b --- /dev/null +++ b/resources/views/components/icons/down-arrow.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/components/icons/up-arrow.blade.php b/resources/views/components/icons/up-arrow.blade.php new file mode 100644 index 0000000..7867615 --- /dev/null +++ b/resources/views/components/icons/up-arrow.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/nomination_ensembles/scobda/entries/index.blade.php b/resources/views/nomination_ensembles/scobda/entries/index.blade.php index c486156..ad9020b 100644 --- a/resources/views/nomination_ensembles/scobda/entries/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/entries/index.blade.php @@ -23,7 +23,7 @@ {{ $nomination->data['rank'] }} {{ $nomination->student->full_name() }} {{ $nomination->data['instrument'] }} - + student->full_name() }}
for the {{ $ensemble->name }} ensemble.
+
+ @csrf + + + +
+
+ @csrf + + + +
@endforeach diff --git a/routes/nominationEnsemble.php b/routes/nominationEnsemble.php index de35017..66a1acf 100644 --- a/routes/nominationEnsemble.php +++ b/routes/nominationEnsemble.php @@ -19,5 +19,6 @@ Route::middleware(['auth', 'verified'])->prefix('nominations/')->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'); }); }); From 78e07c94d836e8fbd1165cbb24d015074b47a7af Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 7 Feb 2025 15:25:06 -0600 Subject: [PATCH 15/25] Stubs for nomination admin page setup. --- .../NominationAdminController.php | 22 ++++++++++ .../ScobdaNominationAdminController.php | 44 +++++++++++++++++++ app/Providers/AppServiceProvider.php | 3 ++ .../layout/navbar/menus/admin.blade.php | 3 ++ .../scobda/admin/index.blade.php | 4 ++ routes/nominationEnsemble.php | 5 +++ 6 files changed, 81 insertions(+) create mode 100644 app/Http/Controllers/NominationEnsembles/NominationAdminController.php create mode 100644 app/Http/Controllers/NominationEnsembles/ScobdaNominationAdminController.php create mode 100644 resources/views/nomination_ensembles/scobda/admin/index.blade.php diff --git a/app/Http/Controllers/NominationEnsembles/NominationAdminController.php b/app/Http/Controllers/NominationEnsembles/NominationAdminController.php new file mode 100644 index 0000000..a1db51c --- /dev/null +++ b/app/Http/Controllers/NominationEnsembles/NominationAdminController.php @@ -0,0 +1,22 @@ +app->bind(NominationEnsembleController::class, ScobdaNominationEnsembleController::class); $this->app->bind(NominationEnsembleEntryController::class, ScobdaNominationEnsembleEntryController::class); + $this->app->bind(NominationAdminController::class, ScobdaNominationAdminController::class); } /** diff --git a/resources/views/components/layout/navbar/menus/admin.blade.php b/resources/views/components/layout/navbar/menus/admin.blade.php index b9cff60..cfae63a 100644 --- a/resources/views/components/layout/navbar/menus/admin.blade.php +++ b/resources/views/components/layout/navbar/menus/admin.blade.php @@ -25,6 +25,9 @@ Schools Students Entries + @if(auditionSetting('nomination_ensemble_rules') !== 'disabled') + Nominations + @endif View Logs Export Results Export Entries diff --git a/resources/views/nomination_ensembles/scobda/admin/index.blade.php b/resources/views/nomination_ensembles/scobda/admin/index.blade.php new file mode 100644 index 0000000..5d8d658 --- /dev/null +++ b/resources/views/nomination_ensembles/scobda/admin/index.blade.php @@ -0,0 +1,4 @@ + + Nomination Administration + + diff --git a/routes/nominationEnsemble.php b/routes/nominationEnsemble.php index 66a1acf..376669a 100644 --- a/routes/nominationEnsemble.php +++ b/routes/nominationEnsemble.php @@ -1,5 +1,6 @@ prefix('nomination 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::middleware(['auth', 'verified'])->prefix('nominations/')->group(function () { From 7da191aa82c552f669b026f495435d7e6b12f89f Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 8 Feb 2025 01:31:35 -0600 Subject: [PATCH 16/25] List nominations on admin page. --- .../ScobdaNominationAdminController.php | 4 +- app/Models/NominationEnsembleEntry.php | 4 +- .../scobda/admin/index.blade.php | 80 +++++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationAdminController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationAdminController.php index 337e89e..99196f7 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationAdminController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationAdminController.php @@ -9,7 +9,9 @@ class ScobdaNominationAdminController extends Controller implements NominationAd { public function index() { - return view('nomination_ensembles.scobda.admin.index'); + $nominations = NominationEnsembleEntry::with('student')->with('ensemble')->get(); + + return view('nomination_ensembles.scobda.admin.index', compact('nominations')); } public function show(NominationEnsembleEntry $entry) diff --git a/app/Models/NominationEnsembleEntry.php b/app/Models/NominationEnsembleEntry.php index a5ba469..c09fbdd 100644 --- a/app/Models/NominationEnsembleEntry.php +++ b/app/Models/NominationEnsembleEntry.php @@ -19,12 +19,12 @@ class NominationEnsembleEntry extends Model ]; } - protected function ensemble(): BelongsTo + public function ensemble(): BelongsTo { return $this->belongsTo(NominationEnsemble::class, 'nomination_ensemble_id'); } - protected function student(): BelongsTo + public function student(): BelongsTo { return $this->belongsTo(Student::class); } diff --git a/resources/views/nomination_ensembles/scobda/admin/index.blade.php b/resources/views/nomination_ensembles/scobda/admin/index.blade.php index 5d8d658..1d5f461 100644 --- a/resources/views/nomination_ensembles/scobda/admin/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/admin/index.blade.php @@ -1,4 +1,84 @@ + Nomination Administration + + Nominations + + + Name + School + Nomination + Rank + Instrument + + + + @foreach($nominations as $nomination) + + {{ $nomination->student->full_name('lf') }} + {{ $nomination->student->school->name }} + {{ $nomination->ensemble->name }} + {{ $nomination->data['rank'] }} + {{ $nomination->data['instrument'] }} + + @endforeach + + + From 5c7dacc3da55da913f09d9b4b2de48c2e03efc59 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 8 Feb 2025 15:04:48 -0600 Subject: [PATCH 17/25] Set up seating controller interface and SCOBDA implementation. --- .../NominationSeatingController.php | 8 ++++++++ .../ScobdaNominationSeatingController.php | 13 +++++++++++++ app/Providers/AppServiceProvider.php | 3 +++ 3 files changed, 24 insertions(+) create mode 100644 app/Http/Controllers/NominationEnsembles/NominationSeatingController.php create mode 100644 app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php diff --git a/app/Http/Controllers/NominationEnsembles/NominationSeatingController.php b/app/Http/Controllers/NominationEnsembles/NominationSeatingController.php new file mode 100644 index 0000000..e4e31e7 --- /dev/null +++ b/app/Http/Controllers/NominationEnsembles/NominationSeatingController.php @@ -0,0 +1,8 @@ +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); } /** From ec6aa4aa5369e0b406b42181d07d580d92360325 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 9 Feb 2025 16:47:28 -0600 Subject: [PATCH 18/25] Work on seating function for SCOBDA nomination ensembles. --- .../NominationSeatingController.php | 6 +++ .../ScobdaNominationSeatingController.php | 48 +++++++++++++++++++ .../layout/navbar/menus/tabulation.blade.php | 3 ++ .../scobda/admin/seating/index.blade.php | 38 +++++++++++++++ routes/nominationEnsemble.php | 7 +++ 5 files changed, 102 insertions(+) create mode 100644 resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php diff --git a/app/Http/Controllers/NominationEnsembles/NominationSeatingController.php b/app/Http/Controllers/NominationEnsembles/NominationSeatingController.php index e4e31e7..e8d7e8f 100644 --- a/app/Http/Controllers/NominationEnsembles/NominationSeatingController.php +++ b/app/Http/Controllers/NominationEnsembles/NominationSeatingController.php @@ -2,7 +2,13 @@ namespace App\Http\Controllers\NominationEnsembles; +use App\Models\NominationEnsemble; + interface NominationSeatingController { public function index(); + + public function show(NominationEnsemble $ensemble); + + public function seat(NominationEnsemble $ensemble); } diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php index 1f34564..4cfd8a1 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php @@ -3,11 +3,59 @@ namespace App\Http\Controllers\NominationEnsembles; use App\Http\Controllers\Controller; +use App\Models\NominationEnsemble; +use App\Models\NominationEnsembleEntry; 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(); + + return view('nomination_ensembles.scobda.admin.seating.index', compact('ensembles', 'ensemble')); } + + public function seat(NominationEnsemble $ensemble) + { + $nominations = NominationEnsembleEntry::where('nomination_ensemble_id', + $ensemble->id)->orderByRaw('CAST(data->"$.rank" AS UNSIGNED)')->get(); + $rankGroupedNominations = $nominations->groupBy(function ($entry) { + return $entry->data['rank']; + }); + + $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; + } + } + + dd($acceptedNominations); + } } diff --git a/resources/views/components/layout/navbar/menus/tabulation.blade.php b/resources/views/components/layout/navbar/menus/tabulation.blade.php index bc0f361..ba33e98 100644 --- a/resources/views/components/layout/navbar/menus/tabulation.blade.php +++ b/resources/views/components/layout/navbar/menus/tabulation.blade.php @@ -33,6 +33,9 @@ @if(auditionSetting('advanceTo')) {{ auditionSetting('advanceTo') }} Status @endif + @if(auditionSetting('nomination_ensemble_rules') !== 'disabled') + Nomination Ensemble Seating + @endif diff --git a/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php b/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php new file mode 100644 index 0000000..fb2e0a5 --- /dev/null +++ b/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php @@ -0,0 +1,38 @@ + + Nomination Ensemble Seating + +
+ +
+ +
+ +
+ @if($ensemble) + + + {{ $ensemble->name }} + + + Seat Ensemble + + + + + + @endif +
+ +
+ +
diff --git a/routes/nominationEnsemble.php b/routes/nominationEnsemble.php index 376669a..c3e4dd6 100644 --- a/routes/nominationEnsemble.php +++ b/routes/nominationEnsemble.php @@ -3,6 +3,7 @@ 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; @@ -17,6 +18,12 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('nomination 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 () { From ca06563b1c8a76027dacf9d56cca5663c36dc582 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 9 Feb 2025 19:06:42 -0600 Subject: [PATCH 19/25] Seating nomination ensembles is working. --- .../ScobdaNominationSeatingController.php | 17 ++++++++++++++- .../scobda/admin/seating/index.blade.php | 21 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php index 4cfd8a1..f87bd87 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php @@ -6,6 +6,8 @@ use App\Http\Controllers\Controller; use App\Models\NominationEnsemble; use App\Models\NominationEnsembleEntry; +use function dd; + class ScobdaNominationSeatingController extends Controller implements NominationSeatingController { public function index() @@ -19,8 +21,15 @@ class ScobdaNominationSeatingController extends Controller implements Nomination 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')); + return view('nomination_ensembles.scobda.admin.seating.index', compact('ensembles', 'ensemble', 'acceptedNominations')); } @@ -56,6 +65,12 @@ class ScobdaNominationSeatingController extends Controller implements Nomination } } + foreach ($acceptedNominations as $nomination) { + $data = $nomination->data; + $data['accepted'] = true; + $nomination->update(['data' => $data]); + } + dd($acceptedNominations); } } diff --git a/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php b/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php index fb2e0a5..d0d1099 100644 --- a/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php @@ -23,12 +23,29 @@ {{ $ensemble->name }} - + Seat Ensemble - + + @foreach($ensemble->data['instruments'] as $instrument) + @php($seatOn = 1) + @continue(! $acceptedNominations->has($instrument)) + + {{ $instrument }} + + @foreach($acceptedNominations[$instrument] as $nom) + + {{ $seatOn }} + {{ $nom->student->full_name() }} + {{ $nom->student->school->name }} + + @php($seatOn++) + @endforeach + @endforeach + @endif From c4818876b2c4f0ac0de247858fc7378d101a7e7c Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 9 Feb 2025 19:39:24 -0600 Subject: [PATCH 20/25] Minimum and maximum grades on student creation and edit forms should include grades for nomination ensembles. --- app/Http/Controllers/Admin/StudentController.php | 11 +++++++---- resources/views/students/index.blade.php | 13 +++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Admin/StudentController.php b/app/Http/Controllers/Admin/StudentController.php index 9c0d9e3..0055604 100644 --- a/app/Http/Controllers/Admin/StudentController.php +++ b/app/Http/Controllers/Admin/StudentController.php @@ -7,6 +7,7 @@ use App\Models\Audition; use App\Models\AuditLogEntry; use App\Models\Entry; use App\Models\Event; +use App\Models\NominationEnsemble; use App\Models\School; use App\Models\Student; use Illuminate\Support\Facades\Auth; @@ -14,6 +15,8 @@ use Illuminate\Support\Facades\Auth; use function abort; use function auth; use function compact; +use function max; +use function min; use function request; use function to_route; use function view; @@ -54,8 +57,8 @@ class StudentController extends Controller if (! Auth::user()->is_admin) { abort(403); } - $minGrade = Audition::min('minimum_grade'); - $maxGrade = Audition::max('maximum_grade'); + $minGrade = min(Audition::min('minimum_grade'), NominationEnsemble::min('minimum_grade')); + $maxGrade = max(Audition::max('maximum_grade'), NominationEnsemble::max('maximum_grade')); $schools = School::orderBy('name')->get(); return view('admin.students.create', ['schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]); @@ -105,8 +108,8 @@ class StudentController extends Controller if (! Auth::user()->is_admin) { abort(403); } - $minGrade = Audition::min('minimum_grade'); - $maxGrade = Audition::max('maximum_grade'); + $minGrade = min(Audition::min('minimum_grade'), NominationEnsemble::min('minimum_grade')); + $maxGrade = max(Audition::max('maximum_grade'), NominationEnsemble::max('maximum_grade')); $schools = School::orderBy('name')->get(); $student->loadCount('entries'); $entries = $student->entries; diff --git a/resources/views/students/index.blade.php b/resources/views/students/index.blade.php index b4c577a..5b9bd5f 100644 --- a/resources/views/students/index.blade.php +++ b/resources/views/students/index.blade.php @@ -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 Students @@ -8,13 +8,13 @@ Add Student - + Grade - @php($n = Audition::min('minimum_grade')) - @php($maxGrade = Audition::max('maximum_grade')) + @php($n = min(Audition::min('minimum_grade'),NominationEnsemble::min('minimum_grade'))) + @php($maxGrade = max(Audition::max('maximum_grade'), NominationEnsemble::max('maximum_grade'))) @while($n <= $maxGrade) @php($n++); @@ -54,7 +54,7 @@ - + @foreach($students as $student) {{ $student->full_name(true) }} @@ -65,7 +65,8 @@ @if( $student->entries_count === 0) -
+ @csrf @method('DELETE') Date: Mon, 10 Feb 2025 22:12:48 -0600 Subject: [PATCH 21/25] Create seeder for ScobdaNominationEnsembles --- ...ScobdaNominationEnsembleAndEntrySeeder.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 database/seeders/ScobdaNominationEnsembleAndEntrySeeder.php diff --git a/database/seeders/ScobdaNominationEnsembleAndEntrySeeder.php b/database/seeders/ScobdaNominationEnsembleAndEntrySeeder.php new file mode 100644 index 0000000..d53668f --- /dev/null +++ b/database/seeders/ScobdaNominationEnsembleAndEntrySeeder.php @@ -0,0 +1,76 @@ +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++; + } + } + } +} From 66016fb2ecd423fdc52783a98fa4f024d2fa367b Mon Sep 17 00:00:00 2001 From: Matt Young Date: Mon, 10 Feb 2025 22:35:24 -0600 Subject: [PATCH 22/25] Update seating controller for SCOBDA nomination ensembles to correctly redirect. --- .../ScobdaNominationSeatingController.php | 9 +++++---- .../scobda/admin/seating/index.blade.php | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php index f87bd87..efa5be9 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php @@ -6,7 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\NominationEnsemble; use App\Models\NominationEnsembleEntry; -use function dd; +use function redirect; class ScobdaNominationSeatingController extends Controller implements NominationSeatingController { @@ -29,14 +29,15 @@ class ScobdaNominationSeatingController extends Controller implements Nomination return $item->data['instrument']; }); - return view('nomination_ensembles.scobda.admin.seating.index', compact('ensembles', 'ensemble', 'acceptedNominations')); + 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)')->get(); + $ensemble->id)->orderByRaw('CAST(data->"$.rank" AS UNSIGNED)')->inRandomOrder()->get(); $rankGroupedNominations = $nominations->groupBy(function ($entry) { return $entry->data['rank']; }); @@ -71,6 +72,6 @@ class ScobdaNominationSeatingController extends Controller implements Nomination $nomination->update(['data' => $data]); } - dd($acceptedNominations); + return redirect()->route('nomination.admin.seating.show', ['ensemble' => $ensemble])->with('Seating Complete'); } } diff --git a/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php b/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php index d0d1099..9d1acd9 100644 --- a/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php @@ -25,7 +25,7 @@ - Seat Ensemble + Reseat Ensemble @@ -33,14 +33,16 @@ @foreach($ensemble->data['instruments'] as $instrument) @php($seatOn = 1) @continue(! $acceptedNominations->has($instrument)) - - {{ $instrument }} + + {{ $instrument }} + Student Name + School (Nom Rank) @foreach($acceptedNominations[$instrument] as $nom) {{ $seatOn }} {{ $nom->student->full_name() }} - {{ $nom->student->school->name }} + {{ $nom->student->school->name }} ({{ $nom->data['rank'] }}) @php($seatOn++) @endforeach From e112e3be8989f16784108921f7083cf238d4d8bd Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 12 Feb 2025 14:53:59 -0600 Subject: [PATCH 23/25] Check deadlines on nominations for SCOBDA rules --- ...cobdaNominationEnsembleEntryController.php | 38 ++++++++++++- .../scobda/entries/index.blade.php | 57 +++++++++++-------- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php index 83aaf40..95fa5b0 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationEnsembleEntryController.php @@ -7,11 +7,16 @@ 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 = []; @@ -51,7 +56,7 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi return view('nomination_ensembles.scobda.entries.index', compact('ensembles', 'availableStudents', 'availableInstruments', 'nominatedStudents', - 'nominationsAvailable')); + 'nominationsAvailable', 'currentDate')); } public function show(NominationEnsembleEntry $entry) @@ -87,6 +92,13 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi $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'); @@ -132,6 +144,14 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi 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'); @@ -167,13 +187,27 @@ class ScobdaNominationEnsembleEntryController extends Controller implements Nomi public function move() { - // TODO: Verify the student being moved is from the users school $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; diff --git a/resources/views/nomination_ensembles/scobda/entries/index.blade.php b/resources/views/nomination_ensembles/scobda/entries/index.blade.php index ad9020b..e99734e 100644 --- a/resources/views/nomination_ensembles/scobda/entries/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/entries/index.blade.php @@ -1,4 +1,5 @@ @php($n=1) + Nomination Entries @@ -7,7 +8,8 @@ @foreach($ensembles as $ensemble) {{ $ensemble->name }} - {{ $ensemble->data['max_nominations'] }} nominations accepted + {{ $ensemble->data['max_nominations'] }} nominations accepted + @@ -17,38 +19,45 @@ -{{-- List existing nominations--}} + {{-- List existing nominations--}} @foreach($nominatedStudents[$ensemble->id] as $nomination) {{ $nomination->data['rank'] }} {{ $nomination->student->full_name() }} {{ $nomination->data['instrument'] }} - - - Confirm you wish to delete the nomination of {{ $nomination->student->full_name() }}
- for the {{ $ensemble->name }} ensemble. -
- - @csrf - - - - -
- @csrf - - - -
-
+ @if($currentDate <= $ensemble->entry_deadline) + + + Confirm you wish to delete the nomination + of {{ $nomination->student->full_name() }}
+ for the {{ $ensemble->name }} ensemble. +
+
+ @csrf + + + +
+
+ @csrf + + + +
+
+ @endif @endforeach {{-- LINE TO ADD A NOMINATION--}} - @if($nominationsAvailable[$ensemble->id] && $availableStudents[$ensemble->id]->count() > 0) + @if($currentDate <= $ensemble->entry_deadline && $nominationsAvailable[$ensemble->id] && $availableStudents[$ensemble->id]->count() > 0) From 8e70f97fb03ce3f4a1b046c4ab012bc67a3e75da Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 12 Feb 2025 14:59:36 -0600 Subject: [PATCH 24/25] Show deadline on user nominations page. --- .../views/nomination_ensembles/scobda/entries/index.blade.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/views/nomination_ensembles/scobda/entries/index.blade.php b/resources/views/nomination_ensembles/scobda/entries/index.blade.php index e99734e..0e7c6d6 100644 --- a/resources/views/nomination_ensembles/scobda/entries/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/entries/index.blade.php @@ -8,7 +8,9 @@ @foreach($ensembles as $ensemble) {{ $ensemble->name }} - {{ $ensemble->data['max_nominations'] }} nominations accepted + + {{ $ensemble->data['max_nominations'] }} nominations accepted
+ Entry Deadline {{ \Carbon\Carbon::parse($ensemble->entry_deadline)->format('M j, Y') }}
From 9a9d567c78f80d62387e23b4af2620b395876e38 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 12 Feb 2025 15:37:26 -0600 Subject: [PATCH 25/25] Show appropriate options on seating page for SCOBDA nomination ensembles --- .../ScobdaNominationSeatingController.php | 26 +++++++++++++++++++ .../scobda/admin/seating/index.blade.php | 22 +++++++++++----- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php b/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php index efa5be9..72ae571 100644 --- a/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php +++ b/app/Http/Controllers/NominationEnsembles/ScobdaNominationSeatingController.php @@ -42,6 +42,27 @@ class ScobdaNominationSeatingController extends Controller implements Nomination 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 @@ -72,6 +93,11 @@ class ScobdaNominationSeatingController extends Controller implements Nomination $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'); } } diff --git a/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php b/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php index 9d1acd9..4370a76 100644 --- a/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php +++ b/resources/views/nomination_ensembles/scobda/admin/seating/index.blade.php @@ -22,11 +22,20 @@ {{ $ensemble->name }} - - - Reseat Ensemble - + + @if($ensemble->data['seated'] ?? false) + + + Clear Seats + + @else + + + Seat Ensemble + + @endif @@ -42,7 +51,8 @@ {{ $seatOn }} {{ $nom->student->full_name() }} - {{ $nom->student->school->name }} ({{ $nom->data['rank'] }}) + {{ $nom->student->school->name }} ({{ $nom->data['rank'] }}) + @php($seatOn++) @endforeach