Add delete modal. Finish tests for AdminSchools and AdminStudents
This commit is contained in:
parent
0c782c9a64
commit
cd26479fd9
|
|
@ -49,6 +49,7 @@ class SchoolController extends Controller
|
||||||
if (! Auth::user()->is_admin) {
|
if (! Auth::user()->is_admin) {
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
$school->loadCount('students');
|
||||||
|
|
||||||
return view('admin.schools.edit', ['school' => $school]);
|
return view('admin.schools.edit', ['school' => $school]);
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +72,8 @@ class SchoolController extends Controller
|
||||||
'zip' => request('zip'),
|
'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()
|
public function create()
|
||||||
|
|
@ -104,6 +106,17 @@ class SchoolController extends Controller
|
||||||
return redirect('/admin/schools')->with('success', 'School '.$school->name.' created');
|
return redirect('/admin/schools')->with('success', 'School '.$school->name.' created');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
public function add_domain(School $school)
|
||||||
{
|
{
|
||||||
if (! Auth::user()->is_admin) {
|
if (! Auth::user()->is_admin) {
|
||||||
|
|
@ -115,7 +128,8 @@ class SchoolController extends Controller
|
||||||
]);
|
]);
|
||||||
SchoolEmailDomain::updateOrInsert([
|
SchoolEmailDomain::updateOrInsert([
|
||||||
'school_id' => $school->id,
|
'school_id' => $school->id,
|
||||||
'domain' => request('domain')]);
|
'domain' => request('domain'),
|
||||||
|
]);
|
||||||
|
|
||||||
return redirect()->route('admin.schools.show', $school)->with('success', 'Domain Added');
|
return redirect()->route('admin.schools.show', $school)->with('success', 'Domain Added');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ use App\Models\Student;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use function abort;
|
use function abort;
|
||||||
|
use function to_route;
|
||||||
use function view;
|
use function view;
|
||||||
|
|
||||||
class StudentController extends Controller
|
class StudentController extends Controller
|
||||||
|
|
@ -19,7 +21,7 @@ class StudentController extends Controller
|
||||||
if (! Auth::user()->is_admin) {
|
if (! Auth::user()->is_admin) {
|
||||||
abort(403);
|
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]);
|
return view('admin.students.index', ['students' => $students]);
|
||||||
}
|
}
|
||||||
|
|
@ -66,8 +68,9 @@ class StudentController extends Controller
|
||||||
$minGrade = Audition::min('minimum_grade');
|
$minGrade = Audition::min('minimum_grade');
|
||||||
$maxGrade = Audition::max('maximum_grade');
|
$maxGrade = Audition::max('maximum_grade');
|
||||||
$schools = School::orderBy('name')->get();
|
$schools = School::orderBy('name')->get();
|
||||||
|
$student->loadCount('entries');
|
||||||
return view('admin.students.edit', ['student' => $student, 'schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]);
|
return view('admin.students.edit',
|
||||||
|
['student' => $student, 'schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, Student $student)
|
public function update(Request $request, Student $student)
|
||||||
|
|
@ -84,7 +87,8 @@ class StudentController extends Controller
|
||||||
|
|
||||||
foreach ($student->entries as $entry) {
|
foreach ($student->entries as $entry) {
|
||||||
if ($entry->audition->minimum_grade > request('grade') || $entry->audition->maximum_grade < request('grade')) {
|
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');
|
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.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"codedge/laravel-fpdf": "^1.12",
|
"codedge/laravel-fpdf": "^1.12",
|
||||||
"laravel/fortify": "^1.21",
|
"laravel/fortify": "^1.21",
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^11.0",
|
||||||
|
"laravel/pail": "^1.1",
|
||||||
"laravel/tinker": "^2.9",
|
"laravel/tinker": "^2.9",
|
||||||
"predis/predis": "^2.2",
|
"predis/predis": "^2.2",
|
||||||
"symfony/http-client": "^7.1",
|
"symfony/http-client": "^7.1",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "91ced10faccf6fc9b3fe05fe288ec1d6",
|
"content-hash": "a21ed75b45b3f61cbc76446701fbc3ce",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
|
|
@ -1491,6 +1491,84 @@
|
||||||
},
|
},
|
||||||
"time": "2024-05-21T17:57:45+00:00"
|
"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",
|
"name": "laravel/prompts",
|
||||||
"version": "v0.1.22",
|
"version": "v0.1.22",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
<x-layout.app>
|
<x-layout.app>
|
||||||
|
|
||||||
<x-card.card class="mx-auto max-w-xl">
|
<x-card.card class="mx-auto max-w-xl">
|
||||||
<x-card.heading>Edit School</x-card.heading>
|
<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.form method="PATCH" action="{{ route('admin.schools.update',$school) }}">
|
||||||
<x-form.body-grid>
|
<x-form.body-grid>
|
||||||
<x-form.field name="name" label_text="Name" colspan="6" value="{{ $school->name }}" />
|
<x-form.field name="name" label_text="Name" colspan="6" value="{{ $school->name }}" />
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,19 @@
|
||||||
<x-layout.app>
|
<x-layout.app>
|
||||||
<x-card.card class="mx-auto max-w-xl">
|
<x-card.card class="mx-auto max-w-xl">
|
||||||
<x-card.heading>Edit Student</x-card.heading>
|
<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.form method="PATCH" action="{{ route('admin.students.update',$student) }}">
|
||||||
<x-form.body-grid columns="12">
|
<x-form.body-grid columns="8">
|
||||||
<x-form.field name="first_name" label_text="First Name" colspan="5" value="{{ $student->first_name }}" />
|
<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="5" value="{{ $student->last_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-form.select name="grade" colspan="2">
|
||||||
<x-slot:label>Grade</x-slot:label>
|
<x-slot:label>Grade</x-slot:label>
|
||||||
@php($n = $minGrade)
|
@php($n = $minGrade)
|
||||||
|
|
@ -13,7 +22,7 @@
|
||||||
@php($n++);
|
@php($n++);
|
||||||
@endwhile
|
@endwhile
|
||||||
</x-form.select>
|
</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>
|
<x-slot:label>School</x-slot:label>
|
||||||
@foreach ($schools as $school)
|
@foreach ($schools as $school)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<x-table.td><a href="{{ route('admin.students.edit',$student) }}">{{ $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->school->name }}</x-table.td>
|
||||||
<x-table.td>{{ $student->grade }}</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>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</x-table.body>
|
</x-table.body>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
@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>
|
||||||
|
|
@ -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="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"/>
|
<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>
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
|
||||||
Route::post('/', 'store')->name('admin.students.store');
|
Route::post('/', 'store')->name('admin.students.store');
|
||||||
Route::get('/{student}/edit', 'edit')->name('admin.students.edit');
|
Route::get('/{student}/edit', 'edit')->name('admin.students.edit');
|
||||||
Route::patch('/{student}', 'update')->name('admin.students.update');
|
Route::patch('/{student}', 'update')->name('admin.students.update');
|
||||||
|
Route::delete('/{student}', 'destroy')->name('admin.students.destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Admin School Routes
|
// Admin School Routes
|
||||||
|
|
@ -103,6 +104,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
|
||||||
Route::patch('/{school}', 'update')->name('admin.schools.update');
|
Route::patch('/{school}', 'update')->name('admin.schools.update');
|
||||||
Route::post('/', 'store')->name('admin.schools.store');
|
Route::post('/', 'store')->name('admin.schools.store');
|
||||||
Route::delete('/domain/{domain}', 'destroy_domain')->name('admin.schools.destroy_domain');
|
Route::delete('/domain/{domain}', 'destroy_domain')->name('admin.schools.destroy_domain');
|
||||||
|
Route::delete('/{school}', 'destroy')->name('admin.schools.destroy');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,9 @@ use Illuminate\Support\Facades\Artisan;
|
||||||
Artisan::command('inspire', function () {
|
Artisan::command('inspire', function () {
|
||||||
$this->comment(Inspiring::quote());
|
$this->comment(Inspiring::quote());
|
||||||
})->purpose('Display an inspiring quote')->hourly();
|
})->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');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Entry;
|
||||||
use App\Models\School;
|
use App\Models\School;
|
||||||
use App\Models\SchoolEmailDomain;
|
use App\Models\SchoolEmailDomain;
|
||||||
|
use App\Models\Student;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Database\Factories\SchoolEmailDomainFactory;
|
use Database\Factories\SchoolEmailDomainFactory;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
@ -235,3 +237,69 @@ it('allows an administrator to update a school', function () {
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee($newData['name']);
|
->assertSee($newData['name']);
|
||||||
});
|
});
|
||||||
|
it('includes a form to destroy the school IF it has no students', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedSchool = School::factory()->create();
|
||||||
|
actingAs($this->adminUser);
|
||||||
|
// Act & Assert
|
||||||
|
get(route('admin.schools.edit', $condemnedSchool))
|
||||||
|
->assertOk()
|
||||||
|
->assertSeeInOrder([
|
||||||
|
'form',
|
||||||
|
'method',
|
||||||
|
'POST',
|
||||||
|
'action=',
|
||||||
|
route('admin.schools.destroy', $condemnedSchool),
|
||||||
|
'/form',
|
||||||
|
], false)
|
||||||
|
->assertSee('<input type="hidden" name="_method" value="DELETE">', false);
|
||||||
|
});
|
||||||
|
it('does not include the destruction form if the school has students', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedSchool = School::factory()->create();
|
||||||
|
Student::factory()->create(['school_id' => $condemnedSchool->id]);
|
||||||
|
actingAs($this->adminUser);
|
||||||
|
// Act & Assert
|
||||||
|
get(route('admin.schools.edit', $condemnedSchool))
|
||||||
|
->assertOk()
|
||||||
|
->assertDontSee('<input type="hidden" name="_method" value="DELETE">', false);
|
||||||
|
});
|
||||||
|
it('allows an administrator to destroy a school without students', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedSchool = School::factory()->create();
|
||||||
|
// Act & Assert
|
||||||
|
expect($condemnedSchool->exists())->toBeTrue();
|
||||||
|
actingAs($this->adminUser);
|
||||||
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
delete(route('admin.schools.destroy', $condemnedSchool))
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertRedirect(route('admin.schools.index'));
|
||||||
|
expect(School::find($condemnedSchool->id))->toBeNull();
|
||||||
|
});
|
||||||
|
it('does not allow an administrator to destroy a student with entries', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedSchool = School::factory()->create();
|
||||||
|
Student::factory()->create(['school_id' => $condemnedSchool->id]);
|
||||||
|
// Act & Assert
|
||||||
|
expect($condemnedSchool->exists())->toBeTrue();
|
||||||
|
actingAs($this->adminUser);
|
||||||
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
delete(route('admin.schools.destroy', $condemnedSchool))
|
||||||
|
->assertSessionHas('error', 'You cannot delete a school with students.')
|
||||||
|
->assertRedirect(route('admin.schools.index'))
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
expect(School::find($condemnedSchool->id))->toBeInstanceOf(School::class);
|
||||||
|
});
|
||||||
|
it('does not allow a non administrator to delete a student', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedSchool = School::factory()->create();
|
||||||
|
// Act & Assert
|
||||||
|
expect($condemnedSchool->exists())->toBeTrue();
|
||||||
|
actingAs(User::factory()->create());
|
||||||
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
delete(route('admin.schools.destroy', $condemnedSchool))
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertSessionHas('error', 'You do not have admin access.')
|
||||||
|
->assertRedirect(route('dashboard'));
|
||||||
|
expect(School::find($condemnedSchool->id))->toBeInstanceOf(School::class);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Models\Audition;
|
use App\Models\Audition;
|
||||||
|
use App\Models\Entry;
|
||||||
use App\Models\School;
|
use App\Models\School;
|
||||||
use App\Models\Student;
|
use App\Models\Student;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
use function Pest\Laravel\actingAs;
|
use function Pest\Laravel\actingAs;
|
||||||
|
use function Pest\Laravel\delete;
|
||||||
use function Pest\Laravel\get;
|
use function Pest\Laravel\get;
|
||||||
use function Pest\Laravel\patch;
|
use function Pest\Laravel\patch;
|
||||||
|
|
||||||
|
|
@ -107,7 +109,6 @@ it('is populated with existing data', function () {
|
||||||
// Arrange
|
// Arrange
|
||||||
Audition::factory()->create(['minimum_grade' => 1, 'maximum_grade' => 18]); // Needed for the grade select
|
Audition::factory()->create(['minimum_grade' => 1, 'maximum_grade' => 18]); // Needed for the grade select
|
||||||
actingAs($this->adminUser);
|
actingAs($this->adminUser);
|
||||||
$fieldList = ['first_name', 'last_name', 'grade', 'school_id'];
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
$response = get(route('admin.students.edit', $this->student));
|
$response = get(route('admin.students.edit', $this->student));
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
|
|
@ -157,3 +158,69 @@ it('allows an administrator to edit a student', function () {
|
||||||
->assertSee($newData['grade'])
|
->assertSee($newData['grade'])
|
||||||
->assertSee($newSchool->name);
|
->assertSee($newSchool->name);
|
||||||
});
|
});
|
||||||
|
it('includes a form to destroy the student IF they have no entries', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedStudent = Student::factory()->create();
|
||||||
|
actingAs($this->adminUser);
|
||||||
|
// Act & Assert
|
||||||
|
get(route('admin.students.edit', $condemnedStudent))
|
||||||
|
->assertOk()
|
||||||
|
->assertSeeInOrder([
|
||||||
|
'form',
|
||||||
|
'method',
|
||||||
|
'POST',
|
||||||
|
'action=',
|
||||||
|
route('admin.students.destroy', $condemnedStudent),
|
||||||
|
'/form',
|
||||||
|
], false)
|
||||||
|
->assertSee('<input type="hidden" name="_method" value="DELETE">', false);
|
||||||
|
});
|
||||||
|
it('does not include the destruction form if the student has entries', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedStudent = Student::factory()->create();
|
||||||
|
Entry::factory()->create(['student_id' => $condemnedStudent->id]);
|
||||||
|
actingAs($this->adminUser);
|
||||||
|
// Act & Assert
|
||||||
|
get(route('admin.students.edit', $condemnedStudent))
|
||||||
|
->assertOk()
|
||||||
|
->assertDontSee('<input type="hidden" name="_method" value="DELETE">', false);
|
||||||
|
});
|
||||||
|
it('allows an administrator to destroy a student without entries', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedStudent = Student::factory()->create();
|
||||||
|
// Act & Assert
|
||||||
|
expect($condemnedStudent->exists())->toBeTrue();
|
||||||
|
actingAs($this->adminUser);
|
||||||
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
delete(route('admin.students.destroy', $condemnedStudent))
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertRedirect(route('admin.students.index'));
|
||||||
|
expect(Student::find($condemnedStudent->id))->toBeNull();
|
||||||
|
});
|
||||||
|
it('does not allow an administrator to destroy a student with entries', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedStudent = Student::factory()->create();
|
||||||
|
Entry::factory()->create(['student_id' => $condemnedStudent->id]);
|
||||||
|
// Act & Assert
|
||||||
|
expect($condemnedStudent->exists())->toBeTrue();
|
||||||
|
actingAs($this->adminUser);
|
||||||
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
delete(route('admin.students.destroy', $condemnedStudent))
|
||||||
|
->assertSessionHas('error', 'You cannot delete a student with entries.')
|
||||||
|
->assertRedirect(route('admin.students.index'))
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
expect(Student::find($condemnedStudent->id))->toBeInstanceOf(Student::class);
|
||||||
|
});
|
||||||
|
it('does not allow a non administrator to delete a student', function () {
|
||||||
|
// Arrange
|
||||||
|
$condemnedStudent = Student::factory()->create();
|
||||||
|
// Act & Assert
|
||||||
|
expect($condemnedStudent->exists())->toBeTrue();
|
||||||
|
actingAs(User::factory()->create());
|
||||||
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
delete(route('admin.students.destroy', $condemnedStudent))
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertSessionHas('error', 'You do not have admin access.')
|
||||||
|
->assertRedirect(route('dashboard'));
|
||||||
|
expect(Student::find($condemnedStudent->id))->toBeInstanceOf(Student::class);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue