Bonus Score Admin Entry

#20 Implement bonus scores
Entry form for judges designed.
This commit is contained in:
Matt Young 2024-07-16 02:15:13 -05:00
parent d1cab82622
commit 83eb11e151
12 changed files with 181 additions and 9 deletions

View File

@ -75,7 +75,8 @@ class AllowForOlympicScoring implements CalculateEntryScore
} }
} }
// add the bonus points for a seating mode // add the bonus points for a seating mode
if ($mode === 'seating') { if ($mode === 'seating' && $sums) {
$sums[0] += $this->getBonusPoints($entry); $sums[0] += $this->getBonusPoints($entry);
} }

View File

@ -9,6 +9,7 @@ use App\Models\BonusScore;
use App\Models\Entry; use App\Models\Entry;
use App\Models\User; use App\Models\User;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\App;
class EnterBonusScore class EnterBonusScore
{ {
@ -18,9 +19,10 @@ class EnterBonusScore
public function __invoke(User $judge, Entry $entry, int $score): void public function __invoke(User $judge, Entry $entry, int $score): void
{ {
$getRelatedEntries = App::make(GetBonusScoreRelatedEntries::class);
$this->basicValidations($judge, $entry); $this->basicValidations($judge, $entry);
$this->validateJudgeValidity($judge, $entry, $score); $this->validateJudgeValidity($judge, $entry, $score);
$entries = $this->getRelatedEntries($entry); $entries = $getRelatedEntries($entry);
// Create the score for each related entry // Create the score for each related entry
foreach ($entries as $relatedEntry) { foreach ($entries as $relatedEntry) {

View File

@ -0,0 +1,29 @@
<?php
namespace App\Actions\Tabulation;
use App\Models\Entry;
use Illuminate\Database\Eloquent\Collection;
class GetBonusScoreRelatedEntries
{
public function __construct()
{
}
public function __invoke(Entry $entry): Collection
{
return $this->getRelatedEntries($entry);
}
public function getRelatedEntries(Entry $entry): Collection
{
$bonusScore = $entry->audition->bonusScore->first();
$relatedAuditions = $bonusScore->auditions;
// Get all entries that have a student_id equal to that of entry and an audition_id in the related auditions
return Entry::where('student_id', $entry->student_id)
->whereIn('audition_id', $relatedAuditions->pluck('id'))
->get();
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Tabulation;
use App\Actions\Tabulation\GetBonusScoreRelatedEntries;
use App\Http\Controllers\Controller;
use App\Models\BonusScore;
use App\Models\Entry;
use function request;
class BonusScoreController extends Controller
{
public function chooseEntry()
{
$method = 'GET';
$formRoute = 'bonus-scores.entryBonusScoreSheet';
$title = 'Enter Bonus Scores';
return view('tabulation.choose_entry', compact('method', 'formRoute', 'title'));
}
public function entryBonusScoreSheet(GetBonusScoreRelatedEntries $getRelatedEntries)
{
$validData = request()->validate([
'entry_id' => 'required|exists:entries,id',
]);
$entry = Entry::find($validData['entry_id']);
$bonusScoreDefinition = $entry->audition->bonusScore->first();
$assignedJudges = $bonusScoreDefinition->judges;
$relatedEntries = $getRelatedEntries($entry);
$existingScores = [];
foreach ($relatedEntries as $related) {
$existingScores[$related->id] = BonusScore::where('entry_id', $related->id)
->with('judge')
->with('entry')
->with('originallyScoredEntry')
->get();
}
return view('tabulation.bonus-score-sheet',
compact('entry', 'bonusScoreDefinition', 'assignedJudges', 'existingScores', 'relatedEntries'));
}
public function saveEntryBonusScoreSheet()
{
}
public function destroyBonusScore()
{
}
}

View File

@ -15,8 +15,9 @@ class EntryFlagController extends Controller
{ {
$method = 'GET'; $method = 'GET';
$formRoute = 'entry-flags.confirmNoShow'; $formRoute = 'entry-flags.confirmNoShow';
$title = 'No Show';
return view('tabulation.choose_entry', compact('method', 'formRoute')); return view('tabulation.choose_entry', compact('method', 'formRoute', 'title'));
} }
public function noShowConfirm(Request $request) public function noShowConfirm(Request $request)

View File

@ -14,8 +14,9 @@ class ScoreController extends Controller
{ {
$method = 'GET'; $method = 'GET';
$formRoute = 'scores.entryScoreSheet'; $formRoute = 'scores.entryScoreSheet';
$title = 'Enter Scores';
return view('tabulation.choose_entry', compact('method', 'formRoute')); return view('tabulation.choose_entry', compact('method', 'formRoute', 'title'));
} }
public function destroyScore(ScoreSheet $score) public function destroyScore(ScoreSheet $score)

View File

@ -7,10 +7,10 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Illuminate\Support\Facades\Cache;
class Entry extends Model class Entry extends Model
{ {
@ -55,6 +55,11 @@ class Entry extends Model
} }
public function bonusScores(): BelongsToMany
{
return $this->belongsToMany(BonusScore::class);
}
public function advancementVotes(): HasMany public function advancementVotes(): HasMany
{ {
return $this->hasMany(JudgeAdvancementVote::class); return $this->hasMany(JudgeAdvancementVote::class);

View File

@ -21,6 +21,7 @@
<div class="absolute left-1/2 z-10 mt-5 flex w-screen max-w-min -translate-x-1/2 px-4" x-show="open" x-cloak> <div class="absolute left-1/2 z-10 mt-5 flex w-screen max-w-min -translate-x-1/2 px-4" x-show="open" x-cloak>
<div class="w-56 shrink rounded-xl bg-white p-4 text-sm font-semibold leading-6 text-gray-900 shadow-lg ring-1 ring-gray-900/5"> <div class="w-56 shrink rounded-xl bg-white p-4 text-sm font-semibold leading-6 text-gray-900 shadow-lg ring-1 ring-gray-900/5">
<a href="{{ route('scores.chooseEntry') }}" class="block p-2 hover:text-indigo-600">Enter Scores</a> <a href="{{ route('scores.chooseEntry') }}" class="block p-2 hover:text-indigo-600">Enter Scores</a>
<a href="{{ route('bonus-scores.chooseEntry') }}" class="block p-2 hover:text-indigo-600">Enter Bonus Scores</a>
<a href="{{ route('entry-flags.noShowSelect') }}" class="block p-2 hover:text-indigo-600">Enter No-Shows</a> <a href="{{ route('entry-flags.noShowSelect') }}" class="block p-2 hover:text-indigo-600">Enter No-Shows</a>
<a href="{{ route('seating.status') }}" class="block p-2 hover:text-indigo-600">Audition Status</a> <a href="{{ route('seating.status') }}" class="block p-2 hover:text-indigo-600">Audition Status</a>
<a href="{{ route('advancement.status') }}" class="block p-2 hover:text-indigo-600">{{ auditionSetting('advanceTo') }} Status</a> <a href="{{ route('advancement.status') }}" class="block p-2 hover:text-indigo-600">{{ auditionSetting('advanceTo') }} Status</a>

View File

@ -11,7 +11,7 @@
<div> <div>
@if($with_title_area) @if($with_title_area)
<div class="mb-4 mt-4 sm:px 4 sm:flex sm:items-center"> <div class="mb-2 mt-4 sm:px 4 sm:flex sm:items-center">
<div class="sm:flex-auto sm:items-center"> <div class="sm:flex-auto sm:items-center">
@if($title)<h1 {{ $title->attributes->merge(['class' => 'text-base font-semibold leading-6 text-gray-900']) }}>{{ $title }}</h1>@endif @if($title)<h1 {{ $title->attributes->merge(['class' => 'text-base font-semibold leading-6 text-gray-900']) }}>{{ $title }}</h1>@endif
@if($subtitle)<p {{ $subtitle->attributes->merge(['class' => 'mt-2 text-sm text-gray-700']) }}>{{ $subtitle }}</p>@endif @if($subtitle)<p {{ $subtitle->attributes->merge(['class' => 'mt-2 text-sm text-gray-700']) }}>{{ $subtitle }}</p>@endif

View File

@ -0,0 +1,69 @@
@php use Illuminate\Support\Carbon; @endphp
<x-layout.app>
<x-slot:page_title>Enter {{ $bonusScoreDefinition->name }} Score</x-slot:page_title>
<x-card.card>
<x-card.heading>
{{ $entry->student->full_name() }}
<x-slot:subheading>{{ $entry->student->school->name }}</x-slot:subheading>
</x-card.heading>
</x-card.card>
<div class="grid grid-cols-3 gap-3 w-full mt-5">
<x-card.card class="col-span-2">
<x-card.heading>Existing Scores</x-card.heading>
<div class="px-6">
@foreach($relatedEntries as $related)
<x-table.table class="mb-8 ">
<x-slot:title class="border rounded-md pl-3 bg-gray-100">
{{ $related->audition->name }} #{{ $related->draw_number }}
</x-slot:title>
<x-slot:subtitle>
Entry ID: {{ $related->id }}
</x-slot:subtitle>
<thead>
<tr>
<x-table.th>Judge</x-table.th>
<x-table.th>Audition Scored</x-table.th>
<x-table.th>Score</x-table.th>
<x-table.th>Timestamp</x-table.th>
</tr>
</thead>
<x-table.body>
@foreach($existingScores[$related->id] as $score)
<tr>
<td>{{ $score->judge->full_name() }}</td>
<td>{{ $score->originallyScoredEntry->audition->name }}</td>
<td>{{ $score->score }}</td>
<td>{{ Carbon::create($score->created_at)->setTimezone('America/Chicago')->format('m/d/y H:i') }}</td>
</tr>
@endforeach
</x-table.body>
</x-table.table>
@endforeach
</div>
</x-card.card>
<x-card.card >
<x-card.heading>Enter Score</x-card.heading>
<div class="mx-5 border-b text-sm">
NOTE: Entering score will delete any existing scores for that entry by that judge
</div>
<x-form.form class="mt-3" method="POST" action="{{route('bonus-scores.saveEntryBonusScoreSheet', $entry)}}">
<x-form.select name="judge_id" class="mb-5">
<x-slot:label>Judge</x-slot:label>
@foreach($assignedJudges as $judge)
<option value="{{ $judge->id }}">{{ $judge->full_name() }}</option>
@endforeach
</x-form.select>
<x-form.select name="entry_id" class="mb-5">
<x-slot:label>Scored Audition</x-slot:label>
@foreach($relatedEntries as $related)
<option value="{{$related->id}}" {{$related->id == $entry->id ? 'selected':' '}}>{{ $related->audition->name }}</option>
@endforeach
</x-form.select>
<x-form.field label_text="Score" name="score" type="number" max="{{ $bonusScoreDefinition->max_score }}" />
<x-form.button class="mt-5">Enter Score</x-form.button>
</x-form.form>
</x-card.card>
</div>
</x-layout.app>

View File

@ -2,13 +2,14 @@
/** /**
* @var string $method Method for the select form * @var string $method Method for the select form
* @var string $formRoute Route for the form action. Should be a route name * @var string $formRoute Route for the form action. Should be a route name
* @var string $title Title of the page
*/ */
@endphp @endphp
<x-layout.app> <x-layout.app>
<x-slot:page_title>Choose Entry</x-slot:page_title> <x-slot:page_title>{{ $title }}</x-slot:page_title>
<x-card.card class="mx-auto max-w-sm"> <x-card.card class="mx-auto max-w-sm">
<x-card.heading>Choose Entry</x-card.heading> <x-card.heading>{{ $title }} - Choose Entry</x-card.heading>
<div class=""> <div class="">
<x-form.form method="{{ $method }}" action="{{ route($formRoute) }}" class="mb-4 mt-3" id="entry-select-form"> <x-form.form method="{{ $method }}" action="{{ route($formRoute) }}" class="mb-4 mt-3" id="entry-select-form">
<x-form.field name="entry_id" label_text="Entry ID"></x-form.field> <x-form.field name="entry_id" label_text="Entry ID"></x-form.field>

View File

@ -2,13 +2,13 @@
// Tabulation Routes // Tabulation Routes
use App\Http\Controllers\Tabulation\AdvancementController; use App\Http\Controllers\Tabulation\AdvancementController;
use App\Http\Controllers\Tabulation\BonusScoreController;
use App\Http\Controllers\Tabulation\DoublerDecisionController; use App\Http\Controllers\Tabulation\DoublerDecisionController;
use App\Http\Controllers\Tabulation\EntryFlagController; use App\Http\Controllers\Tabulation\EntryFlagController;
use App\Http\Controllers\Tabulation\ScoreController; use App\Http\Controllers\Tabulation\ScoreController;
use App\Http\Controllers\Tabulation\SeatAuditionFormController; use App\Http\Controllers\Tabulation\SeatAuditionFormController;
use App\Http\Controllers\Tabulation\SeatingPublicationController; use App\Http\Controllers\Tabulation\SeatingPublicationController;
use App\Http\Controllers\Tabulation\SeatingStatusController; use App\Http\Controllers\Tabulation\SeatingStatusController;
use App\Http\Controllers\Tabulation\TabulationController;
use App\Http\Middleware\CheckIfCanTab; use App\Http\Middleware\CheckIfCanTab;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -22,6 +22,14 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function ()
Route::delete('/{score}', 'destroyScore')->name('scores.destroy'); Route::delete('/{score}', 'destroyScore')->name('scores.destroy');
}); });
// Bonus Score Management
Route::prefix('bonus-scores/')->controller(BonusScoreController::class)->group(function () {
Route::get('/choose_entry', 'chooseEntry')->name('bonus-scores.chooseEntry');
Route::get('/entry', 'entryBonusScoreSheet')->name('bonus-scores.entryBonusScoreSheet');
Route::post('/entry/{entry}', 'saveEntryBonusScoreSheet')->name('bonus-scores.saveEntryBonusScoreSheet');
Route::delete('/{bonusScore}', 'destroyBonusScore')->name('bonus-scores.destroy');
});
// Entry Flagging // Entry Flagging
Route::prefix('entry-flags/')->controller(EntryFlagController::class)->group(function () { Route::prefix('entry-flags/')->controller(EntryFlagController::class)->group(function () {
Route::get('/choose_no_show', 'noShowSelect')->name('entry-flags.noShowSelect'); Route::get('/choose_no_show', 'noShowSelect')->name('entry-flags.noShowSelect');