Etude upload form working.

This commit is contained in:
Matt Young 2025-12-17 14:01:50 -06:00
parent 39a598c67b
commit f7c75fa1f9
5 changed files with 112 additions and 18 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Http\Requests\EtudeUploadRequest;
use App\Models\AuditionedEnsemble;
use App\Models\AuditionEtude;
use App\Models\Instrument;
@ -24,6 +25,7 @@ class AuditionEtudeController extends Controller
{
$instruments = Instrument::orderBy('score_order')->get();
$ensembles = AuditionedEnsemble::all();
return view('admin.audition_etude.create', [
'instruments' => $instruments,
'ensembles' => $ensembles,
@ -33,9 +35,22 @@ class AuditionEtudeController extends Controller
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
public function store(EtudeUploadRequest $request)
{
//
$path = $request->file('file_upload')->store('etudes', 'public');
$originalFilename = $request->file('file_upload')->getClientOriginalName();
$fileSize = $request->file('file_upload')->getSize();
AuditionEtude::create([
'instrument_id' => $request->instrument_id,
'auditioned_ensemble_id' => $request->auditioned_ensemble_id,
'set' => $request->set,
'file_path' => $path,
'original_filename' => $originalFilename,
'file_size' => $fileSize,
]);
return redirect()->route('admin.etudes.index')->with('success', 'Etude uploaded successfully.');
}
/**

View File

@ -0,0 +1,44 @@
<?php
namespace App\Http\Requests;
use App\Models\AuditionedEnsemble;
use Illuminate\Foundation\Http\FormRequest;
class EtudeUploadRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'auditioned_ensemble_id' => ['required', 'exists:auditioned_ensembles,id'],
'instrument_id' => ['required', 'exists:instruments,id'],
'set' => [
'required',
'numeric',
'min:1',
function ($attribute, $value, $fail) {
/** @noinspection PhpUndefinedFieldInspection */
$ensemble = AuditionedEnsemble::find($this->audtioned_ensemble_id);
if ($ensemble && $value > $ensemble->set_count) {
$fail("The set number cannot exceed {$ensemble->set_count} for this ensemble.");
}
},
],
'file_upload' => ['required', 'file', 'mimes:pdf', 'mimetypes:application/pdf', 'max:10240'],
];
}
}

View File

@ -2,13 +2,35 @@
<x-card class="max-w-xl mx-auto">
<x-slot:header class="bg-brand-600!">New Etude</x-slot:header>
<x-slot:body class="bg-white border border-brand-600">
<x-form.form method="POST" action="{{ route('admin.etudes.store') }}" x-data="{ selectedEnsemble: '', selectedInstrument: '', selectedSetNumber: '' }">
@if($errors->any())
<div class="mb-4 rounded-md bg-red-50 p-4 dark:bg-red-900/20">
<div class="flex">
<div class="shrink-0">
<svg class="size-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">There were {{ $errors->count() }} error(s) with your submission</h3>
<div class="mt-2 text-sm text-red-700 dark:text-red-300">
<ul class="list-disc space-y-1 pl-5">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
</div>
</div>
@endif
<x-form.form method="POST" action="{{ route('admin.etudes.store') }}" enctype="multipart/form-data" x-data="{ selectedEnsemble: '', selectedInstrument: '', selectedSetNumber: '' }">
<div>
<x-form.select name="auditioned_ensemble_id" x-model="selectedEnsemble">
<x-slot:label>Ensemble</x-slot:label>
<option value="">Select Ensemble...</option>
@foreach($ensembles as $ensemble)
<option value="{{ $ensemble->id }}">{{ $ensemble->name }}</option>
<option value="{{ $ensemble->id }}" {{ old('auditioned_ensemble_id') === $ensemble->id ? ' SELECTED ':'' }}>{{ $ensemble->name }}</option>
@endforeach
</x-form.select>
</div>
@ -17,15 +39,18 @@
<option value="">Select Instrument...</option>
<x-slot:label>Instrument</x-slot:label>
@foreach($instruments as $instrument)
<option value="{{ $instrument->id }}">{{ $instrument->instrument }}</option>
<option value="{{ $instrument->id }}" {{ old('instrument_id') === $instrument->id ? ' SELECTED ':'' }}>{{ $instrument->instrument }}</option>
@endforeach
</x-form.select>
</div>
<div class="mt-3" x-show="selectedEnsemble" x-cloak x-model="selectedSetNumber">
<x-form.input name="set" label="Set Number" type="number" min="1" x-bind:max="selectedEnsemble ? getSetCount(selectedEnsemble) : ''" />
<div class="mt-3">
<x-form.input name="set" label="Set Number" value="{{ old('set') }}" type="number" min="1" x-bind:max="selectedEnsemble ? getSetCount(selectedEnsemble) : ''" x-bind:disabled="!selectedEnsemble" x-model="selectedSetNumber" />
</div>
<div class="mt-3" x-show="selectedEnsemble && selectedInstrument && selectedSetNumber" x-cloak>
<x-form.input name="file" type="file" label="Etude PDF" />
<div class="mt-3">
<x-form.input name="file_upload" type="file" label="Etude PDF" />
</div>
<div class="mt-3 text-right">
<x-form.button type="submit" x-bind:disabled="!selectedEnsemble || !selectedInstrument || !selectedSetNumber">Save Etude</x-form.button>
</div>
</x-form.form>
</x-slot:body>

View File

@ -2,7 +2,7 @@
@php
$classes = [
'bg-brand-600 font-semibold text-white shadow-xs hover:bg-brand-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-600 dark:bg-brand-500 dark:shadow-none dark:hover:bg-brand-400 dark:focus-visible:outline-brand-500',
'bg-brand-600 font-semibold text-white shadow-xs hover:bg-brand-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-600 dark:bg-brand-500 dark:shadow-none dark:hover:bg-brand-400 dark:focus-visible:outline-brand-500 disabled:bg-brand-200 disabled:cursor-not-allowed',
'rounded-sm px-2 py-1 text-xs' => $size == 1,
'rounded-sm px-2 py-1 text-sm' => $size == 2,
'rounded-md px-2.5 py-1.5 text-sm' => $size == 3,

View File

@ -1,10 +1,20 @@
@props(['id', 'name', 'label'])
<label for="{{ $id ?? $name }}" class="" {{ $label->attributes->merge(['class'=>'block text-sm/6 font-medium text-gray-900 dark:text-white']) }}>{{ $label }}</label>
<div class="mt-2 grid grid-cols-1">
<select id="{{ $id ?? $name }}" name="{{ $name }}" {{ $attributes->merge(['class' => 'col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-gray-800 dark:focus-visible:outline-indigo-500']) }}>
<div>
<label for="{{ $id ?? $name }}"
class="" {{ $label->attributes->merge(['class'=>'block text-sm/6 font-medium text-gray-900 dark:text-white']) }}>{{ $label }}</label>
<div class="mt-2 grid grid-cols-1">
<select id="{{ $id ?? $name }}"
name="{{ $name }}" {{ $attributes->merge(['class' => 'col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-gray-800 dark:focus-visible:outline-indigo-500']) }}>
{{ $slot }}
</select>
<svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4 dark:text-gray-400">
<path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
<svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true"
class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4 dark:text-gray-400">
<path
d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd" fill-rule="evenodd"/>
</svg>
</div>
@error($name)
<p id="{{ $id ?? $name }}-error" class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
@enderror
</div>