Allow admin and tabulators to enter and modify prelim scores.

This commit is contained in:
Matt Young 2025-10-20 01:01:27 -05:00
parent 62a3694c03
commit 30cbaf69f8
4 changed files with 206 additions and 15 deletions

View File

@ -2,11 +2,13 @@
namespace App\Http\Controllers\Tabulation; namespace App\Http\Controllers\Tabulation;
use App\Actions\Tabulation\EnterPrelimScore;
use App\Actions\Tabulation\EnterScore; use App\Actions\Tabulation\EnterScore;
use App\Exceptions\AuditionAdminException; use App\Exceptions\AuditionAdminException;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Entry; use App\Models\Entry;
use App\Models\EntryTotalScore; use App\Models\EntryTotalScore;
use App\Models\PrelimScoreSheet;
use App\Models\ScoreSheet; use App\Models\ScoreSheet;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -67,8 +69,25 @@ class ScoreController extends Controller
'This entry is marked as a no-show. Entering a score will remove the no-show flag'); 'This entry is marked as a no-show. Entering a score will remove the no-show flag');
} }
if ($entry->audition->prelimDefinition) {
$existing_prelim_sheets = [];
$prelim_subscores = $entry->audition->prelimDefinition->scoringGuide->subscores->sortBy('display_order');
$prelim_judges = $entry->audition->prelimDefinition->room->judges;
foreach ($prelim_judges as $judge) {
$prelim_scoreSheet = PrelimScoreSheet::where('entry_id', $entry->id)->where('user_id', $judge->id)->first();
if ($prelim_scoreSheet) {
Session::flash('caution', 'Prelim scores exist for this entry. Now editing existing scores');
$existing_prelim_sheets[$judge->id] = $prelim_scoreSheet;
}
}
} else {
$prelim_subscores = null;
$prelim_judges = null;
$existing_prelim_sheets = null;
}
return view('tabulation.entry_score_sheet', return view('tabulation.entry_score_sheet',
compact('entry', 'judges', 'scoring_guide', 'subscores', 'existing_sheets')); compact('entry', 'judges', 'scoring_guide', 'subscores', 'existing_sheets', 'prelim_subscores', 'prelim_judges', 'existing_prelim_sheets'));
} }
public function saveEntryScoreSheet(Request $request, Entry $entry, EnterScore $scoreRecorder) public function saveEntryScoreSheet(Request $request, Entry $entry, EnterScore $scoreRecorder)
@ -85,7 +104,7 @@ class ScoreController extends Controller
* The array should have a key for each subscore and the value of the score submitted * The array should have a key for each subscore and the value of the score submitted
*/ */
foreach ($request->all() as $key => $value) { foreach ($request->all() as $key => $value) {
// We're not interested in submission values that don't ahve judge in the name // We're not interested in submission values that don't have judge in the name
if (! str_contains($key, 'judge')) { if (! str_contains($key, 'judge')) {
continue; continue;
} }
@ -114,6 +133,50 @@ class ScoreController extends Controller
return redirect()->route('scores.chooseEntry')->with('success', 'Scores saved'); return redirect()->route('scores.chooseEntry')->with('success', 'Scores saved');
} }
public function savePrelimEntryScoreSheet(Request $request, Entry $entry, EnterPrelimScore $scoreRecorder)
{
$publishedCheck = $this->checkIfPublished($entry);
if ($publishedCheck) {
return $publishedCheck;
}
/**
* Here we process the submission from the scoring form.
* We're expecting submitted data to include an array for each judge.
* Each array should be called judge+ the judges ID number
* The array should have a key for each subscore and the value of the score submitted
*/
foreach ($request->all() as $key => $value) {
// We're not interested in submission values that don't have judge in the name
if (! str_contains($key, 'judge')) {
continue;
}
// Extract the judge ID from the field name and load the user
$judge_id = str_replace('judge', '', $key);
$judge = User::find($judge_id);
// Check for existing scores, if so, tell EnterScores action that we're updating it, otherwise a new score
$existingScore = PrelimScoreSheet::where('entry_id', $entry->id)
->where('user_id', $judge->id)->first();
if ($existingScore === null) {
$existingScore = false;
}
try {
$scoreRecorder($judge, $entry, $value, $existingScore);
} catch (AuditionAdminException $e) {
return redirect()->route('scores.entryScoreSheet', ['entry_id' => $entry->id])
->with('error', $e->getMessage());
}
}
// Since we're entering a score, this apparently isn't a no show.
$entry->removeFlag('no_show');
return redirect()->route('scores.chooseEntry')->with('success', 'Prelim Scores Saved');
}
protected function checkIfPublished($entry) protected function checkIfPublished($entry)
{ {
// We're not going to enter scores if seats are published // We're not going to enter scores if seats are published

View File

@ -25,4 +25,10 @@ class PrelimScoreSheet extends Model
{ {
return $this->hasOne(Entry::class); return $this->hasOne(Entry::class);
} }
public function getSubscore($id)
{
return $this->subscores[$id]['score'] ?? false;
// this function is used at resources/views/tabulation/entry_score_sheet.blade.php
}
} }

