diff --git a/app/Http/Controllers/Admin/AdminPeminjamanController.php b/app/Http/Controllers/Admin/AdminPeminjamanController.php index 2b12e54..3d6a9cb 100644 --- a/app/Http/Controllers/Admin/AdminPeminjamanController.php +++ b/app/Http/Controllers/Admin/AdminPeminjamanController.php @@ -8,6 +8,7 @@ use App\Models\User; use Carbon\Carbon; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; class AdminPeminjamanController extends Controller { @@ -24,7 +25,7 @@ public function index(Request $request) $firstLoan = $userLoans->first(); return [ - 'id_peminjaman' => 'PIN-ADM-' . sprintf('%03d', $userId), + 'id_peminjaman' => 'PIN-ADM-'.sprintf('%03d', $userId), 'user_id' => $userId, 'peminjam' => $user->nama_lengkap ?? 'Unknown', 'nomor_hp' => $user->phone ?? '-', @@ -32,7 +33,7 @@ public function index(Request $request) 'tenggat_kembali' => $firstLoan->due_at, 'status' => $firstLoan->status, 'role' => $user->role, - 'books' => $userLoans->map(fn($l) => [ + 'books' => $userLoans->map(fn ($l) => [ 'id' => $l->book->id, 'judul' => $l->book->judul, 'cover' => $l->book->cover, @@ -63,11 +64,11 @@ public function create() $user->disabled = $user->kena_limit || $user->is_banned; if ($user->is_banned) { - $user->status_text = "(Akun Dibekukan)"; + $user->status_text = '(Akun Dibekukan)'; } elseif ($user->kena_limit) { - $user->status_text = "(Limit Penuh: 2/2)"; + $user->status_text = '(Limit Penuh: 2/2)'; } else { - $user->status_text = ""; + $user->status_text = ''; } return $user; @@ -75,6 +76,7 @@ public function create() $daftarBuku = Book::where('status', 'Tersedia') ->whereJsonContains('tipe_akses', 'offline') + ->where('stok', '>', 0) ->get(); return view('admin.peminjaman.create', [ @@ -104,28 +106,92 @@ public function store(Request $request) throw new \Exception("Buku '{$book->judul}' tidak tersedia."); } + // Check stock + if ($book->stok <= 0) { + throw new \Exception("Buku '{$book->judul}' stok habis."); + } + // Create loan record Loan::create([ 'user_id' => $validated['peminjam_id'], 'book_id' => $bookId, - 'loan_code' => 'LOAN-' . date('Ymd') . '-' . strtoupper(substr(md5(uniqid()), 0, 6)), + 'loan_code' => 'LOAN-'.date('Ymd').'-'.strtoupper(substr(md5(uniqid()), 0, 6)), 'borrowed_at' => $validated['tanggal_pinjam'], 'due_at' => $validated['tanggal_kembali'], 'status' => 'Dipinjam', ]); - // Update book status - $book->update(['status' => 'Dipinjam']); + // Update book status and decrement stock + $book->update([ + 'status' => 'Dipinjam', + 'stok' => $book->stok - 1, + ]); } \DB::commit(); + return redirect()->route('admin.peminjaman.index')->with('success', 'Peminjaman berhasil dibuat.'); } catch (\Exception $e) { \DB::rollBack(); + return back()->withErrors(['error' => $e->getMessage()])->withInput(); } } + public function export(Request $request) + { + $request->validate([ + 'bulan_laporan' => 'nullable|date', + ]); + + $query = Loan::with(['user', 'book'])->orderBy('borrowed_at', 'asc'); + + if ($request->filled('bulan_laporan')) { + $date = Carbon::parse($request->bulan_laporan); + $query->whereMonth('borrowed_at', $date->month) + ->whereYear('borrowed_at', $date->year); + $fileName = 'Laporan_Peminjaman_'.$date->format('Y-m').'.csv'; + } else { + $fileName = 'Laporan_Peminjaman_Semua.csv'; + } + + $loans = $query->get(); + + $headers = [ + "Content-type" => "text/csv", + "Content-Disposition" => "attachment; filename=$fileName", + "Pragma" => "no-cache", + "Cache-Control" => "must-revalidate, post-check=0, pre-check=0", + "Expires" => "0" + ]; + + $columns = ['NO', 'ID PEMINJAMAN', 'PEMINJAM', 'ROLE', 'JUDUL BUKU', 'TGL PINJAM', 'T tenggat KEMBALI', 'STATUS', 'DENDA KETERLAMBATAN']; + + $callback = function() use($loans, $columns) { + $file = fopen('php://output', 'w'); + fputcsv($file, $columns); + + $i = 1; + foreach ($loans as $loan) { + $row['NO'] = $i++; + $row['ID_PEMINJAMAN'] = $loan->loan_code; + $row['PEMINJAM'] = $loan->user->nama_lengkap ?? 'Unknown'; + $row['ROLE'] = $loan->user->role ?? '-'; + $row['JUDUL_BUKU'] = $loan->book->judul ?? 'Unknown'; + $row['TGL_PINJAM'] = $loan->borrowed_at->format('d/m/Y'); + $row['TENGGAT_KEMBALI'] = $loan->due_at ? $loan->due_at->format('d/m/Y') : '-'; + $row['STATUS'] = $loan->status; + $row['DENDA'] = $loan->fine_overdue ?? 0; + + fputcsv($file, array_values($row)); + } + + fclose($file); + }; + + return response()->stream($callback, 200, $headers); + } + public function dendaIndex() { $now = Carbon::now(); @@ -157,17 +223,19 @@ public function dendaIndex() $hp = $user->phone ?? ''; $waLink = '#'; if ($hp) { - if (substr($hp, 0, 1) == '0') $hp = '62' . substr($hp, 1); + if (substr($hp, 0, 1) == '0') { + $hp = '62'.substr($hp, 1); + } if ($isGuru && $hariTelat > 0) { $pesan = "Halo Bapak/Ibu {$user->nama_lengkap}, anda terlambat pengembalian buku selama {$hariTelat} hari. Mohon segera dikembalikan ke perpustakaan. Terima kasih."; } else { $pesan = $hariTelat > 0 - ? "Halo {$user->nama_lengkap}, anda terlambat pengembalian buku. Total Denda: Rp " . number_format($totalDenda, 0, ',', '.') + ? "Halo {$user->nama_lengkap}, anda terlambat pengembalian buku. Total Denda: Rp ".number_format($totalDenda, 0, ',', '.') : "Halo {$user->nama_lengkap}, akun anda sedang dinonaktifkan sementara. Mohon hubungi petugas."; } - $waLink = "https://wa.me/{$hp}?text=" . urlencode($pesan); + $waLink = "https://wa.me/{$hp}?text=".urlencode($pesan); } return [ @@ -182,7 +250,7 @@ public function dendaIndex() 'wa_link' => $waLink, 'is_banned' => $user->is_banned, 'tenggat_kembali' => $firstLoan->due_at, - 'books' => $userLoans->map(fn($l) => [ + 'books' => $userLoans->map(fn ($l) => [ 'id' => $l->book->id, 'judul' => $l->book->judul, ])->toArray(), @@ -194,7 +262,7 @@ public function dendaIndex() return view('admin.denda.index', [ 'pageTitle' => 'Manajemen Denda & Sanksi', 'siswaTelat' => $siswaTelat, - 'listKelas' => $listKelas + 'listKelas' => $listKelas, ]); } @@ -211,7 +279,7 @@ public function berikanSanksi(Request $request) return response()->json([ 'status' => 'success', - 'message' => $user->is_banned ? "Akun {$user->nama_lengkap} berhasil dibekukan." : "Akun {$user->nama_lengkap} telah diaktifkan kembali." + 'message' => $user->is_banned ? "Akun {$user->nama_lengkap} berhasil dibekukan." : "Akun {$user->nama_lengkap} telah diaktifkan kembali.", ]); } @@ -245,15 +313,20 @@ public function kembalikan(Request $request) 'return_notes' => $item['notes'], ]); - // Update book status - $loan->book->update(['status' => 'Tersedia']); + // Update book status and increment stock + $loan->book->update([ + 'status' => 'Tersedia', + 'stok' => $loan->book->stok + 1, + ]); } } \DB::commit(); + return response()->json(['status' => 'success', 'message' => 'Buku berhasil dikembalikan.']); } catch (\Exception $e) { \DB::rollBack(); + return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500); } } diff --git a/app/Http/Controllers/Admin/BookController.php b/app/Http/Controllers/Admin/BookController.php index caf1908..3f78646 100644 --- a/app/Http/Controllers/Admin/BookController.php +++ b/app/Http/Controllers/Admin/BookController.php @@ -12,29 +12,33 @@ class BookController extends Controller public function index(Request $request) { $filters = $request->only(['search']); - + $query = Book::with('category'); if ($request->filled('search')) { - $query->where('judul', 'like', '%' . $request->search . '%'); + $query->where('judul', 'like', '%'.$request->search.'%'); } $semuaBuku = $query->latest()->get(); - // Memisahkan buku menjadi dua koleksi: online dan offline secara independen $bukuOnline = $semuaBuku->filter(function ($buku) { - return in_array('online', $buku->tipe_akses ?? []); + return in_array('online', $buku->tipe_akses ?? []) && ! $buku->is_arsip; }); $bukuOffline = $semuaBuku->filter(function ($buku) { - return in_array('offline', $buku->tipe_akses ?? []); + return in_array('offline', $buku->tipe_akses ?? []) && ! $buku->is_arsip; + }); + + $bukuArsip = $semuaBuku->filter(function ($buku) { + return $buku->is_arsip; }); return view('admin.buku.index', [ 'pageTitle' => 'Manajemen Buku', 'bukuOnline' => $bukuOnline, 'bukuOffline' => $bukuOffline, - 'input' => $filters + 'bukuArsip' => $bukuArsip, + 'input' => $filters, ]); } @@ -45,7 +49,7 @@ public function create() { return view('admin.buku.create', [ 'pageTitle' => 'Tambah Buku Baru', - 'categories' => Category::all() + 'categories' => Category::all(), ]); } @@ -54,9 +58,9 @@ public function edit($id) $buku = Book::with('category')->findOrFail($id); return view('admin.buku.edit', [ - 'pageTitle' => 'Edit Buku: ' . $buku->judul, + 'pageTitle' => 'Edit Buku: '.$buku->judul, 'buku' => $buku, - 'categories' => Category::all() + 'categories' => Category::all(), ]); } @@ -68,6 +72,7 @@ public function store(Request $request) 'category_id' => 'required|exists:categories,id', 'tahun' => 'required|integer', 'kode_buku' => 'nullable|string', + 'stok' => 'required|integer|min:0', 'tipe_akses' => 'required|array', 'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', 'file_pdf' => 'nullable|mimes:pdf|max:10240', @@ -75,7 +80,7 @@ public function store(Request $request) if ($request->hasFile('cover')) { $path = $request->file('cover')->store('covers', 'public'); - $validated['cover'] = 'storage/' . $path; + $validated['cover'] = 'storage/'.$path; } if ($request->hasFile('file_pdf')) { @@ -98,6 +103,7 @@ public function update(Request $request, $id) 'category_id' => 'required|exists:categories,id', 'tahun' => 'required|integer', 'kode_buku' => 'nullable|string', + 'stok' => 'required|integer|min:0', 'tipe_akses' => 'required|array', 'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', 'file_pdf' => 'nullable|mimes:pdf|max:10240', @@ -105,7 +111,7 @@ public function update(Request $request, $id) if ($request->hasFile('cover')) { $path = $request->file('cover')->store('covers', 'public'); - $validated['cover'] = 'storage/' . $path; + $validated['cover'] = 'storage/'.$path; } if ($request->hasFile('file_pdf')) { @@ -125,4 +131,20 @@ public function destroy($id) return redirect()->route('admin.buku.index')->with('success', 'Buku berhasil dihapus.'); } -} \ No newline at end of file + + public function arsip(Request $request) + { + $buku = Book::findOrFail($request->id); + $buku->update(['is_arsip' => true]); + + return response()->json(['status' => 'success', 'message' => 'Buku berhasil diarsipkan.']); + } + + public function pulihkan(Request $request) + { + $buku = Book::findOrFail($request->id); + $buku->update(['is_arsip' => false]); + + return response()->json(['status' => 'success', 'message' => 'Buku berhasil dipulihkan.']); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index ad343c4..e4c87fd 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -52,6 +52,7 @@ public function store(Request $request) 'phone' => 'nullable|string|max:20', 'role' => 'required|in:siswa,guru,penjaga perpus', 'kelas' => 'nullable|string|max:50', + 'golongan' => 'nullable|string|max:50', 'password' => 'required|string|min:8|confirmed', ]); @@ -74,6 +75,7 @@ public function update(Request $request, $id) 'phone' => 'nullable|string|max:20', 'role' => 'required|in:siswa,guru,penjaga perpus', 'kelas' => 'nullable|string|max:50', + 'golongan' => 'nullable|string|max:50', 'password' => 'nullable|string|min:8|confirmed', ]); diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index b4a33f1..08bc932 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -3,8 +3,8 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; -use App\Models\User; use App\Models\MasterInduk; +use App\Models\User; use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -19,6 +19,7 @@ class RegisteredUserController extends Controller public function create(Request $request): View { $role = $request->query('role', 'siswa'); + return view('auth.register', ['role' => $role]); } @@ -26,7 +27,6 @@ public function store(Request $request): RedirectResponse { $role = $request->input('role'); - $rules = [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], @@ -43,27 +43,24 @@ public function store(Request $request): RedirectResponse $request->validate($rules); - $nomorInduk = ($role === 'siswa') ? $request->nisn : $request->nip; $isWhitelisted = MasterInduk::where('nomor_induk', $nomorInduk) ->where('role', $role) ->exists(); - if (!$isWhitelisted) { + if (! $isWhitelisted) { throw ValidationException::withMessages([ ($role === 'siswa' ? 'nisn' : 'nip') => ['Nomor induk tidak terdaftar di Data Sekolah. Hubungi petugas.'], ]); } - $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), 'role' => $role, - 'nisn' => ($role === 'siswa') ? $nomorInduk : null, - 'nip' => ($role === 'guru') ? $nomorInduk : null, + 'nomor_induk' => $nomorInduk, ]); event(new Registered($user)); diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php new file mode 100644 index 0000000..b0e7334 --- /dev/null +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -0,0 +1,103 @@ +validate([ + 'user_id' => 'required|exists:users,id', + ]); + + $user = User::findOrFail($request->user_id); + + // Generate a 6-digit OTP + $otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); + + // Store OTP in cache keyed by user email — expires in 15 minutes + Cache::put('reset_otp_' . $user->email, $otp, now()->addMinutes(15)); + + return response()->json([ + 'status' => 'success', + 'otp' => $otp, + ]); + } + + /** + * Verify the OTP provided by the user against cache. + */ + public function verifyOtp(Request $request) + { + $request->validate([ + 'otp' => 'required|string|size:6', + 'email' => 'required|email|exists:users,email', + ]); + + $cachedOtp = Cache::get('reset_otp_' . $request->email); + + if (!$cachedOtp) { + return response()->json([ + 'status' => 'error', + 'message' => 'OTP sudah kedaluwarsa. Minta admin untuk generate ulang.', + ], 422); + } + + if ($request->otp !== $cachedOtp) { + return response()->json([ + 'status' => 'error', + 'message' => 'Kode OTP salah! Cek lagi kode yang dikirim admin.', + ], 422); + } + + // Store verified email in a separate short-lived cache key + Cache::put('reset_otp_verified_' . $request->email, true, now()->addMinutes(15)); + + return response()->json([ + 'status' => 'success', + 'email' => $request->email, + ]); + } + + /** + * Update the password for the user whose OTP was verified. + */ + public function updatePassword(Request $request) + { + $request->validate([ + 'email' => 'required|email|exists:users,email', + 'password' => 'required|string|min:8|confirmed', + ]); + + // Ensure OTP was verified + if (!Cache::get('reset_otp_verified_' . $request->email)) { + return response()->json([ + 'status' => 'error', + 'message' => 'Sesi tidak valid. Silakan verifikasi OTP terlebih dahulu.', + ], 403); + } + + $user = User::where('email', $request->email)->firstOrFail(); + $user->password = Hash::make($request->password); + $user->save(); + + // Clear OTP cache data + Cache::forget('reset_otp_' . $request->email); + Cache::forget('reset_otp_verified_' . $request->email); + + return response()->json([ + 'status' => 'success', + 'message' => 'Password berhasil diperbarui.', + ]); + } +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 2b027da..baa8758 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -18,7 +18,7 @@ class ProfileController extends Controller /** * Tampilkan halaman profil user. */ - public function index(Request $request): View + public function index(Request $request): \Illuminate\View\View|\Illuminate\Http\RedirectResponse { $user = Auth::user(); if (!$user) { @@ -62,7 +62,22 @@ public function index(Request $request): View // Analytics for Guru (simplified for profile view) $viewData['laporan'] = [ - 'buku_terpopuler' => Book::withCount('loans')->orderBy('loans_count', 'desc')->take(3)->get(), + 'buku_terpopuler' => Book::withCount('loans') + ->orderBy('loans_count', 'desc') + ->take(3) + ->get() + ->map(fn($b) => [ + 'judul' => $b->judul, + 'total_pembaca' => $b->loans_count + ]), + 'kategori_populer' => \App\Models\Category::select('categories.name as nama') + ->join('books', 'categories.id', '=', 'books.category_id') + ->join('loans', 'books.id', '=', 'loans.book_id') + ->selectRaw('count(loans.id) as total_pembaca') + ->groupBy('categories.id', 'categories.name') + ->orderBy('total_pembaca', 'desc') + ->take(3) + ->get(), 'insight' => 'Siswa aktif meminjam buku kategori Sains.' ]; } else { diff --git a/app/Models/Book.php b/app/Models/Book.php index 1caa369..16930c5 100644 --- a/app/Models/Book.php +++ b/app/Models/Book.php @@ -8,7 +8,7 @@ class Book extends Model { protected $fillable = [ 'judul', 'penulis', 'cover', 'kode_buku', - 'category_id', 'tahun', 'status', 'is_new', 'tipe_akses', 'file_pdf' + 'category_id', 'tahun', 'status', 'is_new', 'tipe_akses', 'file_pdf', 'is_arsip' ]; protected $casts = [ diff --git a/app/Services/DummyDataService.php b/app/Services/DummyDataService.php index 4ead6c1..5695f27 100644 --- a/app/Services/DummyDataService.php +++ b/app/Services/DummyDataService.php @@ -37,7 +37,7 @@ public static function getAllSiswa(): array 'nisn' => '9988776655', 'nama_lengkap' => 'Siti Nurhaliza', 'email' => 'siti.nurhaliza@smkn1perpus.sch.id', - 'nomor_hp' => '081998877665', + 'nomor_hp' => '0895618643811', 'password' => 'password', 'role' => 'siswa', 'kelas' => 'XII RPL A', diff --git a/database/migrations/2026_02_11_152425_create_master_induks_table.php b/database/migrations/2026_03_04_065853_add_stok_to_books_table.php similarity index 54% rename from database/migrations/2026_02_11_152425_create_master_induks_table.php rename to database/migrations/2026_03_04_065853_add_stok_to_books_table.php index 477da4a..de7134a 100644 --- a/database/migrations/2026_02_11_152425_create_master_induks_table.php +++ b/database/migrations/2026_03_04_065853_add_stok_to_books_table.php @@ -11,12 +11,8 @@ */ public function up(): void { - Schema::create('master_induks', function (Blueprint $table) { - $table->id(); - $table->string('nomor_induk')->unique(); - $table->string('nama_pemilik'); - $table->string('role'); - $table->timestamps(); + Schema::table('books', function (Blueprint $table) { + $table->integer('stok')->default(1)->after('status'); }); } @@ -25,6 +21,8 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists('master_induks'); + Schema::table('books', function (Blueprint $table) { + // + }); } }; diff --git a/database/migrations/2026_03_04_070748_add_is_arsip_to_books_table.php b/database/migrations/2026_03_04_070748_add_is_arsip_to_books_table.php new file mode 100644 index 0000000..533062d --- /dev/null +++ b/database/migrations/2026_03_04_070748_add_is_arsip_to_books_table.php @@ -0,0 +1,28 @@ +boolean('is_arsip')->default(false)->after('stok'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('books', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/seeders/BookSeeder.php b/database/seeders/BookSeeder.php index df74ab0..01479dc 100644 --- a/database/seeders/BookSeeder.php +++ b/database/seeders/BookSeeder.php @@ -2,11 +2,10 @@ namespace Database\Seeders; -use Illuminate\Database\Seeder; use App\Models\Book; use App\Models\Category; use App\Services\DummyDataService; -use Illuminate\Support\Str; +use Illuminate\Database\Seeder; class BookSeeder extends Seeder { @@ -20,6 +19,8 @@ public function run(): void foreach ($books as $data) { $category = Category::where('name', $data['kategori'])->first(); + $stok = isset($data['stok']) ? $data['stok'] : (($data['status'] === 'Tersedia') ? 1 : 0); + Book::updateOrCreate( ['kode_buku' => $data['kode_buku']], [ @@ -30,6 +31,7 @@ public function run(): void 'category_id' => $category ? $category->id : null, 'tahun' => $data['tahun'], 'status' => $data['status'], + 'stok' => $stok, 'is_new' => $data['is_new'], 'tipe_akses' => is_array($data['tipe_akses']) ? $data['tipe_akses'] : [$data['tipe_akses']], ] diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index dc9f3eb..cf82272 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,11 +2,11 @@ namespace Database\Seeders; -use Illuminate\Database\Seeder; -use App\Models\User; use App\Models\MasterInduk; -use Illuminate\Support\Facades\Hash; +use App\Models\User; +use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Hash; class DatabaseSeeder extends Seeder { @@ -29,7 +29,7 @@ public function run() MasterInduk::create($w); } - // ISI USER ASLI + // ISI USER ASLI // ID 1: Silvi (Siswa) User::create([ @@ -38,10 +38,10 @@ public function run() 'email' => 'silvi.rahmawati@smkn1perpus.sch.id', 'password' => Hash::make('password'), 'role' => 'siswa', - 'nisn' => '1234567890', - 'no_hp' => '08123456789', + 'nomor_induk' => '1234567890', + 'phone' => '08123456789', 'kelas' => 'XII RPL', - 'golongan' => 'A' + 'golongan' => 'A', ]); // ID 2: Budi (Admin/Penjaga) @@ -51,7 +51,7 @@ public function run() 'email' => 'budi.santoso@smkn1perpus.sch.id', 'password' => Hash::make('password'), 'role' => 'penjaga perpus', - 'nip' => '197812312005011', + 'nomor_induk' => '197812312005011', ]); // ID 3: Siti (Siswa) @@ -61,10 +61,10 @@ public function run() 'email' => 'siti.nurhaliza@smkn1perpus.sch.id', 'password' => Hash::make('password'), 'role' => 'siswa', - 'nisn' => '9988776655', - 'no_hp' => '081998877665', + 'nomor_induk' => '9988776655', + 'phone' => '0895618643811', 'kelas' => 'XII RPL', - 'golongan' => 'B' + 'golongan' => 'B', ]); // ID 4: Andi (Siswa) @@ -74,10 +74,10 @@ public function run() 'email' => 'andi.pratama@smkn1perpus.sch.id', 'password' => Hash::make('password'), 'role' => 'siswa', - 'nisn' => '5566778899', - 'no_hp' => '081556677889', + 'nomor_induk' => '5566778899', + 'phone' => '081556677889', 'kelas' => 'XII RPL', - 'golongan' => 'C' + 'golongan' => 'C', ]); // ID 5: Rina (Guru) @@ -87,7 +87,15 @@ public function run() 'email' => 'rina.marlina@smkn1perpus.sch.id', 'password' => Hash::make('password'), 'role' => 'guru', - 'nip' => '198506152010012', + 'nomor_induk' => '198506152010012', + ]); + + $this->call([ + CategorySeeder::class, + BookSeeder::class, + AnnouncementSeeder::class, + RecommendationSeeder::class, + LoanSeeder::class, ]); } } diff --git a/resources/scss/_custom.scss b/resources/scss/_custom.scss index 2b46a15..6dccc8b 100644 --- a/resources/scss/_custom.scss +++ b/resources/scss/_custom.scss @@ -4,8 +4,7 @@ // UTILITIES (GENERATED FROM MAPS) // =================================== // Generator otomatis untuk variant warna (background light, soft, alert) -@each $color, -$value in $theme-colors { +@each $color, $value in $theme-colors { .bg-#{$color}-light { background-color: rgba($value, 0.25) !important; } @@ -63,14 +62,14 @@ html { .navbar-nav { .nav-link-landing { padding: 0.5rem 1rem; - color: #49769F; + color: #49769f; text-decoration: none; display: block; transition: color 0.3s ease; &:hover, &.active { - color: #0C5495; + color: #0c5495; font-weight: 500; transition: all 0.3s ease-in; } @@ -109,9 +108,11 @@ html { // Hero Section .landing-hero-section { - background: linear-gradient(135deg, - #0C5495 0%, - color.adjust(#0C5495, $lightness: 10%) 100%); + background: linear-gradient( + 135deg, + #0c5495 0%, + color.adjust(#0c5495, $lightness: 10%) 100% + ); padding: 60px 0; @media (min-width: 992px) { @@ -143,7 +144,6 @@ html { i { font-size: 5rem; } - } .card-img-top { @@ -186,9 +186,11 @@ html { // CTA Section .landing-cta-section { - background: linear-gradient(135deg, - #0C5495 0%, - color.adjust(#0C5495, $lightness: 10%) 100%); + background: linear-gradient( + 135deg, + #0c5495 0%, + color.adjust(#0c5495, $lightness: 10%) 100% + ); border-radius: 1rem; @media (min-width: 768px) { @@ -196,7 +198,6 @@ html { } } - // =================================== // BASE COMPONENTS // =================================== @@ -294,9 +295,10 @@ html { body { background-color: map-get($grays, "light"); + overflow-x: hidden; } -// Sidebar +// Sidebar .sidebar { width: 270px; position: fixed; @@ -308,6 +310,8 @@ body { .main-wrapper { transition: margin-left 0.3s ease; + overflow-x: hidden; + min-width: 0; } // Overlay gelap untuk mobile sidebar @@ -634,9 +638,11 @@ nav { border-radius: $border-radius-sm; &-container { - background: linear-gradient(135deg, - rgba(map-get($grays, "light"), 0.5) 0%, - rgba(map-get($grays, "light"), 0.8) 100%); + background: linear-gradient( + 135deg, + rgba(map-get($grays, "light"), 0.5) 0%, + rgba(map-get($grays, "light"), 0.8) 100% + ); border-radius: $border-radius-sm 0 0 $border-radius-sm; } } @@ -679,17 +685,13 @@ nav { --bs-btn-border-color: #{map-get($theme-colors, "primary")}; --bs-btn-hover-color: #{map-get($grays, "dark")}; --bs-btn-hover-bg: #{color.adjust( - map-get($theme-colors, "primary"), - $lightness: -5%) -} - -; ---bs-btn-hover-border-color: #{color.adjust( - map-get($theme-colors, "primary"), -$lightness: -7.5%) -} - -; + map-get($theme-colors, "primary"), + $lightness: -5% + )}; + --bs-btn-hover-border-color: #{color.adjust( + map-get($theme-colors, "primary"), + $lightness: -7.5% + )}; } // Text clamp untuk truncate multi line @@ -829,9 +831,11 @@ $lightness: -7.5%) // Background gradient hero section .hero-gradient { - background: linear-gradient(135deg, - map-get($theme-colors, "primary") 0%, - color.adjust(map-get($theme-colors, "primary"), $lightness: 10%) 100%); + background: linear-gradient( + 135deg, + map-get($theme-colors, "primary") 0%, + color.adjust(map-get($theme-colors, "primary"), $lightness: 10%) 100% + ); } // Card untuk pilih role (admin/user) @@ -851,9 +855,11 @@ $lightness: -7.5%) // Panel info di halaman auth .info-panel { - background: linear-gradient(135deg, - map-get($theme-colors, "primary") 0%, - color.adjust(map-get($theme-colors, "primary"), $lightness: 10%) 100%); + background: linear-gradient( + 135deg, + map-get($theme-colors, "primary") 0%, + color.adjust(map-get($theme-colors, "primary"), $lightness: 10%) 100% + ); } // Panel kiri auth (logo dan branding) @@ -906,7 +912,7 @@ $lightness: -7.5%) @media (max-width: 991.98px) { .auth-left-panel { background-image: none !important; - background-color: #0C5495; + background-color: #0c5495; position: absolute !important; min-height: 40vh; } @@ -936,7 +942,6 @@ $lightness: -7.5%) // Override styling DataTables .dataTables_wrapper { - .dataTables_length, .dataTables_filter, .dataTables_info, @@ -953,7 +958,8 @@ $lightness: -7.5%) &:focus { outline: none; border-color: map-get($theme-colors, "primary"); - box-shadow: 0 0 0 0.2rem rgba(map-get($theme-colors, "primary"), 0.25); + box-shadow: 0 0 0 0.2rem + rgba(map-get($theme-colors, "primary"), 0.25); } } @@ -968,7 +974,6 @@ $lightness: -7.5%) // Responsive DataTables untuk mobile @media screen and (max-width: 576px) { .dataTables_wrapper { - .dataTables_length, .dataTables_filter { text-align: center; @@ -1003,11 +1008,10 @@ $lightness: -7.5%) } // =================================== -// PROFILE PAGE +// PROFILE PAGE // =================================== - -// Avatar +// Avatar .profile-avatar-lg { width: 80px; height: 80px; diff --git a/resources/views/admin/buku/create.blade.php b/resources/views/admin/buku/create.blade.php index 28f6534..e90bd52 100644 --- a/resources/views/admin/buku/create.blade.php +++ b/resources/views/admin/buku/create.blade.php @@ -28,7 +28,7 @@ placeholder="Masukkan nama penulis" required>
| No | @@ -47,7 +47,7 @@{{ $user->name }} |
{{ $user->email }}
- {{ $user->no_hp ??
+ {{ $user->phone ??
'-' }}
|
@@ -60,7 +60,7 @@ @endif | - {{ $user->nisn ?? ($user->nip ?? '-') }} + {{ $user->nomor_induk ?? '-' }} |
@if($user->role == 'siswa')
@@ -78,7 +78,7 @@ class="btn btn-sm btn-warning" title="Edit Pengguna">
@@ -295,19 +295,17 @@ class="form-delete-whitelist" data-induk="{{ $item->nomor_induk }}">
});
@if(session('success'))
- document.addEventListener('DOMContentLoaded', function() {
- Toast.fire({
- icon: 'success',
- title: 'Berhasil',
- text: '{{ session("success") }}'
- });
+ Toast.fire({
+ icon: 'success',
+ title: 'Berhasil',
+ text: '{{ session("success") }}'
});
@endif
- // GENERATE OTP & LINK RESET
+ // GENERATE OTP & LINK RESET
$(document).on('click', '.btn-reset-password', function() {
const nama = $(this).data('nama');
- const otpCode = "678901";
+ const userId = $(this).data('id');
const linkReset = "{{ route('reset.password-request') }}";
modernSwal.fire({
@@ -322,27 +320,44 @@ class="form-delete-whitelist" data-induk="{{ $item->nomor_induk }}">
if (result.isConfirmed) {
modernSwal.fire({
title: 'Generating...',
- timer: 1000,
- didOpen: () => Swal.showLoading()
- }).then(() => {
- modernSwal.fire({
- title: 'OTP Berhasil Dibuat!',
- html: `
-
- Kode OTP: -${otpCode}-Link Reset: -
-
+ didOpen: () => Swal.showLoading(),
+ allowOutsideClick: false
+ });
+
+ $.ajax({
+ url: "{{ route('reset.generate-otp') }}",
+ method: 'POST',
+ data: {
+ _token: '{{ csrf_token() }}',
+ user_id: userId
+ },
+ success: function(response) {
+ if (response.status === 'success') {
+ modernSwal.fire({
+ title: 'OTP Berhasil Dibuat!',
+ html: `
+
-
+
- Kode OTP: +${response.otp}+Link Reset: +
+
+
- Silakan salin Kode OTP & Link di atas lalu kirim ke WhatsApp user.
-
- `,
- icon: 'success',
- confirmButtonText: 'Selesai'
- });
+
+ Salin Kode OTP & Link lalu kirim ke WhatsApp pengguna.
+
+ `,
+ icon: 'success',
+ confirmButtonText: 'Selesai'
+ });
+ } else {
+ modernSwal.fire('Gagal', response.message || 'Gagal membuat OTP.', 'error');
+ }
+ },
+ error: function() {
+ modernSwal.fire('Error', 'Terjadi kesalahan saat membuat OTP.', 'error');
+ }
});
}
});
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php
index 896ca13..e168b72 100644
--- a/resources/views/auth/login.blade.php
+++ b/resources/views/auth/login.blade.php
@@ -71,7 +71,7 @@
{{-- BANTUAN / LUPA PASSWORD --}}
Mengalami kendala login? - Hubungi Petugas via WA diff --git a/resources/views/auth/reset-password-request.blade.php b/resources/views/auth/reset-password-request.blade.php index 3aa623a..aca95b3 100644 --- a/resources/views/auth/reset-password-request.blade.php +++ b/resources/views/auth/reset-password-request.blade.php @@ -3,10 +3,15 @@Verifikasi Kode OTP-Masukkan 6 digit kode yang diberikan Admin via WhatsApp. +Masukkan email Anda dan 6 digit kode yang diberikan Admin via WhatsApp. |
|---|