Write tests - Write tests for what was done to this point that will be kept #11

Merged
okorpheus merged 61 commits from write-tests into master 2024-07-05 21:21:32 +00:00
144 changed files with 6684 additions and 416 deletions

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Audition;
use App\Models\Event;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@ -14,16 +15,15 @@ use function compact;
use function redirect;
use function request;
use function response;
use function to_route;
use function view;
class AuditionController extends Controller
{
public function index()
{
if (! Auth::user()->is_admin) {
abort(403);
}
$auditions = Audition::with(['event', 'entries'])->orderBy('score_order')->orderBy('created_at', 'desc')->get();
$auditions = Audition::with(['event'])->withCount('entries')->orderBy('score_order')->orderBy('created_at',
'desc')->get();
return view('admin.auditions.index', ['auditions' => $auditions]);
}
@ -50,12 +50,16 @@ class AuditionController extends Controller
'entry_fee' => ['required', 'numeric'],
'minimum_grade' => ['required', 'integer'],
'maximum_grade' => 'required|numeric|gte:minimum_grade',
'scoring_guide_id' => 'nullable|exists:scoring_guides,id',
], [
'maximum_grade.gte' => 'The maximum grade must be greater than the minimum grade.',
]);
$validData['for_seating'] = $request->get('for_seating') ? 1 : 0;
$validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0;
if (empty($alidData['scoring_guide_id'])) {
$validData['scoring_guide_id'] = 0;
}
Audition::create([
'event_id' => $validData['event_id'],
@ -66,9 +70,10 @@ class AuditionController extends Controller
'maximum_grade' => $validData['maximum_grade'],
'for_seating' => $validData['for_seating'],
'for_advancement' => $validData['for_advancement'],
'scoring_guide_id' => $validData['scoring_guide_id'],
]);
return redirect('/admin/auditions');
return to_route('admin.auditions.index')->with('success', 'Audition created successfully');
}
public function edit(Audition $audition)
@ -93,7 +98,7 @@ class AuditionController extends Controller
'entry_deadline' => ['required', 'date'],
'entry_fee' => ['required', 'numeric'],
'minimum_grade' => ['required', 'integer'],
'maximum_grade' => 'required|numeric|gt:minimum_grade',
'maximum_grade' => 'required | numeric | gt:minimum_grade',
], [
'maximum_grade.gt' => 'The maximum grade must be greater than the minimum grade.',
]);
@ -112,7 +117,7 @@ class AuditionController extends Controller
'for_advancement' => $validData['for_advancement'],
]);
return redirect('/admin/auditions');
return to_route('admin.auditions.index')->with('success', 'Audition updated successfully');
}
public function reorder(Request $request)
@ -144,6 +149,13 @@ class AuditionController extends Controller
return response()->json(['status' => 'success']);
}
/**
* Update the scoring guide for an audition
* Used by AJAX call on the scoring guide index page
* request should include scoring_guide_id and audition_id
*
* @return JsonResponse
*/
public function scoringGuideUpdate(Request $request)
{
@ -162,16 +174,12 @@ class AuditionController extends Controller
public function destroy(Audition $audition)
{
if (! Auth::user()->is_admin) {
abort(403);
}
// if($audition->entries->count() > 0) abort(403, 'Cannot delete an audition with entries.'
if ($audition->entries->count() > 0) {
return redirect()->route('admin.auditions.index')->with('error', 'Cannot delete an audition with entries.');
}
$audition->delete();
return redirect('/admin/auditions');
return to_route('admin.auditions.index')->with('success', 'Audition deleted successfully');
}
public function prepareDraw()
@ -190,7 +198,8 @@ class AuditionController extends Controller
return $audition->has_partial_draw();
});
return view('admin.entries.prepare_draw', compact('nodraw_auditions', 'drawn_auditions', 'partial_draw_auditions'));
return view('admin.entries.prepare_draw',
compact('nodraw_auditions', 'drawn_auditions', 'partial_draw_auditions'));
}
public function runDraw(Request $request)
@ -203,6 +212,6 @@ class AuditionController extends Controller
$audition->runDraw();
}
return redirect('/admin/auditions/run_draw');
return redirect(' / admin / auditions / run_draw');
}
}

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Settings;
use Illuminate\Http\Request;
use function to_route;
class AuditionSettings extends Controller
{
@ -35,6 +36,6 @@ class AuditionSettings extends Controller
Settings::set($key, $value);
}
return view('admin.audition-settings')->with('success', 'Settings Saved');
return to_route('audition-settings')->with('success', 'Settings Saved');
}
}

View File

