From 3ff12fe57ec90886090bddee684cb5b2164d8361 Mon Sep 17 00:00:00 2001 From: dkecskes Date: Mon, 3 Nov 2025 00:35:11 +0100 Subject: [PATCH] feat: add student deletion functionality with confirmation dialog --- .../Auth/RegisteredUserController.php | 27 ++++- .../Controllers/StudentDataController.php | 66 +++++++++++++ backend/app/Models/Internship.php | 8 ++ backend/app/Models/User.php | 8 ++ backend/routes/api.php | 1 + .../dashboard/admin/students/edit/[id].vue | 91 +++++++++++++++++ .../pages/dashboard/admin/students/index.vue | 98 +++++++++++++++++++ 7 files changed, 295 insertions(+), 4 deletions(-) diff --git a/backend/app/Http/Controllers/Auth/RegisteredUserController.php b/backend/app/Http/Controllers/Auth/RegisteredUserController.php index 98509eb..ba2d421 100644 --- a/backend/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/backend/app/Http/Controllers/Auth/RegisteredUserController.php @@ -26,7 +26,7 @@ class RegisteredUserController extends Controller $password = bin2hex(random_bytes(16)); $request->validate([ - 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class], + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class], 'first_name' => ['required', 'string', 'max:64'], 'last_name' => ['required', 'string', 'max:64'], 'phone' => ['required', 'string', 'max:13'], @@ -56,14 +56,14 @@ class RegisteredUserController extends Controller 'password' => Hash::make($password), ]); - if($user->role === "STUDENT") { + if ($user->role === "STUDENT") { StudentData::create([ 'user_id' => $user->id, 'address' => $request->student_data['address'], 'personal_email' => $request->student_data['personal_email'], 'study_field' => $request->student_data['study_field'], ]); - } else if($user->role === "EMPLOYER") { + } else if ($user->role === "EMPLOYER") { Company::create([ 'name' => $request->company_data['name'], 'address' => $request->company_data['address'], @@ -79,7 +79,8 @@ class RegisteredUserController extends Controller return response()->noContent(); } - public function reset_password(Request $request): Response { + public function reset_password(Request $request): Response + { $request->validate([ 'email' => ['required', 'string', 'lowercase', 'email', 'max:255'], ]); @@ -97,4 +98,22 @@ class RegisteredUserController extends Controller return response()->noContent(); } + + public function reset_password_2(Request $request): Response + { + $request->validate([ + 'id' => ['required', 'string', 'lowercase', 'email', 'max:255'], + 'password' => ['required', 'string', 'lowercase', 'email', 'max:255'], + ]); + + $user = User::whereEmail($request->email)->first(); + if (!$user) { + return response(status: 400); + } + + $user->password = Hash::make($request->password); + $user->save(); + + return response()->noContent(); + } } \ No newline at end of file diff --git a/backend/app/Http/Controllers/StudentDataController.php b/backend/app/Http/Controllers/StudentDataController.php index 1187b0d..6506fa3 100644 --- a/backend/app/Http/Controllers/StudentDataController.php +++ b/backend/app/Http/Controllers/StudentDataController.php @@ -4,7 +4,9 @@ namespace App\Http\Controllers; use App\Models\StudentData; use App\Models\User; +use App\Models\InternshipStatus; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; class StudentDataController extends Controller { @@ -168,4 +170,68 @@ class StudentDataController extends Controller { // } + + /** + * Delete a student and all related data. + */ + public function delete(int $id) + { + $user = auth()->user(); + + // Admin kontrola + if ($user->role !== 'ADMIN') { + abort(403, 'Unauthorized'); + } + + $student = User::find($id); + + if (!$student) { + return response()->json([ + 'message' => 'No such student exists.' + ], 400); + } + + if ($student->role !== 'STUDENT') { + return response()->json([ + 'message' => 'User is not a student.' + ], 400); + } + + try { + DB::beginTransaction(); + + // 1. Získaj internship IDs + $internshipIds = $student->internships()->pluck('id')->toArray(); + + // 2. Vymaž internship statuses + if (!empty($internshipIds)) { + InternshipStatus::whereIn('internship_id', $internshipIds)->delete(); + } + + // 3. Vymaž internships + $student->internships()->delete(); + + // 4. Vymaž student_data + if ($student->studentData) { + $student->studentData()->delete(); + } + + // 5. Vymaž usera + $student->delete(); + + DB::commit(); + + return response()->json([ + 'message' => 'Student successfully deleted.' + ], 200); + + } catch (\Exception $e) { + DB::rollBack(); + + return response()->json([ + 'message' => 'Error deleting student.', + 'error' => $e->getMessage() + ], 500); + } + } } diff --git a/backend/app/Models/Internship.php b/backend/app/Models/Internship.php index a549885..17cd8e7 100644 --- a/backend/app/Models/Internship.php +++ b/backend/app/Models/Internship.php @@ -25,4 +25,12 @@ class Internship extends Model 'position_description', 'agreement', ]; + + /** + * Get the statuses for the internship. + */ + public function statuses() + { + return $this->hasMany(InternshipStatus::class, 'internship_id'); + } } diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index a052880..de3bb49 100644 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -57,4 +57,12 @@ class User extends Authenticatable { return $this->hasOne(StudentData::class, 'user_id'); } + + /** + * Get the internships for the user. + */ + public function internships() + { + return $this->hasMany(Internship::class, 'user_id'); + } } diff --git a/backend/routes/api.php b/backend/routes/api.php index 8eef125..6af5e6e 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -26,6 +26,7 @@ Route::middleware(['auth:sanctum'])->prefix('/students')->group(function () { Route::get('/', [StudentDataController::class, 'all']); Route::get('/{id}', [StudentDataController::class, 'get']); Route::post('/{id}', [StudentDataController::class, 'update_all']); + Route::delete('/{id}', [StudentDataController::class, 'delete']); }); Route::post('/password-reset', [RegisteredUserController::class, 'reset_password']) diff --git a/frontend/app/pages/dashboard/admin/students/edit/[id].vue b/frontend/app/pages/dashboard/admin/students/edit/[id].vue index 43c7d44..5ebb8e4 100644 --- a/frontend/app/pages/dashboard/admin/students/edit/[id].vue +++ b/frontend/app/pages/dashboard/admin/students/edit/[id].vue @@ -14,6 +14,12 @@ const student = ref(null); const loading = ref(true); const saving = ref(false); +// Delete state +const deleteDialog = ref(false); +const deleteLoading = ref(false); +const deleteError = ref(null); +const deleteSuccess = ref(false); + const form = ref({ name: '', email: '', @@ -70,6 +76,49 @@ async function saveChanges() { function cancel() { navigateTo('/dashboard/admin/students'); } + +// Funkcia na otvorenie delete dialogu +const openDeleteDialog = () => { + deleteDialog.value = true; + deleteError.value = null; +}; + +// Funkcia na zatvorenie dialogu +const closeDeleteDialog = () => { + deleteDialog.value = false; + deleteError.value = null; + deleteSuccess.value = false; +}; + +// Funkcia na vymazanie študenta +const deleteStudent = async () => { + if (!studentId) return; + + deleteLoading.value = true; + deleteError.value = null; + + try { + await client(`/api/students/${studentId}`, { + method: 'DELETE' + }); + + deleteSuccess.value = true; + + // Presmeruj na zoznam po 1.5 sekundách + setTimeout(() => { + navigateTo('/dashboard/admin/students'); + }, 1500); + + } catch (e) { + if (e instanceof FetchError) { + deleteError.value = e.response?._data?.message || 'Chyba pri mazaní študenta.'; + } else { + deleteError.value = 'Neznáma chyba pri mazaní študenta.'; + } + } finally { + deleteLoading.value = false; + } +}; diff --git a/frontend/app/pages/dashboard/admin/students/index.vue b/frontend/app/pages/dashboard/admin/students/index.vue index d3eb4c7..ebd743a 100644 --- a/frontend/app/pages/dashboard/admin/students/index.vue +++ b/frontend/app/pages/dashboard/admin/students/index.vue @@ -22,8 +22,66 @@ const headers = [ { title: 'Operácie', key: 'ops', align: 'middle' }, ]; +const client = useSanctumClient(); + // Načítame všetkých študentov const { data: students, error } = await useSanctumFetch('/api/students'); + +// State pre delete dialog +const deleteDialog = ref(false); +const studentToDelete = ref(null); +const deleteLoading = ref(false); +const deleteError = ref(null); +const deleteSuccess = ref(false); + +// Funkcia na otvorenie delete dialogu +const openDeleteDialog = (student: User) => { + studentToDelete.value = student; + deleteDialog.value = true; + deleteError.value = null; +}; + +// Funkcia na zatvorenie dialogu +const closeDeleteDialog = () => { + deleteDialog.value = false; + studentToDelete.value = null; + deleteError.value = null; + deleteSuccess.value = false; +}; + +// Funkcia na vymazanie študenta +const deleteStudent = async () => { + if (!studentToDelete.value) return; + + deleteLoading.value = true; + deleteError.value = null; + + try { + await client(`/api/students/${studentToDelete.value.id}`, { + method: 'DELETE' + }); + + // Odstránime študenta zo zoznamu + if (students.value) { + const index = students.value.findIndex(s => s.id === studentToDelete.value!.id); + if (index > -1) { + students.value.splice(index, 1); + } + } + + deleteSuccess.value = true; + + // Zavri dialog po 1.5 sekundách + setTimeout(() => { + closeDeleteDialog(); + }, 1500); + + } catch (err: any) { + deleteError.value = err.data?.message || 'Chyba pri mazaní študenta.'; + } finally { + deleteLoading.value = false; + } +};