diff --git a/app/Enum/StudentStatus.php b/app/Enum/StudentStatus.php new file mode 100644 index 0000000..f56d6bf --- /dev/null +++ b/app/Enum/StudentStatus.php @@ -0,0 +1,23 @@ +ajax()) { + return DataTables::eloquent(Student::query()) + ->addColumn('action', function ($row) { + return ''; + }) + ->editColumn('image', fn ($row) => 'user-avatar') + ->editColumn('gender', fn ($row) => __('label.'.$row->gender)) + ->editColumn('status', fn ($row) => __('label.'.$row->status)) + ->filterColumn('gender', fn ($query, $keyword) => $query->where('gender', $keyword)) + ->rawColumns(['action', 'image']) + ->toJson(); + } + + return view('pages.student.index'); + } + + /** + * Show the form for creating a new resource. + */ + public function create(): View + { + return view('pages.student.create', [ + 'guardians' => User::role(Role::STUDENT_GUARDIAN)->get(), + ]); + } + + /** + * Store a newly created resource in storage. + */ + public function store(StoreStudentRequest $request): RedirectResponse + { + try { + $data = $request->validated(); + if ($request->hasFile('image')) { + $data['image'] = time().random_int(0, PHP_INT_MAX).'.'.$request->file('image')->extension(); + Storage::putFileAs('public', $request->file('image'), $data['image']); + } + + Student::create($data); + + return redirect()->route('student.index')->with('notification', $this->successNotification('notification.success_create', 'menu.student')); + } catch (\Throwable $throwable) { + Log::error($throwable->getMessage()); + + return back()->with('notification', $this->successNotification('notification.fail_create', 'menu.student')); + } + } + + /** + * Display the specified resource. + */ + public function show(Student $student): View + { + return view('pages.student.show', [ + 'student' => $student, + ]); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Student $student): View + { + return view('pages.student.edit', [ + 'student' => $student, + 'guardians' => User::role(Role::STUDENT_GUARDIAN)->get(), + ]); + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateStudentRequest $request, Student $student): RedirectResponse + { + try { + $data = $request->validated(); + if ($request->hasFile('image')) { + if ($student->image) { + Storage::delete("public/{$student->image}"); + } + + $data['image'] = time().random_int(0, PHP_INT_MAX).'.'.$request->file('image')->extension(); + Storage::putFileAs('public', $request->file('image'), $data['image']); + } + + $student->update($data); + + return back()->with('notification', $this->successNotification('notification.success_update', 'menu.student')); + } catch (\Throwable $throwable) { + Log::error($throwable->getMessage()); + + return back()->with('notification', $this->successNotification('notification.fail_update', 'menu.student')); + } + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Student $student): RedirectResponse + { + try { + $student->delete(); + + return back()->with('notification', $this->successNotification('notification.success_delete', 'menu.student')); + } catch (\Throwable $throwable) { + Log::error($throwable->getMessage()); + + return back()->with('notification', $this->successNotification('notification.fail_delete', 'menu.student')); + } + } +} diff --git a/app/Http/Requests/StoreStudentRequest.php b/app/Http/Requests/StoreStudentRequest.php new file mode 100644 index 0000000..a166bf3 --- /dev/null +++ b/app/Http/Requests/StoreStudentRequest.php @@ -0,0 +1,59 @@ +user()->isRole(Role::OWNER) || auth()->user()->isRole(Role::HEADMASTER) || auth()->user()->isRole(Role::ADMINISTRATOR); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'student_id_number' => ['nullable', Rule::unique('students', 'student_id_number')], + 'name' => ['required'], + 'nickname' => ['nullable'], + 'birthplace' => ['nullable'], + 'birthdate' => ['required', 'date'], + 'gender' => ['required'], + 'status' => ['required'], + 'admission_date' => ['required', 'date'], + 'departure_date' => ['nullable', Rule::requiredIf(StudentStatus::isIncluded($this->status, StudentStatus::EXPELLED, StudentStatus::QUIT, StudentStatus::ON_LEAVE, StudentStatus::GRADUATED)), 'after:admission_date'], + 'image' => ['nullable', 'image'], + 'student_guardian_id' => ['required'], + ]; + } + + public function attributes(): array + { + return [ + 'student_id_number' => __('field.student_id_number'), + 'name' => __('field.name'), + 'nickname' => __('field.nickname'), + 'birthplace' => __('field.birthplace'), + 'birthdate' => __('field.birthdate'), + 'gender' => __('field.gender'), + 'status' => __('field.status'), + 'admission_date' => __('field.admission_date'), + 'departure_date' => __('field.departure_date'), + 'image' => __('field.image'), + 'student_guardian_id' => __('field.student_guardian_id'), + ]; + } +} diff --git a/app/Http/Requests/UpdateStudentRequest.php b/app/Http/Requests/UpdateStudentRequest.php new file mode 100644 index 0000000..fda7a14 --- /dev/null +++ b/app/Http/Requests/UpdateStudentRequest.php @@ -0,0 +1,59 @@ +user()->isRole(Role::OWNER) || auth()->user()->isRole(Role::HEADMASTER) || auth()->user()->isRole(Role::ADMINISTRATOR); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'student_id_number' => ['nullable', Rule::unique('students', 'student_id_number')->ignore($this->id)], + 'name' => ['required'], + 'nickname' => ['nullable'], + 'birthplace' => ['nullable'], + 'birthdate' => ['required', 'date'], + 'gender' => ['required'], + 'status' => ['required'], + 'admission_date' => ['required', 'date'], + 'departure_date' => ['nullable', Rule::requiredIf(StudentStatus::isIncluded($this->status, StudentStatus::EXPELLED, StudentStatus::QUIT, StudentStatus::ON_LEAVE, StudentStatus::GRADUATED)), 'after:admission_date'], + 'image' => ['nullable', 'image'], + 'student_guardian_id' => ['required'], + ]; + } + + public function attributes(): array + { + return [ + 'student_id_number' => __('field.student_id_number'), + 'name' => __('field.name'), + 'nickname' => __('field.nickname'), + 'birthplace' => __('field.birthplace'), + 'birthdate' => __('field.birthdate'), + 'gender' => __('field.gender'), + 'status' => __('field.status'), + 'admission_date' => __('field.admission_date'), + 'departure_date' => __('field.departure_date'), + 'image' => __('field.image'), + 'student_guardian_id' => __('field.student_guardian_id'), + ]; + } +} diff --git a/app/Models/Student.php b/app/Models/Student.php new file mode 100644 index 0000000..f6ad4de --- /dev/null +++ b/app/Models/Student.php @@ -0,0 +1,51 @@ +image) { + return asset('storage/'.$this->image); + } + + return asset('404_Black.jpg'); + } + ); + } + + protected static function boot(): void + { + parent::boot(); + static::creating(function (Student $student) { + if (is_null($student->student_id_number)) { + $student->student_id_number = time().random_int(0, 1000).time(); + } + }); + static::deleting(function (Student $student) { + if ($student->image) { + Storage::delete("public/$student->image"); + } + }); + } +} diff --git a/database/factories/StudentFactory.php b/database/factories/StudentFactory.php new file mode 100644 index 0000000..675e1bc --- /dev/null +++ b/database/factories/StudentFactory.php @@ -0,0 +1,36 @@ + + */ +class StudentFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $gender = fake()->randomElement(Gender::cases()); + $status = fake()->randomElement(StudentStatus::cases()); + + return [ + 'student_id_number' => fake()->unique()->creditCardNumber(), + 'name' => fake()->name($gender->value), + 'birthplace' => fake()->city(), + 'birthdate' => fake()->dateTimeBetween('-12 years', '-6 years'), + 'gender' => $gender, + 'status' => $status, + 'admission_date' => fake()->dateTimeBetween('-2 years', '-2 weeks'), + 'departure_date' => $status === StudentStatus::ACTIVE ? null : fake()->dateTimeBetween('-2 weeks'), + ]; + } +} diff --git a/database/migrations/2024_06_03_130911_create_students_table.php b/database/migrations/2024_06_03_130911_create_students_table.php new file mode 100644 index 0000000..90b90a7 --- /dev/null +++ b/database/migrations/2024_06_03_130911_create_students_table.php @@ -0,0 +1,38 @@ +id(); + $table->string('student_id_number')->unique(); + $table->string('name'); + $table->string('nickname')->nullable(); + $table->string('birthplace')->nullable(); + $table->date('birthdate')->nullable(); + $table->string('gender'); + $table->string('status')->default(\App\Enum\StudentStatus::CANDIDATE); + $table->date('admission_date'); + $table->date('departure_date')->nullable(); + $table->string('image')->nullable(); + $table->foreignId('student_guardian_id')->constrained('users'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('students'); + } +}; diff --git a/database/seeders/Dummy/StudentSeeder.php b/database/seeders/Dummy/StudentSeeder.php new file mode 100644 index 0000000..8cc5112 --- /dev/null +++ b/database/seeders/Dummy/StudentSeeder.php @@ -0,0 +1,25 @@ +pluck('id')->toArray(); + + foreach ($guardianIds as $id) { + Student::factory(rand(0, 3))->create([ + 'student_guardian_id' => $id, + ]); + } + } +} diff --git a/lang/en/field.php b/lang/en/field.php index 9a4010c..86ef375 100644 --- a/lang/en/field.php +++ b/lang/en/field.php @@ -23,4 +23,12 @@ 'address' => 'Address', 'marital_status' => 'Marital Status', 'gender' => 'Gender', + 'student_id_number' => 'Student ID Number', + 'nickname' => 'Nickname', + 'birthplace' => 'Birthplace', + 'birthdate' => 'Birthdate', + 'status' => 'Status', + 'admission_date' => 'Admission Date', + 'departure_date' => 'Departure Date', + 'student_guardian_id' => 'Student Guardian', ]; diff --git a/lang/en/label.php b/lang/en/label.php index 6f90594..7392a6a 100644 --- a/lang/en/label.php +++ b/lang/en/label.php @@ -42,4 +42,10 @@ 'female' => 'Female', 'required' => 'Required', 'allowed_image_upload' => 'Allowed JPG or PNG. Max size of 2MB', + 'candidate' => 'Candidate', + 'active' => 'Active', + 'graduated' => 'Graduated', + 'expelled' => 'Expelled', + 'quit' => 'Quit', + 'on_leave' => 'On Leave', ]; diff --git a/lang/en/menu.php b/lang/en/menu.php index d64213e..c4ed1eb 100644 --- a/lang/en/menu.php +++ b/lang/en/menu.php @@ -21,4 +21,6 @@ 'setting' => 'Setting', 'administrator' => 'Administrator', 'student_guardian' => 'Student Guardian', + 'student' => 'Student', + 'academic' => 'Academic', ]; diff --git a/lang/id/field.php b/lang/id/field.php index df30697..8f405cf 100644 --- a/lang/id/field.php +++ b/lang/id/field.php @@ -23,4 +23,12 @@ 'address' => 'Alamat', 'marital_status' => 'Status Perkawinan', 'gender' => 'Jenis Kelamin', + 'student_id_number' => 'Nomor Induk Santri', + 'nickname' => 'Nama Panggilan', + 'birthplace' => 'Tempat Lahir', + 'birthdate' => 'Tanggal Lahir', + 'status' => 'Status', + 'admission_date' => 'Tanggal Masuk', + 'departure_date' => 'Tanggal Keluar', + 'student_guardian_id' => 'Wali Santri', ]; diff --git a/lang/id/label.php b/lang/id/label.php index 8622315..a2684d8 100644 --- a/lang/id/label.php +++ b/lang/id/label.php @@ -42,4 +42,10 @@ 'female' => 'Perempuan', 'required' => 'Wajib', 'allowed_image_upload' => 'Hanya boleh JPG atau PNG. Ukuran maksimal 2MB', + 'candidate' => 'Calon Santri', + 'active' => 'Aktif', + 'graduated' => 'Lulus', + 'expelled' => 'Dikeluarkan', + 'quit' => 'Berhenti', + 'on_leave' => 'Cuti', ]; diff --git a/lang/id/menu.php b/lang/id/menu.php index 54520f5..1207d13 100644 --- a/lang/id/menu.php +++ b/lang/id/menu.php @@ -21,4 +21,6 @@ 'setting' => 'Pengaturan', 'administrator' => 'Admin', 'student_guardian' => 'Wali Santri', + 'student' => 'Santri', + 'academic' => 'Akademik', ]; diff --git a/resources/menu/verticalMenu.json b/resources/menu/verticalMenu.json index 5fbdca3..5f76bc0 100644 --- a/resources/menu/verticalMenu.json +++ b/resources/menu/verticalMenu.json @@ -53,6 +53,27 @@ "administrator" ] }, + { + "menuHeader": "menu.academic", + "role": [ + "owner", + "headmaster", + "administrator", + "teacher" + ] + }, + { + "url": "student.index", + "name": "menu.student", + "icon": "menu-icon tf-icons bx bxs-graduation", + "slug": "student.*", + "role": [ + "owner", + "headmaster", + "administrator", + "teacher" + ] + }, { "menuHeader": "menu.setting", "role": [ diff --git a/resources/views/pages/student/create.blade.php b/resources/views/pages/student/create.blade.php new file mode 100644 index 0000000..9cfa8f5 --- /dev/null +++ b/resources/views/pages/student/create.blade.php @@ -0,0 +1,131 @@ +@extends('layouts.dashboard') + +@section('content') +
+

+ {{ __('menu.student') }} / + {{ __('label.new') }} +

+
+ + {{ __('button.back') }} + +
+
+ +
+
+ @csrf +
+
+ + user-avatar + +
+ + + + {{ __('label.allowed_image_upload') }} +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+
+ +@endsection + +@push('script') + +@endpush diff --git a/resources/views/pages/student/edit.blade.php b/resources/views/pages/student/edit.blade.php new file mode 100644 index 0000000..104ed6c --- /dev/null +++ b/resources/views/pages/student/edit.blade.php @@ -0,0 +1,134 @@ +@extends('layouts.dashboard') + +@section('content') +
+

+ {{ __('menu.student') }} / + {{ __('label.edit') }} +

+
+ + {{ __('button.back') }} + +
+
+ +
+
+
+
+ + user-avatar + +
+ + + + {{ __('label.allowed_image_upload') }} +
+
+
+
+
+ @csrf + @method('PUT') + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+
+ +@endsection + +@push('script') + +@endpush diff --git a/resources/views/pages/student/index.blade.php b/resources/views/pages/student/index.blade.php new file mode 100644 index 0000000..4f8ab0c --- /dev/null +++ b/resources/views/pages/student/index.blade.php @@ -0,0 +1,65 @@ +@extends('layouts.dashboard') + +@section('content') +
+

{{ __('menu.student') }}

+
+ + + {{ __('menu.student') }} + +
+
+ +
+
+ + + + + + + + + + + + + +
{{ __('field.student_id_number') }}{{ __('field.name') }}{{ __('field.gender') }}{{ __('field.status') }}
+
+
+@endsection + +@push('style') + +@endpush + +@push('script') + + +@endpush diff --git a/routes/web.php b/routes/web.php index 28e302e..fef9b6a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -21,6 +21,8 @@ Route::resource('teacher', \App\Http\Controllers\TeacherController::class); Route::resource('student-guardian', \App\Http\Controllers\StudentGuardianController::class); + Route::resource('student', \App\Http\Controllers\StudentController::class); + Route::as('account.')->group(function () { Route::get('/account/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/account/profile', [ProfileController::class, 'update'])->name('profile.update');