@ -8,7 +8,7 @@ use App\Models\Event;
use App\Models\SeatingLimit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use function redirect;
class EnsembleController extends Controller
@ -16,16 +16,19 @@ class EnsembleController extends Controller
public function index()
{
$events = Event::with('ensembles')->get();
return view('admin.ensembles.index',compact('events'));
return view('admin.ensembles.index', compact('events'));
}
public function store(Request $request)
{
if(! Auth::user()->is_admin) abort(403);
if (! Auth::user()->is_admin) {
abort(403);
}
request()->validate([
'name' => 'required',
'code' => ['required','max:6'],
'event_id' => ['required','exists:events,id']
'code' => ['required', 'max:6'],
'event_id' => ['required', 'exists:events,id'],
]);
Ensemble::create([
@ -34,64 +37,78 @@ class EnsembleController extends Controller
'event_id' => request('event_id'),
]);
return redirect()->route('admin.ensembles.index')->with('success','Ensemble created successfully');
return redirect()->route('admin.ensembles.index')->with('success', 'Ensemble created successfully');
}
public function destroy(Request $request, Ensemble $ensemble)
{
if(! Auth::user()->is_admin) abort(403);
if ($ensemble->seats->count() > 0) {
return redirect()->route('admin.ensembles.index')->with('error', 'Ensemble has students seated and cannot be deleted');
}
$ensemble->delete();
return redirect()->route('admin.ensembles.index')->with('success', 'Ensemble deleted successfully');
}
public function updateEnsemble(Request $request, Ensemble $ensemble)
{
if(! Auth::user()->is_admin) abort(403);
request()->validate([
'name' => 'required',
'code' => 'required|max:6'
'code' => 'required|max:6',
]);
$ensemble->update([
'name' => request('name'),
'code' => request('code')
'code' => request('code'),
]);
return redirect()->route('admin.ensembles.index')->with('success','Ensemble updated successfully');
return redirect()->route('admin.ensembles.index')->with('success', 'Ensemble updated successfully');
}
public function seatingLimits(Ensemble $ensemble)
{
$ensembles = Ensemble::with('event')->orderBy('event_id')->get();
$limits = [];
$ensembles = Ensemble::with(['event'])->orderBy('event_id')->get();
if ($ensemble->exists()) {
$ensemble->load('seatingLimits');
foreach ($ensemble->seatingLimits as $lim) {
$limits[$lim->audition_id] = $lim->maximum_accepted;
}
return view('admin.ensembles.seatingLimits',compact('ensemble','ensembles'));
}
return view('admin.ensembles.seatingLimits', compact('ensemble', 'ensembles', 'limits'));
}
public function seatingLimitsSet(Request $request, Ensemble $ensemble)
{
$request->validate([
'audition' => 'required',
'audition.*' => ['integer','min:0']
'audition.*' => ['integer', 'min:0'],
]);
foreach($ensemble->auditions as $audition) {
foreach ($ensemble->auditions as $audition) {
SeatingLimit::upsert(
[[
[
[
'ensemble_id' => $ensemble->id,
'audition_id' => $audition->id,
'maximum_accepted' => $request->audition[$audition->id]
]],
uniqueBy: ['ensemble_id','audition_id'],
'maximum_accepted' => $request->audition[$audition->id],
],
],
uniqueBy: ['ensemble_id', 'audition_id'],
update: ['maximum_accepted']
);
}
return redirect()->route('admin.ensembles.seatingLimits')->with('success', 'Seating limits set for ' . $ensemble->name);
return redirect()->route('admin.ensembles.seatingLimits.ensemble', $ensemble)->with('success',
'Seating limits set for '.$ensemble->name);
}
public function updateEnsembleRank(Request $request)
{
if(! Auth::user()->is_admin) abort(403);
if (! Auth::user()->is_admin) {
abort(403);
}
$order = $request->input('order');
$eventId = $request->input('event_id');

View File

@ -11,7 +11,9 @@ use App\Models\Student;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use function auditionSetting;
use function compact;
use function to_route;
class EntryController extends Controller
{
@ -62,21 +64,24 @@ class EntryController extends Controller
$entries = $entries->paginate(10);
return view('admin.entries.index', ['entries' => $entries,
return view('admin.entries.index', [
'entries' => $entries,
'auditions' => $auditions,
'schools' => $schools,
'minGrade' => $minGrade,
'maxGrade' => $maxGrade,
'filters' => $filters]);
'filters' => $filters,
]);
}
public function create()
{
if (! Auth::user()->is_admin) {
abort(403);
}
$students = Student::with('school')->orderBy('last_name')->orderBy('first_name')->get();
$auditions = Audition::orderBy('score_order')->get();
$auditionsRaw = Audition::with('flags')->orderBy('score_order')->get();
$auditions = $auditionsRaw->reject(function ($audition) {
return $audition->hasFlag('seats_published') || $audition->hasFlag('advancement_published');
});
return view('admin.entries.create', ['students' => $students, 'auditions' => $auditions]);
}
@ -106,9 +111,16 @@ class EntryController extends Controller
public function edit(Entry $entry)
{
if (! Auth::user()->is_admin) {
abort(403);
if ($entry->audition->hasFlag('seats_published')) {
return to_route('admin.entries.index')->with('error',
'Entries in auditions with seats published cannot be modified');
}
if ($entry->audition->hasFlag('advancement_published')) {
return to_route('admin.entries.index')->with('error',
'Entries in auditions with advancement results published cannot be modified');
}
$students = Student::with('school')->orderBy('last_name')->orderBy('first_name')->get();
$auditions = Audition::orderBy('score_order')->get();
$scores = $entry->scoreSheets()->get();
@ -119,31 +131,45 @@ class EntryController extends Controller
public function update(Request $request, Entry $entry)
{
if (! Auth::user()->is_admin) {
abort(403);
if ($entry->audition->hasFlag('seats_published')) {
return to_route('admin.entries.index')->with('error',
'Entries in auditions with seats published cannot be modified');
}
if ($entry->audition->hasFlag('advancement_published')) {
return to_route('admin.entries.index')->with('error',
'Entries in auditions with advancement results published cannot be modified');
}
$validData = request()->validate([
'student_id' => ['required', 'exists:students,id'],
'audition_id' => ['required', 'exists:auditions,id'],
]);
$validData['for_seating'] = $request->get('for_seating') ? 1 : 0;
$validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0;
if (! auditionSetting('advanceTo')) {
$validData['for_seating'] = 1;
}
$entry->update([
'student_id' => $validData['student_id'],
'audition_id' => $validData['audition_id'],
'for_seating' => $validData['for_seating'],
'for_advancement' => $validData['for_advancement'],
]);
return redirect('/admin/entries');
return to_route('admin.entries.index')->with('success', 'Entry updated successfully');
}
public function destroy(Request $request, Entry $entry)
{
if (! Auth::user()->is_admin) {
abort(403);
if ($entry->audition->hasFlag('seats_published')) {
return to_route('admin.entries.index')->with('error',
'Entries in auditions with seats published cannot be deleted');
}
if ($entry->audition->hasFlag('advancement_published')) {
return to_route('admin.entries.index')->with('error',
'Entries in auditions with advancement results published cannot be deleted');
}
if (Seat::where('entry_id', $entry->id)->exists()) {
return redirect()->route('admin.entries.index')->with('error', 'Cannot delete an entry that is seated');

View File

@ -37,8 +37,9 @@ class EventController extends Controller
public function destroy(Request $request, Event $event)
{
if (! Auth::user()->is_admin) {
abort(403);
if ($event->auditions()->count() > 0) {
return redirect()->route('admin.events.index')->with('error',
'Cannot delete an event with auditions');
}
$event->delete();

View File

@ -24,9 +24,6 @@ class RoomController extends Controller
public function judgingAssignment() // Show form for assigning judges
{
if (! Auth::user()->is_admin) {
abort(403);
}
$usersWithoutRooms = User::doesntHave('rooms')->orderBy('last_name')->orderBy('first_name')->get();
$usersWithRooms = User::has('rooms')->orderBy('last_name')->orderBy('first_name')->get();
$rooms = Room::with(['judges.school', 'auditions'])->get();
@ -100,7 +97,7 @@ class RoomController extends Controller
$room->description = $validData['description'];
$room->save();
return redirect()->route('admin.rooms.index')->with('success', 'Room updated.');
return redirect()->route('admin.rooms.index')->with('success', 'Room updated successfully');
}
public function destroy(Room $room)

View File

@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
use App\Models\School;
use App\Models\SchoolEmailDomain;
use App\Services\Invoice\InvoiceDataService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use function abort;
@ -15,7 +14,7 @@ use function request;
class SchoolController extends Controller
{
protected $invoiceService;
protected InvoiceDataService $invoiceService;
public function __construct(InvoiceDataService $invoiceController)
{
@ -36,7 +35,7 @@ class SchoolController extends Controller
return view('admin.schools.index', compact('schools', 'schoolTotalFees'));
}
public function show(Request $request, School $school)
public function show(School $school)
{
if (! Auth::user()->is_admin) {
abort(403);
@ -50,16 +49,13 @@ class SchoolController extends Controller
if (! Auth::user()->is_admin) {
abort(403);
}
$school->loadCount('students');
return view('admin.schools.edit', ['school' => $school]);
}
public function update(School $school)
{
if (! Auth::user()->is_admin) {
abort(403);
}
request()->validate([
'name' => ['required'],
'address' => ['required'],
@ -76,7 +72,8 @@ class SchoolController extends Controller
'zip' => request('zip'),
]);
return redirect()->route('admin.schools.show', ['school' => $school->id])->with('success', 'School '.$school->name.' updated');
return redirect()->route('admin.schools.show', ['school' => $school->id])->with('success',
'School '.$school->name.' updated');
}
public function create()
@ -88,7 +85,7 @@ class SchoolController extends Controller
return view('admin.schools.create');
}
public function store(Request $request)
public function store()
{
request()->validate([
'name' => ['required'],
@ -109,7 +106,18 @@ class SchoolController extends Controller
return redirect('/admin/schools')->with('success', 'School '.$school->name.' created');
}
public function add_domain(Request $request, School $school)
public function destroy(School $school)
{
if ($school->students()->count() > 0) {
return to_route('admin.schools.index')->with('error', 'You cannot delete a school with students.');
}
$name = $school->name;
$school->delete();
return to_route('admin.schools.index')->with('success', 'School '.$school->name.' deleted');
}
public function add_domain(School $school)
{
if (! Auth::user()->is_admin) {
abort(403);
@ -120,13 +128,14 @@ class SchoolController extends Controller
]);
SchoolEmailDomain::updateOrInsert([
'school_id' => $school->id,
'domain' => request('domain')], []);
'domain' => request('domain'),
]);
return redirect('/admin/schools/'.$school->id.'/edit')->with('success', 'Domain Added');
return redirect()->route('admin.schools.show', $school)->with('success', 'Domain Added');
}
public function destroy_domain(Request $request, SchoolEmailDomain $domain)
public function destroy_domain(SchoolEmailDomain $domain)
{
// Destroy the $domain
$domain->delete();
@ -135,7 +144,7 @@ class SchoolController extends Controller
return redirect()->back();
}
public function viewInvoice(Request $request, School $school)
public function viewInvoice(School $school)
{
$invoiceData = $this->invoiceService->allData($school->id);

View File

@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use function abort;
use function auditionSetting;
use function request;
use function response;
@ -17,13 +18,10 @@ class ScoringGuideController extends Controller
{
public function index()
{
if (! Auth::user()->is_admin) {
abort(403);
}
DB::table('auditions')
->whereNull('scoring_guide_id')
->update(['scoring_guide_id' => 0]);
$guides = ScoringGuide::with('auditions')->orderBy('name')->get();
$guides = ScoringGuide::with(['auditions'])->withCount('subscores')->orderBy('name')->get();
return view('admin.scoring.index', ['guides' => $guides]);
}
@ -42,15 +40,14 @@ class ScoringGuideController extends Controller
'name' => request('name'),
]);
return redirect('/admin/scoring');
return redirect(route('admin.scoring.index'))->with('success', 'Scoring guide created');
}
public function edit(Request $request, ScoringGuide $guide)
public function edit(Request $request, ScoringGuide $guide, string $tab = 'detail')
{
if (! Auth::user()->is_admin) {
abort(403);
}
$tab = $request->query('tab') ?? 'detail';
if ($tab == 'tiebreakOrder') {
$subscores = SubscoreDefinition::where('scoring_guide_id', $guide->id)->orderBy('tiebreak_order')->get();
} else {
@ -93,9 +90,6 @@ class ScoringGuideController extends Controller
public function subscore_store(Request $request, ScoringGuide $guide)
{
if (! Auth::user()->is_admin) {
abort(403);
}
if (! $guide->exists()) {
abort(409);
}
@ -109,6 +103,9 @@ class ScoringGuideController extends Controller
$for_seating = $request->has('for_seating') ? (bool) $request->input('for_seating') : false;
$for_advance = $request->has('for_advance') ? (bool) $request->input('for_advance') : false;
if (! auditionSetting('advanceTo')) {
$for_seating = true;
}
$display_order = SubscoreDefinition::where('scoring_guide_id', '=', $guide->id)->max('display_order') + 1;
$tiebreak_order = SubscoreDefinition::where('scoring_guide_id', '=', $guide->id)->max('tiebreak_order') + 1;
@ -124,7 +121,7 @@ class ScoringGuideController extends Controller
'for_advance' => $for_advance,
]);
return redirect('/admin/scoring/guides/'.$guide->id.'/edit')->with('success', 'Subscore added');
return redirect(route('admin.scoring.edit', $guide))->with('success', 'Subscore added');
}
public function subscore_update(ScoringGuide $guide, SubscoreDefinition $subscore)
@ -149,6 +146,10 @@ class ScoringGuideController extends Controller
$for_seating = request()->has('for_seating') ? (bool) request()->input('for_seating') : false;
$for_advance = request()->has('for_advance') ? (bool) request()->input('for_advance') : false;
if (! auditionSetting('advanceTo')) {
$for_seating = true;
}
$subscore->update([
'name' => $validateData['name'],
'max_score' => $validateData['max_score'],
@ -178,7 +179,6 @@ class ScoringGuideController extends Controller
}
public function reorder_display(Request $request)
{
if (! Auth::user()->is_admin) {
@ -187,7 +187,7 @@ class ScoringGuideController extends Controller
$order = $request->order;
foreach ($order as $index => $id) {
$subscore = SubscoreDefinition::find($id);
$subscore->update(['display_order' => $index]);
$subscore->update(['display_order' => $index + 1]);
}
return response()->json(['status' => 'success']);
@ -202,7 +202,7 @@ class ScoringGuideController extends Controller
$order = $request->order;
foreach ($order as $index => $id) {
$subscore = SubscoreDefinition::find($id);
$subscore->update(['tiebreak_order' => $index]);
$subscore->update(['tiebreak_order' => $index + 1]);
}
return response()->json(['status' => 'success']);

View File

@ -9,7 +9,9 @@ use App\Models\Student;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use function abort;
use function to_route;
use function view;
class StudentController extends Controller
@ -19,7 +21,7 @@ class StudentController extends Controller
if (! Auth::user()->is_admin) {
abort(403);
}
$students = Student::with(['school', 'entries'])->orderBy('last_name')->orderBy('first_name')->paginate(15);
$students = Student::with(['school'])->withCount('entries')->orderBy('last_name')->orderBy('first_name')->paginate(15);
return view('admin.students.index', ['students' => $students]);
}
@ -66,8 +68,9 @@ class StudentController extends Controller
$minGrade = Audition::min('minimum_grade');
$maxGrade = Audition::max('maximum_grade');
$schools = School::orderBy('name')->get();
return view('admin.students.edit', ['student' => $student, 'schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]);
$student->loadCount('entries');
return view('admin.students.edit',
['student' => $student, 'schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]);
}
public function update(Request $request, Student $student)
@ -84,7 +87,8 @@ class StudentController extends Controller
foreach ($student->entries as $entry) {
if ($entry->audition->minimum_grade > request('grade') || $entry->audition->maximum_grade < request('grade')) {
return redirect('/admin/students/'.$student->id.'/edit')->with('error', 'This student is entered in an audition that is not available to their new grade.');
return redirect('/admin/students/'.$student->id.'/edit')->with('error',
'This student is entered in an audition that is not available to their new grade.');
}
}
@ -98,4 +102,16 @@ class StudentController extends Controller
return redirect('/admin/students');
}
public function destroy(Student $student)
{
Log::debug('Deleting student '.$student->id);
if($student->entries()->count() > 0) {
return to_route('admin.students.index')->with('error', 'You cannot delete a student with entries.');
}
$name = $student->full_name();
$student->delete();
return to_route('admin.students.index')->with('success', 'Student '.$name.' deleted successfully.');
}
}

View File

@ -2,8 +2,6 @@
namespace App\Http\Controllers\Admin;
use App\Events\AuditionChange;
use App\Events\RoomJudgeChange;
use App\Http\Controllers\Controller;
use App\Mail\NewUserPassword;
use App\Models\School;
@ -18,28 +16,39 @@ class UserController extends Controller
{
public function index()
{
if (! Auth::user()->is_admin) abort(403);
if (! Auth::user()->is_admin) {
abort(403);
}
$users = User::with('school')->orderBy('last_name')->orderBy('first_name')->get();
return view('admin.users.index', ['users' => $users]);
}
public function edit(User $user)
{
if (! Auth::user()->is_admin) abort(403);
if (! Auth::user()->is_admin) {
abort(403);
}
$schools = School::orderBy('name')->get();
return view('admin.users.edit', ['user' => $user,'schools' => $schools]);
return view('admin.users.edit', ['user' => $user, 'schools' => $schools]);
}
public function create()
{
if (! Auth::user()->is_admin) abort(403);
if (! Auth::user()->is_admin) {
abort(403);
}
$schools = School::orderBy('name')->get();
return view('admin.users.create', ['schools' => $schools]);
}
public function update(Request $request, User $user)
{
if (! Auth::user()->is_admin) abort(403);
if (! Auth::user()->is_admin) {
abort(403);
}
request()->validate([
'first_name' => ['required'],
@ -47,7 +56,7 @@ class UserController extends Controller
'email' => ['required', 'email'],
'cell_phone' => ['required'],
'judging_preference' => ['required'],
'school_id' => ['required','exists:schools,id'],
'school_id' => ['required', 'exists:schools,id'],
]);
$user->update([
@ -56,8 +65,9 @@ class UserController extends Controller
'email' => request('email'),
'cell_phone' => request('cell_phone'),
'judging_preference' => request('judging_preference'),
'school_id' => request('school_id')
'school_id' => request('school_id'),
]);
return redirect('/admin/users');
}
@ -66,10 +76,10 @@ class UserController extends Controller
$request->validate([
'first_name' => ['required'],
'last_name' => ['required'],
'email' => ['required', 'email','unique:users'],
'email' => ['required', 'email', 'unique:users'],
]);
// Genearte a random password
// Generate a random password
$randomPassword = Str::random(12);
$user = \App\Models\User::make([
@ -81,9 +91,9 @@ class UserController extends Controller
'password' => Hash::make($randomPassword),
]);
if (!is_null(request('school_id'))) {
if (! is_null(request('school_id'))) {
$request->validate([
'school_id' => ['exists:schools,id']
'school_id' => ['exists:schools,id'],
]);
}
$user->school_id = request('school_id');
@ -93,4 +103,13 @@ class UserController extends Controller
return redirect('/admin/users');
}
public function destroy(Request $request, User $user)
{
if (! Auth::user()->is_admin) {
abort(403);
}
$user->delete();
return redirect()->route('admin.users.index')->with('success', 'User deleted successfully');
}
}

View File

@ -56,7 +56,7 @@ class EntryController extends Controller
}
$entry->delete();
return redirect('/entries')->with('success', 'The '.$entry->audition->name.'entry for '.$entry->student->full_name().'has been deleted.');
return redirect()->route('entries.index')->with('success', 'The '.$entry->audition->name.'entry for '.$entry->student->full_name().'has been deleted.');
}
}

View File

@ -16,14 +16,13 @@ class FilterController extends Controller
$filters['first_name'] = request('first_name_filter') ? request('first_name_filter') : null;
$filters['last_name'] = request('last_name_filter') ? request('last_name_filter') : null;
// session(['admin_entry_filter', $filters]);
session(['adminEntryFilters' => $filters]);
return redirect('/admin/entries');
return redirect('/admin/entries')->with('success', 'Filters Applied');
}
public function clearAdminEntryFilter(Request $request)
{
session()->forget('adminEntryFilters');
return redirect('/admin/entries');
return redirect('/admin/entries')->with('success', 'Filters Cleared');
}
}

View File

@ -5,10 +5,10 @@ namespace App\Http\Controllers;
use App\Models\Audition;
use App\Models\School;
use App\Models\Student;
use App\Models\User;
use App\Rules\UniqueFullNameAtSchool;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use function abort;
use function redirect;
@ -20,9 +20,13 @@ class StudentController extends Controller
*/
public function index()
{
$students = Auth::user()->students()->with('entries')->get();
if (! Auth::user()->school_id) {
return redirect()->route('dashboard');
}
$students = Auth::user()->students()->withCount('entries')->get();
$auditions = Audition::all();
return view('students.index',['students' => $students, 'auditions' => $auditions]);
return view('students.index', ['students' => $students, 'auditions' => $auditions]);
}
/**
@ -38,10 +42,15 @@ class StudentController extends Controller
*/
public function store(Request $request)
{
if ($request->user()->cannot('create', Student::class)) abort(403);
if ($request->user()->cannot('create', Student::class)) {
abort(403);
}
$request->validate([
'first_name' => ['required'],
'last_name' => ['required', new UniqueFullNameAtSchool(request('first_name'),request('last_name'), Auth::user()->school_id)],
'last_name' => [
'required',
new UniqueFullNameAtSchool(request('first_name'), request('last_name'), Auth::user()->school_id),
],
'grade' => ['required', 'integer'],
]);
@ -49,10 +58,10 @@ class StudentController extends Controller
'first_name' => request('first_name'),
'last_name' => request('last_name'),
'grade' => request('grade'),
'school_id' => Auth::user()->school_id
'school_id' => Auth::user()->school_id,
]);
$request->session()->put('auditionMessages',['success','I did it again ma']);
$request->session()->put('auditionMessages', ['success', 'I did it again ma']);
return redirect('/students');
}
@ -70,7 +79,10 @@ class StudentController extends Controller
*/
public function edit(Request $request, Student $student)
{
if ($request->user()->cannot('update', $student)) abort(403);
if ($request->user()->cannot('update', $student)) {
abort(403);
}
return view('students.edit', ['student' => $student]);
}
@ -79,7 +91,9 @@ class StudentController extends Controller
*/
public function update(Request $request, Student $student)
{
if ($request->user()->cannot('update', $student)) abort(403);
if ($request->user()->cannot('update', $student)) {
abort(403);
}
request()->validate([
'first_name' => ['required'],
'last_name' => ['required'],
@ -89,13 +103,12 @@ class StudentController extends Controller
$student->update([
'first_name' => request('first_name'),
'last_name' => request('last_name'),
'grade' => request('grade')
'grade' => request('grade'),
]);
// TODO if a students grade is changed, we need to be sure they are still eligible for the auditions in which they are entered.
return redirect('/students');
return redirect('/students')->with('success', 'Student updated successfully.');
}
/**
@ -103,8 +116,11 @@ class StudentController extends Controller
*/
public function destroy(Request $request, Student $student)
{
if ($request->user()->cannot('delete', $student)) abort(403);
if ($request->user()->cannot('delete', $student)) {
abort(403);
}
$student->delete();
return redirect('/students');
return redirect(route('students.index'));
}
}

View File

@ -20,7 +20,7 @@ class CheckIfAdmin
return $next($request);
}
return redirect('/')->with('error', 'You do not have admin access.');
return redirect(route('dashboard'))->with('error', 'You are not authorized to perform this action');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
use function redirect;
use function route;
class CheckIfHasSchool
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::check() && Auth::user()->school_id) {
return $next($request);
}
return redirect(route('dashboard'))->with('error', 'You do not have a school to view students for.');
}
}

View File

@ -10,8 +10,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use function now;
class Audition extends Model
{
use HasFactory;
@ -26,7 +24,6 @@ class Audition extends Model
protected $scored_entries_count; //Set by TabulationService
public function event(): BelongsTo
{
return $this->belongsTo(Event::class);
@ -175,12 +172,14 @@ class Audition extends Model
}
$this->flags()->create(['flag_name' => $flag]);
$this->load('flags');
}
public function removeFlag($flag): void
{
// remove related auditionFlag where flag_name = $flag
$this->flags()->where('flag_name', $flag)->delete();
$this->load('flags');
}
public function scopeOpen(Builder $query): void

View File

@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -40,4 +41,16 @@ class Ensemble extends Model
{
return $this->hasMany(Seat::class);
}
public function scopeForAudition(Builder $query, $audition_id): Builder
{
$audition = Audition::find($audition_id);
// get instances of this class where the event_id is equal to the event_id of the audition with $audition_id
return $query->where('event_id', $audition->event_id);
}
public function scopeForEvent(Builder $query, $event_id): Builder
{
return $query->where('event_id', $event_id);
}
}

View File

@ -75,14 +75,15 @@ class Entry extends Model
if ($this->hasFlag($flag)) {
return;
}
$this->flags()->create(['flag_name' => $flag]);
$this->load('flags');
}
public function removeFlag($flag): void
{
// remove related auditionFlag where flag_name = $flag
$this->flags()->where('flag_name', $flag)->delete();
$this->load('flags');
}
/**

View File

@ -45,12 +45,14 @@ class Room extends Model
public function addJudge($userId): void
{
$this->judges()->attach($userId);
$this->load('judges');
AuditionChange::dispatch();
}
public function removeJudge($userId): void
{
$this->judges()->detach($userId);
$this->load('judges');
AuditionChange::dispatch();
}
}

View File

@ -9,6 +9,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class RoomUser extends Model
{
protected $table = 'room_user';
protected $guarded = [];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
@ -16,7 +19,7 @@ class RoomUser extends Model
public function judge(): BelongsTo
{
return $this->belongsTo(User::class);
return $this->belongsTo(User::class, 'user_id');
}
public function room(): BelongsTo

View File

@ -38,11 +38,6 @@ class ScoreSheet extends Model
);
}
public function getSubscore($id)
{
return $this->subscores[$id]['score'] ?? false;
}
public function isValid()
{
// TODO move to either TabulationService or a specific service for scoreValidation

View File

@ -21,7 +21,19 @@ class Student extends Model
public function users(): HasManyThrough
{
return $this->hasManyThrough(User::class, School::class);
return $this->hasManyThrough(
User::class, // The target model we want to access
School::class, // The intermediate model through which we access the target model
'id', // The foreign key on the intermediate model
'school_id', // The foreign key on the target model
'school_id', // The local key
'id' // The local key on the intermediate model
);
}
public function directors(): HasManyThrough
{
return $this->users();
}
public function entries(): HasMany
@ -32,8 +44,9 @@ class Student extends Model
public function full_name(bool $last_name_first = false): string
{
if ($last_name_first) {
return ($this->last_name.', '.$this->first_name);
return $this->last_name.', '.$this->first_name;
}
return $this->first_name.' '.$this->last_name;
}
}

View File

@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
class User extends Authenticatable implements MustVerifyEmail
{
@ -131,7 +132,7 @@ class User extends Authenticatable implements MustVerifyEmail
*
* @return SchoolEmailDomain[]
*/
public function possibleSchools()
public function possibleSchools(): Collection
{
if ($this->school_id) {
$return[] = $this->school;
@ -148,7 +149,11 @@ class User extends Authenticatable implements MustVerifyEmail
return true;
}
return $this->is_tab;
if ($this->is_tab) {
return true;
}
return false;
}
public function scoreSheets(): HasMany

