From bf12a38ccbafae12841c8f6a31b24c9755e94c71 Mon Sep 17 00:00:00 2001 From: cukiprit Date: Sat, 14 Mar 2026 01:16:16 +0700 Subject: [PATCH] feat: Implement Master Induk (whitelist) for user registration and add return receipt email functionality. --- .../Admin/AdminPeminjamanController.php | 34 ++++ app/Http/Controllers/Admin/UserController.php | 29 ++- app/Http/Controllers/ProfileController.php | 4 +- app/Mail/ReturnReceipt.php | 58 ++++++ app/Models/Book.php | 2 +- app/Providers/AppServiceProvider.php | 3 + database/factories/MasterIndukFactory.php | 25 +++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/UserSeeder.php | 25 +++ .../views/admin/peminjaman/index.blade.php | 36 ++-- .../views/admin/pengguna/create.blade.php | 37 +++- .../views/admin/pengguna/index.blade.php | 177 ++++++++++-------- .../views/emails/return_receipt.blade.php | 65 +++++++ resources/views/peminjaman/form.blade.php | 8 +- resources/views/profile/index.blade.php | 2 +- .../partials/personal-activities.blade.php | 6 +- .../update-profile-information-form.blade.php | 4 +- 17 files changed, 396 insertions(+), 120 deletions(-) create mode 100644 app/Mail/ReturnReceipt.php create mode 100644 database/factories/MasterIndukFactory.php create mode 100644 resources/views/emails/return_receipt.blade.php diff --git a/app/Http/Controllers/Admin/AdminPeminjamanController.php b/app/Http/Controllers/Admin/AdminPeminjamanController.php index 084ade2..6863c02 100644 --- a/app/Http/Controllers/Admin/AdminPeminjamanController.php +++ b/app/Http/Controllers/Admin/AdminPeminjamanController.php @@ -9,6 +9,9 @@ use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Log; +use App\Mail\ReturnReceipt; class AdminPeminjamanController extends Controller { @@ -301,6 +304,7 @@ public function kembalikan(Request $request) 'returns.*.fine_damage' => 'required|integer', 'returns.*.fine_overdue' => 'required|integer', 'returns.*.notes' => 'nullable|string', + 'send_email' => 'nullable|boolean', ]); \DB::beginTransaction(); @@ -331,6 +335,36 @@ public function kembalikan(Request $request) \DB::commit(); + // Send email if requested + if ($request->send_email && $request->user_id) { + $user = User::findOrFail($request->user_id); + $totalDenda = 0; + $emailReturns = []; + + foreach ($validated['returns'] as $item) { + $book = Book::find($item['book_id']); + $emailReturns[] = [ + 'judul' => $book->judul ?? 'Buku Unknown', + 'condition' => $item['condition'], + 'fine_damage' => $item['fine_damage'], + 'fine_overdue' => $item['fine_overdue'], + ]; + $totalDenda += ($item['fine_damage'] + $item['fine_overdue']); + } + + try { + Mail::to($user->email)->send(new ReturnReceipt($user, $emailReturns, $totalDenda)); + } catch (\Exception $e) { + // Log the error but don't fail the return process + \Log::error('Gagal mengirim email pengembalian: ' . $e->getMessage()); + return response()->json([ + 'status' => 'success', + 'message' => 'Buku berhasil dikembalikan, namun gagal mengirim email.', + 'email_error' => $e->getMessage() + ]); + } + } + return response()->json(['status' => 'success', 'message' => 'Buku berhasil dikembalikan.']); } catch (\Exception $e) { \DB::rollBack(); diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index e4c87fd..6d7f814 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -18,9 +18,9 @@ public function index() $query->where('role', request('role')); } - $users = $query->paginate(10)->appends(request()->query()); + $users = $query->paginate(10, ['*'], 'page')->appends(request()->query()); - $whitelists = MasterInduk::orderBy('created_at', 'desc')->get(); + $whitelists = MasterInduk::orderBy('created_at', 'desc')->paginate(10, ['*'], 'whitelist_page')->appends(request()->query()); return view('admin.pengguna.index', [ 'pageTitle' => 'Daftar Pengguna', @@ -29,9 +29,17 @@ public function index() ]); } - public function create() + public function create(Request $request) { - return view('admin.pengguna.create', ['pageTitle' => 'Tambah Pengguna Baru']); + $prefilledData = null; + if ($request->has('nomor_induk')) { + $prefilledData = MasterInduk::where('nomor_induk', $request->nomor_induk)->first(); + } + + return view('admin.pengguna.create', [ + 'pageTitle' => 'Tambah Pengguna Baru', + 'prefilledData' => $prefilledData + ]); } public function edit($id) @@ -48,7 +56,7 @@ public function store(Request $request) $validated = $request->validate([ 'nama_lengkap' => 'required|string|max:255', 'email' => 'required|email|unique:users,email', - 'nomor_induk' => 'nullable|string|max:50', + 'nomor_induk' => 'required|string|max:50|unique:users,nomor_induk', 'phone' => 'nullable|string|max:20', 'role' => 'required|in:siswa,guru,penjaga perpus', 'kelas' => 'nullable|string|max:50', @@ -56,6 +64,17 @@ public function store(Request $request) 'password' => 'required|string|min:8|confirmed', ]); + // Validasi Whitelist untuk Siswa & Guru + if (in_array($validated['role'], ['siswa', 'guru'])) { + $isWhitelisted = MasterInduk::where('nomor_induk', $validated['nomor_induk']) + ->where('role', $validated['role']) + ->exists(); + + if (!$isWhitelisted) { + return back()->withErrors(['nomor_induk' => 'Nomor Induk ini tidak terdaftar dalam Data Induk (Whitelist) atau Role tidak sesuai.'])->withInput(); + } + } + $validated['password'] = Hash::make($validated['password']); $validated['name'] = $validated['nama_lengkap']; // Set name field for compatibility diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index d2449c9..094f389 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -49,7 +49,7 @@ public function index(Request $request): \Illuminate\View\View|\Illuminate\Http\ $viewData['bukuOffline'] = $loans->map(fn($loan) => [ 'judul' => $loan->book->judul, 'penulis' => $loan->book->penulis, - 'sisa_hari' => (int) now()->diffInDays(Carbon::parse($loan->due_at), false), + 'sisa_hari' => (int) now()->startOfDay()->diffInDays(Carbon::parse($loan->due_at)->startOfDay(), false), 'cover' => $loan->book->cover, ]); @@ -87,7 +87,7 @@ public function index(Request $request): \Illuminate\View\View|\Illuminate\Http\ $viewData['bukuOffline'] = $loans->map(fn($loan) => [ 'judul' => $loan->book->judul, 'penulis' => $loan->book->penulis, - 'sisa_hari' => (int) now()->diffInDays(Carbon::parse($loan->due_at), false), + 'sisa_hari' => (int) now()->startOfDay()->diffInDays(Carbon::parse($loan->due_at)->startOfDay(), false), 'cover' => $loan->book->cover, ]); diff --git a/app/Mail/ReturnReceipt.php b/app/Mail/ReturnReceipt.php new file mode 100644 index 0000000..40e609e --- /dev/null +++ b/app/Mail/ReturnReceipt.php @@ -0,0 +1,58 @@ +user = $user; + $this->returns = $returns; + $this->totalDenda = $totalDenda; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + subject: 'Bukti Pengembalian Buku - ' . config('app.name'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + view: 'emails.return_receipt', + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/Book.php b/app/Models/Book.php index 16930c5..1bd55a3 100644 --- a/app/Models/Book.php +++ b/app/Models/Book.php @@ -7,7 +7,7 @@ class Book extends Model { protected $fillable = [ - 'judul', 'penulis', 'cover', 'kode_buku', + 'judul', 'penulis', 'cover', 'kode_buku', 'stok', 'category_id', 'tahun', 'status', 'is_new', 'tipe_akses', 'file_pdf', 'is_arsip' ]; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d90c3ed..ed5d1d8 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,6 +5,7 @@ use App\Models\Loan; use Carbon\Carbon; use Illuminate\Support\Facades\Auth; +use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\View; @@ -28,6 +29,8 @@ public function boot(): void URL::forceScheme('https'); } + Paginator::useBootstrapFive(); + // View Composer untuk semua view (*) View::composer('*', function ($view) { if (Auth::check()) { diff --git a/database/factories/MasterIndukFactory.php b/database/factories/MasterIndukFactory.php new file mode 100644 index 0000000..8fe2e31 --- /dev/null +++ b/database/factories/MasterIndukFactory.php @@ -0,0 +1,25 @@ + + */ +class MasterIndukFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'nomor_induk' => fake()->unique()->numerify('##########'), + 'role' => 'siswa', + 'nama_pemilik' => fake()->name(), + ]; + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index cf82272..5404757 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -96,6 +96,7 @@ public function run() AnnouncementSeeder::class, RecommendationSeeder::class, LoanSeeder::class, + UserSeeder::class, ]); } } diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index 54cd82d..464b042 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -49,5 +49,30 @@ public function run(): void 'role' => 'penjaga perpus', ] ); + + // Generate 95 additional users to reach around 100 total + $additionalCount = 95; + for ($i = 0; $i < $additionalCount; $i++) { + $nomorInduk = fake()->unique()->numerify('##########'); + $name = fake()->name(); + + \App\Models\MasterInduk::create([ + 'nomor_induk' => $nomorInduk, + 'role' => 'siswa', + 'nama_pemilik' => $name, + ]); + + User::create([ + 'name' => $name, + 'nama_lengkap' => $name, + 'email' => fake()->unique()->safeEmail(), + 'password' => Hash::make('password'), + 'nomor_induk' => $nomorInduk, + 'role' => 'siswa', + 'phone' => fake()->phoneNumber(), + 'kelas' => fake()->randomElement(['X RPL', 'XI RPL', 'XII RPL', 'X TKJ', 'XI TKJ', 'XII TKJ']), + 'golongan' => fake()->randomElement(['A', 'B', 'C']), + ]); + } } } diff --git a/resources/views/admin/peminjaman/index.blade.php b/resources/views/admin/peminjaman/index.blade.php index a68e5d2..21a8903 100644 --- a/resources/views/admin/peminjaman/index.blade.php +++ b/resources/views/admin/peminjaman/index.blade.php @@ -376,31 +376,29 @@ function hitungTotalDenda(modal) { // Loading Kirim Email flow if (isEmailChecked) { modernSwal.fire({ - title: 'Mengirim Email...', + title: 'Memproses...', html: `Mengirim nota ke: ${userEmail}`, - timer: 2000, - timerProgressBar: true, - didOpen: () => Swal.showLoading() - }).then(() => { - finishTransaction(returnsData, userId, waLink); + didOpen: () => Swal.showLoading(), + allowOutsideClick: false }); - } else { - finishTransaction(returnsData, userId, waLink); } + + finishTransaction(returnsData, userId, waLink, isEmailChecked); }); } else { modalInstance.show(); } }); - function finishTransaction(returnsData, userId, waLink) { + function finishTransaction(returnsData, userId, waLink, isEmailChecked) { $.ajax({ url: "{{ route('admin.peminjaman.kembali') }}", method: 'POST', data: { _token: '{{ csrf_token() }}', user_id: userId, - returns: returnsData + returns: returnsData, + send_email: isEmailChecked ? 1 : 0 }, success: function(response) { if (response.status === 'success') { @@ -408,11 +406,19 @@ function finishTransaction(returnsData, userId, waLink) { window.open(waLink, '_blank'); } - Toast.fire({ - icon: 'success', - title: 'Berhasil', - text: 'Buku berhasil dikembalikan.' - }); + if (response.email_error) { + Toast.fire({ + icon: 'warning', + title: 'Berhasil (Email Gagal)', + text: response.message + }); + } else { + Toast.fire({ + icon: 'success', + title: 'Berhasil', + text: 'Buku berhasil dikembalikan.' + }); + } setTimeout(() => location.reload(), 1500); } else { modernSwal.fire('Gagal', response.message, 'error'); diff --git a/resources/views/admin/pengguna/create.blade.php b/resources/views/admin/pengguna/create.blade.php index 2c830fd..890b678 100644 --- a/resources/views/admin/pengguna/create.blade.php +++ b/resources/views/admin/pengguna/create.blade.php @@ -7,6 +7,15 @@

Formulir Tambah Pengguna

+ @if($prefilledData) + + @endif +
@@ -16,6 +25,14 @@
+ @if($prefilledData) + + + @else + id="nama_lengkap" name="nama_lengkap" value="{{ old('nama_lengkap', $prefilledData->nama_pemilik ?? '') }}" required + {{ $prefilledData ? 'readonly' : '' }}>
+ @if($prefilledData) + + @endif + id="nomor_induk" name="{{ $prefilledData ? '' : 'nomor_induk' }}" value="{{ old('nomor_induk', $prefilledData->nomor_induk ?? '') }}" + placeholder="Masukkan Nomor Induk" {{ $prefilledData ? 'readonly' : '' }}> @error('nomor_induk')
{{ $message }}
@enderror @@ -64,7 +86,7 @@
-
- -
- - {{-- BAGIAN DATA INDUK (WHITELIST) --}} -
-
-

Data Induk (Whitelist) -

-

Daftar NIP/NISN/NIK yang diizinkan untuk mendaftar.

-
- -
- -
-
-
- - - - - - - - - - - - - @forelse($whitelists as $index => $item) - - - - - - - - - @empty - - - - @endforelse - -
NoNIP / NISNNama PemilikRoleStatus AkunAksi
{{ $index + 1 }}{{ $item->nomor_induk }}{{ $item->nama_pemilik }} - @if($item->role == 'guru') - Guru - @elseif($item->role == 'siswa') - Siswa - @else - Petugas - @endif - - @php - $isRegistered = \App\Models\User::where('nomor_induk', $item->nomor_induk)->exists(); - @endphp - @if ($isRegistered) - Terdaftar - @else - Belum Daftar - @endif - -
-
- @csrf - @method('DELETE') - -
-
-
Belum ada data whitelist. Silakan - tambah data.
-
-
-
{{-- MODAL TAMBAH DATA INDUK --}} diff --git a/resources/views/emails/return_receipt.blade.php b/resources/views/emails/return_receipt.blade.php new file mode 100644 index 0000000..283634b --- /dev/null +++ b/resources/views/emails/return_receipt.blade.php @@ -0,0 +1,65 @@ + + + + + Bukti Pengembalian Buku + + + +
+
+

