Ensembles page working, need to add delete link

This commit is contained in:
Matt Young 2024-06-18 23:59:55 -05:00
parent 82b56a3f4f
commit 3a6ac955c0
10 changed files with 226 additions and 0 deletions

View File

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Ensemble;
use App\Models\Event;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class EnsembleController extends Controller
{
public function index()
{
$events = Event::with('ensembles')->get();
return view('admin.ensembles.index',compact('events'));
}
public function store(Request $request)
{
if(! Auth::user()->is_admin) abort(403);
request()->validate([
'name' => 'required',
'code' => 'required',
'event_id' => ['required','exists:events,id']
]);
Ensemble::create([
'name' => request('name'),
'code' => request('code'),
'event_id' => request('event_id'),
]);
return redirect()->route('admin.ensembles.index')->with('success','Ensemble created successfully');
}
public function updateEnsembleRank(Request $request)
{
$order = $request->input('order');
$eventId = $request->input('event_id');
foreach ($order as $item) {
Ensemble::where('id', $item['id'])
->where('event_id', $eventId)
->update(['rank' => $item['rank']]);
}
return response()->json(['status' => 'success']);
}
}

18
app/Models/Ensemble.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Ensemble extends Model
{
use HasFactory;
protected $guarded = [];
public function event(): BelongsTo
{
return $this->belongsTo(Event::class);
}
}

View File

@ -15,4 +15,10 @@ class Event extends Model
{
return $this->hasMany(Audition::class);
}
public function ensembles(): HasMany
{
return $this->hasMany(Ensemble::class)
->orderBy('rank');
}
}

View File

@ -0,0 +1,32 @@
<?php
use App\Models\Event;
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::create('ensembles', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Event::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
$table->string('name');
$table->string('code');
$table->integer('rank')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ensembles');
}
};

View File

@ -0,0 +1,29 @@
<x-card.card class="mb-6 mx-auto max-w-lg">
<x-card.heading>Ensembles for {{ $event->name }}</x-card.heading>
<x-table.table class="ml-3 sortable-table" id="event-{{$event->id}}">
<thead>
<tr>
<x-table.td>&nbsp;</x-table.td>
<x-table.th>Code</x-table.th>
<x-table.th>Name</x-table.th>
<x-table.th>&nbsp;</x-table.th>
</tr>
</thead>
<x-table.body>
@foreach($event->ensembles as $ensemble)
<tr data-id="{{ $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"/>
</svg>
</x-table.td>
{{-- <x-table.td class="handle"><i class="fas fa-bars"></i></x-table.td>--}}
<x-table.td>{{ $ensemble->code }}</x-table.td>
<x-table.td>{{ $ensemble->name }}</x-table.td>
</tr>
@endforeach
</x-table.body>
</x-table.table>
</x-card.card>

View File

@ -0,0 +1,10 @@
<x-help-modal>
<x-slot:title>About Ensembles in AuditionAdmin</x-slot:title>
<ul class="text-sm text-gray-600 list-disc ml-5 space-y-2">
{{-- move help text to a popout modal --}}
<li>After scoring is complete, students are seated into ensembles</li>
<li>The highest scoring entries will fill the highest ranked ensembles first</li>
<li>An ensemble cannot be deleted if students are seated into it</li>
<li>After creating an ensemble, you'll set the maximum number of students accepted from each audition into that ensemble</li>
</ul>
</x-help-modal>

View File

@ -0,0 +1,67 @@
<x-layout.app>
<x-slot:page_title>Ensembles</x-slot:page_title>
<x-slot:title_bar_right>@include('admin.ensembles.index-help-modal')</x-slot:title_bar_right>
<x-card.card class="mb-6 mx-auto max-w-lg">
<x-card.heading>Create New Ensemble</x-card.heading>
<x-form.form method="POST" action="{{ route('admin.ensembles.store') }}">
<x-form.body-grid columns="3" class="mb-5">
<x-form.field name="name" label_text="Name" colspan="2" />
<x-form.field name="code" label_text="Code" colspan="1" />
<x-form.select name="event_id" colspan="2">
<x-slot:label>Event</x-slot:label>
<option disabled selected>Select Event</option>
@foreach($events as $event)
<option value="{{ $event->id }}" class="text-gray-500">{{ $event->name }}</option>
@endforeach
</x-form.select>
<x-form.button class="mt-6">Create</x-form.button>
</x-form.body-grid>
</x-form.form>
</x-card.card>
<div id="app">
@foreach($events as $event)
@include('admin.ensembles.index-event-table')
@endforeach
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const tables = document.querySelectorAll('.sortable-table');
tables.forEach(table => {
new Sortable(table.querySelector('tbody'), {
handle: '.handle',
animation: 150,
onEnd: function (evt) {
const rows = Array.from(evt.from.children);
const order = rows.map((row, index) => ({
id: row.dataset.id,
rank: index + 1
}));
// Send the new order to the server
fetch('/admin/ensembles/updateEnsembleRank', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({
event_id: table.id.split('-')[1],
order: order
})
}).then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
}
});
});
});
</script>
</x-layout.app>

View File

@ -18,10 +18,12 @@
From: "opacity-100 translate-y-0"
To: "opacity-0 translate-y-1"
-->
{{-- TODO conver to named routes --}}
<div class="absolute left-1/2 z-10 mt-5 flex w-screen max-w-min -translate-x-1/2 px-4" x-show="open" x-cloak>
<div class="w-56 shrink rounded-xl bg-white p-4 text-sm font-semibold leading-6 text-gray-900 shadow-lg ring-1 ring-gray-900/5">
<a href="/admin/events" class="block p-2 hover:text-indigo-600">Events</a>
<a href="/admin/auditions" class="block p-2 hover:text-indigo-600">Auditions</a>
<a href="{{ route('admin.ensembles.index') }}" class="block p-2 hover:text-indigo-600">Ensembles</a>
<a href="/admin/scoring" class="block p-2 hover:text-indigo-600">Scoring</a>
<a href="/admin/rooms" class="block p-2 hover:text-indigo-600">Rooms</a>
<a href="/admin/rooms/judging_assignments" class="block p-2 hover:text-indigo-600">Judges</a>

View File

@ -5,6 +5,10 @@
'subtitle' => false,
'sortable' => true
])
@php
if ($title) $with_title_area = true;
@endphp
<div>
@if($with_title_area)
<div class="mb-4 mt-4 sm:px 4 sm:flex sm:items-center">

View File

@ -58,6 +58,13 @@ Route::middleware(['auth','verified',CheckIfAdmin::class])->prefix('admin/')->gr
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
// Admin Ensemble Routes
Route::prefix('ensembles')->controller(\App\Http\Controllers\Admin\EnsembleController::class)->group(function() {
Route::get('/','index')->name('admin.ensembles.index');
Route::post('/','store')->name('admin.ensembles.store');
Route::post('/updateEnsembleRank','updateEnsembleRank')->name('admin.ensembles.updateEnsembleRank');
});
// Admin Event Routes
Route::prefix('events')->controller(\App\Http\Controllers\Admin\EventController::class)->group(function() {
Route::get('/','index')->name('admin.events.index');