View File

@ -3,7 +3,17 @@
<x-slot:page_title>Entry Score Sheet</x-slot:page_title> <x-slot:page_title>Entry Score Sheet</x-slot:page_title>
<x-card.card class="mx-auto max-w-7xl"> <x-card.card class="mx-auto max-w-7xl">
<x-card.heading> <x-card.heading>
{{ $entry->audition->name }} #{{ $entry->draw_number }} {{ $entry->audition->name }} #{{ $entry->draw_number }} FINALS SCORES
@if($entry->hasFlag('failed_prelim'))
<span
class="inline-flex items-center rounded-md bg-red-100 px-2 py-1 text-sm font-medium text-red-700 ">Failed Prelim</span>
@elseif($entry->hasFlag('passed_prelim'))
<span
class="inline-flex items-center rounded-md bg-green-100 px-2 py-1 text-sm font-medium text-green-700">Passed Prelim</span>
@elseif($entry->audition->prelimDefinition)
<span
class="inline-flex items-center rounded-md bg-yellow-100 px-2 py-1 text-sm font-medium text-yellow-800 ">Prelim Pending</span>
@endif
<x-slot:subheading>ID #{{ $entry->id }}</x-slot:subheading> <x-slot:subheading>ID #{{ $entry->id }}</x-slot:subheading>
<x-slot:right_side class="text-right"> <x-slot:right_side class="text-right">
<p>{{ $entry->student->full_name() }}</p> <p>{{ $entry->student->full_name() }}</p>
@ -11,7 +21,8 @@
</x-slot:right_side> </x-slot:right_side>
</x-card.heading> </x-card.heading>
<x-form.form method="POST" id='scoreForm' action="{{ route('scores.saveEntryScoreSheet',[ 'entry' => $entry->id]) }}"> <x-form.form method="POST" id='scoreForm'
action="{{ route('scores.saveEntryScoreSheet',[ 'entry' => $entry->id]) }}">
<x-table.table> <x-table.table>
<thead> <thead>
<tr> <tr>
@ -33,7 +44,7 @@
<x-table.body :sortable="false"> <x-table.body :sortable="false">
@foreach($judges as $judge) @foreach($judges as $judge)
@php($existingSheet = $existing_sheets[$judge->id] ?? null) @php($existingSheet = $existing_sheets[$judge->id] ?? null)
<tr > <tr>
<x-table.td>{{ $judge->full_name() }}</x-table.td> <x-table.td>{{ $judge->full_name() }}</x-table.td>
@foreach($subscores as $subscore) @foreach($subscores as $subscore)
<x-table.td> <x-table.td>
@ -48,11 +59,11 @@
value="{{ $existingSheet->getSubscore($subscore->id) }}" value="{{ $existingSheet->getSubscore($subscore->id) }}"
@endif @endif
required required
{{-- onchange="judge{{$judge->id}}sum()"--}} {{-- onchange="judge{{$judge->id}}sum()"--}}
> >
</x-table.td> </x-table.td>
@endforeach @endforeach
<x-table.td > <x-table.td>
<p id="judge{{ $judge->id }}total" class="pr-3"> <p id="judge{{ $judge->id }}total" class="pr-3">
0.000 0.000
</p> </p>
@ -67,10 +78,83 @@
</x-form.form> </x-form.form>
</x-card.card> </x-card.card>
@if($entry->audition->prelimDefinition)
<x-card.card class="mx-auto max-w-7xl mt-5">
<x-card.heading>
{{ $entry->audition->name }} #{{ $entry->draw_number }} PRELIM SCORES
<x-slot:subheading>ID #{{ $entry->id }}</x-slot:subheading>
<x-slot:right_side class="text-right">
<p>{{ $entry->student->full_name() }}</p>
<p>{{ $entry->student->school->name }}</p>
</x-slot:right_side>
</x-card.heading>
<x-form.form method="POST" id='scoreForm'
action="{{ route('scores.savePrelimEntryScoreSheet',[ 'entry' => $entry->id]) }}">
<x-table.table>
<thead>
<tr>
<x-table.th>Judges</x-table.th>
@foreach($prelim_subscores as $subscore)
<x-table.th>
<div class="">
<div>{{ $subscore->name }}</div>
<div class="text-xs text-gray-500">
<span>Max: {{ $subscore->max_score }}</span>
<span class="pl-2">Weight: {{ $subscore->weight }}</span>
</div>
</div>
</x-table.th>
@endforeach
<x-table.th>Total</x-table.th>
</tr>
</thead>
<x-table.body :sortable="false">
@foreach($prelim_judges as $judge)
@php($existingSheet = $existing_prelim_sheets[$judge->id] ?? null)
<tr>
<x-table.td>{{ $judge->full_name() }}</x-table.td>
@foreach($prelim_subscores as $subscore)
<x-table.td>
<input type="number"
max="{{ $subscore->max_score }}"
id="j{{ $judge->id }}ss{{ $subscore->id }}"
name="judge{{ $judge->id }}[{{ $subscore->id }}]"
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 judge{{$judge->id}}score"
@if($oldScores)
value="{{ $oldScores['judge'.$judge->id][$subscore->id] }}"
@elseif($existingSheet)
value="{{ $existingSheet->getSubscore($subscore->id) }}"
@endif
required
{{-- onchange="judge{{$judge->id}}sum()"--}}
>
</x-table.td>
@endforeach
<x-table.td>
<p id="judge{{ $judge->id }}total" class="pr-3">
0.000
</p>
</x-table.td>
</tr>
@endforeach
</x-table.body>
</x-table.table>
<x-form.footer class="mb-3">
<x-form.button>Save Scores</x-form.button>
</x-form.footer>
</x-form.form>
</x-card.card>
@endif
<script> <script>
function calculateTotal(judgeId) { function calculateTotal(judgeId) {
let total = 0; let total = 0;
let totalWeights = 0; let totalWeights = 0;
let maxPossible = 0;
let thisSubscore let thisSubscore
@foreach($subscores as $subscore) @foreach($subscores as $subscore)
thisSubscore = parseFloat(document.getElementById("j" + judgeId + "ss{{ $subscore->id }}").value) * {{ $subscore->weight }}; thisSubscore = parseFloat(document.getElementById("j" + judgeId + "ss{{ $subscore->id }}").value) * {{ $subscore->weight }};
@ -78,24 +162,61 @@
total += thisSubscore; total += thisSubscore;
} }
totalWeights += {{ $subscore->weight }}; totalWeights += {{ $subscore->weight }};
maxPossible += {{ $subscore->weight * $subscore->max_score }};
@endforeach @endforeach
let finalTotal = (total / totalWeights).toFixed(3); let finalTotal = ((total / maxPossible) * 100).toFixed(3);
document.getElementById('judge' + judgeId + 'total').innerHTML = finalTotal; document.getElementById('judge' + judgeId + 'total').innerHTML = finalTotal;
} }
@foreach($judges as $judge) @foreach($judges as $judge)
document.querySelectorAll('.judge' + {{ $judge->id }} + 'score').forEach(function(el) { document.querySelectorAll('.judge' + {{ $judge->id }} + 'score').forEach(function (el) {
el.addEventListener('change', function() { el.addEventListener('change', function () {
calculateTotal({{ $judge->id }}); calculateTotal({{ $judge->id }});
}); });
}); });
@endforeach @endforeach
window.onload = function() { window.onload = function () {
// Call the function for each judge // Call the function for each judge
@foreach($judges as $judge) @foreach($judges as $judge)
calculateTotal({{ $judge->id }}); calculateTotal({{ $judge->id }});
@if($entry->audition->prelimDefinition)
@foreach($prelim_judges as $judge)
calculatePrelimTotal({{ $judge->id }});
@endforeach
@endif
@endforeach @endforeach
}; };
</script> </script>
@if($entry->audition->prelimDefinition)
<script>
function calculatePrelimTotal(judgeId) {
let total = 0;
let totalWeights = 0;
let maxPossible = 0;
let thisSubscore
@foreach($prelim_subscores as $subscore)
thisSubscore = parseFloat(document.getElementById("j" + judgeId + "ss{{ $subscore->id }}").value) * {{ $subscore->weight }};
if (!isNaN(thisSubscore)) {
total += thisSubscore;
}
totalWeights += {{ $subscore->weight }};
maxPossible += {{ $subscore->weight * $subscore->max_score }};
@endforeach
let finalTotal = ((total / maxPossible) * 100).toFixed(3);
document.getElementById('judge' + judgeId + 'total').innerHTML = finalTotal;
}
@foreach($prelim_judges as $judge)
document.querySelectorAll('.judge' + {{ $judge->id }} + 'score').forEach(function (el) {
el.addEventListener('change', function () {
calculatePrelimTotal({{ $judge->id }});
});
});
@endforeach
</script>
@endif
</x-layout.app> </x-layout.app>

View File

@ -21,6 +21,7 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function ()
Route::get('/choose_entry', 'chooseEntry')->name('scores.chooseEntry'); Route::get('/choose_entry', 'chooseEntry')->name('scores.chooseEntry');
Route::get('/entry', 'entryScoreSheet')->name('scores.entryScoreSheet'); Route::get('/entry', 'entryScoreSheet')->name('scores.entryScoreSheet');
Route::post('/entry/{entry}', 'saveEntryScoreSheet')->name('scores.saveEntryScoreSheet'); Route::post('/entry/{entry}', 'saveEntryScoreSheet')->name('scores.saveEntryScoreSheet');
Route::post('/entry/prelim/{entry}', 'savePrelimEntryScoreSheet')->name('scores.savePrelimEntryScoreSheet');
Route::delete('/{score}', 'destroyScore')->name('scores.destroy'); Route::delete('/{score}', 'destroyScore')->name('scores.destroy');
}); });