BUKTI PENGEMBALIAN BUKU

+

{{ config('app.name') }}

+
+ +
+

Halo, {{ $user->nama_lengkap }},

+

Terima kasih telah mengembalikan buku tepat waktu atau telah menyelesaikan denda yang berlaku. Berikut adalah rincian buku yang Anda kembalikan:

+ + + + + + + + + + + @foreach($returns as $item) + + + + + + @endforeach + + + + + + + +
Judul BukuKondisiDenda
{{ $item['judul'] }}{{ ucfirst($item['condition']) }}Rp {{ number_format($item['fine_damage'] + $item['fine_overdue'], 0, ',', '.') }}
TOTAL DENDARp {{ number_format($totalDenda, 0, ',', '.') }}
+ +

Status: Berhasil Dikembalikan

+

Pesan ini dikirim secara otomatis sebagai bukti sah bahwa Anda telah mengembalikan buku di atas ke perpustakaan.

+
+ + +
+ + diff --git a/resources/views/peminjaman/form.blade.php b/resources/views/peminjaman/form.blade.php index cce825d..651c751 100644 --- a/resources/views/peminjaman/form.blade.php +++ b/resources/views/peminjaman/form.blade.php @@ -37,13 +37,13 @@ @if(in_array($user->role, ['guru', 'penjaga perpus']))
-

{{ $user->nip ?? '-' }}

+

{{ $user->nomor_induk ?? '-' }}

{{-- KONDISI UNTUK SISWA --}} @else
-

{{ $user->nisn ?? '-' }}

+

{{ $user->nomor_induk ?? '-' }}

@endif @@ -58,8 +58,8 @@

- @if($user->no_hp) - {{ $user->no_hp + @if($user->phone) + {{ $user->phone }} @else - Belum diatur - diff --git a/resources/views/profile/index.blade.php b/resources/views/profile/index.blade.php index 7575e10..841420b 100644 --- a/resources/views/profile/index.blade.php +++ b/resources/views/profile/index.blade.php @@ -119,7 +119,7 @@ class="btn btn-outline-primary rounded-pill w-100 w-sm-auto">

Nomor HP -

{{ $user->no_hp ?? '-' }}

+

{{ $user->phone ?? '-' }}

diff --git a/resources/views/profile/partials/personal-activities.blade.php b/resources/views/profile/partials/personal-activities.blade.php index 1862ad0..2a74608 100644 --- a/resources/views/profile/partials/personal-activities.blade.php +++ b/resources/views/profile/partials/personal-activities.blade.php @@ -7,7 +7,11 @@
  • {{ $buku['judul'] }}
    {{ $buku['penulis'] }} - Sisa {{ $buku['sisa_hari'] }} hari + @if($buku['sisa_hari'] < 0) + Terlambat {{ abs($buku['sisa_hari']) }} hari + @else + Sisa {{ $buku['sisa_hari'] }} hari + @endif
  • @empty
  • Tidak ada buku yang sedang dipinjam.
  • diff --git a/resources/views/profile/partials/update-profile-information-form.blade.php b/resources/views/profile/partials/update-profile-information-form.blade.php index 5eaa948..64171a8 100644 --- a/resources/views/profile/partials/update-profile-information-form.blade.php +++ b/resources/views/profile/partials/update-profile-information-form.blade.php @@ -87,8 +87,8 @@ class="form-control @error('email') is-invalid @enderror" value="{{ old('email', @else {{-- Untuk Guru atau Penjaga Perpus --}}
    - - + + ID Kepegawaian tidak dapat diubah.
    @endif