Progress on draw. Edit form and destroy method next.
This commit is contained in:
parent
575ce9854b
commit
a28380e2fe
|
|
@ -3,14 +3,54 @@
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\RunDrawRequest;
|
||||||
|
use App\Models\Audition;
|
||||||
use App\Models\Event;
|
use App\Models\Event;
|
||||||
|
use App\Services\DrawService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
use function array_keys;
|
||||||
|
use function to_route;
|
||||||
|
|
||||||
class DrawController extends Controller
|
class DrawController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
protected $drawService;
|
||||||
|
|
||||||
|
public function __construct(DrawService $drawService)
|
||||||
|
{
|
||||||
|
$this->drawService = $drawService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$events = Event::with('auditions')->get();
|
$events = Event::with('auditions')->get();
|
||||||
|
// $drawnAuditionsExist is true if any audition->hasFlag('drawn') is true
|
||||||
|
$drawnAuditionsExist = Audition::whereHas('flags', function ($query) {
|
||||||
|
$query->where('flag_name', 'drawn');
|
||||||
|
})->exists();
|
||||||
|
|
||||||
return view('admin.draw.index', compact('events'));
|
return view('admin.draw.index', compact('events', 'drawnAuditionsExist'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(RunDrawRequest $request)
|
||||||
|
{
|
||||||
|
$auditions = Audition::with('flags')->findMany(array_keys($request->input('audition', [])));
|
||||||
|
|
||||||
|
if ($this->drawService->checkCollectionForDrawnAuditions($auditions)) {
|
||||||
|
return to_route('admin.draw.index')->with('error',
|
||||||
|
'Invalid attempt to draw an audition that has already been drawn');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->drawService->runDrawsOnCollection($auditions);
|
||||||
|
|
||||||
|
return to_route('admin.draw.index')->with('status', 'Draw completed successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(Request $request)
|
||||||
|
{
|
||||||
|
$drawnAuditions = Audition::whereHas('flags', function ($query) {
|
||||||
|
$query->where('flag_name', 'drawn');
|
||||||
|
})->get();
|
||||||
|
return view('admin.draw.edit', compact('drawnAuditions'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Models\Audition;
|
||||||
|
use Illuminate\Contracts\Validation\Validator;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
use function to_route;
|
||||||
|
|
||||||
|
class RunDrawRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): true
|
||||||
|
{
|
||||||
|
// Return true if the user is authorized to make this request.
|
||||||
|
// You might want to check if the user is an admin, for example.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'audition' => ['required', 'array'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'audition.required' => 'No auditions were selected',
|
||||||
|
'audition.array' => 'Invalid request format',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withValidator($validator): void
|
||||||
|
{
|
||||||
|
$validator->after(function ($validator) {
|
||||||
|
foreach ($this->input('audition', []) as $auditionId => $value) {
|
||||||
|
if (! is_numeric($auditionId) || ! Audition::where('id', $auditionId)->exists()) {
|
||||||
|
$validator->errors()->add('audition', 'One or more invalid auditions were selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function failedValidation(Validator $validator)
|
||||||
|
{
|
||||||
|
$msg = $validator->errors()->get('audition')[0];
|
||||||
|
|
||||||
|
return to_route('admin.draw.index')->with('error', $msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ use App\Observers\SubscoreDefinitionObserver;
|
||||||
use App\Observers\UserObserver;
|
use App\Observers\UserObserver;
|
||||||
use App\Services\AuditionService;
|
use App\Services\AuditionService;
|
||||||
use App\Services\DoublerService;
|
use App\Services\DoublerService;
|
||||||
|
use App\Services\DrawService;
|
||||||
use App\Services\EntryService;
|
use App\Services\EntryService;
|
||||||
use App\Services\ScoreService;
|
use App\Services\ScoreService;
|
||||||
use App\Services\SeatingService;
|
use App\Services\SeatingService;
|
||||||
|
|
@ -43,6 +44,7 @@ use App\Services\TabulationService;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,6 +52,10 @@ class AppServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
|
$this->app->singleton(DrawService::class, function () {
|
||||||
|
return new DrawService();
|
||||||
|
});
|
||||||
|
|
||||||
$this->app->singleton(AuditionService::class, function () {
|
$this->app->singleton(AuditionService::class, function () {
|
||||||
return new AuditionService();
|
return new AuditionService();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Audition;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class DrawService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new class instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runOneDraw(Audition $audition): void
|
||||||
|
{
|
||||||
|
// set draw number null on each entry before beginning
|
||||||
|
DB::table('entries')->where('audition_id', $audition->id)->update(['draw_number' => null]);
|
||||||
|
|
||||||
|
$randomizedEntries = $audition->entries->shuffle();
|
||||||
|
foreach ($randomizedEntries as $index => $entry) {
|
||||||
|
$entry->draw_number = $index + 1;
|
||||||
|
$entry->save();
|
||||||
|
}
|
||||||
|
$audition->addFlag('drawn');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runDrawsOnCollection($auditions): void
|
||||||
|
{
|
||||||
|
$auditions->each(fn ($audition) => $this->runOneDraw($audition));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkCollectionForDrawnAuditions($auditions): bool
|
||||||
|
{
|
||||||
|
|
||||||
|
$auditions->loadMissing('flags');
|
||||||
|
|
||||||
|
return $auditions->contains(fn ($audition) => $audition->hasFlag('drawn'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
@php
|
||||||
|
/**
|
||||||
|
* @var int $size=20 Size of the icon
|
||||||
|
*/
|
||||||
|
@endphp
|
||||||
|
@props(['size' => 20])
|
||||||
|
|
||||||
|
<div
|
||||||
|
@keydown.escape="showModal = false"
|
||||||
|
>
|
||||||
|
<!-- Trigger for Modal -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="relative z-10"
|
||||||
|
aria-labelledby="modal-title"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
x-show="showModal"
|
||||||
|
x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0"
|
||||||
|
x-cloak>
|
||||||
|
<!--
|
||||||
|
Background backdrop, show/hide based on modal state.
|
||||||
|
|
||||||
|
Entering: "ease-out duration-300"
|
||||||
|
From: "opacity-0"
|
||||||
|
To: "opacity-100"
|
||||||
|
Leaving: "ease-in duration-200"
|
||||||
|
From: "opacity-100"
|
||||||
|
To: "opacity-0"
|
||||||
|
-->
|
||||||
|
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
|
||||||
|
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"
|
||||||
|
x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-cloak
|
||||||
|
x-show="showModal">
|
||||||
|
<!--
|
||||||
|
Modal panel, show/hide based on modal state.
|
||||||
|
|
||||||
|
Entering: "ease-out duration-300"
|
||||||
|
From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
To: "opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
Leaving: "ease-in duration-200"
|
||||||
|
From: "opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
-->
|
||||||
|
<div
|
||||||
|
class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6"
|
||||||
|
x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-cloak
|
||||||
|
x-show="showModal"
|
||||||
|
@click.away="showModal = false">
|
||||||
|
<div class="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
|
||||||
|
<button type="button"
|
||||||
|
@click="showModal = false"
|
||||||
|
class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||||
|
<span class="sr-only">Close</span>
|
||||||
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
stroke="currentColor" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div
|
||||||
|
class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
stroke="currentColor" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
|
||||||
|
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
|
||||||
|
Really Clear the draw??
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
Click confirm below if you're sure. After doing so, be sure to destroy any materials you may have printed for those
|
||||||
|
auditions to avoid any chance of confusion on audition day.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
|
<button type="submit"
|
||||||
|
class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto">
|
||||||
|
Confirm Clear Draw
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
@click="showModal = false"
|
||||||
|
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div class="rounded-md bg-red-100 p-4 mb-7">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0 text-red-400">
|
||||||
|
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
|
||||||
|
clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<h3 class="text-sm font-medium text-red-800">Caution!!!</h3>
|
||||||
|
<div class="mt-2 text-sm text-red-700">
|
||||||
|
<ul role="list" class="list-disc space-y-1 pl-5">
|
||||||
|
<li>This will clear any existing draw numbers</li>
|
||||||
|
<li>Any cards, sign in sheets or other materials you've printed will be invalid</li>
|
||||||
|
<li>This action cannot be undone</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<div class="rounded-md bg-blue-100 p-4 mb-5 max-w-2xl mx-auto" x-data="{drawnAuditions: true}" x-show="drawnAuditions">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-3 flex-1 md:flex md:justify-between">
|
||||||
|
<div class="text-sm text-blue-700">
|
||||||
|
<p>Some auditions have already been drawn.
|
||||||
|
<a href="{{ route('admin.draw.edit') }}" class="text-sm font-medium text-blue-800 underline hover:text-blue-600">Click here</a>
|
||||||
|
to clear previous draws.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="ml-auto pl-3">
|
||||||
|
<div class="-mx-1.5 -my-1.5">
|
||||||
|
<button type="button"
|
||||||
|
x-on:click="drawnAuditions = false"
|
||||||
|
class="inline-flex rounded-md bg-blue-100 p-1.5 text-blue-500 hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 focus:ring-offset-blue-50">
|
||||||
|
<span class="sr-only">Dismiss</span>
|
||||||
|
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path
|
||||||
|
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
@php
|
||||||
|
/**
|
||||||
|
* @var \App\Models\Audition[] $drawnAuditions A collection of all auditions that have been drawn
|
||||||
|
*/
|
||||||
|
@endphp
|
||||||
|
<x-layout.app>
|
||||||
|
|
||||||
|
@include('admin.draw.clear-draw-warning')
|
||||||
|
|
||||||
|
<x-form.form method="DELETE" action="{{ route('admin.draw.destroy') }}">
|
||||||
|
<x-card.card class="mb-5 mx-auto max-w-3xl" id="drawn-auditions-card" x-data="{ checked: false, showModal: false }">
|
||||||
|
<x-card.heading>
|
||||||
|
Previously Drawn Auditions
|
||||||
|
<x-slot:right_side>
|
||||||
|
<button @click="checked = true" class="rounded bg-indigo-50 px-2 py-1 text-xs font-semibold text-indigo-600 shadow-sm hover:bg-indigo-100 mr-3" type="button">Check All</button>
|
||||||
|
<button @click="checked = false" class="rounded bg-indigo-50 px-2 py-1 text-xs font-semibold text-indigo-600 shadow-sm hover:bg-indigo-100" type="button">Uncheck All</button>
|
||||||
|
</x-slot:right_side>
|
||||||
|
</x-card.heading>
|
||||||
|
<div class="grid gap-y-3 md:grid-cols-2 lg:grid-cols-3 px-5 my-3 pb-3 border-b border-gray-100">
|
||||||
|
@foreach($drawnAuditions as $audition)
|
||||||
|
<div id="auditiongroup-{{$audition->id}}" class="flex align-middle">
|
||||||
|
<x-form.checkbox id="auditionCheckbox-{{$audition->id}}" name="audition[{{$audition->id}}]" x-bind:checked="checked"/>
|
||||||
|
{{$audition->name}}
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full justify-between ">
|
||||||
|
<div></div>
|
||||||
|
<div class="mb-5 mr-10 ">
|
||||||
|
<x-form.button type="button" class="ml-auto" @click="showModal=true">Clear Draw</x-form.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@include('admin.draw.clear-draw-modal-confirm')
|
||||||
|
</x-card.card>
|
||||||
|
|
||||||
|
</x-form.form>
|
||||||
|
</x-layout.app>
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
@php
|
@php
|
||||||
/**
|
/**
|
||||||
* @var \App\Models\Event[] $events A collection of all events with auditions
|
* @var \App\Models\Event[] $events A collection of all events with auditions
|
||||||
|
* @var bool $drawnAuditionsExist A boolean value indicating if there are any drawn auditions
|
||||||
*/
|
*/
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<x-layout.app>
|
<x-layout.app>
|
||||||
|
@if($drawnAuditionsExist)
|
||||||
|
@include('admin.draw.drawn-auditions-exist-notification')
|
||||||
|
@endif
|
||||||
<x-form.form action="{{ route('admin.draw.store') }}" method="POST" id="draw-form">
|
<x-form.form action="{{ route('admin.draw.store') }}" method="POST" id="draw-form">
|
||||||
@foreach($events as $event)
|
@foreach($events as $event)
|
||||||
@continue($event->auditions->isEmpty())
|
@continue($event->auditions->isEmpty())
|
||||||
|
|
@ -18,6 +22,7 @@
|
||||||
</x-card.heading>
|
</x-card.heading>
|
||||||
<div class="grid gap-y-3 md:grid-cols-2 lg:grid-cols-3 px-5 my-3 pb-3 border-b border-gray-100">
|
<div class="grid gap-y-3 md:grid-cols-2 lg:grid-cols-3 px-5 my-3 pb-3 border-b border-gray-100">
|
||||||
@foreach($event->auditions as $audition)
|
@foreach($event->auditions as $audition)
|
||||||
|
@continue($audition->hasFlag('drawn'))
|
||||||
<div id="auditiongroup-{{$audition->id}}" class="flex align-middle">
|
<div id="auditiongroup-{{$audition->id}}" class="flex align-middle">
|
||||||
<x-form.checkbox id="auditionCheckbox-{{$audition->id}}" name="audition[{{$audition->id}}]" x-bind:checked="checked{{$event->id}}"/>
|
<x-form.checkbox id="auditionCheckbox-{{$audition->id}}" name="audition[{{$audition->id}}]" x-bind:checked="checked{{$event->id}}"/>
|
||||||
{{$audition->name}}
|
{{$audition->name}}
|
||||||
|
|
|
||||||
|
|
@ -130,5 +130,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,11 @@
|
||||||
@inject('auditionService','App\Services\AuditionService');
|
@inject('auditionService','App\Services\AuditionService');
|
||||||
@inject('entryService','App\Services\EntryService')
|
@inject('entryService','App\Services\EntryService')
|
||||||
@inject('seatingService','App\Services\SeatingService')
|
@inject('seatingService','App\Services\SeatingService')
|
||||||
|
@inject('drawService', 'App\Services\DrawService')
|
||||||
<x-layout.app>
|
<x-layout.app>
|
||||||
<x-slot:page_title>Test Page</x-slot:page_title>
|
<x-slot:page_title>Test Page</x-slot:page_title>
|
||||||
@php
|
@php
|
||||||
dump(Audition::open()->get());
|
$drawService->hello();
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,8 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
|
||||||
Route::prefix('draw')->controller(\App\Http\Controllers\Admin\DrawController::class)->group(function () {
|
Route::prefix('draw')->controller(\App\Http\Controllers\Admin\DrawController::class)->group(function () {
|
||||||
Route::get('/', 'index')->name('admin.draw.index');
|
Route::get('/', 'index')->name('admin.draw.index');
|
||||||
Route::post('/', 'store')->name('admin.draw.store');
|
Route::post('/', 'store')->name('admin.draw.store');
|
||||||
Route::delete('/', 'destroy')->name('admin.draw.destroy');
|
Route::get('/clear', 'edit')->name('admin.draw.edit'); // Select auditions for which the user would like to clear the draw
|
||||||
|
Route::delete('/', 'destroy')->name('admin.draw.destroy'); // Clear the draw for the selected auditions
|
||||||
});
|
});
|
||||||
|
|
||||||
// Admin Entries Routes
|
// Admin Entries Routes
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ it('has a section for each event that has auditions', function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('lists auditions in each section', function () {
|
it('lists auditions that have not been drawn in each section', function () {
|
||||||
// Arrange
|
// Arrange
|
||||||
$events = Event::factory()->count(2)->create();
|
$events = Event::factory()->count(2)->create();
|
||||||
foreach ($events as $event) {
|
foreach ($events as $event) {
|
||||||
|
|
@ -52,12 +52,26 @@ it('lists auditions in each section', function () {
|
||||||
foreach ($events as $event) {
|
foreach ($events as $event) {
|
||||||
$response->assertElementExists('#event-section-'.$event->id, function (AssertElement $element) use ($event) {
|
$response->assertElementExists('#event-section-'.$event->id, function (AssertElement $element) use ($event) {
|
||||||
foreach ($event->auditions as $audition) {
|
foreach ($event->auditions as $audition) {
|
||||||
|
if ($audition->hasFlag('drawn')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$element->contains('#auditiongroup-'.$audition->id);
|
$element->contains('#auditiongroup-'.$audition->id);
|
||||||
$element->containsText($audition->name);
|
$element->containsText($audition->name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
it('does not list auditions that are already drawn', function () {
|
||||||
|
// Arrange
|
||||||
|
$audition = Audition::factory()->create();
|
||||||
|
$audition->addFlag('drawn');
|
||||||
|
actAsAdmin();
|
||||||
|
// Act & Assert
|
||||||
|
$response = $this->get(route('admin.draw.index'));
|
||||||
|
$response
|
||||||
|
->assertOk()
|
||||||
|
->assertDontSee($audition->name);
|
||||||
|
});
|
||||||
it('each audition has a checkbox with its name', function () {
|
it('each audition has a checkbox with its name', function () {
|
||||||
// Arrange
|
// Arrange
|
||||||
$events = Event::factory()->count(2)->create();
|
$events = Event::factory()->count(2)->create();
|
||||||
|
|
@ -151,3 +165,15 @@ it('submits to the route admin.draw.store and has CSRF protection', function ()
|
||||||
$form->hasCSRF();
|
$form->hasCSRF();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('displays a warning if some auditions are already drawn with a link to undo draws', function () {
|
||||||
|
// Arrange
|
||||||
|
$audition = Audition::factory()->create();
|
||||||
|
$audition->addFlag('drawn');
|
||||||
|
actAsAdmin();
|
||||||
|
// Act & Assert
|
||||||
|
$response = $this->get(route('admin.draw.index'));
|
||||||
|
$response
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Some auditions have already been drawn.', false)
|
||||||
|
->assertSee(route('admin.draw.edit'), false);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Audition;
|
||||||
|
use App\Models\Entry;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
it('allows admin to seat entries and returns to the index with a status message', function () {
|
||||||
|
$audition = Audition::factory()->create();
|
||||||
|
Entry::factory()->count(10)->create(['audition_id' => $audition->id]);
|
||||||
|
actAsAdmin();
|
||||||
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
$this->post(route('admin.draw.store'), ['audition' => [$audition->id => 'on']])
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertSessionHas('status', 'Draw completed successfully')
|
||||||
|
->assertRedirect(route('admin.draw.index'));
|
||||||
|
});
|
||||||
|
it('returns an error message if no auditions were selected', function () {
|
||||||
|
// Arrange
|
||||||
|
actAsAdmin();
|
||||||
|
// Act & Assert
|
||||||
|
$response = $this->post(route('admin.draw.store'));
|
||||||
|
$response
|
||||||
|
->assertSessionHas('error', 'No auditions were selected')
|
||||||
|
->assertRedirect(route('admin.draw.index'));
|
||||||
|
});
|
||||||
|
it('only allows admin to run a draw', function () {
|
||||||
|
// Act & Assert
|
||||||
|
$this->post(route('admin.draw.store'))->assertRedirect(route('home'));
|
||||||
|
actAsNormal();
|
||||||
|
$this->post(route('admin.draw.store'))
|
||||||
|
->assertSessionHas('error', 'You are not authorized to perform this action')
|
||||||
|
->assertRedirect(route('dashboard'));
|
||||||
|
});
|
||||||
|
it('returns with error if a draw is requested for a non-extant audition', function () {
|
||||||
|
// Arrange
|
||||||
|
actAsAdmin();
|
||||||
|
// Act & Assert
|
||||||
|
$response = $this->post(route('admin.draw.store', ['audition[999]' => 'on']));
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSessionHas('error', 'One or more invalid auditions were selected')
|
||||||
|
->assertRedirect(route('admin.draw.index'));
|
||||||
|
});
|
||||||
|
it('sets a drawn flag on the audition once a draw is run', function () {
|
||||||
|
// Arrange
|
||||||
|
$audition = Audition::factory()->create();
|
||||||
|
actAsAdmin();
|
||||||
|
// Act & Assert
|
||||||
|
$response = $this->post(route('admin.draw.store', ['audition['.$audition->id.']' => 'on']));
|
||||||
|
expect($audition->hasFlag('drawn'))->toBeTrue();
|
||||||
|
});
|
||||||
|
it('refuses to draw an audition that has an existing draw', function () {
|
||||||
|
// Arrange
|
||||||
|
$audition = Audition::factory()->create();
|
||||||
|
$audition->addFlag('drawn');
|
||||||
|
actAsAdmin();
|
||||||
|
// Act & Assert
|
||||||
|
$this->post(route('admin.draw.store', ['audition['.$audition->id.']' => 'on']))
|
||||||
|
->assertSessionHas('error', 'Invalid attempt to draw an audition that has already been drawn')
|
||||||
|
->assertRedirect(route('admin.draw.index'));
|
||||||
|
});
|
||||||
|
it('randomizes the order of the entries in the audition', function () {
|
||||||
|
// Arrange
|
||||||
|
$audition = Audition::factory()->hasEntries(10)->create();
|
||||||
|
$entries = Entry::factory()->count(30)->create(['audition_id' => $audition->id]);
|
||||||
|
actAsAdmin();
|
||||||
|
// Act & Assert
|
||||||
|
$this->post(route('admin.draw.store'), ['audition['.$audition->id.']' => 'on']);
|
||||||
|
// assert that entries sorted by draw_number are in a random order
|
||||||
|
expect($audition->entries->sortBy('draw_number')->pluck('id'))
|
||||||
|
->not()->toBe($entries->pluck('id'));
|
||||||
|
});
|
||||||
|
// sets the draw_number column on each entry in the audition based on the randomized entry order
|
||||||
Loading…
Reference in New Issue