From 8125ef6a322f36daaa61436b5dfd6d8a3e6cabcf Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 26 Jun 2024 12:29:55 -0500 Subject: [PATCH 1/4] First steps on advancement. Some cleanup in supporting services --- .../Tabulation/AdvancementController.php | 35 +++++++++++++ app/Services/DoublerService.php | 15 ++++-- app/Services/EntryCacheService.php | 17 ++++++- app/Services/ScoreService.php | 4 +- app/Services/TabulationService.php | 11 ++-- .../layout/navbar/menus/tabulation.blade.php | 3 +- .../tabulation/advancement/ranking.blade.php | 23 +++++++++ .../advancement/results-table.blade.php | 22 ++++++++ .../tabulation/advancement/status.blade.php | 51 +++++++++++++++++++ routes/tabulation.php | 8 ++- 10 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 app/Http/Controllers/Tabulation/AdvancementController.php create mode 100644 resources/views/tabulation/advancement/ranking.blade.php create mode 100644 resources/views/tabulation/advancement/results-table.blade.php create mode 100644 resources/views/tabulation/advancement/status.blade.php diff --git a/app/Http/Controllers/Tabulation/AdvancementController.php b/app/Http/Controllers/Tabulation/AdvancementController.php new file mode 100644 index 0000000..4626872 --- /dev/null +++ b/app/Http/Controllers/Tabulation/AdvancementController.php @@ -0,0 +1,35 @@ +tabulationService = $tabulationService; + } + + public function status() + { + $auditions = $this->tabulationService->getAuditionsWithStatus('advancement'); + + return view('tabulation.advancement.status', compact('auditions')); + } + + public function ranking(Request $request, Audition $audition) + { + $entries = $this->tabulationService->auditionEntries($audition->id); + $entries = $entries->filter(function ($entry) { + return $entry->for_advancement; + }); + + return view('tabulation.advancement.ranking', compact('audition', 'entries')); + } +} diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index daffd3a..5a1f777 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -4,6 +4,7 @@ namespace App\Services; use App\Models\Entry; use App\Models\Student; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\Cache; class DoublerService @@ -32,13 +33,19 @@ class DoublerService public function getDoublers(): \Illuminate\Database\Eloquent\Collection { // TODO creating or destroying an entry should refresh the doubler cache - // TODO this currently counts total entries, only need to count seating_entries. Would be an edge case, but needs to be fixed. - return Cache::remember($this->doublersCacheKey, 60, function () { - return Student::withCount('entries') - ->with('entries') + // TODO needs to split by event so that a doubler may enter jazz and concert events for example + $doublers = Cache::remember($this->doublersCacheKey, 60, function () { + return Student::withCount(['entries' => function (Builder $query) { + $query->where('for_seating', true); + }]) + ->with(['entries' => function (Builder $query) { + $query->where('for_seating', true); + }]) ->havingRaw('entries_count > ?', [1]) ->get(); }); + + return $doublers; } public function refreshDoublerCache() diff --git a/app/Services/EntryCacheService.php b/app/Services/EntryCacheService.php index 5ee0e2c..d353d74 100644 --- a/app/Services/EntryCacheService.php +++ b/app/Services/EntryCacheService.php @@ -24,17 +24,30 @@ class EntryCacheService * * @return \Illuminate\Database\Eloquent\Collection */ - public function getEntriesForAudition($auditionId) + public function getEntriesForAudition($auditionId, $mode = 'seating') { // TODO this invokes a lot of lazy loading. Perhaps cache the data for all entries then draw from that for each audition $cacheKey = 'audition'.$auditionId.'entries'; - return Cache::remember($cacheKey, 3600, function () use ($auditionId) { + $entries = Cache::remember($cacheKey, 3600, function () use ($auditionId) { return Entry::where('audition_id', $auditionId) ->with('student.school') ->get() ->keyBy('id'); }); + + switch ($mode) { + case 'seating': + return $entries->filter(function ($entry) { + return $entry->for_seating; + }); + case 'advancement': + return $entries->filter(function ($entry) { + return $entry->for_advancement; + }); + default: + return $entries; + } } /** diff --git a/app/Services/ScoreService.php b/app/Services/ScoreService.php index ad3ec19..128f5f4 100644 --- a/app/Services/ScoreService.php +++ b/app/Services/ScoreService.php @@ -101,7 +101,7 @@ class ScoreService * * @return void */ - public function calculateScoresForAudition($auditionId) + public function calculateScoresForAudition($auditionId, $mode= 'seating') { static $alreadyChecked = []; // if $auditionId is in the array $alreadyChecked return @@ -111,7 +111,7 @@ class ScoreService $alreadyChecked[] = $auditionId; $audition = $this->auditionCache->getAudition($auditionId); $scoringGuideId = $audition->scoring_guide_id; - $entries = $this->entryCache->getEntriesForAudition($auditionId); + $entries = $this->entryCache->getEntriesForAudition($auditionId, $mode); $entries->load('scoreSheets'); // TODO Cache this somehow, it's expensive and repetitive on the seating page foreach ($entries as $entry) { diff --git a/app/Services/TabulationService.php b/app/Services/TabulationService.php index 7c8e3b6..822ff14 100644 --- a/app/Services/TabulationService.php +++ b/app/Services/TabulationService.php @@ -44,7 +44,7 @@ class TabulationService * * @return \Illuminate\Support\Collection|mixed */ - public function auditionEntries(int $auditionId) + public function auditionEntries(int $auditionId, $mode = 'seating') { static $cache = []; if (isset($cache[$auditionId])) { @@ -52,7 +52,7 @@ class TabulationService } $audition = $this->auditionCacheService->getAudition($auditionId); - $entries = $this->entryCacheService->getEntriesForAudition($auditionId); + $entries = $this->entryCacheService->getEntriesForAudition($auditionId, $mode); $this->scoreService->calculateScoresForAudition($auditionId); foreach ($entries as $entry) { @@ -117,6 +117,7 @@ class TabulationService case 'advancement': return $audition->advancement_entries_count - $audition->scored_entries_count; } + return $audition->entries_count - $audition->scored_entries_count; } @@ -132,9 +133,7 @@ class TabulationService return Cache::remember('auditionsWithStatus', 30, function () use ($mode) { // Retrieve auditions from the cache and load entry IDs - $auditions = $this->auditionCacheService->getAuditions(); - - + $auditions = $this->auditionCacheService->getAuditions($mode); // Iterate over the auditions and calculate the scored_entries_count foreach ($auditions as $audition) { $scored_entries_count = 0; @@ -168,7 +167,9 @@ class TabulationService $audition->scored_entries_count = $scored_entries_count; } + return $auditions; + }); } } diff --git a/resources/views/components/layout/navbar/menus/tabulation.blade.php b/resources/views/components/layout/navbar/menus/tabulation.blade.php index 9e1906f..10617d7 100644 --- a/resources/views/components/layout/navbar/menus/tabulation.blade.php +++ b/resources/views/components/layout/navbar/menus/tabulation.blade.php @@ -21,7 +21,8 @@
diff --git a/resources/views/tabulation/advancement/ranking.blade.php b/resources/views/tabulation/advancement/ranking.blade.php new file mode 100644 index 0000000..e6b1f65 --- /dev/null +++ b/resources/views/tabulation/advancement/ranking.blade.php @@ -0,0 +1,23 @@ + + {{ auditionSetting('advanceTo') }} Advancement - {{ $audition->name }} +
+
+
+ @include('tabulation.advancement.results-table') +
+
+{{-- @if($audition->hasFlag('seats_published'))--}} +{{-- @include('tabulation.auditionSeating-show-published-seats')--}} +{{-- @elseif(! $auditionComplete)--}} +{{-- @include('tabulation.auditionSeating-unable-to-seat-card')--}} +{{-- @else--}} +{{-- @include('tabulation.auditionSeating-fill-seats-form')--}} +{{-- @include('tabulation.auditionSeating-show-proposed-seats')--}} +{{-- @endif--}} + + +
+
+ + +
diff --git a/resources/views/tabulation/advancement/results-table.blade.php b/resources/views/tabulation/advancement/results-table.blade.php new file mode 100644 index 0000000..ca825be --- /dev/null +++ b/resources/views/tabulation/advancement/results-table.blade.php @@ -0,0 +1,22 @@ + + + + + Rank + ID + Draw # + Student Name + Total Score + All Scores? + + + + + @foreach($entries as $entry) + + {{ $entry->rank }} + + @endforeach + + + diff --git a/resources/views/tabulation/advancement/status.blade.php b/resources/views/tabulation/advancement/status.blade.php new file mode 100644 index 0000000..d2e269a --- /dev/null +++ b/resources/views/tabulation/advancement/status.blade.php @@ -0,0 +1,51 @@ + + {{ auditionSetting('advanceTo') }} Status + + + {{ auditionSetting('advanceTo') }} Advancement Status + + + + + Audition + Scoring Complete + Advancement Published + + + + @foreach($auditions as $audition) + @php + $percent = 100; + if($audition->advancement_entries_count > 0) { + $percent = round(($audition->scored_entries_count / $audition->advancement_entries_count) * 100); + } + @endphp + + + + +
+ {{ $audition->name }} + {{ $audition->scored_entries_count }} / {{ $audition->advancement_entries_count }} Scored +
+
+
+
+
+
+ + @if( $audition->scored_entries_count == $audition->advancement_entries_count) + + @endif + + + @if( $audition->hasFlag('advancement_published')) + + @endif + + + @endforeach +
+
+
+
diff --git a/routes/tabulation.php b/routes/tabulation.php index 82a9b58..f79826a 100644 --- a/routes/tabulation.php +++ b/routes/tabulation.php @@ -16,12 +16,18 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () // Generic Tabulation Routes Route::prefix('tabulation/')->controller(\App\Http\Controllers\Tabulation\TabulationController::class)->group(function () { - Route::get('/status', 'status'); + Route::get('/status', 'status')->name('tabulation.status'); Route::match(['get', 'post'], '/auditions/{audition}', 'auditionSeating')->name('tabulation.audition.seat'); Route::post('/auditions/{audition}/publish-seats', 'publishSeats')->name('tabulation.seat.publish'); Route::post('/auditions/{audition}/unpublish-seats', 'unpublishSeats')->name('tabulation.seat.unpublish'); }); + // Advancement Routes + Route::prefix('advancement/')->controller(\App\Http\Controllers\Tabulation\AdvancementController::class)->group(function () { + Route::get('/status', 'status')->name('advancement.status'); + Route::get('/{audition}', 'ranking')->name('advancement.ranking'); + }); + // Doubler decision routes Route::prefix('doubler-decision')->controller(DoublerDecisionController::class)->group(function () { Route::post('{entry}/accept', 'accept')->name('doubler.accept'); From f5db2eae787a2072f47d9a8a5c4c02c7f65fdffb Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 26 Jun 2024 15:58:54 -0500 Subject: [PATCH 2/4] Add ceckboxes to advancement page and form to autmoatcially check X boxes --- .../Tabulation/AdvancementController.php | 9 +++-- app/Services/TabulationService.php | 9 +++-- .../views/components/form/checkbox.blade.php | 10 +++-- .../tabulation/advancement/ranking.blade.php | 40 ++++++++++++++----- .../advancement/results-table.blade.php | 21 ++++++++++ 5 files changed, 67 insertions(+), 22 deletions(-) diff --git a/app/Http/Controllers/Tabulation/AdvancementController.php b/app/Http/Controllers/Tabulation/AdvancementController.php index 4626872..0ab0e21 100644 --- a/app/Http/Controllers/Tabulation/AdvancementController.php +++ b/app/Http/Controllers/Tabulation/AdvancementController.php @@ -25,11 +25,12 @@ class AdvancementController extends Controller public function ranking(Request $request, Audition $audition) { - $entries = $this->tabulationService->auditionEntries($audition->id); - $entries = $entries->filter(function ($entry) { - return $entry->for_advancement; + $entries = $this->tabulationService->auditionEntries($audition->id, 'advancement'); + + $scoringComplete = $entries->every(function ($entry) { + return $entry->scoring_complete; }); - return view('tabulation.advancement.ranking', compact('audition', 'entries')); + return view('tabulation.advancement.ranking', compact('audition', 'entries','scoringComplete')); } } diff --git a/app/Services/TabulationService.php b/app/Services/TabulationService.php index 822ff14..e8899f2 100644 --- a/app/Services/TabulationService.php +++ b/app/Services/TabulationService.php @@ -54,7 +54,7 @@ class TabulationService $audition = $this->auditionCacheService->getAudition($auditionId); $entries = $this->entryCacheService->getEntriesForAudition($auditionId, $mode); $this->scoreService->calculateScoresForAudition($auditionId); - + // TODO will need to pass a mode to the above function to only use subscores for hte appropriate mode foreach ($entries as $entry) { $entry->final_score_array = $this->scoreService->entryTotalScores($entry); $entry->scoring_complete = ($this->scoreService->entryScoreSheetCounts()[$entry->id] == $audition->judges_count); @@ -69,18 +69,20 @@ class TabulationService return 0; }); - //TODO verify this actually sorts by subscores correctly + + // Assign a rank to each entry. In the case of a declined seat by a doubler, indicate as so and do not increment rank $n = 1; /** @var Entry $entry */ foreach ($entries as $entry) { - if (! $entry->hasFlag('declined')) { + if (! $entry->hasFlag('declined') or $mode != 'seating') { $entry->rank = $n; $n++; } else { $entry->rank = $n.' - declined'; } } + $cache[$auditionId] = $entries->keyBy('id'); return $entries->keyBy('id'); @@ -167,7 +169,6 @@ class TabulationService $audition->scored_entries_count = $scored_entries_count; } - return $auditions; }); diff --git a/resources/views/components/form/checkbox.blade.php b/resources/views/components/form/checkbox.blade.php index ac46979..ea0af3a 100644 --- a/resources/views/components/form/checkbox.blade.php +++ b/resources/views/components/form/checkbox.blade.php @@ -1,4 +1,4 @@ -@props(['name','label','description' => '', 'checked' => false]) +@props(['name','label' => false,'description' => '', 'checked' => false])
+ {{ $attributes->merge(['class' => "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"]) }}>
- -

{{ $description }}

+ @if($label) + +

{{ $description }}

+ @endif @error($name)

{{ $message }}

@enderror diff --git a/resources/views/tabulation/advancement/ranking.blade.php b/resources/views/tabulation/advancement/ranking.blade.php index e6b1f65..e730d41 100644 --- a/resources/views/tabulation/advancement/ranking.blade.php +++ b/resources/views/tabulation/advancement/ranking.blade.php @@ -1,22 +1,42 @@ {{ auditionSetting('advanceTo') }} Advancement - {{ $audition->name }} -
-
+
@include('tabulation.advancement.results-table')
-{{-- @if($audition->hasFlag('seats_published'))--}} -{{-- @include('tabulation.auditionSeating-show-published-seats')--}} -{{-- @elseif(! $auditionComplete)--}} -{{-- @include('tabulation.auditionSeating-unable-to-seat-card')--}} -{{-- @else--}} -{{-- @include('tabulation.auditionSeating-fill-seats-form')--}} -{{-- @include('tabulation.auditionSeating-show-proposed-seats')--}} -{{-- @endif--}} + @if($scoringComplete) + + Pass Entries +
+ +
+
+ Mark +
+
+
+ @endif
+ +
diff --git a/resources/views/tabulation/advancement/results-table.blade.php b/resources/views/tabulation/advancement/results-table.blade.php index ca825be..4d602d8 100644 --- a/resources/views/tabulation/advancement/results-table.blade.php +++ b/resources/views/tabulation/advancement/results-table.blade.php @@ -8,6 +8,9 @@ Student Name Total Score All Scores? + @if($scoringComplete) + Pass? + @endif @@ -15,6 +18,24 @@ @foreach($entries as $entry) {{ $entry->rank }} + {{ $entry->id }} + {{ $entry->draw_number }} + + {{ $entry->student->full_name() }} + {{ $entry->student->school->name }} + + {{ number_format($entry->final_score_array[0] ?? 0,4) }} + + @if($entry->scoring_complete) + + @endif + + @if($scoringComplete) + + + + @endif + @endforeach From 27522e79033703a32a0b9ebc76582bcdaefaf837 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 26 Jun 2024 16:03:07 -0500 Subject: [PATCH 3/4] Add Pass Checked Entries button --- .../tabulation/advancement/ranking.blade.php | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/resources/views/tabulation/advancement/ranking.blade.php b/resources/views/tabulation/advancement/ranking.blade.php index e730d41..ad2f5a3 100644 --- a/resources/views/tabulation/advancement/ranking.blade.php +++ b/resources/views/tabulation/advancement/ranking.blade.php @@ -1,43 +1,45 @@ {{ auditionSetting('advanceTo') }} Advancement - {{ $audition->name }} -
-
- @include('tabulation.advancement.results-table') -
-
- @if($scoringComplete) + +
+
+ @include('tabulation.advancement.results-table') +
+
+ @if($scoringComplete) - - Pass Entries -
- + + Pass Entries +
+ -
-
- Mark +
+
+ Mark +
-
-
- @endif -
+
- -
- - + +
+
From 6e4e17f0a9602f1c340301a44f7b059aa86ea763 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 26 Jun 2024 17:16:39 -0500 Subject: [PATCH 4/4] Tabulation page for managing advancement is working as intended --- .../Tabulation/AdvancementController.php | 26 ++++++++++++++++++- .../tabulation/advancement/ranking.blade.php | 7 +++-- .../advancement/results-table.blade.php | 10 +++++-- routes/tabulation.php | 2 ++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Tabulation/AdvancementController.php b/app/Http/Controllers/Tabulation/AdvancementController.php index 0ab0e21..9581faa 100644 --- a/app/Http/Controllers/Tabulation/AdvancementController.php +++ b/app/Http/Controllers/Tabulation/AdvancementController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Tabulation; use App\Http\Controllers\Controller; use App\Models\Audition; +use App\Models\Entry; use App\Services\TabulationService; use Illuminate\Http\Request; @@ -31,6 +32,29 @@ class AdvancementController extends Controller return $entry->scoring_complete; }); - return view('tabulation.advancement.ranking', compact('audition', 'entries','scoringComplete')); + return view('tabulation.advancement.ranking', compact('audition', 'entries', 'scoringComplete')); + } + + public function setAuditionPassers(Request $request, Audition $audition) + { + $passingEntries = $request->input('pass'); + $passingEntries = array_keys($passingEntries); + $audition->addFlag('advancement_published'); + $entries = Entry::whereIn('id', $passingEntries)->get(); + foreach ($entries as $entry) { + $entry->addFlag('will_advance'); + } + + return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', 'Passers have been set successfully'); + } + + public function clearAuditionPassers(Request $request, Audition $audition) + { + $audition->removeFlag('advancement_published'); + foreach ($audition->entries as $entry) { + $entry->removeFlag('will_advance'); + } + + return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', 'Passers have been cleared successfully'); } } diff --git a/resources/views/tabulation/advancement/ranking.blade.php b/resources/views/tabulation/advancement/ranking.blade.php index ad2f5a3..948a549 100644 --- a/resources/views/tabulation/advancement/ranking.blade.php +++ b/resources/views/tabulation/advancement/ranking.blade.php @@ -1,12 +1,15 @@ {{ auditionSetting('advanceTo') }} Advancement - {{ $audition->name }} - +
@include('tabulation.advancement.results-table')
- @if($scoringComplete) + @if( $audition->hasFlag('advancement_published') ) + @method('DELETE') + Clear Advancement + @elseif($scoringComplete) Pass Entries diff --git a/resources/views/tabulation/advancement/results-table.blade.php b/resources/views/tabulation/advancement/results-table.blade.php index 4d602d8..5fcd732 100644 --- a/resources/views/tabulation/advancement/results-table.blade.php +++ b/resources/views/tabulation/advancement/results-table.blade.php @@ -30,9 +30,15 @@ @endif - @if($scoringComplete) + @if( $audition->hasFlag('advancement_published') ) - + @if($entry->hasFlag('will_advance')) + + @endif + + @elseif($scoringComplete) + + @endif diff --git a/routes/tabulation.php b/routes/tabulation.php index f79826a..b2523cf 100644 --- a/routes/tabulation.php +++ b/routes/tabulation.php @@ -26,6 +26,8 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () Route::prefix('advancement/')->controller(\App\Http\Controllers\Tabulation\AdvancementController::class)->group(function () { Route::get('/status', 'status')->name('advancement.status'); Route::get('/{audition}', 'ranking')->name('advancement.ranking'); + Route::post('/{audition}', 'setAuditionPassers')->name('advancement.setAuditionPassers'); + Route::delete('/{audition}', 'clearAuditionPassers')->name('advancement.clearAuditionPassers'); }); // Doubler decision routes