View File

@ -13,8 +13,7 @@ class EntryObserver
*/
public function created(Entry $entry): void
{
AuditionChange::dispatch();
EntryChange::dispatch($entry->audition_id);
}
/**
@ -22,8 +21,7 @@ class EntryObserver
*/
public function updated(Entry $entry): void
{
AuditionChange::dispatch();
EntryChange::dispatch($entry->audition_id);
}
/**
@ -31,8 +29,7 @@ class EntryObserver
*/
public function deleted(Entry $entry): void
{
AuditionChange::dispatch();
EntryChange::dispatch($entry->audition_id);
}
/**
@ -40,8 +37,7 @@ class EntryObserver
*/
public function restored(Entry $entry): void
{
AuditionChange::dispatch();
EntryChange::dispatch($entry->audition_id);
}
/**
@ -49,6 +45,6 @@ class EntryObserver
*/
public function forceDeleted(Entry $entry): void
{
EntryChange::dispatch($entry->audition_id);
}
}

View File

@ -21,8 +21,7 @@ class StudentObserver
*/
public function updated(Student $student): void
{
AuditionChange::dispatch();
EntryChange::dispatch();
//
}
/**
@ -30,7 +29,7 @@ class StudentObserver
*/
public function deleted(Student $student): void
{
AuditionChange::dispatch();
//
}
/**

View File

@ -4,7 +4,7 @@ namespace App\Policies;
use App\Models\Entry;
use App\Models\User;
use Illuminate\Auth\Access\Response;
use function is_null;
class EntryPolicy
@ -14,7 +14,7 @@ class EntryPolicy
*/
public function viewAny(User $user): bool
{
//
return true;
}
/**
@ -22,8 +22,11 @@ class EntryPolicy
*/
public function view(User $user, Entry $entry): bool
{
if($user->is_admin) return true;
return $user->school_id == $entry->student()->school_id;
if ($user->is_admin) {
return true;
}
return $user->school_id == $entry->student->school_id;
}
/**
@ -31,7 +34,10 @@ class EntryPolicy
*/
public function create(User $user): bool
{
if($user->is_admin) return true;
if ($user->is_admin) {
return true;
}
return ! is_null($user->school_id);
}
@ -40,8 +46,11 @@ class EntryPolicy
*/
public function update(User $user, Entry $entry): bool
{
if($user->is_admin) return true;
return $user->school_id == $entry->student()->school_id;
if ($user->is_admin) {
return true;
}
return $user->school_id == $entry->student->school_id;
}
/**
@ -49,13 +58,15 @@ class EntryPolicy
*/
public function delete(User $user, Entry $entry): bool
{
if($user->is_admin) return true;
if ($user->is_admin) {
return true;
}
// Return false if $entry->audition->entry_deadline is in the past, continue if not
if ($entry->audition->entry_deadline < now()) {
return false;
}
return $user->school_id == $entry->student()->school_id;
return $user->school_id == $entry->student->school_id;
}
/**
@ -63,7 +74,7 @@ class EntryPolicy
*/
public function restore(User $user, Entry $entry): bool
{
//
return true;
}
/**
@ -71,6 +82,6 @@ class EntryPolicy
*/
public function forceDelete(User $user, Entry $entry): bool
{
//
return true;
}
}

View File

@ -5,7 +5,7 @@ namespace App\Policies;
use App\Models\Entry;
use App\Models\Student;
use App\Models\User;
use Illuminate\Auth\Access\Response;
use function is_null;
class StudentPolicy
@ -31,7 +31,10 @@ class StudentPolicy
*/
public function create(User $user): bool
{
if($user->is_admin) return true;
if ($user->is_admin) {
return true;
}
return ! is_null($user->school_id);
}
@ -41,7 +44,10 @@ class StudentPolicy
public function update(User $user, Student $student): bool
{
if($user->is_admin) return true;
if ($user->is_admin) {
return true;
}
return $user->school_id == $student->school_id;
}
@ -50,7 +56,10 @@ class StudentPolicy
*/
public function delete(User $user, Student $student): bool
{
if (Entry::where('student_id','=',$student->id)->exists()) return false; // Don't allow deletion of a student with entries
if (Entry::where('student_id', '=', $student->id)->exists()) {
return false;
} // Don't allow deletion of a student with entries
return $user->school_id == $student->school_id;
}

View File

@ -19,7 +19,7 @@ class UniqueFullNameAtSchool implements ValidationRule
$this->school_id = $schoolID;
}
public function passes($attributies, $value)
public function studentExists()
{
return Student::where('first_name', $this->first_name)
->where('last_name', $this->last_name)
@ -38,6 +38,8 @@ class UniqueFullNameAtSchool implements ValidationRule
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
//
if($this->studentExists()) {
$fail($this->message());
}
}
}

View File

@ -69,9 +69,6 @@ class AuditionService
public function clearCache(): void
{
if (App::environment('local')) {
Session::flash('success', 'Audition Cache Cleared');
}
Cache::forget($this->cacheKey);
}

View File

@ -9,6 +9,7 @@
"codedge/laravel-fpdf": "^1.12",
"laravel/fortify": "^1.21",
"laravel/framework": "^11.0",
"laravel/pail": "^1.1",
"laravel/tinker": "^2.9",
"predis/predis": "^2.2",
"symfony/http-client": "^7.1",
@ -16,6 +17,7 @@
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.13",
"brianium/paratest": "^7.4",
"fakerphp/faker": "^1.23",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
@ -23,6 +25,7 @@
"nunomaduro/collision": "^8.0",
"pestphp/pest": "^2.34",
"pestphp/pest-plugin-laravel": "^2.4",
"sinnbeck/laravel-dom-assertions": "^1.5",
"spatie/laravel-ignition": "^2.4"
},
"autoload": {

150
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8a539bb700e8acfbb39c896521c16609",
"content-hash": "7aab57ef52f0152526434decd76ef1e1",
"packages": [
{
"name": "bacon/bacon-qr-code",
@ -1491,6 +1491,84 @@
},
"time": "2024-05-21T17:57:45+00:00"
},
{
"name": "laravel/pail",
"version": "v1.1.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/pail.git",
"reference": "c22fe771277971eb9cd224955996bcf39c1a710d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pail/zipball/c22fe771277971eb9cd224955996bcf39c1a710d",
"reference": "c22fe771277971eb9cd224955996bcf39c1a710d",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-pcntl": "*",
"illuminate/console": "^10.24|^11.0",
"illuminate/contracts": "^10.24|^11.0",
"illuminate/log": "^10.24|^11.0",
"illuminate/process": "^10.24|^11.0",
"illuminate/support": "^10.24|^11.0",
"nunomaduro/termwind": "^1.15|^2.0",
"php": "^8.2",
"symfony/console": "^6.0|^7.0"
},
"require-dev": {
"laravel/pint": "^1.13",
"orchestra/testbench": "^8.12|^9.0",
"pestphp/pest": "^2.20",
"pestphp/pest-plugin-type-coverage": "^2.3",
"phpstan/phpstan": "^1.10",
"symfony/var-dumper": "^6.3|^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
},
"laravel": {
"providers": [
"Laravel\\Pail\\PailServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Pail\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
},
{
"name": "Nuno Maduro",
"email": "enunomaduro@gmail.com"
}
],
"description": "Easily delve into your Laravel application's log files directly from the command line.",
"homepage": "https://github.com/laravel/pail",
"keywords": [
"laravel",
"logs",
"php",
"tail"
],
"support": {
"issues": "https://github.com/laravel/pail/issues",
"source": "https://github.com/laravel/pail"
},
"time": "2024-05-08T18:19:39+00:00"
},
{
"name": "laravel/prompts",
"version": "v0.1.22",
@ -9301,6 +9379,76 @@
],
"time": "2023-02-07T11:34:05+00:00"
},
{
"name": "sinnbeck/laravel-dom-assertions",
"version": "v1.5.3",
"source": {
"type": "git",
"url": "https://github.com/sinnbeck/laravel-dom-assertions.git",
"reference": "a2ce7540023fac4e6e010cbe5396b7aad9d22765"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sinnbeck/laravel-dom-assertions/zipball/a2ce7540023fac4e6e010cbe5396b7aad9d22765",
"reference": "a2ce7540023fac4e6e010cbe5396b7aad9d22765",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"illuminate/testing": "^9.0|^10.0|^11.0",
"php": "^8.0",
"symfony/css-selector": "^6.0|^7.0"
},
"require-dev": {
"laravel/pint": "^1.2",
"nunomaduro/larastan": "^2.2",
"orchestra/testbench": "^7.0|^8.0|^9.0",
"pestphp/pest": "^1.0|^2.34",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.1",
"vimeo/psalm": "^4.29|^5.22"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Sinnbeck\\DomAssertions\\DomAssertionsServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Sinnbeck\\DomAssertions\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "René Sinnbeck",
"email": "rene.sinnbeck@gmail.com",
"homepage": "https://sinnbeck.dev",
"role": "Developer"
}
],
"homepage": "https://github.com/sinnbeck/laravel-dom-assertions",
"keywords": [
"assertions",
"blade",
"dom",
"laravel",
"view"
],
"support": {
"issues": "https://github.com/sinnbeck/laravel-dom-assertions/issues",
"source": "https://github.com/sinnbeck/laravel-dom-assertions/tree/v1.5.3"
},
"time": "2024-06-17T12:30:14+00:00"
},
{
"name": "spatie/backtrace",
"version": "1.6.1",

View File

@ -41,14 +41,18 @@ class AuditionFactory extends Factory
return [
'event_id' => $event->id,
'name' => $this->faker->randomElement($instruments).$this->faker->randomNumber(1),
'score_order' => 1,
#'name' => $this->faker->randomElement($instruments).$this->faker->numberBetween(1, 1000),
'name' => 'New Instrument ' . $this->faker->unique()->words(4,true),
'score_order' => $this->faker->numberBetween(2, 50),
'entry_deadline' => Carbon::tomorrow(),
'entry_fee' => 1000,
'minimum_grade' => 7,
'maximum_grade' => 12,
'minimum_grade' => $this->faker->numberBetween(7, 9),
'maximum_grade' => $this->faker->numberBetween(8, 12),
'for_seating' => 1,
'for_advancement' => 1,
'room_id' => null,
'order_in_room' => 0,
'scoring_guide_id' => null,
];
}
@ -58,4 +62,17 @@ class AuditionFactory extends Factory
fn (array $attributes) => ['entry_deadline' => $entryDeadline ?? Carbon::yesterday()]
);
}
public function seatingOnly(): self
{
return $this->state(
fn (array $attributes) => ['for_advancement' => 0]
);
}
public function advancementOnly(): self
{
return $this->state(
fn (array $attributes) => ['for_seating' => 0]
);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Ensemble>
*/
class EnsembleFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => $this->faker->name,
'event_id' => \App\Models\Event::factory(),
'code' => $this->faker->randomLetter().$this->faker->randomLetter,
'rank' => $this->faker->numberBetween(1, 10),
];
}
}

View File

@ -26,6 +26,7 @@ class EntryFactory extends Factory
'draw_number' => null,
'for_seating' => 1,
'for_advancement' => 1,
];
}

View File

@ -17,7 +17,7 @@ class EventFactory extends Factory
public function definition(): array
{
return [
'name' => $this->faker->name(),
'name' => $this->faker->unique()->name(),
];
}
}

View File

@ -17,8 +17,8 @@ class RoomFactory extends Factory
public function definition(): array
{
return [
'name' => 'Room ' . fake()->numberBetween(7,500),
'description' => fake()->sentence()
'name' => 'Room '.fake()->unique()->numberBetween(7, 500),
'description' => fake()->sentence(),
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Database\Factories;
use App\Models\School;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\SchoolEmailDomain>
*/
class SchoolEmailDomainFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$school = School::factory()->create();
return [
'school_id' => $school->id,
'domain' => $this->faker->unique()->domainName,
];
}
}

View File

@ -17,7 +17,7 @@ class ScoringGuideFactory extends Factory
public function definition(): array
{
return [
//
'name' => $this->faker->sentence(3),
];
}
}

View File

@ -2,6 +2,7 @@
namespace Database\Factories;
use App\Models\ScoringGuide;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
@ -16,8 +17,45 @@ class SubscoreDefinitionFactory extends Factory
*/
public function definition(): array
{
$sg = ScoringGuide::factory()->create();
return [
//
'scoring_guide_id' => $sg->id,
'name' => $this->faker->word,
'max_score' => 100,
'weight' => $this->faker->numberBetween(1, 4),
'display_order' => $this->faker->numberBetween(1, 20),
'tiebreak_order' => $this->faker->numberBetween(1, 20),
'for_seating' => 1,
'for_advance' => 1,
];
}
public function seatingOnly(): self
{
return $this->state(
fn (array $attributes) => ['for_advance' => 0]
);
}
public function advanceOnly(): self
{
return $this->state(
fn (array $attributes) => ['for_seating' => 0]
);
}
public function displayFirst(): self
{
return $this->state(
fn (array $attributes) => ['display_order' => 0]
);
}
public function tiebreakFirst(): self
{
return $this->state(
fn (array $attributes) => ['tiebreak_order' => 0]
);
}
}

View File

@ -1,10 +1,7 @@
<?php
use App\Models\Room;
use App\Models\ScoringGuide;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
@ -16,10 +13,10 @@ return new class extends Migration
if (! Room::find(0)) {
$room = Room::create([
'id' => 0,
'name' => 'No Guide Assigned'
'name' => 'No Guide Assigned',
]);
$room->update([
'id' => 0
'id' => 0,
]);
}
}

View File

@ -1,6 +1,5 @@
<?php
use App\Models\Room;
use App\Models\ScoringGuide;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
@ -20,10 +19,10 @@ return new class extends Migration
if (! ScoringGuide::find(0)) {
$sg = ScoringGuide::create([
'id' => 0,
'name' => 'No Guide Assigned'
'name' => 'No Guide Assigned',
]);
$sg->update([
'id' => 0
'id' => 0,
]);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace Database\Seeders;
use App\Models\SiteSetting;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SampleSettingsSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
SiteSetting::create([
'setting_key' => 'auditionName',
'setting_value' => 'Somewhere Band Directors Association',
]);
SiteSetting::create([
'setting_key' => 'auditionAbbreviation',
'setting_value' => 'SBDA',
]);
SiteSetting::create([
'setting_key' => 'registrationCode',
'setting_value' => 'secret',
]);
SiteSetting::create([
'setting_key' => 'advanceTo',
'setting_value' => 'OMEA',
]);
SiteSetting::create([
'setting_key' => 'judging_enabled',
'setting_value' => '1',
]);
SiteSetting::create([
'setting_key' => 'organizerName',
'setting_value' => 'John Doe',
]);
SiteSetting::create([
'setting_key' => 'organizerEmail',
'setting_value' => 'jdoe@sbda.null',
]);
SiteSetting::create([
'setting_key' => 'olympic_scoring',
'setting_value' => '0',
]);
SiteSetting::create([
'setting_key' => 'fee_structure',
'setting_value' => 'oneFeePerEntry',
]);
SiteSetting::create([
'setting_key' => 'late_fee',
'setting_value' => '1000',
]);
SiteSetting::create([
'setting_key' => 'school_fee',
'setting_value' => '2500',
]);
SiteSetting::create([
'setting_key' => 'payment_address',
'setting_value' => '143 Sousa Lane',
]);
SiteSetting::create([
'setting_key' => 'payment_city',
'setting_value' => 'Maud',
]);
SiteSetting::create([
'setting_key' => 'payment_state',
'setting_value' => 'OK',
]);
SiteSetting::create([
'setting_key' => 'payment_zip',
'setting_value' => '77777',
]);
}
}

View File

@ -1,7 +1,7 @@
<x-layout.app>
<x-slot:page_title>Audition Settings</x-slot:page_title>
<x-layout.page-section-container>
<x-form.form method="POST" action="{{ route('audition-settings-save') }}">
<x-form.form id="settingsForm" method="POST" action="{{ route('audition-settings-save') }}">
<x-layout.page-section>
<x-slot:section_name>Group Information</x-slot:section_name>
@ -60,6 +60,15 @@
</x-form.body-grid>
</x-layout.page-section>
<x-layout.page-section>
<x-slot:section_name>Payment Address</x-slot:section_name>
<x-form.body-grid columns="12" class="m-3">
<x-form.field label_text="Payment Address" name="payment_address" colspan="5" :value="auditionSetting('payment_address')"/>
<x-form.field label_text="Payment City" name="payment_city" colspan="3" :value="auditionSetting('payment_city')"/>
<x-form.field label_text="Payment State" name="payment_state" colspan="2" :value="auditionSetting('payment_state')"/>
<x-form.field label_text="Payment Zip" type="number" name="payment_zip" colspan="2" :value="auditionSetting('payment_zip')"/>
</x-form.body-grid>
</x-layout.page-section>
<div class="grid grid-cols-12">
<div class="col-span-2 col-start-11 my-5 mr-3">

View File

@ -3,6 +3,13 @@
<x-card.card class="max-w-lg mx-auto">
<x-card.heading>
Edit Audition
<x-slot:right_side>
@if($audition->entries->count() == 0)
<x-delete-resource-modal title="Delete Audition {{ $audition->name }}" action="{{ route('admin.auditions.destroy', $audition) }}">
Please confirm that you would like to delete the audition {{ $audition->name }}. This action cannot be undone.
</x-delete-resource-modal>
@endif
</x-slot:right_side>
{{-- TODO implement a way to update multiple auditions as once --}}
</x-card.heading>
<x-form.form method="PATCH" action="/admin/auditions/{{ $audition->id }}">
@ -39,18 +46,13 @@
</x-form.body-grid>
<x-form.footer submit-button-text="Update Audition" class="pb-4 !justify-between">
<div>
@if($audition->entries->count() == 0)
<x-form.red-trash-button form="deleteResource" size="20"></x-form.red-trash-button>
@endif
</div>
<div></div>
<div>
<x-form.button>Edit Audition</x-form.button>
</div>
</x-form.footer>
</x-form.form>
<x-form.delete-form action="/admin/auditions/{{ $audition->id }}"></x-form.delete-form>
</x-card.card>
</x-layout.app>

View File

@ -1,11 +1,11 @@
<x-layout.app>
<x-slot:page_title>Audition Administration</x-slot:page_title>
<x-card.card>
<x-table.table with_title_area sortable="false">
<x-table.table with_title_area sortable="false" id="auditions-table">
<x-slot:title class="ml-3">Auditions</x-slot:title>
<x-slot:subtitle class="ml-3">Drag to reorder. Double click to edit.</x-slot:subtitle>
<x-slot:title_block_right class="mr-3">
<x-form.button href="/admin/auditions/create">New Audition</x-form.button>
<x-form.button href="{{ route('admin.auditions.create') }}">New Audition</x-form.button>
</x-slot:title_block_right>
<thead>
@ -27,7 +27,7 @@
<div x-data="sortableList()" x-init="init">
<x-table.body id="sortable-list">
@foreach($auditions as $audition)
<tr data-id="{{ $audition->id }}"
<tr data-id="{{ $audition->id }}" id="auditionRow-{{ $audition->id }}"
@dblclick="window.location.href='/admin/auditions/{{ $audition->id }}/edit'">
<x-table.td>{{ $audition->event->name }}</x-table.td>
<x-table.td>{{ $audition->name }}</x-table.td>
@ -50,7 +50,7 @@
@endif
</x-table.td>
@endif
<x-table.td>{{ $audition->entries->count() }}</x-table.td>
<x-table.td>{{ $audition->entries_count }}</x-table.td>
</tr>
@endforeach
</x-table.body>

View File

@ -15,7 +15,7 @@
</thead>
<x-table.body>
@foreach($event->ensembles as $ensemble)
<tr data-id="{{ $ensemble->id }}">
<tr data-id="{{ $ensemble->id }}" id="ensembleRow-{{$ensemble->id}}">
<x-table.td class="handle">
<svg class="w-6 h-6 text-gray-800 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-width="2" d="M5 7h14M5 12h14M5 17h14"/>

View File

@ -1,7 +1,7 @@
<x-modal>
<x-slot:button_text>{{ $ensemble->name }}</x-slot:button_text>
<x-slot:title class="font-semibold">Rename Ensemble {{ $ensemble->name }}</x-slot:title>
<x-form.form method="PATCH" action="{{ route('admin.ensembles.updateEnsemble', ['ensemble' => $ensemble->rank]) }}">
<x-form.form method="PATCH" action="{{ route('admin.ensembles.update', ['ensemble' => $ensemble->rank]) }}">
<x-form.body-grid columns="4">
<x-form.field colspan="2" label_text="New Name" value="{{ $ensemble->name }}" name="name" />
<x-form.field colspan="1" label_text="New Code" value="{{ $ensemble->code }}" name="code" />

View File

@ -1,9 +1,8 @@
@php
$limits = [];
foreach ($ensemble->seatingLimits as $lim) {
$limits[$lim->audition_id] = $lim->maximum_accepted;
}
/**
* @var \App\Models\Ensemble $ensemble The ensemble for which a seating form is shown
* @var array $limits An array of audition_id => maximum_accepted for the ensemble
**/
@endphp
<x-card.card class="mt-5 max-w-md mx-auto">
<x-card.heading>{{ $ensemble->name }}</x-card.heading>

View File

@ -11,7 +11,14 @@
`{{ route('admin.ensembles.seatingLimits') }}/${event.target.value}`">
<option value=""></option>
@foreach($ensembles as $optionEnsemble)
<option value ="{{$optionEnsemble->id}}">{{$optionEnsemble->event->name}} - {{$optionEnsemble->name}}</option>
@php
if ($ensemble && $ensemble->id == $optionEnsemble->id) {
$selected = 'selected';
} else {
$selected = '';
}
@endphp
<option value ="{{$optionEnsemble->id}}" {{$selected}}>{{$optionEnsemble->event->name}} - {{$optionEnsemble->name}}</option>
@endforeach
</x-form.select>
</div>

View File

@ -1,7 +1,7 @@
<x-layout.app>
<x-card.card class="mx-auto max-w-2xl">
<x-card.heading>Create Entry</x-card.heading>
<x-form.form method="POST" action="/admin/entries">
<x-form.form id='createEntryForm' method="POST" action="/admin/entries">
<x-form.body-grid columns="3" x-data="studentAuditionFilter()">
<x-form.select name="student_id" colspan="2" x-model="selectedStudentId" @change="filterAuditions">
@ -35,7 +35,7 @@
<input type="hidden" name="for_seating" value="on">
@endif
</x-form.body-grid>
<x-form.footer>
<x-form.footer class="mb-5">
<x-form.button>Create Entry</x-form.button>
</x-form.footer>
</x-form.form>

View File

@ -5,19 +5,13 @@
Edit Entry #{{ $entry->id }}
<x-slot:right_side>
@if(! Seat::where('entry_id', $entry->id)->exists())
<form method="POST" action="{{ route('admin.entries.destroy',['entry' => $entry->id]) }}">
@csrf
@method('DELETE')
<x-form.red-trash-button type="submit" />
</form>
@else
Seated: {{ $entry->seat->ensemble->name }} #{{ $entry->seat->seat }}
@endif
<x-delete-resource-modal action="{{route('admin.entries.destroy',$entry->id)}}" title="Delete entry #{{ $entry->id }}">
Confirm you would like to delete entry #{{$entry->id}} by {{$entry->student->full_name()}} on {{$entry->audition->name}}.
</x-delete-resource-modal>
</x-slot:right_side>
</x-card.heading>
<x-form.form method="PATCH" action="/admin/entries/{{ $entry->id }}">
<x-form.form id='entryEditForm' method="PATCH" action="/admin/entries/{{ $entry->id }}">
<x-form.body-grid columns="6">
@if(! Seat::where('entry_id', $entry->id)->exists())
<x-form.select name="student_id" colspan="4" disabled>

View File

@ -4,7 +4,7 @@
{{-- Filter Control--}}
<x-card.card>
<x-card.heading>Set Filters</x-card.heading>
<x-form.form action="/filters/admin_entry_filter" method="POST">
<x-form.form action="{{ route('admin_entry_filter.set') }}" method="POST">
<x-form.body-grid columns="12">
<x-form.field name="id_filter" label_text="Entry ID" colspan="2" value="{{ $filters['id'] ?? '' }}"/>
<x-form.select name="audition_filter" colspan="4">
@ -40,7 +40,7 @@
<x-form.field name="last_name_filter" colspan="6" label_text="Last Name" value="{{ ($filters['last_name'] ?? null) }}"/>
</x-form.body-grid>
<x-form.footer class="pb-4">
<x-form.button-nocolor href="/filters/admin_entry_filter/clear">Clear Filters</x-form.button-nocolor>
<x-form.button-nocolor href="{{ route('admin_entry_filter.clear') }}">Clear Filters</x-form.button-nocolor>
<x-form.button>Apply Filters</x-form.button>
</x-form.footer>
</x-form.form>
@ -52,7 +52,7 @@
<x-slot:title class="ml-3">Entries</x-slot:title>
<x-slot:subtitle class="ml-3">Double click row to edit</x-slot:subtitle>
<x-slot:title_block_right class="mr-3">
<x-form.button href="/admin/entries/create">New Entry</x-form.button>
<x-form.button href="{{ route('admin.entries.create') }}">New Entry</x-form.button>
</x-slot:title_block_right>
<thead>
@ -71,7 +71,7 @@
</thead>
<x-table.body>
@foreach($entries as $entry)
<tr @dblclick="window.location.href='{{ route('admin.entries.edit',['entry' => $entry->id]) }}'">
<tr @dblclick="window.location.href='{{ route('admin.entries.edit', ['entry' => $entry->id]) }}'">
<x-table.td><a href="/admin/entries/{{ $entry->id }}/edit"> {{ $entry->id }} </a></x-table.td>
<x-table.td>{{ $entry->audition->name }}</x-table.td>
<x-table.td>{{ $entry->student->full_name() }}</x-table.td>

View File

