Auditionadmin 20 - Bonus scores are fully functional #25
|
|
@ -3,12 +3,29 @@
|
|||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\BonusScoreDefinition;
|
||||
|
||||
use function to_route;
|
||||
|
||||
class BonusScoreDefinitionController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('admin.bonus-scores.index');
|
||||
$bonusScores = BonusScoreDefinition::all();
|
||||
|
||||
return view('admin.bonus-scores.index', compact('bonusScores'));
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$validData = request()->validate([
|
||||
'name' => 'required',
|
||||
'max_score' => 'required|numeric',
|
||||
'weight' => 'required|numeric',
|
||||
]);
|
||||
|
||||
BonusScoreDefinition::create($validData);
|
||||
|
||||
return to_route('admin.bonus-scores.index')->with('success', 'Bonus Score Created');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,6 @@ use Illuminate\Database\Eloquent\Model;
|
|||
class BonusScoreDefinition extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['name', 'max_score', 'weight'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\BonusScoreDefinition>
|
||||
*/
|
||||
class BonusScoreDefinitionFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->word,
|
||||
'max_score' => $this->faker->randomNumber(2),
|
||||
'weight' => $this->faker->randomFloat(2, 0, 2),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,11 @@ return new class extends Migration
|
|||
{
|
||||
Schema::create('bonus_score_audition_assignment', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(BonusScoreDefinition::class)->constrained()->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreignIdFor(Audition::class)->constrained()->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreignIdFor(BonusScoreDefinition::class)
|
||||
->constrained('bonus_score_definitions', 'id', 'bs_audition_assignment_bonus_score_definition_id')
|
||||
->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreignIdFor(Audition::class)
|
||||
->constrained()->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<x-modal-body show-var="showAddBonusScoreModal">
|
||||
<x-slot:title>
|
||||
Add Bonus Score
|
||||
</x-slot:title>
|
||||
<x-form.form id="create-bonus-score-form" action="{{ route('admin.bonus-scores.store') }}">
|
||||
<x-form.body-grid columns="12">
|
||||
<x-form.field name="name" label_text="Name" colspan="8" />
|
||||
<x-form.field name="max_score" type="number" label_text="Max Points" colspan="2" />
|
||||
<x-form.field name="weight" label_text="Weight" colspan="2" />
|
||||
<div class="col-start-9 col-span-4 row-start-2">
|
||||
<x-form.button >Create Bonus Score</x-form.button>
|
||||
</div>
|
||||
|
||||
</x-form.body-grid>
|
||||
</x-form.form>
|
||||
</x-modal-body>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<x-help-modal title="Bonus Score Definitions">
|
||||
<p class="mb-5">Bonus scores are most often used for an improvisation score for jazz band auditions. A bonus score
|
||||
earned by an entry will be directly added
|
||||
to that entries final score. When you create a bonus score, you will also specify to which auditions that bonus
|
||||
score should apply. When a student
|
||||
earns a bonus score for one entry, that bonus will be applied to all entries that receive that bonus score.</p>
|
||||
|
||||
<p class="mb-5">
|
||||
Let's say you create a bonus score called, "Saxophone Improvisation," and assign Jazz Alto, Jazz Tenor, and Jazz
|
||||
Bari auditions to that bonus
|
||||
score. If a student is entered on all three saxes, when they receive an improv score on one sax, that score will
|
||||
apply to all 3. The system
|
||||
will not allow another improv score to be assigned by the same judge unless the first one is deleted. If you
|
||||
want that student to improv on each instrument
|
||||
separately, you will need to create a separate bonus score for each instrument.
|
||||
</p>
|
||||
|
||||
<P>
|
||||
The weight allows you to control how much influence the bonus score has on the outcome of the audition. The
|
||||
bonus score is
|
||||
multiplied by the weight then added to the final score. The weight may be any positive number, including
|
||||
decimals.
|
||||
</P>
|
||||
</x-help-modal>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<div class="text-center">
|
||||
<svg class="mx-auto w-12 h-12 text-gray-400 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/>
|
||||
</svg>
|
||||
|
||||
|
||||
<h3 class="mt-2 text-sm font-semibold text-gray-900">No bonus scores have been created</h3>
|
||||
<p class="mt-1 text-sm text-gray-500">Get started by creating a new bonus score.</p>
|
||||
<div class="mt-6">
|
||||
<button type="button"
|
||||
x-on:click="showAddBonusScoreModal = true"
|
||||
class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"/>
|
||||
</svg>
|
||||
New Bonus Score
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -1,4 +1,22 @@
|
|||
<x-layout.app>
|
||||
<x-layout.app x-data="{ showAddBonusScoreModal: false }">
|
||||
<x-slot:page_title>Bonus Score Management</x-slot:page_title>
|
||||
<x-slot:title_bar_right>
|
||||
@include('admin.bonus-scores.index-help-modal')
|
||||
</x-slot:title_bar_right>
|
||||
@if($bonusScores->count() === 0)
|
||||
@include('admin.bonus-scores.index-no-bonus-scores-message')
|
||||
@endif
|
||||
|
||||
@foreach($bonusScores as $bonusScore)
|
||||
<x-card.card>
|
||||
<x-card.heading>
|
||||
{{ $bonusScore->name }}
|
||||
<x-slot:subheading>
|
||||
Max Points: {{ $bonusScore->max_score }} | Weight: {{ $bonusScore->weight }}
|
||||
</x-slot:subheading>
|
||||
</x-card.heading>
|
||||
</x-card.card>
|
||||
@endforeach
|
||||
|
||||
@include('admin.bonus-scores.index-add-bonus-score-modal')
|
||||
</x-layout.app>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
@props(['title'=>false])
|
||||
@props(['title'=>false, 'showVar'=>'showModal'])
|
||||
<div
|
||||
class="fixed inset-0 z-30 flex items-center justify-center overflow-auto bg-black bg-opacity-50"
|
||||
x-show="showModal" x-cloak
|
||||
x-show="{{ $showVar }}" x-cloak
|
||||
>
|
||||
<!-- Modal inner -->
|
||||
<div
|
||||
class="max-w-3xl px-6 py-4 mx-auto text-left bg-white rounded shadow-lg"
|
||||
@click.away="showModal = false"
|
||||
@click.away="{{ $showVar }} = false"
|
||||
x-transition:enter="motion-safe:ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 scale-90"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<h5 {{ $title->attributes->merge(['class' => 'mr-3 text-black max-w-none']) }}>{{ $title ?? '' }}</h5>
|
||||
@endif
|
||||
|
||||
<button type="button" class="z-50 cursor-pointer" @click="showModal = false">
|
||||
<button type="button" class="z-50 cursor-pointer" @click="{{ $showVar}} = false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
|
|||
Route::prefix('bonus-scores')->controller(\App\Http\Controllers\Admin\BonusScoreDefinitionController::class)->group(function () {
|
||||
Route::get('/', 'index')->name('admin.bonus-scores.index');
|
||||
// Route::get('/create', 'create')->name('admin.bonus-scores.create');
|
||||
// Route::post('/', 'store')->name('admin.bonus-scores.store');
|
||||
Route::post('/', 'store')->name('admin.bonus-scores.store');
|
||||
// Route::get('/{bonusScoreDefinition}/edit', 'edit')->name('admin.bonus-scores.edit');
|
||||
// Route::patch('/{bonusScoreDefinition}', 'update')->name('admin.bonus-scores.update');
|
||||
// Route::delete('/{bonusScoreDefinition}', 'destroy')->name('admin.bonus-scores.destroy');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
use App\Models\BonusScoreDefinition;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Sinnbeck\DomAssertions\Asserts\AssertForm;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
|
|
@ -26,3 +28,51 @@ it('grants access to an administrator', function () {
|
|||
->assertOk()
|
||||
->assertViewIs('admin.bonus-scores.index');
|
||||
});
|
||||
it('if no bonus scores exist, show a create bonus score message', function () {
|
||||
// Arrange
|
||||
actAsAdmin();
|
||||
// Act & Assert
|
||||
$this->get(route('admin.bonus-scores.index'))
|
||||
->assertOk()
|
||||
->assertSee('No bonus scores have been created');
|
||||
});
|
||||
it('includes a form to add a new bonus score', function () {
|
||||
// Arrange
|
||||
actAsAdmin();
|
||||
// Act & Assert
|
||||
$this->get(route('admin.bonus-scores.index'))
|
||||
->assertOk()
|
||||
->assertFormExists('#create-bonus-score-form', function (AssertForm $form) {
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
$form->hasCSRF()
|
||||
->hasMethod('POST')
|
||||
->hasAction(route('admin.bonus-scores.store'))
|
||||
->containsInput(['name' => 'name'])
|
||||
->containsInput(['name' => 'max_score', 'type' => 'number'])
|
||||
->containsInput(['name' => 'weight']);
|
||||
});
|
||||
});
|
||||
it('can create a new subscore', function () {
|
||||
// Arrange
|
||||
$submissionData = [
|
||||
'name' => 'New Bonus Score',
|
||||
'max_score' => 10,
|
||||
'weight' => 1,
|
||||
];
|
||||
// Act & Assert
|
||||
actAsAdmin();
|
||||
$this->post(route('admin.bonus-scores.store'), $submissionData)
|
||||
->assertRedirect(route('admin.bonus-scores.index'))
|
||||
->assertSessionHas('success', 'Bonus Score Created');
|
||||
$test = BonusScoreDefinition::where('name', 'New Bonus Score')->first();
|
||||
expect($test->exists())->toBeTrue();
|
||||
});
|
||||
it('shows existing bonus scores', function () {
|
||||
// Arrange
|
||||
$bonusScores = BonusScoreDefinition::factory()->count(3)->create();
|
||||
actAsAdmin();
|
||||
// Act & Assert
|
||||
$response = $this->get(route('admin.bonus-scores.index'));
|
||||
$response->assertOk();
|
||||
$bonusScores->each(fn ($bonusScore) => $response->assertSee($bonusScore->name));
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue