Scoring Guide Assignments

This commit is contained in:
Matt Young 2024-06-04 23:34:44 -05:00
parent 28ab05d1f6
commit 73eede9fe2
13 changed files with 230 additions and 83 deletions

View File

@ -117,6 +117,22 @@ class AuditionController extends Controller
return response()->json(['status' => 'success']); return response()->json(['status' => 'success']);
} }
public function scoringGuideUpdate(Request $request)
{
$audition = Audition::find($request->audition_id);
if ($audition) {
$audition->update([
'scoring_guide_id' => $request->new_guide_id
]);
return response()->json(['success' => true]);
}
return response()->json(['success' => false], 404);
}
public function destroy(Audition $audition) public function destroy(Audition $audition)
{ {
if(! Auth::user()->is_admin) abort(403); if(! Auth::user()->is_admin) abort(403);

View File

@ -7,6 +7,7 @@ use App\Models\ScoringGuide;
use App\Models\SubscoreDefinition; use App\Models\SubscoreDefinition;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use function abort; use function abort;
use function dd; use function dd;
use function request; use function request;
@ -18,7 +19,10 @@ class ScoringGuideController extends Controller
public function index() public function index()
{ {
if (! Auth::user()->is_admin) abort(403); if (! Auth::user()->is_admin) abort(403);
$guides = ScoringGuide::orderBy('name')->get(); DB::table('auditions')
->whereNull('scoring_guide_id')
->update(['scoring_guide_id' => 0]);
$guides = ScoringGuide::with('auditions')->orderBy('name')->get();
return view('admin.scoring.index',['guides'=>$guides]); return view('admin.scoring.index',['guides'=>$guides]);
} }

View File

@ -35,6 +35,11 @@ class Audition extends Model
return $this->belongsTo(Room::class); return $this->belongsTo(Room::class);
} }
public function scoringGuide(): BelongsTo
{
return $this->belongsTo(ScoringGuide::class);
}
public function dislpay_fee(): String public function dislpay_fee(): String
{ {
return '$' . number_format($this->entry_fee / 100, 2); return '$' . number_format($this->entry_fee / 100, 2);

View File

@ -4,9 +4,15 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class ScoringGuide extends Model class ScoringGuide extends Model
{ {
use HasFactory; use HasFactory;
protected $guarded = []; protected $guarded = [];
public function auditions(): HasMany
{
return $this->hasMany(Audition::class)->orderBy('score_order');
}
} }

View File

@ -1,6 +1,7 @@
<?php <?php
use App\Models\Room; use App\Models\Room;
use App\Models\ScoringGuide;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@ -12,14 +13,15 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Room::upsert([ if (! Room::find(0)) {
$room = Room::create([
'id' => 0, 'id' => 0,
'name' => 'Unassigned', 'name' => 'No Guide Assigned'
'description' => 'These auditions have no room', ]);
], $room->update([
uniqueBy: ['id'], 'id' => 0
update: ['name','description'] ]);
); }
} }
/** /**

View File

@ -0,0 +1,41 @@
<?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
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('auditions', function (Blueprint $table) {
$table->foreignIdFor(ScoringGuide::class)->nullable()->constrained()->cascadeOnUpdate()->nullOnDelete();
});
if (! ScoringGuide::find(0)) {
$sg = ScoringGuide::create([
'id' => 0,
'name' => 'No Guide Assigned'
]);
$sg->update([
'id' => 0
]);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('auditions', function (Blueprint $table) {
$table->dropForeignIdFor(ScoringGuide::class);
$table->dropColumn('scoring_guide_id');
});
}
};

View File

@ -1,46 +1,50 @@
<div x-data="auditionManager()"> <div x-data="sortableData()">
<template x-for="room in rooms" :key="room.id"> @foreach($guides as $guide)
<div> <div class="grid md:grid-cols-4 sm:grid-cols-2 mx-6 gap-2 mt-3 mb-3 guide-list" id="guide-{{ $guide->id }}" data-guide-id="{{ $guide->id }}">
<h2 x-text="room.name"></h2> @foreach($guide->auditions as $audition)
<ul :id="'room-' + room.id" class="audition-list"> <div class="px-3 py-3 border border-indigo-300 bg-gray-100" data-id="{{ $audition->id }}">{{ $audition->name }}</div>
<template x-for="audition in room.auditions" :key="audition.id"> @endforeach
<li x-text="audition.name" :data-id="audition.id"></li>
</template>
</ul>
</div> </div>
</template> @endforeach
</div> </div>
<script> <script>
function auditionManager() { function sortableData() {
return { return {
rooms: @json($rooms), // Pass rooms data from the controller
init() { init() {
this.rooms.forEach(room => { this.initSortable();
new Sortable(document.getElementById('room-' + room.id), { },
initSortable() {
document.querySelectorAll('.guide-list').forEach((el) => {
Sortable.create(el, {
group: 'shared', group: 'shared',
animation: 150, animation: 150,
onEnd: this.updateOrder.bind(this) onEnd: (evt) => {
}); let itemEl = evt.item; // dragged HTMLElement
}); let newGuideId = evt.to.getAttribute('data-guide-id');
}, let auditionId = itemEl.getAttribute('data-id');
updateOrder(event) {
let order = [];
event.to.querySelectorAll('li').forEach((el, index) => {
order.push({
id: el.dataset.id,
room_id: event.to.id.split('-')[1],
room_order: index
});
});
fetch('/update-order', { // Make an AJAX request to update the audition_guide_id
fetch('/update-audition-guide', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}' 'X-CSRF-TOKEN': '{{ csrf_token() }}'
}, },
body: JSON.stringify(order) body: JSON.stringify({
audition_id: auditionId,
new_guide_id: newGuideId
})
}).then(response => response.json())
.then(data => {
if (data.success) {
console.log('Audition updated successfully');
} else {
console.error('Failed to update audition');
}
});
}
});
}); });
} }
} }

View File

@ -0,0 +1,56 @@
<div x-data="sortableData()">
@foreach($guides as $guide)
<x-card.card class="mb-4">
<x-card.heading>{{ $guide->name }}</x-card.heading>
<div class="grid md:grid-cols-4 sm:grid-cols-2 mx-6 gap-2 mt-3 mb-3 guide-list" id="guide-{{ $guide->id }}" data-guide-id="{{ $guide->id }}">
@foreach($guide->auditions as $audition)
<div class="px-3 py-3 border border-indigo-300 bg-gray-100" data-id="{{ $audition->id }}">{{ $audition->name }}</div>
@endforeach
</div>
</x-card.card>
@endforeach
</div>
<script>
function sortableData() {
return {
init() {
this.initSortable();
},
initSortable() {
document.querySelectorAll('.guide-list').forEach((el) => {
Sortable.create(el, {
group: 'shared',
animation: 150,
onEnd: (evt) => {
let itemEl = evt.item; // dragged HTMLElement
let newGuideId = evt.to.getAttribute('data-guide-id');
let auditionId = itemEl.getAttribute('data-id');
// Make an AJAX request to update the audition_guide_id
fetch('/admin/scoring/assign_guide_to_audition', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({
audition_id: auditionId,
new_guide_id: newGuideId
})
}).then(response => response.json())
.then(data => {
if (data.success) {
console.log('Audition updated successfully');
} else {
console.error('Failed to update audition');
}
});
}
});
});
}
}
}
</script>

View File

@ -0,0 +1,39 @@
<x-card.card>
<x-card.heading>
Scoring Guides
<x-slot:subheading>Each audition will be assigned a scoring guide.</x-slot:subheading>
</x-card.heading>
<x-table.table>
<thead>
<tr>
<x-table.th>Name</x-table.th>
<x-table.th spacer_only></x-table.th>
</tr>
</thead>
<x-table.body>
@foreach($guides as $guide)
@if($guide->id == 0)
@continue
@endif
<tr>
<x-table.td>{{ $guide->name }}</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>
{{-- TODO add a link to delete if the guide is not in use--}}
</tr>
@endforeach
</x-table.body>
<tfoot>
<tr>
<x-form.form method="POST" action="/admin/scoring/guides" class="!px-0 !py-0">
<x-table.td>
<x-form.field name="name" label_text="Add New Scoring Guide" />
</x-table.td>
<x-table.td>
<x-form.button class="mt-6">Create</x-form.button>
</x-table.td>
</x-form.form>
</tr>
</tfoot>
</x-table.table>
</x-card.card>

View File

@ -1,41 +1,10 @@
@props(['test'=>'get it'])
<x-layout.app> <x-layout.app>
<x-slot:page_title>Scoring Rules</x-slot:page_title> <x-slot:page_title>Scoring Rules</x-slot:page_title>
<div class="mx-auto max-w-lg"> <div class="mx-auto max-w-lg">
<x-card.card> @include('admin.scoring.index-scoring-guide-management-card')
<x-card.heading> </div>
Scoring Guides <div class="mx-auto max-w-5xl mt-16">
<x-slot:subheading>Each audition will be assigned a scoring guide.</x-slot:subheading> @include('admin.scoring.index-audition-scoring-guide-assignment-card')
</x-card.heading>
<x-table.table>
<thead>
<tr>
<x-table.th>Name</x-table.th>
<x-table.th spacer_only></x-table.th>
</tr>
</thead>
<x-table.body>
@foreach($guides as $guide)
<tr>
<x-table.td>{{ $guide->name }}</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>
{{-- TODO add a link to delete if the guide is not in use--}}
</tr>
@endforeach
</x-table.body>
<tfoot>
<tr>
<x-form.form method="POST" action="/admin/scoring/guides" class="!px-0 !py-0">
<x-table.td>
<x-form.field name="name" label_text="Add New Scoring Guide" />
</x-table.td>
<x-table.td>
<x-form.button class="mt-6">Create</x-form.button>
</x-table.td>
</x-form.form>
</tr>
</tfoot>
</x-table.table>
</x-card.card>
</div> </div>
</x-layout.app> </x-layout.app>

View File

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

View File

@ -1,8 +1,11 @@
@php use App\Models\Audition;use App\Models\School;use App\Models\SchoolEmailDomain;use App\Models\User;use App\Settings;use Illuminate\Support\Facades\Auth;use Illuminate\Support\Facades\Session; @endphp @php use App\Models\Audition;use App\Models\School;use App\Models\SchoolEmailDomain;use App\Models\ScoringGuide;use App\Models\User;use App\Settings;use Illuminate\Support\Facades\Auth;use Illuminate\Support\Facades\DB;use Illuminate\Support\Facades\Session; @endphp
<x-layout.app> <x-layout.app>
<x-slot:page_title>Test Page</x-slot:page_title> <x-slot:page_title>Test Page</x-slot:page_title>
{{ Settings::auditionName() }} @php
@endphp
</x-layout.app> </x-layout.app>

View File

@ -14,6 +14,8 @@ use Illuminate\Support\Facades\Route;
Route::get('/test',[TestController::class,'flashTest'])->middleware('auth','verified'); Route::get('/test',[TestController::class,'flashTest'])->middleware('auth','verified');
Route::post('/admin/scoring/assign_guide_to_audition',[\App\Http\Controllers\Admin\AuditionController::class,'scoringGuideUpdate']);
Route::view('/','welcome')->middleware('guest'); Route::view('/','welcome')->middleware('guest');
// Admin Routes // Admin Routes
@ -21,6 +23,7 @@ Route::middleware(['auth','verified',CheckIfAdmin::class])->prefix('admin/')->gr
Route::view('/','admin.dashboard'); Route::view('/','admin.dashboard');
Route::post('/auditions/roomUpdate',[\App\Http\Controllers\Admin\AuditionController::class,'roomUpdate']); Route::post('/auditions/roomUpdate',[\App\Http\Controllers\Admin\AuditionController::class,'roomUpdate']);
// Rooms // Rooms
Route::prefix('rooms')->controller(\App\Http\Controllers\Admin\RoomController::class)->group(function() { Route::prefix('rooms')->controller(\App\Http\Controllers\Admin\RoomController::class)->group(function() {
Route::get('/','index'); Route::get('/','index');
@ -136,7 +139,6 @@ Route::prefix('filters')->middleware(['auth','verified'])->controller(FilterCont
Route::get('/admin_entry_filter/clear','clearAdminEntryFilter'); Route::get('/admin_entry_filter/clear','clearAdminEntryFilter');
}); });
//Route::get('/my_school', [SchoolController::class, 'my_school'])->middleware('auth','verified'); //Route::get('/my_school', [SchoolController::class, 'my_school'])->middleware('auth','verified');
//Route::get('/schools/create', [SchoolController::class, 'create'])->middleware('auth','verified'); //Route::get('/schools/create', [SchoolController::class, 'create'])->middleware('auth','verified');
//Route::post('/schools', [SchoolController::class, 'store'])->middleware('auth','verified'); //Route::post('/schools', [SchoolController::class, 'store'])->middleware('auth','verified');