@ -1,10 +1,12 @@
<x-card.card x-data="{ showModal: false }" class="relative">
@if($room->id != '0')
@include('admin.rooms.index-edit-room-modal')
@endif
<x-card.heading @click=" showModal = ! showModal">
{{ $room->name }}
<x-slot:subheading>{{ $room->description }}</x-slot:subheading>
<x-slot:right_side>
@if($room->entries->count() === 0 and $room->id != '0')
@if($room->auditions->count() === 0 and $room->id != '0')
<div class="flex justify-end">
<x-form.form method="DELETE"
action="{{ route('admin.rooms.destroy', ['room'=>$room->id]) }}"

View File

@ -1,6 +1,6 @@
<x-layout.app>
<x-slot:page_title>Rooms</x-slot:page_title>
<x-slot:title_bar_right><x-form.button href="/admin/rooms/create">New Room</x-form.button></x-slot:title_bar_right>
<x-slot:title_bar_right><x-form.button href="{{route('admin.rooms.create')}}">New Room</x-form.button></x-slot:title_bar_right>
<!--suppress JSUnresolvedReference -->
<div class="grid md:grid-cols-4 gap-5" x-data="roomManager()">

View File

@ -5,7 +5,7 @@
@if($room->id == 0)
@continue
@endif
<li class=" rounded-xl border border-gray-200 bg-gray-50 "> {{-- card wrapper --}}
<li id="room-{{$room->id}}-card" class=" rounded-xl border border-gray-200 bg-gray-50 "> {{-- card wrapper --}}
<div class="flex items-center gap-x-4 border-b border-gray-900/5 bg-white pt-2 pb-6 px-6"> {{-- card header --}}
<div class="text-sm font-medium leading-6 text-gray-900">
<p class="text-sm font-medium leading-6 text-gray-900">{{ $room->name }}</p>
@ -60,8 +60,8 @@
<div class="flex justify-between items-center gap-x-4 py-1"> {{-- Judge Line --}}
<dt>
<p>
<span class="text-gray-700">{{ $judge->full_name() }}, </span>
<span class="text-gray-500 text-xs">{{ $judge->school->name }}</span>
<span class="text-gray-700">{{ $judge->full_name() }} </span>
<span class="text-gray-500 text-xs">{{ $judge->school->name ?? '' }}</span>
</p>
<p class="text-gray-500 text-xs">{{ $judge->judging_preference }}</p>
</dt>

View File

@ -5,7 +5,7 @@
</x-card.heading>
<x-form.form method="POST" action="/admin/schools" class="!mt-4">
<x-form.form method="POST" action="{{ route('admin.schools.store') }}" class="!mt-4">
<x-form.body-grid columns="6" class="max-w-full">
<x-form.field name="name" label_text="School Name" colspan="6"/>
<x-form.field name="address" label_text="School Address" colspan="6"/>

View File

@ -1,7 +1,54 @@
<x-layout.app>
<x-school.school-edit-form :form_action="'admin/schools/' . $school->id " :school="$school" />
<x-card.card class="mx-auto max-w-xl">
<x-card.heading>
Edit School
@if($school->students_count === 0)
<x-slot:right_side>
<x-delete-resource-modal action="{{ route('admin.schools.destroy',$school) }}" title="Delete school {{ $school->name }}">
Confirm you would like to delete the school {{ $school->name }}. This action cannot be undone.
</x-delete-resource-modal>
</x-slot:right_side>
@endif
</x-card.heading>
<x-form.form method="PATCH" action="{{ route('admin.schools.update',$school) }}">
<x-form.body-grid>
<x-form.field name="name" label_text="Name" colspan="6" value="{{ $school->name }}" />
<x-form.field name="address" label_text="Address" colspan="6" value="{{ $school->address }}" />
<x-form.field name="city" label_text="City" colspan="3" value="{{ $school->city }}" />
<x-form.field name="state" label_text="State" colspan="2" value="{{ $school->state }}" />
<x-form.field name="zip" label_text="Zip" colspan="1" value="{{ $school->zip }}" />
</x-form.body-grid>
<x-form.footer>
<x-form.button class="mb-5">Update School</x-form.button>
</x-form.footer>
</x-form.form>
</x-card.card>
<x-school.school-domain-form :school="$school" />
<x-card.card class="mx-auto max-w-sm mt-8">
<x-card.heading>Associated Domains</x-card.heading>
<x-card.list.body>
@foreach($school->emailDomains as $domain)
<x-card.list.row class="!py-1.5 align-middle">
<form method="POST" action="{{ route('admin.schools.destroy_domain',$domain) }}" id="deleteDomain{{$domain->id}}">
@csrf
@method('DELETE')
<button type="submit">
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm7.707-3.707a1 1 0 0 0-1.414 1.414L10.586 12l-2.293 2.293a1 1 0 1 0 1.414 1.414L12 13.414l2.293 2.293a1 1 0 0 0 1.414-1.414L13.414 12l2.293-2.293a1 1 0 0 0-1.414-1.414L12 10.586 9.707 8.293Z" clip-rule="evenodd"/>
</svg>
</button><span class="px-3">{{ $domain->domain }}</span>
</form>
</x-card.list.row>
@endforeach
<x-card.list.row class="!py-1.5">
<form method="POST" action="{{ route('admin.schools.add_domain',$school) }}" class="grid sm:grid-cols-2 gap-4">
@csrf
<x-form.field name="domain" label_text="Add Domain" /><x-form.button class="sm:mt-6">Add Domain</x-form.button>
</form>
</x-card.list.row>
</x-card.list.body>
</x-card.card>
</x-layout.app>

View File

@ -22,9 +22,9 @@
<x-table.body>
@foreach($schools as $school)
<tr>
<x-table.td><a href="/admin/schools/{{ $school->id }}">{{ $school->name }}</a></x-table.td>
<x-table.td><a href="{{ route('admin.schools.show',$school) }}">{{ $school->name }}</a></x-table.td>
<x-table.td>
<a href="{{ route('admin.schools.invoice',$school->id) }}">
<a href="{{ route('admin.schools.invoice',$school) }}">
${{ number_format($schoolTotalFees[$school->id],2) }}
</a>
</x-table.td>

View File

@ -3,7 +3,7 @@
<x-card.list.body>
@foreach($school->emailDomains as $domain)
<x-card.list.row class="!py-1.5 align-middle">
<form method="POST" action="/admin/schools/domain/{{ $domain->id }}" id="deleteDomain{{$domain->id}}">
<form method="POST" action="{{ route('admin.schools.destroy_domain',$domain) }}" id="deleteDomain{{$domain->id}}">
@csrf
@method('DELETE')
<button type="submit">
@ -16,7 +16,7 @@
</x-card.list.row>
@endforeach
<x-card.list.row class="!py-1.5">
<form method="POST" action="/admin/schools/{{ $school->id }}/add_domain" class="grid sm:grid-cols-2 gap-4">
<form method="POST" action="{{ route('admin.schools.add_domain',$school) }}" class="grid sm:grid-cols-2 gap-4">
@csrf
<x-form.field name="domain" label_text="Add Domain" /><x-form.button class="sm:mt-6">Add Domain</x-form.button>
</form>

View File

@ -18,9 +18,10 @@
<div class="border-b border-gray-200">
<nav class="-mb-px flex" aria-label="Tabs">
<!-- Current: "border-indigo-500 text-indigo-600", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" -->
<a href="{{ request()->getPathInfo() }}?tab=detail" class="{{ $tab == 'detail' ? $active_classes : $normal_classes }}">Details</a>
<a href="{{ request()->getPathInfo() }}?tab=displayOrder" class="{{ $tab == 'displayOrder' ? $active_classes : $normal_classes }}">Display Order</a>
<a href="{{ request()->getPathInfo() }}?tab=tiebreakOrder" class="{{ $tab == 'tiebreakOrder' ? $active_classes : $normal_classes }}">Tiebreak Order</a>
<a href="{{ route('admin.scoring.edit',['guide'=>$guide, 'tab'=>'detail']) }}" class="{{ $tab == 'detail' ? $active_classes : $normal_classes }}">Details</a>
<a href="{{ route('admin.scoring.edit',['guide'=>$guide, 'tab'=>'displayOrder']) }}" class="{{ $tab == 'displayOrder' ? $active_classes : $normal_classes }}">Display Order</a>
{{-- <a href="{{ request()->getPathInfo() }}?tab=tiebreakOrder" class="{{ $tab == 'tiebreakOrder' ? $active_classes : $normal_classes }}">Tiebreak Order</a>--}}
<a href="{{ route('admin.scoring.edit',['guide'=>$guide, 'tab'=>'tiebreakOrder']) }}" class="{{ $tab == 'tiebreakOrder' ? $active_classes : $normal_classes }}">Tiebreak Order</a>
</nav>
</div>
</div>

View File

@ -54,7 +54,7 @@
let auditionId = itemEl.getAttribute('data-id');
// Make an AJAX request to update the audition_guide_id
fetch('/admin/scoring/assign_guide_to_audition', {
fetch('{{route('ajax.assignScoringGuideToAudition')}}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -17,14 +17,14 @@
@continue
@endif
<tr>
<x-table.td>{{ $guide->name }} <span class="text-xs text-gray-400">{{ $guide->subscores->count() }} subscores</span></x-table.td>
<x-table.td class="text-right text-indigo-600"><a href="/admin/scoring/guides/{{ $guide->id }}/edit">Edit</a></x-table.td>
<x-table.td>{{ $guide->name }} <span class="text-xs text-gray-400">{{ $guide->subscores_count }} subscores</span></x-table.td>
<x-table.td class="text-right text-indigo-600"><a href="{{ route('admin.scoring.edit', $guide) }}">Edit</a></x-table.td>
</tr>
@endforeach
</x-table.body>
<tfoot>
<tr>
<x-form.form method="POST" action="/admin/scoring/guides" class="!px-0 !py-0">
<x-form.form method="POST" action="{{ route('admin.scoring.store') }}" class="!px-0 !py-0">
<x-table.td>
<x-form.field name="name" label_text="Add New Scoring Guide" />
</x-table.td>

View File

@ -1,7 +1,7 @@
<x-layout.app>
<x-card.card class="mx-auto max-w-xl">
<x-card.heading>Create Student</x-card.heading>
<x-form.form method="POST" action="/admin/students">
<x-form.form method="POST" action="{{ route('admin.students.store') }}">
<x-form.body-grid columns="12">
<x-form.field name="first_name" label_text="First Name" colspan="5" />
<x-form.field name="last_name" label_text="Last Name" colspan="5" />
@ -22,7 +22,7 @@
</x-form.select>
</x-form.body-grid>
<x-form.footer>
<x-form.footer class="pb-5">
<x-form.button>Create Student</x-form.button>
</x-form.footer>
</x-form.form>

View File

@ -1,10 +1,19 @@
<x-layout.app>
<x-card.card class="mx-auto max-w-xl">
<x-card.heading>Edit Student</x-card.heading>
<x-form.form method="PATCH" action="/admin/students/{{ $student->id }}">
<x-form.body-grid columns="12">
<x-form.field name="first_name" label_text="First Name" colspan="5" value="{{ $student->first_name }}" />
<x-form.field name="last_name" label_text="Last Name" colspan="5" value="{{ $student->last_name }}" />
<x-card.heading>
Edit Student
@if($student->entries_count === 0)
<x-slot:right_side>
<x-delete-resource-modal title="Delete Student {{ $student->full_name() }} from {{ $student->school->name }}" :action="route('admin.students.destroy',$student)">
Please confirm you'd like to delete this student. This action cannot be undone.
</x-delete-resource-modal>
</x-slot:right_side>
@endif
</x-card.heading>
<x-form.form method="PATCH" action="{{ route('admin.students.update',$student) }}">
<x-form.body-grid columns="8">
<x-form.field name="first_name" label_text="First Name" colspan="3" value="{{ $student->first_name }}" />
<x-form.field name="last_name" label_text="Last Name" colspan="3" value="{{ $student->last_name }}" />
<x-form.select name="grade" colspan="2">
<x-slot:label>Grade</x-slot:label>
@php($n = $minGrade)
@ -13,7 +22,7 @@
@php($n++);
@endwhile
</x-form.select>
<x-form.select name="school_id" colspan="12">
<x-form.select name="school_id" colspan="8">
<x-slot:label>School</x-slot:label>
@foreach ($schools as $school)

View File

@ -6,7 +6,7 @@
<x-slot:title class="ml-3">Students</x-slot:title>
<x-slot:subtitle class="ml-3">Click name to edit</x-slot:subtitle>
<x-slot:title_block_right class="mr-3">
<x-form.button href="/admin/students/create">New Student</x-form.button>
<x-form.button href="{{ route('admin.students.create') }}">New Student</x-form.button>
</x-slot:title_block_right>
<thead>
@ -20,10 +20,10 @@
<x-table.body>
@foreach($students as $student)
<tr>
<x-table.td><a href="/admin/students/{{ $student->id }}/edit">{{ $student->full_name(true) }}</a></x-table.td>
<x-table.td><a href="{{ route('admin.students.edit',$student) }}">{{ $student->full_name(true) }}</a></x-table.td>
<x-table.td>{{ $student->school->name }}</x-table.td>
<x-table.td>{{ $student->grade }}</x-table.td>
<x-table.td>{{ $student->entries->count() }}</x-table.td>
<x-table.td>{{ $student->entries_count }}</x-table.td>
</tr>
@endforeach
</x-table.body>

View File

@ -1,7 +1,7 @@
<x-layout.app>
<x-card.card class="mx-auto max-w-lg">
<x-card.heading>Create User</x-card.heading>
<x-form.form method="POST" action="/admin/users">
<x-form.form method="POST" action="{{ route('admin.users.store') }}">
<x-form.body-grid>
<x-form.field name="first_name" label_text="First Name" colspan="3" />
<x-form.field name="last_name" label_text="Last Name" colspan="3" />
@ -18,7 +18,7 @@
</x-form.select>
</x-form.body-grid>
<x-form.footer>
<x-form.footer class="pb-5">
<x-form.button>Create User</x-form.button>
</x-form.footer>
</x-form.form>

View File

@ -1,7 +1,18 @@
<x-layout.app>
<x-card.card class="mx-auto max-w-lg">
<x-card.heading>Edit User</x-card.heading>
<x-form.form method="PATCH" action="/admin/users/{{ $user->id }}">
<x-card.heading>
Edit User
<x-slot:right_side>
@if($user->id != Auth::user()->id)
<form method="POST" action="{{ route('admin.users.destroy',['user' => $user->id]) }}">
@csrf
@method('DELETE')
<x-form.red-trash-button type="submit" />
</form>
@endif
</x-slot:right_side>
</x-card.heading>
<x-form.form method="PATCH" action="{{ route('admin.users.update', $user) }}">
<x-form.body-grid>
<x-form.field name="first_name" label_text="First Name" colspan="3" value="{{ $user->first_name }}" />
<x-form.field name="last_name" label_text="Last Name" colspan="3" value="{{ $user->last_name }}" />

View File

@ -4,9 +4,9 @@
<x-card.card>
<x-table.table with_title_area>
<x-slot:title class="ml-3">Users</x-slot:title>
<x-slot:subtitle class="ml-3">Click name to edit</x-slot:subtitle>
<x-slot:subtitle class="ml-3">Click name to edit or delete</x-slot:subtitle>
<x-slot:title_block_right class="mr-3">
<x-form.button href="/admin/users/create">New User</x-form.button>
<x-form.button href="{{ route('admin.users.create') }}">New User</x-form.button>
</x-slot:title_block_right>
<thead>
@ -21,7 +21,7 @@
<x-table.body>
@foreach($users as $user)
<tr>
<x-table.td><a href="/admin/users/{{ $user->id }}/edit">{{ $user->full_name(true) }}</a></x-table.td>
<x-table.td><a href="{{ route('admin.users.edit',$user) }}">{{ $user->full_name(true) }}</a></x-table.td>
<x-table.td>{{ $user->has_school() ? $user->school->name : ' ' }}</x-table.td>
<x-table.td>{{ $user->email }}</x-table.td>
<x-table.td>{{ $user->cell_phone }}</x-table.td>
@ -32,7 +32,3 @@
</x-table.table>
</x-card.card>
</x-layout.app>
{{ $user->has_school() ? $user->school->name : 'No School' }}

View File

@ -0,0 +1,134 @@
@php
/**
* @var int $size=20 Size of the icon
* @var string $title Title of the modal
* @var string $method='DELETE' method used by the form
* @var string $action action used for the form
*/
@endphp
@props(['size' => 20,'title','method'=>'DELETE','action'])
<div
x-data="{ 'showModal': false }"
@keydown.escape="showModal = false"
>
<!-- Trigger for Modal -->
<button type="button" @click="showModal = true" class="rounded-lg bg-red-600 p-1.5 text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">
{{-- trash button to show modal --}}
<svg xmlns="http://www.w3.org/2000/svg" width="{{ $size }}" height="{{ $size }}" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
</svg>
</button>
<!-- 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">
{{ $title }}
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
{{ $slot }}
</p>
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<form method="{{ $method == 'GET' ? 'GET':'POST' }}" action="{{$action}}">
@csrf
@if(! in_array($method, ['POST','GET']))
@method($method)
@endif
<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">
Delete
</button>
</form>
<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>

View File

@ -10,5 +10,4 @@
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
</svg>
</svg>
</button>

View File

@ -1,4 +1,5 @@
@props(['color' => 'currentColor'])
@props(['color' => 'currentColor', 'title'=>false])
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
@if($title)<title>{{ $title }}</title>@endif
<path stroke="{{$color}}" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 11.917 9.724 16.5 19 7.5"/>
</svg>

View File

@ -1,4 +1,5 @@
@props(['color' => 'currentColor'])
@props(['color' => 'currentColor', 'title' => false])
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
@if($title)<title>{{ $title }}</title>@endif
<path stroke="{{ $color }}" stroke-linecap="round" stroke-width="2" d="m6 6 12 12m3-6a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
</svg>

View File

@ -1,3 +1,5 @@
@props(['title' => false])
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
@if($title)<title>{{ $title }}</title>@endif
<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M5 7h14M5 12h14M5 17h14"/>
</svg>

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 346 B

View File

@ -1,4 +1,5 @@
@props(['color' => 'currentColor'])
@props(['color' => 'currentColor','title'=>false])
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="{{ $color }}" viewBox="0 0 24 24">
@if($title)<title>{{ $title }}</title>@endif
<path fill-rule="evenodd" d="M8.97 14.316H5.004c-.322 0-.64-.08-.925-.232a2.022 2.022 0 0 1-.717-.645 2.108 2.108 0 0 1-.242-1.883l2.36-7.201C5.769 3.54 5.96 3 7.365 3c2.072 0 4.276.678 6.156 1.256.473.145.925.284 1.35.404h.114v9.862a25.485 25.485 0 0 0-4.238 5.514c-.197.376-.516.67-.901.83a1.74 1.74 0 0 1-1.21.048 1.79 1.79 0 0 1-.96-.757 1.867 1.867 0 0 1-.269-1.211l1.562-4.63ZM19.822 14H17V6a2 2 0 1 1 4 0v6.823c0 .65-.527 1.177-1.177 1.177Z" clip-rule="evenodd"/>
</svg>

View File

@ -1,4 +1,5 @@
@props(['color' => 'currentColor'])
@props(['color' => 'currentColor','title'=>false])
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="{{ $color }}" viewBox="0 0 24 24">
@if($title)<title>{{ $title }}</title>@endif
<path fill-rule="evenodd" d="M15.03 9.684h3.965c.322 0 .64.08.925.232.286.153.532.374.717.645a2.109 2.109 0 0 1 .242 1.883l-2.36 7.201c-.288.814-.48 1.355-1.884 1.355-2.072 0-4.276-.677-6.157-1.256-.472-.145-.924-.284-1.348-.404h-.115V9.478a25.485 25.485 0 0 0 4.238-5.514 1.8 1.8 0 0 1 .901-.83 1.74 1.74 0 0 1 1.21-.048c.396.13.736.397.96.757.225.36.32.788.269 1.211l-1.562 4.63ZM4.177 10H7v8a2 2 0 1 1-4 0v-6.823C3 10.527 3.527 10 4.176 10Z" clip-rule="evenodd"/>
</svg>

View File

@ -2,7 +2,7 @@
{{-- <button type="button" class="inline-flex items-center gap-x-1 text-sm font-semibold leading-6 text-gray-900" aria-expanded="false" @on:click=" open = ! open">--}}
<button type="button" class="inline-flex items-center gap-x-1 text-white rounded-md px-3 py-2 text-sm font-medium hover:bg-indigo-500 hover:bg-opacity-75" aria-expanded="false" @click=" open = ! open" @click.outside=" open = false">
<span>Admin</span>
<span>Administration</span>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>

View File

@ -14,16 +14,16 @@
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<x-layout.navbar.nav-link href="/dashboard" :active="request()->is('dashboard')">Dashboard
<x-layout.navbar.nav-link href="{{ route('dashboard') }}" :active="request()->is('dashboard')">Dashboard
</x-layout.navbar.nav-link>
@if(Auth::user()->school_id)
<x-layout.navbar.nav-link href="/students" :active="request()->is('students')">Students
<x-layout.navbar.nav-link href="{{ route('students.index') }}" :active="request()->is('students')">Students
</x-layout.navbar.nav-link>
<x-layout.navbar.nav-link href="/entries" :active="request()->is('entries')">Entries
<x-layout.navbar.nav-link href="{{ route('entries.index') }}" :active="request()->is('entries')">Entries
</x-layout.navbar.nav-link>
@endif
@if(Auth::user()->isJudge() AND Settings::get('judging_enabled'))
<x-layout.navbar.nav-link href="/judging" :active="request()->is('judging')">Judging
<x-layout.navbar.nav-link href="{{ route('judging.index') }}" :active="request()->is('judging')">Judging
</x-layout.navbar.nav-link>
@endif
@if(Auth::user()->is_admin)
@ -164,8 +164,7 @@
<!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" -->
<a href="/dashboard" class="bg-indigo-700 text-white block rounded-md px-3 py-2 text-base font-medium"
aria-current="page">Dashboard</a>
<a href="/students"
class="text-white hover:bg-indigo-500 hover:bg-opacity-75 block rounded-md px-3 py-2 text-base font-medium">Students</a>
</div>
<div class="border-t border-indigo-700 pb-3 pt-4">
<div class="flex items-center px-5">

View File

@ -2,7 +2,7 @@
<x-layout.app>
<x-slot:page_title>Dashboard</x-slot:page_title>
@if(! Auth::user()->school_id)
You aren't currently associated with a school. <a href="/my_school" class="text-blue-600">Click here to choose or create one.</a>
<p class="pb-5">You aren't currently associated with a school. <a href="/my_school" class="text-blue-600">Click here to choose or create one.</a></p>
@endif
<div class="grid sm:grid-cols-2 md:grid-cols-4">
<div>{{-- Column 1 --}}

View File

@ -9,7 +9,7 @@
<x-layout.page-section-container>
<x-layout.page-section>
<x-slot:section_name>Add Entry</x-slot:section_name>
<x-form.form method="POST" action="/entries" class="pt-6 pb-8">
<x-form.form method="POST" action="{{ route('entries.store') }}" class="pt-6 pb-8">
<x-form.body-grid columns="6" class="max-w-full" x-data="studentAuditionFilter()">
@ -81,23 +81,33 @@
@if(auditionSetting('advanceTo'))
<x-table.td>
@if($entry->for_seating)
<x-icons.checkmark color="green" />
<div aria-label="{{ $entry->student->full_name() }} on {{ $entry->audition->name }} is entered for seating. Entry ID {{ $entry->id }}">
<x-icons.checkmark color="green"/>
</div>
@else
<x-icons.circle-slash-no color="red" />
<div aria-label="{{ $entry->student->full_name() }} on {{ $entry->audition->name }} is not entered for seating. Entry ID {{ $entry->id }}">
<x-icons.circle-slash-no color="red"/>
</div>
@endif
</x-table.td>
<x-table.td>
@if($entry->for_advancement)
<x-icons.checkmark color="green" />
<div aria-label="{{ $entry->student->full_name() }} on {{ $entry->audition->name }} is entered for advancement. Entry ID {{ $entry->id }}">
<x-icons.checkmark color="green"/>
</div>
@else
<x-icons.circle-slash-no color="red" />
<div aria-label="{{ $entry->student->full_name() }} on {{ $entry->audition->name }} is not entered for advancement. Entry ID {{ $entry->id }}">
<x-icons.circle-slash-no color="red"/>
</div>
@endif
</x-table.td>
@endif
<x-table.td for_button>
@if( $entry->audition->entry_deadline >= now())
<form method="POST" action="/entries/{{ $entry->id }}" class="inline">
<form method="POST" action="{{ route('entries.destroy',$entry) }}" class="inline">
@csrf
@method('DELETE')
<x-table.button

View File

@ -20,7 +20,7 @@
@foreach($entries as $entry)
<tr>
<x-table.td>
<a href="/judging/entry/{{$entry->id}}">
<a href="{{ route('judging.entryScoreSheet',$entry) }}">
{{ $audition->name }} {{ $entry->draw_number }}
</a>
</x-table.td>

View File

@ -8,7 +8,7 @@
<x-card.heading>{{ $room->name }}</x-card.heading>
<x-card.list.body>
@foreach($room->auditions as $audition)
<a href="/judging/audition/{{$audition->id}}">
<a href="{{ route('judging.auditionEntryList', $audition) }}">
<x-card.list.row class="!py-3 ml-3">{{ $audition->name }}</x-card.list.row>
</a>
@endforeach

View File

@ -3,7 +3,7 @@
<x-card.card>
<x-card.heading>Edit Student</x-card.heading>
<x-form.form method="PATCH" class="!pt-2 !pb-6 !space-y-2" action="/students/{{ $student->id }}">
<x-form.form method="PATCH" class="!pt-2 !pb-6 !space-y-2" action="{{route('students.update',$student)}}">
<x-form.field name="first_name" label_text="First Name" type="text" value="{{ $student->first_name }}"/>
<x-form.field name="last_name" label_text="Last Name" type="text" value="{{ $student->last_name }}"/>
<x-form.field name="grade" label_text="Grade" type="number" class="mb-3" value="{{ $student->grade }}"/>

View File

@ -6,7 +6,7 @@
<x-layout.page-section-container>
<x-layout.page-section>
<x-slot:section_name>Add Student</x-slot:section_name>
<x-form.form method="POST" action="/students" class="mb-6 mt-3">
<x-form.form method="POST" action="{{ route('students.store') }}" class="mb-6 mt-3">
<x-form.body-grid columns="8" class="max-w-full">
<x-form.field name="first_name" label_text="First Name" colspan="3"/>
<x-form.field name="last_name" label_text="Last Name" colspan="3"/>
@ -46,10 +46,10 @@
<tr>
<x-table.td first>{{ $student->full_name(true) }}</x-table.td>
<x-table.td>{{ $student->grade }}</x-table.td>
<x-table.td>{{ $student->entries->count() }}</x-table.td>
<x-table.td>{{ $student->entries_count }}</x-table.td>
<x-table.td for_button>
@if( $student->entries->count() > 0)
<form method="POST" action="/students/{{ $student->id }}" class="inline">
@if( $student->entries_count === 0)
<form method="POST" action="{{ route('students.destroy',$student) }}" class="inline">
@csrf
@method('DELETE')
<x-table.button
@ -59,7 +59,7 @@
</form>
|
@endif
<x-table.button href="/students/{{ $student->id }}/edit">Edit</x-table.button>
<x-table.button href="{{ route('students.edit',$student) }}">Edit</x-table.button>
</x-table.td>
</tr>
@endforeach

View File

@ -1,37 +0,0 @@
@php use Illuminate\Support\Facades\Auth; @endphp
@push('scripts')
{{-- Code from https://codepen.io/ryangjchandler/pen/WNQQKeR--}}
<script src="{{ asset('js/sort_table_by_column.js') }}"></script>
@endpush
<x-layout.app>
<x-slot:page_title>Students</x-slot:page_title>
<x-table.container class="mx-auto max-w-2xl" x-data="data()">
<x-table.title_above_table>
<x-slot:title>Students <x-badge_pill>{{ $students->count() }}</x-badge_pill></x-slot:title>
<x-slot:subtitle>Before submitting entries, you must enter your students</x-slot:subtitle>
<x-slot:button>Add Student</x-slot:button>
</x-table.title_above_table>
<x-table.table>
<x-table.table_header_row>
<x-table.th first @click="sortByColumn" class="cursor-pointer select-none">Name</x-table.th>
<x-table.th @click="sortByColumn" class="cursor-pointer select-none">Grade</x-table.th>
<x-table.th :placeholder="true">
<span class="sr-only">Edit</span>
</x-table.th>
<x-table.body x-ref="tbody">
@foreach($students as $student)
<tr>
<x-table.td :first="true">{{ $student->full_name(true) }}</x-table.td>
<x-table.td>{{ $student->grade }}</x-table.td>
<x-table.td_right_link sr_text=", {{ $student->full_name() }}">
<x-slot:a href="/students/{{ $student->id }}/edit">Edit</x-slot:a>
</x-table.td_right_link>
</tr>
@endforeach
</x-table.body>
</x-table.table_header_row>
</x-table.table>
</x-table.container>
</x-layout.app>

View File

@ -5,10 +5,10 @@ use App\Http\Middleware\CheckIfAdmin;
use Illuminate\Support\Facades\Route;
Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->group(function () {
Route::view('/', 'admin.dashboard');
Route::view('/', 'admin.dashboard')->name('admin.dashboard');
Route::post('/auditions/roomUpdate', [\App\Http\Controllers\Admin\AuditionController::class, 'roomUpdate']); // Endpoint for JS assigning auditions to rooms
Route::post('/scoring/assign_guide_to_audition', [\App\Http\Controllers\Admin\AuditionController::class, 'scoringGuideUpdate']); // Endpoint for JS assigning scoring guides to auditions
Route::post('/scoring/assign_guide_to_audition', [\App\Http\Controllers\Admin\AuditionController::class, 'scoringGuideUpdate'])->name('ajax.assignScoringGuideToAudition'); // Endpoint for JS assigning scoring guides to auditions
Route::get('/settings', [\App\Http\Controllers\Admin\AuditionSettings::class, 'index'])->name('audition-settings');
Route::post('/settings', [\App\Http\Controllers\Admin\AuditionSettings::class, 'save'])->name('audition-settings-save');
@ -19,7 +19,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
Route::post('/', 'store')->name('admin.ensembles.store');
Route::delete('/{ensemble}', 'destroy')->name('admin.ensembles.destroy');
Route::post('/updateEnsembleRank', 'updateEnsembleRank')->name('admin.ensembles.updateEnsembleRank');
Route::patch('/{ensemble}', 'updateEnsemble')->name('admin.ensembles.updateEnsemble');
Route::patch('/{ensemble}', 'updateEnsemble')->name('admin.ensembles.update');
Route::get('/seating-limits', 'seatingLimits')->name('admin.ensembles.seatingLimits');
Route::get('/seating-limits/{ensemble}', 'seatingLimits')->name('admin.ensembles.seatingLimits.ensemble');
Route::post('/seating-limits/{ensemble}', 'seatingLimitsSet')->name('admin.ensembles.seatingLimits.ensemble.set');
@ -49,7 +49,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
Route::prefix('scoring')->controller(\App\Http\Controllers\Admin\ScoringGuideController::class)->group(function () {
Route::get('/', 'index')->name('admin.scoring.index'); // Scoring Setup Homepage
Route::post('/guides', 'store')->name('admin.scoring.store'); // Save a new scoring guide
Route::get('/guides/{guide}/edit', 'edit')->name('admin.scoring.edit'); // Edit scoring guide
Route::get('/guides/{guide}/edit/{tab?}', 'edit')->name('admin.scoring.edit'); // Edit scoring guide
Route::patch('/guides/{guide}/edit', 'update')->name('admin.scoring.update'); // Save changes to audition guide (rename)
Route::post('/guides/{guide}/subscore', 'subscore_store')->name('admin.scoring.subscore_store'); // Save a new subscore
Route::patch('/guides/{guide}/subscore/{subscore}', 'subscore_update')->name('admin.scoring.subscore_update'); // Modify a subscore
@ -75,21 +75,22 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
// Admin Entries Routes
Route::prefix('entries')->controller(\App\Http\Controllers\Admin\EntryController::class)->group(function () {
Route::get('/', 'index')->name('admin.entries.index');
Route::get('/create', 'create');
Route::post('/', 'store');
Route::get('/create', 'create')->name('admin.entries.create');
Route::post('/', 'store')->name('admin.entries.store');
Route::get('/{entry}/edit', 'edit')->name('admin.entries.edit');
Route::patch('/{entry}', 'update');
Route::patch('/{entry}', 'update')->name('admin.entries.update');
Route::delete('/{entry}', 'destroy')->name('admin.entries.destroy');
});
// Admin Student Routes
Route::prefix('students')->controller(\App\Http\Controllers\Admin\StudentController::class)->group(function () {
Route::get('/', 'index');
Route::get('/create', 'create');
Route::post('/', 'store');
Route::get('/{student}/edit', 'edit');
Route::patch('/{student}', 'update');
Route::get('/', 'index')->name('admin.students.index');
Route::get('/create', 'create')->name('admin.students.create');
Route::post('/', 'store')->name('admin.students.store');
Route::get('/{student}/edit', 'edit')->name('admin.students.edit');
Route::patch('/{student}', 'update')->name('admin.students.update');
Route::delete('/{student}', 'destroy')->name('admin.students.destroy');
});
// Admin School Routes
@ -103,16 +104,17 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
Route::patch('/{school}', 'update')->name('admin.schools.update');
Route::post('/', 'store')->name('admin.schools.store');
Route::delete('/domain/{domain}', 'destroy_domain')->name('admin.schools.destroy_domain');
Route::delete('/{school}', 'destroy')->name('admin.schools.destroy');
});
// Admin User Routes
Route::prefix('users')->controller(\App\Http\Controllers\Admin\UserController::class)->group(function () {
Route::get('/', 'index');
Route::get('/create', 'create');
Route::post('/', 'store');
Route::get('/{user}/edit', 'edit');
Route::patch('/{user}', 'update');
Route::delete('/{user}', 'destroy');
Route::get('/', 'index')->name('admin.users.index');
Route::get('/create', 'create')->name('admin.users.create');
Route::post('/', 'store')->name('admin.users.store');
Route::get('/{user}/edit', 'edit')->name('admin.users.edit');
Route::patch('/{user}', 'update')->name('admin.users.update');
Route::delete('/{user}', 'destroy')->name('admin.users.destroy');
});
});

View File

@ -6,3 +6,9 @@ use Illuminate\Support\Facades\Artisan;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote')->hourly();
Artisan::command('logs:remove', function () {
exec('rm -f '.storage_path('logs/*.log'));
exec('rm -f '.base_path('*.log'));
$this->comment('Logs have been removed!');
})->describe('Remove log files');

View File

@ -19,32 +19,32 @@ Route::middleware(['auth', 'verified'])->group(function () {
// Entry Related Routes
Route::middleware(['auth', 'verified', 'can:create,App\Models\Entry'])->controller(EntryController::class)->group(function () {
Route::get('/entries', 'index');
Route::get('/entries/create', 'create');
Route::post('/entries', 'store');
Route::delete('/entries/{entry}', 'destroy');
Route::get('/entries', 'index')->name('entries.index');
Route::get('/entries/create', 'create')->name('entries.create');
Route::post('/entries', 'store')->name('entries.store');
Route::delete('/entries/{entry}', 'destroy')->name('entries.destroy');
});
// User Related Routes
Route::middleware(['auth', 'verified'])->controller(UserController::class)->group(function () {
Route::patch('/users/{user}/set_school', 'set_school');
Route::patch('/users/{$user}', 'update');
Route::patch('/users/{user}/set_school', 'set_school')->name('users.set_school');
Route::patch('/users/{$user}', 'update')->name('users.update');
});
// Student Related Routes
Route::middleware(['auth', 'verified', 'can:create,App\Models\Student'])->controller(StudentController::class)->group(function () {
Route::get('/students', 'index');
Route::post('students', 'store');
Route::get('/students/{student}/edit', 'edit');
Route::patch('/students/{student}', 'update');
Route::delete('/students/{student}', 'destroy');
Route::get('/students', 'index')->name('students.index');
Route::post('students', 'store')->name('students.store');
Route::get('/students/{student}/edit', 'edit')->name('students.edit');
Route::patch('/students/{student}', 'update')->name('students.update');
Route::delete('/students/{student}', 'destroy')->name('students.destroy');
});
// School Related Routes
Route::middleware(['auth', 'verified'])->controller(SchoolController::class)->group(function () {
Route::get('/schools/create', 'create');
Route::post('/schools', 'store');
Route::get('/schools/{school}/edit', 'edit');
Route::get('/schools/{school}', 'show')->name('schools.show');
Route::patch('/schools/{school}', 'update');
Route::get('/schools/create', 'create')->name('schools.create');
Route::post('/schools', 'store')->name('schools.store');
Route::get('/schools/{school}/edit', 'edit')->name('schools.edit');
Route::get('/schools/{school}', 'show')->name('schools.show')->name('schools.show');
Route::patch('/schools/{school}', 'update')->name('schools.update');
});

View File

@ -29,8 +29,8 @@ Route::get('/results', [App\Http\Controllers\ResultsPage::class, '__invoke'])->n
// Filter Related Routes
Route::prefix('filters')->middleware(['auth', 'verified'])->controller(FilterController::class)->group(function () {
Route::post('/admin_entry_filter', 'adminEntryFilter');
Route::get('/admin_entry_filter/clear', 'clearAdminEntryFilter');
Route::post('/admin_entry_filter', 'adminEntryFilter')->name('admin_entry_filter.set');
Route::get('/admin_entry_filter/clear', 'clearAdminEntryFilter')->name('admin_entry_filter.clear');
});
//Route::get('/my_school', [SchoolController::class, 'my_school'])->middleware('auth','verified');

2
storage/pail/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1,21 @@
<?php
use App\Models\Audition;
use App\Models\AuditionFlag;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('has an audition', function () {
$audition = Audition::factory()->create();
// Arrange
$flag = AuditionFlag::create([
'audition_id' => $audition->id,
'flag_name' => 'Test Flag',
]);
// Act and Assert
expect($flag->audition->name)->toBe($audition->name);
});

View File

@ -1,6 +1,12 @@
<?php
use App\Models\Audition;
use App\Models\AuditionFlag;
use App\Models\Entry;
use App\Models\Event;
use App\Models\Room;
use App\Models\ScoringGuide;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
@ -63,3 +69,102 @@ it('only returns published advancement auditions for advancementPublished scope'
->first()->id->toEqual($published->id);
});
it('has an event', function () {
// Arrange
$event = Event::factory()->create(['name' => 'Symphonic Concert Wind Ensemble Band']);
$audition = Audition::factory()->create(['event_id' => $event->id]);
// Act & Assert
expect($audition->event->name)->toEqual('Symphonic Concert Wind Ensemble Band');
});
it('has entries', function () {
// Arrange
$audition = Audition::factory()->create();
Entry::factory()->count(10)->create(['audition_id' => $audition->id]);
// Act & Assert
expect($audition->entries->count())->toEqual(10);
});
it('has a room', function () {
// Arrange
$room = Room::factory()->create(['name' => 'Room 1']);
$audition = Audition::factory()->create(['room_id' => $room->id]);
// Act & Assert
expect($audition->room->name)->toEqual('Room 1');
});
it('has a scoring guide', function () {
// Arrange
$sg = ScoringGuide::factory()->create(['name' => 'Sight Reading']);
$audition = Audition::factory()->create(['scoring_guide_id' => $sg->id]);
// Act & Assert
expect($audition->scoringGuide->name)->toEqual('Sight Reading');
});
it('displays the entry fee', function () {
// Arrange
$audition = Audition::factory()->create(['entry_fee' => 1000]);
// Act & Assert
expect($audition->display_fee())->toEqual('$10.00');
});
it('has many judges', function () {
// Arrange
$room = Room::factory()->create();
$audition = Audition::factory()->create(['room_id' => $room->id]);
$judges = User::factory()->count(5)->create();
foreach ($judges as $judge) {
$room->addJudge($judge->id);
}
// Act & Assert
expect($audition->judges->count())->toEqual(5);
});
it('has a judges_count available', function () {
// Arrange
$room = Room::factory()->create();
$audition = Audition::factory()->create(['room_id' => $room->id]);
$judges = User::factory()->count(5)->create();
foreach ($judges as $judge) {
$room->addJudge($judge->id);
}
// Act & Assert
expect($audition->judges_count)->toEqual(5);
});
it('can have flags', function () {
// Arrange
$audition = Audition::factory()->create();
AuditionFlag::create(['audition_id' => $audition->id, 'flag_name' => 'seats_published']);
AuditionFlag::create(['audition_id' => $audition->id, 'flag_name' => 'advance_published']);
// Act
// Assert
expect($audition->hasFlag('seats_published'))->toBeTrue();
expect($audition->hasFlag('notaflag'))->toBeFalse();
expect($audition->flags->count())->toEqual(2);
});
it('can add flags', function () {
// Arrange
$audition = Audition::factory()->create();
// Act
$audition->addFlag('seats_published');
// Assert
expect($audition->hasFlag('seats_published'))->toBeTrue();
});
it('can remove flags', function () {
// Arrange
$audition = Audition::factory()->create();
AuditionFlag::create(['audition_id' => $audition->id, 'flag_name' => 'seats_published']);
// Act & Assert
$audition->addFlag('seats_published');
expect($audition->hasFlag('seats_published'))->toBeTrue();
$audition->removeFlag('seats_published');
expect($audition->hasFlag('seats_published'))->toBeFalse();
});

View File

@ -0,0 +1,89 @@
<?php
use App\Models\Audition;
use App\Models\Ensemble;
use App\Models\Entry;
use App\Models\Event;
use App\Models\Seat;
use App\Models\SeatingLimit;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('has an event', function () {
$event = Event::factory()->create();
$ensemble = Ensemble::factory()->create(['event_id' => $event->id]);
expect($ensemble->event)->toBeInstanceOf(Event::class)
->and($ensemble->event->name)->toBe($event->name);
});
it('has auditions in score order', function () {
// Arrange
$ensemble = Ensemble::factory()->create();
$oboe = Audition::factory()->create(['event_id' => $ensemble->event_id, 'name' => 'Oboe', 'score_order' => 2]);
$flute = Audition::factory()->create(['event_id' => $ensemble->event_id, 'name' => 'Flute', 'score_order' => 1]);
$clarinet = Audition::factory()->create(['event_id' => $ensemble->event_id, 'name' => 'Clarinet',
'score_order' => 3,
]);
// Act & Assert
expect($ensemble->auditions)->toHaveCount(3)
->sequence(
fn ($seatingLimit) => $seatingLimit->audition_id === $flute->id,
fn ($seatingLimit) => $seatingLimit->audition_id === $oboe->id,
fn ($seatingLimit) => $seatingLimit->audition_id === $clarinet->id,
);
});
it('has seating limits',
function () {
// Arrange
$ensemble = Ensemble::factory()->create();
$auditions = Audition::factory()->count(3)->create(['event_id' => $ensemble->event_id]);
foreach ($auditions as $audition) {
SeatingLimit::create([
'ensemble_id' => $ensemble->id, 'audition_id' => $audition->id,
'maximum_accepted' => fake()->numberBetween(1, 10),
]);
}
// Act & Assert
expect($ensemble->seatingLimits)->toHaveCount(3);
});
it('has seats', function () {
// Arrange
$ensemble = Ensemble::factory()->create();
$audition = Audition::factory()->create(['event_id' => $ensemble->event_id]);
$entry = Entry::factory()->create(['audition_id' => $audition->id]);
$seat = Seat::create([
'ensemble_id' => $ensemble->id,
'audition_id' => $audition->id,
'seat' => 1,
'entry_id' => $entry->id,
]);
// Act & Assert
expect($ensemble->seats)->toHaveCount(1)
->and($ensemble->seats->first()->seat)->toBe(1);
});
it('returns only ensembles for a given audition in the audition scope', function () {
// Arrange
$audition = Audition::factory()->create();
$ensemble = Ensemble::factory()->create(['event_id' => $audition->event_id]);
$ensemble2 = Ensemble::factory()->create(['event_id' => $audition->event_id]);
$ensemble3 = Ensemble::factory()->create();
// Act & Assert
expect(Ensemble::forAudition($audition->id)->get())->toHaveCount(2);
});
it('returns only ensembles for a given event in the event scope', function () {
// Arrange
$event = Event::factory()->create();
$ensemble = Ensemble::factory()->create(['event_id' => $event->id]);
$ensemble2 = Ensemble::factory()->create(['event_id' => $event->id]);
$ensemble3 = Ensemble::factory()->create();
// Act & Assert
expect(Ensemble::forEvent($event->id)->get())->toHaveCount(2);
});

View File

@ -0,0 +1,16 @@
<?php
use App\Models\Entry;
use App\Models\EntryFlag;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('has an entry', function () {
$entry = Entry::factory()->create();
$entryFlag = EntryFlag::create(['entry_id' => $entry->id, 'flag_name' => 'declined']);
// Act & Assert
expect($entryFlag->entry->id)->toBe($entry->id)
->and($entryFlag->entry)->toBeInstanceOf(Entry::class);
});

View File

@ -0,0 +1,186 @@
<?php
use App\Models\Audition;
use App\Models\Ensemble;
use App\Models\Entry;
use App\Models\EntryFlag;
use App\Models\JudgeAdvancementVote;
use App\Models\School;
use App\Models\ScoreSheet;
use App\Models\Seat;
use App\Models\Student;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('has a student', function () {
// Arrange
$student = Student::factory()->create();
$entry = Entry::factory()->create(['student_id' => $student->id]);
// Act and Assert
expect($entry->student->first_name)->toBe($student->first_name)
->and($entry->student)->toBeInstanceOf(Student::class);
});
test('has an audition', function () {
// Arrange
$audition = Audition::factory()->create();
$entry = Entry::factory()->create(['audition_id' => $audition->id]);
// Act and Assert
expect($entry->audition)->toBeInstanceOf(Audition::class)
->and($entry->audition->name)->toBe($audition->name);
});
test('has a school', function () {
// Arrange
$student = Student::factory()->create();
$school = $student->school;
$entry = Entry::factory()->create(['student_id' => $student->id]);
// Act and Assert
expect($entry->school->name)->toBe($school->name)
->and($entry->school)->toBeInstanceOf(School::class);
});
test('has score sheets', function () {
// Arrange
$judge = User::factory()->create();
$judge2 = User::factory()->create();
$entry = Entry::factory()->create();
$ss = ScoreSheet::create([
'entry_id' => $entry->id,
'user_id' => $judge->id,
'subscores' => json_encode(['subscore1' => 10, 'subscore2' => 20]),
]);
$ss2 = ScoreSheet::create([
'entry_id' => $entry->id,
'user_id' => $judge2->id,
'subscores' => json_encode(['subscore1' => 10, 'subscore2' => 20]),
]);
// Act and Assert
expect($entry->scoreSheets->count())->toBe(2)
->and($entry->scoreSheets->first())->toBeInstanceOf(ScoreSheet::class);
});
test('has advancement votes', function () {
$entry = Entry::factory()->create();
$judge = User::factory()->create();
$judge2 = User::factory()->create();
JudgeAdvancementVote::create(['user_id' => $judge->id, 'entry_id' => $entry->id, 'vote' => 'yes']);
JudgeAdvancementVote::create(['user_id' => $judge2->id, 'entry_id' => $entry->id, 'vote' => 'no']);
// Act and Assert
expect($entry->advancementVotes->count())->toBe(2)
->and($entry->advancementVotes->first())->toBeInstanceOf(JudgeAdvancementVote::class);
});
it('can have flags and can check for a specific flag', function () {
// Arrange
$entry = Entry::factory()->create();
EntryFlag::create(['entry_id' => $entry->id, 'flag_name' => 'declined']);
EntryFlag::create(['entry_id' => $entry->id, 'flag_name' => 'no-show']);
// Act & Assert
expect($entry->flags->count())->toBe(2)
->and($entry->flags->first()->flag_name)->toBe('declined')
->and($entry->hasFlag('declined'))->toBeTrue()
->and($entry->hasFlag('random'))->toBeFalse();
});
it('can set and remove a flag', function () {
// Arrange
$entry = Entry::factory()->create();
// Act
$entry->addFlag('Test Flag');
$entry->addFlag('Second Flag');
// Assert
expect($entry->hasFlag('Test Flag'))->toBeTrue()
->and($entry->hasFlag('Second Flag'))->toBeTrue()
->and($entry->hasFlag('random'))->toBeFalse();
$entry->removeFlag('Test Flag');
expect($entry->hasFlag('Test Flag'))->toBeFalse()
->and($entry->hasFlag('Second Flag'))->toBeTrue();
});
it('always has a score sheet count available', function () {
// Arrange
// Arrange
$judge = User::factory()->create();
$judge2 = User::factory()->create();
$entry = Entry::factory()->create();
$ss = ScoreSheet::create([
'entry_id' => $entry->id,
'user_id' => $judge->id,
'subscores' => json_encode(['subscore1' => 10, 'subscore2' => 20]),
]);
$ss2 = ScoreSheet::create([
'entry_id' => $entry->id,
'user_id' => $judge2->id,
'subscores' => json_encode(['subscore1' => 10, 'subscore2' => 20]),
]);
// Act & Assert
expect($entry->score_sheets_count)->toBe(2);
});
it('has a seat', function () {
// Arrange
$entry = Entry::factory()->create();
$ensemble = Ensemble::factory()->create();
$seat = Seat::create([
'ensemble_id' => $ensemble->id,
'audition_id' => $entry->audition_id,
'seat' => fake()->numberBetween(1, 15),
'entry_id' => $entry->id,
]);
// Act & Assert
expect($entry->seat->seat)->toBe($seat->seat)
->and($entry->seat->ensemble_id)->toBe($ensemble->id)
->and($entry->seat->audition_id)->toBe($entry->audition_id)
->and($entry->seat)->toBeInstanceOf(Seat::class);
});
it('has a forSeating scope that only returns those entries entered for seating', function () {
// Arrange
$noSeatStudent = Student::factory()->create(['first_name' => 'Advance Only']);
$noAdvanceStudent = Student::factory()->create(['first_name' => 'Seating Only']);
Entry::factory()->create(['student_id' => $noSeatStudent->id, 'for_seating' => false, 'for_advancement' => true]);
Entry::factory()->create([
'student_id' => $noAdvanceStudent->id, 'for_seating' => true,
'for_advancement' => false,
]);
Entry::factory()->count(10)->create(['for_seating' => true, 'for_advancement' => true]);
Entry::factory()->count(5)->create(['for_seating' => false, 'for_advancement' => true]);
// Act & Assert
expect(Entry::forSeating()->count())->toBe(11)
->and(Entry::forSeating()->get()->first())->toBeInstanceOf(Entry::class)
->and(Entry::forSeating()->get()->first()->student->first_name)->toBe('Seating Only');
});
it('has a forAdvancement scope that only returns those entries entered for advancement', function () {
// Arrange
$noSeatStudent = Student::factory()->create(['first_name' => 'Advance Only']);
$noAdvanceStudent = Student::factory()->create(['first_name' => 'Seating Only']);
Entry::factory()->create(['student_id' => $noSeatStudent->id, 'for_seating' => false, 'for_advancement' => true]);
Entry::factory()->create([
'student_id' => $noAdvanceStudent->id, 'for_seating' => true,
'for_advancement' => false,
]);
Entry::factory()->count(10)->create(['for_seating' => true, 'for_advancement' => true]);
Entry::factory()->count(5)->create(['for_seating' => false, 'for_advancement' => true]);
// Act & Assert
expect(Entry::forAdvancement()->count())->toBe(16)
->and(Entry::forAdvancement()->get()->first())->toBeInstanceOf(Entry::class)
->and(Entry::forAdvancement()->get()->first()->student->first_name)->toBe('Advance Only');
});
it('has an available scope that returns only auditions without a flag that would prevent seating', function () {
Entry::factory()->count(10)->create();
Entry::factory()->create()->addFlag('declined');
Entry::factory()->create()->addFlag('no-show');
Entry::factory()->create()->addFlag('failed-prelim');
// Act & Assert
expect(Entry::all()->count())->toBe(13)
->and(Entry::available()->count())->toBe(10)
->and(Entry::available()->get()->first())->toBeInstanceOf(Entry::class)
->and(Entry::available()->get()->first()->flags->count())->toBe(0);
});

View File

@ -1,46 +0,0 @@
<?php
use App\Models\Entry;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('only returns entries for seating with forSeating scope', function () {
// Arrange
Entry::factory()->advanceOnly()->create();
$seatingEntry = Entry::factory()->create();
// Act & Assert
expect(Entry::forSeating()->get())
->toHaveCount(1)
->first()->id->toEqual($seatingEntry->id);
});
it('only returns entries for advancement with for forAdvancement scope', function () {
// Arrange
Entry::factory()->seatingOnly()->create();
$advancementEntry = Entry::factory()->create();
// Act & Assert
expect(Entry::forAdvancement()->get())
->toHaveCount(1)
->first()->id->toEqual($advancementEntry->id);
});
it('only returns entries that do not have a declined, no-show, or failed-prelim flag with available scope',
function () {
// Arrange
$availableEntry = Entry::factory()->create();
$declinedEntry = Entry::factory()->create();
$noShowEntry = Entry::factory()->create();
$failedPrelimEntry = Entry::factory()->create();
$declinedEntry->addFlag('declined');
$noShowEntry->addFlag('no-show');
$failedPrelimEntry->addFlag('failed-prelim');
// Act & Assert
expect(Entry::available()->get())
->toHaveCount(1)
->first()->id->toEqual($availableEntry->id);
});

View File

@ -0,0 +1,28 @@
<?php
use App\Models\Audition;
use App\Models\Ensemble;
use App\Models\Event;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('has auditions', function () {
$event = Event::factory()->create();
$ddAudition = Audition::factory()->create(['event_id' => $event->id, 'name' => 'Digereedoo']);
Audition::factory()->count(7)->create(['event_id' => $event->id]);
expect($event->auditions->count())->toBe(8)
->and($event->auditions->first()->name)->toBe('Digereedoo');
});
it('has ensembles', function () {
// Arrange
$event = Event::factory()->create();
$ensemble = Ensemble::factory()->create(['event_id' => $event->id, 'name' => 'Symphonic Concert Wind Band']);
Ensemble::factory()->count(7)->create();
// Act & Assert
expect($event->ensembles->count())->toBe(1)
->and($event->ensembles->first()->name)->toBe('Symphonic Concert Wind Band');
});

View File

@ -0,0 +1,28 @@
<?php
use App\Models\Entry;
use App\Models\JudgeAdvancementVote;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->judge = User::factory()->create();
$this->entry = Entry::factory()->create();
$this->vote = JudgeAdvancementVote::create([
'user_id' => $this->judge->id,
'entry_id' => $this->entry->id,
'vote' => 'pass',
]);
});
test('has a judge', function () {
expect($this->vote->judge)->toBeInstanceOf(User::class)
->and($this->vote->judge->first_name)->toBe($this->judge->first_name);
});
test('has an entry', function () {
expect($this->vote->entry)->toBeInstanceOf(Entry::class)
->and($this->vote->entry->student->first_name)->toBe($this->entry->student->first_name);
});

View File

@ -0,0 +1,70 @@
<?php
use App\Models\Audition;
use App\Models\Entry;
use App\Models\Room;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->room = Room::factory()->create();
$otherRoom = Room::factory()->create();
$auditions = Audition::factory()->count(3)->create(['room_id' => $this->room->id]);
foreach ($auditions as $audition) {
Entry::factory()->count(5)->create(['audition_id' => $audition->id]);
}
$otherAuditions = Audition::factory()->count(3)->create(['room_id' => $otherRoom->id]);
foreach ($otherAuditions as $audition) {
Entry::factory()->count(5)->create(['audition_id' => $audition->id]);
}
});
test('created 30 entries in prep', function () {
expect(Entry::all()->count())->toBe(30);
});
it('has auditions', function () {
expect($this->room->auditions->count())->toBe(3)
->and($this->room->auditions->first())->toBeInstanceOf(Audition::class);
});
it('has entries', function () {
expect($this->room->entries->count())->toBe(15)
->and($this->room->entries->first())->toBeInstanceOf(Entry::class);
});
it('has users', function () {
$user = User::factory()->create();
$this->room->users()->attach($user->id);
expect($this->room->users->count())->toBe(1)
->and($this->room->users->first()->first_name)->toBe($user->first_name);
});
it('has judges', function () {
$user = User::factory()->create();
$this->room->judges()->attach($user->id);
expect($this->room->judges->count())->toBe(1)
->and($this->room->judges->first()->first_name)->toBe($user->first_name);
});
it('can add a judge', function () {
$user = User::factory()->create();
$this->room->addJudge($user->id);
expect($this->room->judges->count())->toBe(1)
->and($this->room->judges->first()->first_name)->toBe($user->first_name);
});
it('can remove a judge', function () {
// Arrange
$user = User::factory()->create();
$this->room->addJudge($user->id);
// Act & Assert
expect($this->room->judges->count())->toBe(1)
->and($this->room->judges->first()->first_name)->toBe($user->first_name);
// Re Act
$this->room->removeJudge($user->id);
// Reassert
expect($this->room->judges->count())->toBe(0);
});

View File

@ -0,0 +1,26 @@
<?php
use App\Models\Room;
use App\Models\RoomUser;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create();
$this->room = Room::factory()->create();
$this->roomUser = RoomUser::create(['user_id' => $this->user->id, 'room_id' => $this->room->id]);
});
it('has a user', function () {
expect($this->roomUser->user->first_name)->toBe($this->user->first_name);
});
it('has a judge', function () {
expect($this->roomUser->judge->first_name)->toBe($this->user->first_name);
});
it('has a room', function () {
expect($this->roomUser->room->name)->toBe($this->room->name);
});

View File

@ -0,0 +1,14 @@
<?php
use App\Models\School;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('has a school', function () {
$school = School::factory()->create();
$schoolEmailDomain = $school->emailDomains()->create([
'domain' => 'example.com',
]);
expect($schoolEmailDomain->school->id)->toBe($school->id);
});

Some files were not shown because too many files have changed in this diff Show More