feat: Implement book stock and archive functionality, add password reset, and refine UI styling.
This commit is contained in:
parent
f63628e48e
commit
528c120fc3
|
|
@ -8,6 +8,7 @@
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class AdminPeminjamanController extends Controller
|
class AdminPeminjamanController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -24,7 +25,7 @@ public function index(Request $request)
|
||||||
$firstLoan = $userLoans->first();
|
$firstLoan = $userLoans->first();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id_peminjaman' => 'PIN-ADM-' . sprintf('%03d', $userId),
|
'id_peminjaman' => 'PIN-ADM-'.sprintf('%03d', $userId),
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'peminjam' => $user->nama_lengkap ?? 'Unknown',
|
'peminjam' => $user->nama_lengkap ?? 'Unknown',
|
||||||
'nomor_hp' => $user->phone ?? '-',
|
'nomor_hp' => $user->phone ?? '-',
|
||||||
|
|
@ -32,7 +33,7 @@ public function index(Request $request)
|
||||||
'tenggat_kembali' => $firstLoan->due_at,
|
'tenggat_kembali' => $firstLoan->due_at,
|
||||||
'status' => $firstLoan->status,
|
'status' => $firstLoan->status,
|
||||||
'role' => $user->role,
|
'role' => $user->role,
|
||||||
'books' => $userLoans->map(fn($l) => [
|
'books' => $userLoans->map(fn ($l) => [
|
||||||
'id' => $l->book->id,
|
'id' => $l->book->id,
|
||||||
'judul' => $l->book->judul,
|
'judul' => $l->book->judul,
|
||||||
'cover' => $l->book->cover,
|
'cover' => $l->book->cover,
|
||||||
|
|
@ -63,11 +64,11 @@ public function create()
|
||||||
$user->disabled = $user->kena_limit || $user->is_banned;
|
$user->disabled = $user->kena_limit || $user->is_banned;
|
||||||
|
|
||||||
if ($user->is_banned) {
|
if ($user->is_banned) {
|
||||||
$user->status_text = "(Akun Dibekukan)";
|
$user->status_text = '(Akun Dibekukan)';
|
||||||
} elseif ($user->kena_limit) {
|
} elseif ($user->kena_limit) {
|
||||||
$user->status_text = "(Limit Penuh: 2/2)";
|
$user->status_text = '(Limit Penuh: 2/2)';
|
||||||
} else {
|
} else {
|
||||||
$user->status_text = "";
|
$user->status_text = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
|
|
@ -75,6 +76,7 @@ public function create()
|
||||||
|
|
||||||
$daftarBuku = Book::where('status', 'Tersedia')
|
$daftarBuku = Book::where('status', 'Tersedia')
|
||||||
->whereJsonContains('tipe_akses', 'offline')
|
->whereJsonContains('tipe_akses', 'offline')
|
||||||
|
->where('stok', '>', 0)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return view('admin.peminjaman.create', [
|
return view('admin.peminjaman.create', [
|
||||||
|
|
@ -104,28 +106,92 @@ public function store(Request $request)
|
||||||
throw new \Exception("Buku '{$book->judul}' tidak tersedia.");
|
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
|
// Create loan record
|
||||||
Loan::create([
|
Loan::create([
|
||||||
'user_id' => $validated['peminjam_id'],
|
'user_id' => $validated['peminjam_id'],
|
||||||
'book_id' => $bookId,
|
'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'],
|
'borrowed_at' => $validated['tanggal_pinjam'],
|
||||||
'due_at' => $validated['tanggal_kembali'],
|
'due_at' => $validated['tanggal_kembali'],
|
||||||
'status' => 'Dipinjam',
|
'status' => 'Dipinjam',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Update book status
|
// Update book status and decrement stock
|
||||||
$book->update(['status' => 'Dipinjam']);
|
$book->update([
|
||||||
|
'status' => 'Dipinjam',
|
||||||
|
'stok' => $book->stok - 1,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\DB::commit();
|
\DB::commit();
|
||||||
|
|
||||||
return redirect()->route('admin.peminjaman.index')->with('success', 'Peminjaman berhasil dibuat.');
|
return redirect()->route('admin.peminjaman.index')->with('success', 'Peminjaman berhasil dibuat.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\DB::rollBack();
|
\DB::rollBack();
|
||||||
|
|
||||||
return back()->withErrors(['error' => $e->getMessage()])->withInput();
|
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()
|
public function dendaIndex()
|
||||||
{
|
{
|
||||||
$now = Carbon::now();
|
$now = Carbon::now();
|
||||||
|
|
@ -157,17 +223,19 @@ public function dendaIndex()
|
||||||
$hp = $user->phone ?? '';
|
$hp = $user->phone ?? '';
|
||||||
$waLink = '#';
|
$waLink = '#';
|
||||||
if ($hp) {
|
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) {
|
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.";
|
$pesan = "Halo Bapak/Ibu {$user->nama_lengkap}, anda terlambat pengembalian buku selama {$hariTelat} hari. Mohon segera dikembalikan ke perpustakaan. Terima kasih.";
|
||||||
} else {
|
} else {
|
||||||
$pesan = $hariTelat > 0
|
$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.";
|
: "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 [
|
return [
|
||||||
|
|
@ -182,7 +250,7 @@ public function dendaIndex()
|
||||||
'wa_link' => $waLink,
|
'wa_link' => $waLink,
|
||||||
'is_banned' => $user->is_banned,
|
'is_banned' => $user->is_banned,
|
||||||
'tenggat_kembali' => $firstLoan->due_at,
|
'tenggat_kembali' => $firstLoan->due_at,
|
||||||
'books' => $userLoans->map(fn($l) => [
|
'books' => $userLoans->map(fn ($l) => [
|
||||||
'id' => $l->book->id,
|
'id' => $l->book->id,
|
||||||
'judul' => $l->book->judul,
|
'judul' => $l->book->judul,
|
||||||
])->toArray(),
|
])->toArray(),
|
||||||
|
|
@ -194,7 +262,7 @@ public function dendaIndex()
|
||||||
return view('admin.denda.index', [
|
return view('admin.denda.index', [
|
||||||
'pageTitle' => 'Manajemen Denda & Sanksi',
|
'pageTitle' => 'Manajemen Denda & Sanksi',
|
||||||
'siswaTelat' => $siswaTelat,
|
'siswaTelat' => $siswaTelat,
|
||||||
'listKelas' => $listKelas
|
'listKelas' => $listKelas,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +279,7 @@ public function berikanSanksi(Request $request)
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'success',
|
'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'],
|
'return_notes' => $item['notes'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Update book status
|
// Update book status and increment stock
|
||||||
$loan->book->update(['status' => 'Tersedia']);
|
$loan->book->update([
|
||||||
|
'status' => 'Tersedia',
|
||||||
|
'stok' => $loan->book->stok + 1,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
\DB::commit();
|
\DB::commit();
|
||||||
|
|
||||||
return response()->json(['status' => 'success', 'message' => 'Buku berhasil dikembalikan.']);
|
return response()->json(['status' => 'success', 'message' => 'Buku berhasil dikembalikan.']);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\DB::rollBack();
|
\DB::rollBack();
|
||||||
|
|
||||||
return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
|
return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,29 +12,33 @@ class BookController extends Controller
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$filters = $request->only(['search']);
|
$filters = $request->only(['search']);
|
||||||
|
|
||||||
$query = Book::with('category');
|
$query = Book::with('category');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$query->where('judul', 'like', '%' . $request->search . '%');
|
$query->where('judul', 'like', '%'.$request->search.'%');
|
||||||
}
|
}
|
||||||
|
|
||||||
$semuaBuku = $query->latest()->get();
|
$semuaBuku = $query->latest()->get();
|
||||||
|
|
||||||
// Memisahkan buku menjadi dua koleksi: online dan offline secara independen
|
|
||||||
$bukuOnline = $semuaBuku->filter(function ($buku) {
|
$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) {
|
$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', [
|
return view('admin.buku.index', [
|
||||||
'pageTitle' => 'Manajemen Buku',
|
'pageTitle' => 'Manajemen Buku',
|
||||||
'bukuOnline' => $bukuOnline,
|
'bukuOnline' => $bukuOnline,
|
||||||
'bukuOffline' => $bukuOffline,
|
'bukuOffline' => $bukuOffline,
|
||||||
'input' => $filters
|
'bukuArsip' => $bukuArsip,
|
||||||
|
'input' => $filters,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +49,7 @@ public function create()
|
||||||
{
|
{
|
||||||
return view('admin.buku.create', [
|
return view('admin.buku.create', [
|
||||||
'pageTitle' => 'Tambah Buku Baru',
|
'pageTitle' => 'Tambah Buku Baru',
|
||||||
'categories' => Category::all()
|
'categories' => Category::all(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,9 +58,9 @@ public function edit($id)
|
||||||
$buku = Book::with('category')->findOrFail($id);
|
$buku = Book::with('category')->findOrFail($id);
|
||||||
|
|
||||||
return view('admin.buku.edit', [
|
return view('admin.buku.edit', [
|
||||||
'pageTitle' => 'Edit Buku: ' . $buku->judul,
|
'pageTitle' => 'Edit Buku: '.$buku->judul,
|
||||||
'buku' => $buku,
|
'buku' => $buku,
|
||||||
'categories' => Category::all()
|
'categories' => Category::all(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,6 +72,7 @@ public function store(Request $request)
|
||||||
'category_id' => 'required|exists:categories,id',
|
'category_id' => 'required|exists:categories,id',
|
||||||
'tahun' => 'required|integer',
|
'tahun' => 'required|integer',
|
||||||
'kode_buku' => 'nullable|string',
|
'kode_buku' => 'nullable|string',
|
||||||
|
'stok' => 'required|integer|min:0',
|
||||||
'tipe_akses' => 'required|array',
|
'tipe_akses' => 'required|array',
|
||||||
'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
|
'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
|
||||||
'file_pdf' => 'nullable|mimes:pdf|max:10240',
|
'file_pdf' => 'nullable|mimes:pdf|max:10240',
|
||||||
|
|
@ -75,7 +80,7 @@ public function store(Request $request)
|
||||||
|
|
||||||
if ($request->hasFile('cover')) {
|
if ($request->hasFile('cover')) {
|
||||||
$path = $request->file('cover')->store('covers', 'public');
|
$path = $request->file('cover')->store('covers', 'public');
|
||||||
$validated['cover'] = 'storage/' . $path;
|
$validated['cover'] = 'storage/'.$path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->hasFile('file_pdf')) {
|
if ($request->hasFile('file_pdf')) {
|
||||||
|
|
@ -98,6 +103,7 @@ public function update(Request $request, $id)
|
||||||
'category_id' => 'required|exists:categories,id',
|
'category_id' => 'required|exists:categories,id',
|
||||||
'tahun' => 'required|integer',
|
'tahun' => 'required|integer',
|
||||||
'kode_buku' => 'nullable|string',
|
'kode_buku' => 'nullable|string',
|
||||||
|
'stok' => 'required|integer|min:0',
|
||||||
'tipe_akses' => 'required|array',
|
'tipe_akses' => 'required|array',
|
||||||
'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
|
'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
|
||||||
'file_pdf' => 'nullable|mimes:pdf|max:10240',
|
'file_pdf' => 'nullable|mimes:pdf|max:10240',
|
||||||
|
|
@ -105,7 +111,7 @@ public function update(Request $request, $id)
|
||||||
|
|
||||||
if ($request->hasFile('cover')) {
|
if ($request->hasFile('cover')) {
|
||||||
$path = $request->file('cover')->store('covers', 'public');
|
$path = $request->file('cover')->store('covers', 'public');
|
||||||
$validated['cover'] = 'storage/' . $path;
|
$validated['cover'] = 'storage/'.$path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->hasFile('file_pdf')) {
|
if ($request->hasFile('file_pdf')) {
|
||||||
|
|
@ -125,4 +131,20 @@ public function destroy($id)
|
||||||
|
|
||||||
return redirect()->route('admin.buku.index')->with('success', 'Buku berhasil dihapus.');
|
return redirect()->route('admin.buku.index')->with('success', 'Buku berhasil dihapus.');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ public function store(Request $request)
|
||||||
'phone' => 'nullable|string|max:20',
|
'phone' => 'nullable|string|max:20',
|
||||||
'role' => 'required|in:siswa,guru,penjaga perpus',
|
'role' => 'required|in:siswa,guru,penjaga perpus',
|
||||||
'kelas' => 'nullable|string|max:50',
|
'kelas' => 'nullable|string|max:50',
|
||||||
|
'golongan' => 'nullable|string|max:50',
|
||||||
'password' => 'required|string|min:8|confirmed',
|
'password' => 'required|string|min:8|confirmed',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -74,6 +75,7 @@ public function update(Request $request, $id)
|
||||||
'phone' => 'nullable|string|max:20',
|
'phone' => 'nullable|string|max:20',
|
||||||
'role' => 'required|in:siswa,guru,penjaga perpus',
|
'role' => 'required|in:siswa,guru,penjaga perpus',
|
||||||
'kelas' => 'nullable|string|max:50',
|
'kelas' => 'nullable|string|max:50',
|
||||||
|
'golongan' => 'nullable|string|max:50',
|
||||||
'password' => 'nullable|string|min:8|confirmed',
|
'password' => 'nullable|string|min:8|confirmed',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\MasterInduk;
|
use App\Models\MasterInduk;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Auth\Events\Registered;
|
use Illuminate\Auth\Events\Registered;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
@ -19,6 +19,7 @@ class RegisteredUserController extends Controller
|
||||||
public function create(Request $request): View
|
public function create(Request $request): View
|
||||||
{
|
{
|
||||||
$role = $request->query('role', 'siswa');
|
$role = $request->query('role', 'siswa');
|
||||||
|
|
||||||
return view('auth.register', ['role' => $role]);
|
return view('auth.register', ['role' => $role]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +27,6 @@ public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$role = $request->input('role');
|
$role = $request->input('role');
|
||||||
|
|
||||||
|
|
||||||
$rules = [
|
$rules = [
|
||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||||
|
|
@ -43,27 +43,24 @@ public function store(Request $request): RedirectResponse
|
||||||
|
|
||||||
$request->validate($rules);
|
$request->validate($rules);
|
||||||
|
|
||||||
|
|
||||||
$nomorInduk = ($role === 'siswa') ? $request->nisn : $request->nip;
|
$nomorInduk = ($role === 'siswa') ? $request->nisn : $request->nip;
|
||||||
|
|
||||||
$isWhitelisted = MasterInduk::where('nomor_induk', $nomorInduk)
|
$isWhitelisted = MasterInduk::where('nomor_induk', $nomorInduk)
|
||||||
->where('role', $role)
|
->where('role', $role)
|
||||||
->exists();
|
->exists();
|
||||||
|
|
||||||
if (!$isWhitelisted) {
|
if (! $isWhitelisted) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
($role === 'siswa' ? 'nisn' : 'nip') => ['Nomor induk tidak terdaftar di Data Sekolah. Hubungi petugas.'],
|
($role === 'siswa' ? 'nisn' : 'nip') => ['Nomor induk tidak terdaftar di Data Sekolah. Hubungi petugas.'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
'email' => $request->email,
|
'email' => $request->email,
|
||||||
'password' => Hash::make($request->password),
|
'password' => Hash::make($request->password),
|
||||||
'role' => $role,
|
'role' => $role,
|
||||||
'nisn' => ($role === 'siswa') ? $nomorInduk : null,
|
'nomor_induk' => $nomorInduk,
|
||||||
'nip' => ($role === 'guru') ? $nomorInduk : null,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
event(new Registered($user));
|
event(new Registered($user));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class ResetPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Generate an OTP for a specific user and store it in cache.
|
||||||
|
* Called by admin from the user management page.
|
||||||
|
*/
|
||||||
|
public function generateOtp(Request $request)
|
||||||
|
{
|
||||||
|
$request->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.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ class ProfileController extends Controller
|
||||||
/**
|
/**
|
||||||
* Tampilkan halaman profil user.
|
* Tampilkan halaman profil user.
|
||||||
*/
|
*/
|
||||||
public function index(Request $request): View
|
public function index(Request $request): \Illuminate\View\View|\Illuminate\Http\RedirectResponse
|
||||||
{
|
{
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
|
|
@ -62,7 +62,22 @@ public function index(Request $request): View
|
||||||
|
|
||||||
// Analytics for Guru (simplified for profile view)
|
// Analytics for Guru (simplified for profile view)
|
||||||
$viewData['laporan'] = [
|
$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.'
|
'insight' => 'Siswa aktif meminjam buku kategori Sains.'
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ class Book extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'judul', 'penulis', 'cover', 'kode_buku',
|
'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 = [
|
protected $casts = [
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ public static function getAllSiswa(): array
|
||||||
'nisn' => '9988776655',
|
'nisn' => '9988776655',
|
||||||
'nama_lengkap' => 'Siti Nurhaliza',
|
'nama_lengkap' => 'Siti Nurhaliza',
|
||||||
'email' => 'siti.nurhaliza@smkn1perpus.sch.id',
|
'email' => 'siti.nurhaliza@smkn1perpus.sch.id',
|
||||||
'nomor_hp' => '081998877665',
|
'nomor_hp' => '0895618643811',
|
||||||
'password' => 'password',
|
'password' => 'password',
|
||||||
'role' => 'siswa',
|
'role' => 'siswa',
|
||||||
'kelas' => 'XII RPL A',
|
'kelas' => 'XII RPL A',
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,8 @@
|
||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('master_induks', function (Blueprint $table) {
|
Schema::table('books', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->integer('stok')->default(1)->after('status');
|
||||||
$table->string('nomor_induk')->unique();
|
|
||||||
$table->string('nama_pemilik');
|
|
||||||
$table->string('role');
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,6 +21,8 @@ public function up(): void
|
||||||
*/
|
*/
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('master_induks');
|
Schema::table('books', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('books', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_arsip')->default(false)->after('stok');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('books', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -2,11 +2,10 @@
|
||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
use App\Models\Book;
|
use App\Models\Book;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Services\DummyDataService;
|
use App\Services\DummyDataService;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
class BookSeeder extends Seeder
|
class BookSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
|
@ -20,6 +19,8 @@ public function run(): void
|
||||||
foreach ($books as $data) {
|
foreach ($books as $data) {
|
||||||
$category = Category::where('name', $data['kategori'])->first();
|
$category = Category::where('name', $data['kategori'])->first();
|
||||||
|
|
||||||
|
$stok = isset($data['stok']) ? $data['stok'] : (($data['status'] === 'Tersedia') ? 1 : 0);
|
||||||
|
|
||||||
Book::updateOrCreate(
|
Book::updateOrCreate(
|
||||||
['kode_buku' => $data['kode_buku']],
|
['kode_buku' => $data['kode_buku']],
|
||||||
[
|
[
|
||||||
|
|
@ -30,6 +31,7 @@ public function run(): void
|
||||||
'category_id' => $category ? $category->id : null,
|
'category_id' => $category ? $category->id : null,
|
||||||
'tahun' => $data['tahun'],
|
'tahun' => $data['tahun'],
|
||||||
'status' => $data['status'],
|
'status' => $data['status'],
|
||||||
|
'stok' => $stok,
|
||||||
'is_new' => $data['is_new'],
|
'is_new' => $data['is_new'],
|
||||||
'tipe_akses' => is_array($data['tipe_akses']) ? $data['tipe_akses'] : [$data['tipe_akses']],
|
'tipe_akses' => is_array($data['tipe_akses']) ? $data['tipe_akses'] : [$data['tipe_akses']],
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\MasterInduk;
|
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\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +29,7 @@ public function run()
|
||||||
MasterInduk::create($w);
|
MasterInduk::create($w);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ISI USER ASLI
|
// ISI USER ASLI
|
||||||
|
|
||||||
// ID 1: Silvi (Siswa)
|
// ID 1: Silvi (Siswa)
|
||||||
User::create([
|
User::create([
|
||||||
|
|
@ -38,10 +38,10 @@ public function run()
|
||||||
'email' => 'silvi.rahmawati@smkn1perpus.sch.id',
|
'email' => 'silvi.rahmawati@smkn1perpus.sch.id',
|
||||||
'password' => Hash::make('password'),
|
'password' => Hash::make('password'),
|
||||||
'role' => 'siswa',
|
'role' => 'siswa',
|
||||||
'nisn' => '1234567890',
|
'nomor_induk' => '1234567890',
|
||||||
'no_hp' => '08123456789',
|
'phone' => '08123456789',
|
||||||
'kelas' => 'XII RPL',
|
'kelas' => 'XII RPL',
|
||||||
'golongan' => 'A'
|
'golongan' => 'A',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ID 2: Budi (Admin/Penjaga)
|
// ID 2: Budi (Admin/Penjaga)
|
||||||
|
|
@ -51,7 +51,7 @@ public function run()
|
||||||
'email' => 'budi.santoso@smkn1perpus.sch.id',
|
'email' => 'budi.santoso@smkn1perpus.sch.id',
|
||||||
'password' => Hash::make('password'),
|
'password' => Hash::make('password'),
|
||||||
'role' => 'penjaga perpus',
|
'role' => 'penjaga perpus',
|
||||||
'nip' => '197812312005011',
|
'nomor_induk' => '197812312005011',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ID 3: Siti (Siswa)
|
// ID 3: Siti (Siswa)
|
||||||
|
|
@ -61,10 +61,10 @@ public function run()
|
||||||
'email' => 'siti.nurhaliza@smkn1perpus.sch.id',
|
'email' => 'siti.nurhaliza@smkn1perpus.sch.id',
|
||||||
'password' => Hash::make('password'),
|
'password' => Hash::make('password'),
|
||||||
'role' => 'siswa',
|
'role' => 'siswa',
|
||||||
'nisn' => '9988776655',
|
'nomor_induk' => '9988776655',
|
||||||
'no_hp' => '081998877665',
|
'phone' => '0895618643811',
|
||||||
'kelas' => 'XII RPL',
|
'kelas' => 'XII RPL',
|
||||||
'golongan' => 'B'
|
'golongan' => 'B',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ID 4: Andi (Siswa)
|
// ID 4: Andi (Siswa)
|
||||||
|
|
@ -74,10 +74,10 @@ public function run()
|
||||||
'email' => 'andi.pratama@smkn1perpus.sch.id',
|
'email' => 'andi.pratama@smkn1perpus.sch.id',
|
||||||
'password' => Hash::make('password'),
|
'password' => Hash::make('password'),
|
||||||
'role' => 'siswa',
|
'role' => 'siswa',
|
||||||
'nisn' => '5566778899',
|
'nomor_induk' => '5566778899',
|
||||||
'no_hp' => '081556677889',
|
'phone' => '081556677889',
|
||||||
'kelas' => 'XII RPL',
|
'kelas' => 'XII RPL',
|
||||||
'golongan' => 'C'
|
'golongan' => 'C',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ID 5: Rina (Guru)
|
// ID 5: Rina (Guru)
|
||||||
|
|
@ -87,7 +87,15 @@ public function run()
|
||||||
'email' => 'rina.marlina@smkn1perpus.sch.id',
|
'email' => 'rina.marlina@smkn1perpus.sch.id',
|
||||||
'password' => Hash::make('password'),
|
'password' => Hash::make('password'),
|
||||||
'role' => 'guru',
|
'role' => 'guru',
|
||||||
'nip' => '198506152010012',
|
'nomor_induk' => '198506152010012',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->call([
|
||||||
|
CategorySeeder::class,
|
||||||
|
BookSeeder::class,
|
||||||
|
AnnouncementSeeder::class,
|
||||||
|
RecommendationSeeder::class,
|
||||||
|
LoanSeeder::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@
|
||||||
// UTILITIES (GENERATED FROM MAPS)
|
// UTILITIES (GENERATED FROM MAPS)
|
||||||
// ===================================
|
// ===================================
|
||||||
// Generator otomatis untuk variant warna (background light, soft, alert)
|
// Generator otomatis untuk variant warna (background light, soft, alert)
|
||||||
@each $color,
|
@each $color, $value in $theme-colors {
|
||||||
$value in $theme-colors {
|
|
||||||
.bg-#{$color}-light {
|
.bg-#{$color}-light {
|
||||||
background-color: rgba($value, 0.25) !important;
|
background-color: rgba($value, 0.25) !important;
|
||||||
}
|
}
|
||||||
|
|
@ -63,14 +62,14 @@ html {
|
||||||
.navbar-nav {
|
.navbar-nav {
|
||||||
.nav-link-landing {
|
.nav-link-landing {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
color: #49769F;
|
color: #49769f;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
display: block;
|
||||||
transition: color 0.3s ease;
|
transition: color 0.3s ease;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&.active {
|
&.active {
|
||||||
color: #0C5495;
|
color: #0c5495;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s ease-in;
|
transition: all 0.3s ease-in;
|
||||||
}
|
}
|
||||||
|
|
@ -109,9 +108,11 @@ html {
|
||||||
|
|
||||||
// Hero Section
|
// Hero Section
|
||||||
.landing-hero-section {
|
.landing-hero-section {
|
||||||
background: linear-gradient(135deg,
|
background: linear-gradient(
|
||||||
#0C5495 0%,
|
135deg,
|
||||||
color.adjust(#0C5495, $lightness: 10%) 100%);
|
#0c5495 0%,
|
||||||
|
color.adjust(#0c5495, $lightness: 10%) 100%
|
||||||
|
);
|
||||||
padding: 60px 0;
|
padding: 60px 0;
|
||||||
|
|
||||||
@media (min-width: 992px) {
|
@media (min-width: 992px) {
|
||||||
|
|
@ -143,7 +144,6 @@ html {
|
||||||
i {
|
i {
|
||||||
font-size: 5rem;
|
font-size: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-img-top {
|
.card-img-top {
|
||||||
|
|
@ -186,9 +186,11 @@ html {
|
||||||
|
|
||||||
// CTA Section
|
// CTA Section
|
||||||
.landing-cta-section {
|
.landing-cta-section {
|
||||||
background: linear-gradient(135deg,
|
background: linear-gradient(
|
||||||
#0C5495 0%,
|
135deg,
|
||||||
color.adjust(#0C5495, $lightness: 10%) 100%);
|
#0c5495 0%,
|
||||||
|
color.adjust(#0c5495, $lightness: 10%) 100%
|
||||||
|
);
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
|
@ -196,7 +198,6 @@ html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===================================
|
// ===================================
|
||||||
// BASE COMPONENTS
|
// BASE COMPONENTS
|
||||||
// ===================================
|
// ===================================
|
||||||
|
|
@ -294,9 +295,10 @@ html {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: map-get($grays, "light");
|
background-color: map-get($grays, "light");
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sidebar
|
// Sidebar
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 270px;
|
width: 270px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -308,6 +310,8 @@ body {
|
||||||
|
|
||||||
.main-wrapper {
|
.main-wrapper {
|
||||||
transition: margin-left 0.3s ease;
|
transition: margin-left 0.3s ease;
|
||||||
|
overflow-x: hidden;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overlay gelap untuk mobile sidebar
|
// Overlay gelap untuk mobile sidebar
|
||||||
|
|
@ -634,9 +638,11 @@ nav {
|
||||||
border-radius: $border-radius-sm;
|
border-radius: $border-radius-sm;
|
||||||
|
|
||||||
&-container {
|
&-container {
|
||||||
background: linear-gradient(135deg,
|
background: linear-gradient(
|
||||||
rgba(map-get($grays, "light"), 0.5) 0%,
|
135deg,
|
||||||
rgba(map-get($grays, "light"), 0.8) 100%);
|
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;
|
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-border-color: #{map-get($theme-colors, "primary")};
|
||||||
--bs-btn-hover-color: #{map-get($grays, "dark")};
|
--bs-btn-hover-color: #{map-get($grays, "dark")};
|
||||||
--bs-btn-hover-bg: #{color.adjust(
|
--bs-btn-hover-bg: #{color.adjust(
|
||||||
map-get($theme-colors, "primary"),
|
map-get($theme-colors, "primary"),
|
||||||
$lightness: -5%)
|
$lightness: -5%
|
||||||
}
|
)};
|
||||||
|
--bs-btn-hover-border-color: #{color.adjust(
|
||||||
;
|
map-get($theme-colors, "primary"),
|
||||||
--bs-btn-hover-border-color: #{color.adjust(
|
$lightness: -7.5%
|
||||||
map-get($theme-colors, "primary"),
|
)};
|
||||||
$lightness: -7.5%)
|
|
||||||
}
|
|
||||||
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text clamp untuk truncate multi line
|
// Text clamp untuk truncate multi line
|
||||||
|
|
@ -829,9 +831,11 @@ $lightness: -7.5%)
|
||||||
|
|
||||||
// Background gradient hero section
|
// Background gradient hero section
|
||||||
.hero-gradient {
|
.hero-gradient {
|
||||||
background: linear-gradient(135deg,
|
background: linear-gradient(
|
||||||
map-get($theme-colors, "primary") 0%,
|
135deg,
|
||||||
color.adjust(map-get($theme-colors, "primary"), $lightness: 10%) 100%);
|
map-get($theme-colors, "primary") 0%,
|
||||||
|
color.adjust(map-get($theme-colors, "primary"), $lightness: 10%) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Card untuk pilih role (admin/user)
|
// Card untuk pilih role (admin/user)
|
||||||
|
|
@ -851,9 +855,11 @@ $lightness: -7.5%)
|
||||||
|
|
||||||
// Panel info di halaman auth
|
// Panel info di halaman auth
|
||||||
.info-panel {
|
.info-panel {
|
||||||
background: linear-gradient(135deg,
|
background: linear-gradient(
|
||||||
map-get($theme-colors, "primary") 0%,
|
135deg,
|
||||||
color.adjust(map-get($theme-colors, "primary"), $lightness: 10%) 100%);
|
map-get($theme-colors, "primary") 0%,
|
||||||
|
color.adjust(map-get($theme-colors, "primary"), $lightness: 10%) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panel kiri auth (logo dan branding)
|
// Panel kiri auth (logo dan branding)
|
||||||
|
|
@ -906,7 +912,7 @@ $lightness: -7.5%)
|
||||||
@media (max-width: 991.98px) {
|
@media (max-width: 991.98px) {
|
||||||
.auth-left-panel {
|
.auth-left-panel {
|
||||||
background-image: none !important;
|
background-image: none !important;
|
||||||
background-color: #0C5495;
|
background-color: #0c5495;
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
min-height: 40vh;
|
min-height: 40vh;
|
||||||
}
|
}
|
||||||
|
|
@ -936,7 +942,6 @@ $lightness: -7.5%)
|
||||||
// Override styling DataTables
|
// Override styling DataTables
|
||||||
|
|
||||||
.dataTables_wrapper {
|
.dataTables_wrapper {
|
||||||
|
|
||||||
.dataTables_length,
|
.dataTables_length,
|
||||||
.dataTables_filter,
|
.dataTables_filter,
|
||||||
.dataTables_info,
|
.dataTables_info,
|
||||||
|
|
@ -953,7 +958,8 @@ $lightness: -7.5%)
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: map-get($theme-colors, "primary");
|
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
|
// Responsive DataTables untuk mobile
|
||||||
@media screen and (max-width: 576px) {
|
@media screen and (max-width: 576px) {
|
||||||
.dataTables_wrapper {
|
.dataTables_wrapper {
|
||||||
|
|
||||||
.dataTables_length,
|
.dataTables_length,
|
||||||
.dataTables_filter {
|
.dataTables_filter {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -1003,11 +1008,10 @@ $lightness: -7.5%)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================
|
// ===================================
|
||||||
// PROFILE PAGE
|
// PROFILE PAGE
|
||||||
// ===================================
|
// ===================================
|
||||||
|
|
||||||
|
// Avatar
|
||||||
// Avatar
|
|
||||||
.profile-avatar-lg {
|
.profile-avatar-lg {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
placeholder="Masukkan nama penulis" required>
|
placeholder="Masukkan nama penulis" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="category_id" class="form-label">Kategori</label>
|
<label for="category_id" class="form-label">Kategori</label>
|
||||||
<select name="category_id" class="form-select" id="category_id" required>
|
<select name="category_id" class="form-select" id="category_id" required>
|
||||||
<option value="" disabled selected>Pilih Kategori</option>
|
<option value="" disabled selected>Pilih Kategori</option>
|
||||||
|
|
@ -37,16 +37,21 @@
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="tahun" class="form-label">Tahun Terbit</label>
|
<label for="tahun" class="form-label">Tahun Terbit</label>
|
||||||
<input type="number" name="tahun" class="form-control" id="tahun"
|
<input type="number" name="tahun" class="form-control" id="tahun"
|
||||||
placeholder="Contoh: 2024" min="0" required>
|
placeholder="Contoh: 2024" min="0" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="kode_buku" class="form-label">Kode Buku</label>
|
<label for="kode_buku" class="form-label">Kode Buku</label>
|
||||||
<input type="text" name="kode_buku" class="form-control" id="kode_buku"
|
<input type="text" name="kode_buku" class="form-control" id="kode_buku"
|
||||||
placeholder="Contoh: 330">
|
placeholder="Contoh: 330">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-3 mb-3">
|
||||||
|
<label for="stok" class="form-label">Stok</label>
|
||||||
|
<input type="number" name="stok" class="form-control" id="stok"
|
||||||
|
value="1" min="0" required>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
value="{{ old('penulis', $buku->penulis) }}" required>
|
value="{{ old('penulis', $buku->penulis) }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="category_id" class="form-label">Kategori</label>
|
<label for="category_id" class="form-label">Kategori</label>
|
||||||
<select name="category_id" class="form-select" id="category_id" required>
|
<select name="category_id" class="form-select" id="category_id" required>
|
||||||
@foreach($categories as $category)
|
@foreach($categories as $category)
|
||||||
|
|
@ -39,11 +39,16 @@
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="tahun" class="form-label">Tahun Terbit</label>
|
<label for="tahun" class="form-label">Tahun Terbit</label>
|
||||||
<input type="number" name="tahun" class="form-control" id="tahun"
|
<input type="number" name="tahun" class="form-control" id="tahun"
|
||||||
value="{{ old('tahun', $buku->tahun) }}" required>
|
value="{{ old('tahun', $buku->tahun) }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label for="stok" class="form-label">Stok</label>
|
||||||
|
<input type="number" name="stok" class="form-control" id="stok"
|
||||||
|
value="{{ old('stok', $buku->stok ?? 1) }}" min="0" required>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@php
|
@php
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link text-warning" id="arsip-tab" data-bs-toggle="tab"
|
<button class="nav-link text-warning" id="arsip-tab" data-bs-toggle="tab"
|
||||||
data-bs-target="#arsip-tab-pane" type="button" role="tab">
|
data-bs-target="#arsip-tab-pane" type="button" role="tab">
|
||||||
<i class="bi bi-archive-fill me-1"></i>Diarsipkan (<span id="countArsip">0</span>)
|
<i class="bi bi-archive-fill me-1"></i>Diarsipkan (<span id="countArsip">{{ $bukuArsip->count() }}</span>)
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -48,14 +48,17 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse($bukuOffline as $buku)
|
@foreach($bukuOffline as $buku)
|
||||||
<tr>
|
<tr data-tipe="offline" data-id="{{ $buku->id }}">
|
||||||
<td>{{ $loop->iteration }}</td>
|
<td>{{ $loop->iteration }}</td>
|
||||||
<td><img src="{{ asset($buku['cover']) }}" alt="{{ $buku['judul'] }}"
|
<td><img src="{{ asset($buku['cover']) }}" alt="{{ $buku['judul'] }}"
|
||||||
width="50" class="rounded"></td>
|
width="50" class="rounded"></td>
|
||||||
<td>{{ $buku['judul'] }}</td>
|
<td>{{ $buku['judul'] }}</td>
|
||||||
<td>{{ $buku['kode_buku'] }}</td>
|
<td>{{ $buku['kode_buku'] }}</td>
|
||||||
<td>{{ $buku['penulis'] }}</td>
|
<td>{{ $buku['penulis'] }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-info-subtle text-info-emphasis">{{ $buku['stok'] ?? 0 }}</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if ($buku['status'] == 'Tersedia')
|
@if ($buku['status'] == 'Tersedia')
|
||||||
<span
|
<span
|
||||||
|
|
@ -73,22 +76,17 @@ class="badge bg-warning-subtle text-warning-emphasis">Dipinjam</span>
|
||||||
data-kode_buku="{{ $buku['kode_buku'] }}"
|
data-kode_buku="{{ $buku['kode_buku'] }}"
|
||||||
data-penulis="{{ $buku['penulis'] }}"
|
data-penulis="{{ $buku['penulis'] }}"
|
||||||
data-kategori="{{ $buku->category->name ?? '-' }}"
|
data-kategori="{{ $buku->category->name ?? '-' }}"
|
||||||
data-tahun="{{ $buku['tahun'] }}" data-status="{{ $buku['status'] }}">
|
data-tahun="{{ $buku['tahun'] }}" data-status="{{ $buku['status'] }}" data-stok="{{ $buku['stok'] ?? 0 }}">
|
||||||
<i class="bi bi-eye-fill"></i> Detail
|
<i class="bi bi-eye-fill"></i> Detail
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-warning btn-arsipkan"
|
<button class="btn btn-sm btn-outline-warning btn-arsipkan"
|
||||||
data-judul="{{ $buku['judul'] }}" title="Arsipkan Buku">
|
data-id="{{ $buku->id }}" data-judul="{{ $buku->judul }}" data-penulis="{{ $buku->penulis }}" title="Arsipkan Buku">
|
||||||
<i class="bi bi-archive-fill"></i> Arsip
|
<i class="bi bi-archive-fill"></i> Arsip
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@endforeach
|
||||||
<tr class="empty-row">
|
|
||||||
<td colspan="8" class="text-center py-4 text-muted">Tidak ada data buku offline.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -109,8 +107,8 @@ class="badge bg-warning-subtle text-warning-emphasis">Dipinjam</span>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse($bukuOnline as $buku)
|
@foreach($bukuOnline as $buku)
|
||||||
<tr>
|
<tr data-tipe="online" data-id="{{ $buku->id }}">
|
||||||
<td>{{ $loop->iteration }}</td>
|
<td>{{ $loop->iteration }}</td>
|
||||||
<td><img src="{{ asset($buku['cover']) }}" alt="{{ $buku['judul'] }}"
|
<td><img src="{{ asset($buku['cover']) }}" alt="{{ $buku['judul'] }}"
|
||||||
width="50" class="rounded"></td>
|
width="50" class="rounded"></td>
|
||||||
|
|
@ -119,28 +117,24 @@ class="badge bg-warning-subtle text-warning-emphasis">Dipinjam</span>
|
||||||
<td><span
|
<td><span
|
||||||
class="badge bg-info-subtle text-info-emphasis">{{ $buku['file_pdf'] ?? 'N/A' }}</span>
|
class="badge bg-info-subtle text-info-emphasis">{{ $buku['file_pdf'] ?? 'N/A' }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal"
|
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal"
|
||||||
data-bs-target="#detailBukuModal" data-id="{{ $buku['id'] }}"
|
data-bs-target="#detailBukuModal" data-id="{{ $buku['id'] }}"
|
||||||
data-cover="{{ asset($buku['cover']) }}"
|
data-cover="{{ asset($buku['cover']) }}"
|
||||||
data-judul="{{ $buku['judul'] }}"
|
data-judul="{{ $buku['judul'] }}"
|
||||||
data-penulis="{{ $buku['penulis'] }}"
|
data-penulis="{{ $buku['penulis'] }}"
|
||||||
data-kategori="{{ $buku->category->name ?? '-' }}"
|
data-kategori="{{ $buku->category->name ?? '-' }}"
|
||||||
data-tahun="{{ $buku['tahun'] }}" data-status="Dapat Dibaca Online">
|
data-tahun="{{ $buku['tahun'] }}" data-status="Dapat Dibaca Online" data-stok="{{ $buku['stok'] ?? 0 }}">
|
||||||
<i class="bi bi-eye-fill"></i> Detail
|
<i class="bi bi-eye-fill"></i> Detail
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-warning btn-arsipkan"
|
<button class="btn btn-sm btn-outline-warning btn-arsipkan"
|
||||||
data-judul="{{ $buku['judul'] }}" title="Arsipkan Buku">
|
data-id="{{ $buku->id }}" data-judul="{{ $buku->judul }}" data-penulis="{{ $buku->penulis }}" title="Arsipkan Buku">
|
||||||
<i class="bi bi-archive-fill"></i> Arsip
|
<i class="bi bi-archive-fill"></i> Arsip
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@endforeach
|
||||||
<tr class="empty-row">
|
|
||||||
<td colspan="6" class="text-center py-4 text-muted">Tidak ada data buku online.</td>
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -161,12 +155,35 @@ class="badge bg-info-subtle text-info-emphasis">{{ $buku['file_pdf'] ?? 'N/A' }}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@forelse($bukuArsip as $buku)
|
||||||
|
<tr data-id="{{ $buku->id }}" data-tipe="{{ in_array('offline', $buku->tipe_akses ?? []) ? 'offline' : 'online' }}">
|
||||||
|
<td class="row-number">{{ $loop->iteration }}</td>
|
||||||
|
<td><img src="{{ asset($buku['cover']) }}" alt="{{ $buku['judul'] }}"
|
||||||
|
width="50" class="rounded"></td>
|
||||||
|
<td class="fw-bold text-muted">{{ $buku->judul }}</td>
|
||||||
|
<td class="text-muted">{{ $buku->penulis }}</td>
|
||||||
|
<td>
|
||||||
|
@if(in_array('offline', $buku->tipe_akses ?? []))
|
||||||
|
<span class="badge bg-secondary"><i class="bi bi-book me-1"></i>Offline</span>
|
||||||
|
@else
|
||||||
|
<span class="badge bg-info"><i class="bi bi-globe me-1"></i>Online</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-outline-success btn-pulihkan"
|
||||||
|
data-id="{{ $buku->id }}" data-judul="{{ $buku->judul }}" title="Kembalikan ke Daftar Aktif">
|
||||||
|
<i class="bi bi-arrow-counterclockwise me-1"></i>Kembalikan
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
<tr class="empty-row-arsip">
|
<tr class="empty-row-arsip">
|
||||||
<td colspan="6" class="text-center py-5 text-muted">
|
<td colspan="6" class="text-center py-5 text-muted">
|
||||||
<i class="bi bi-inbox fs-1 d-block mb-2 text-secondary opacity-50"></i>
|
<i class="bi bi-inbox fs-1 d-block mb-2 text-secondary opacity-50"></i>
|
||||||
Belum ada buku yang diarsipkan.
|
Belum ada buku yang diarsipkan.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@endforelse
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -257,26 +274,27 @@ function updateTableNumbers() {
|
||||||
// LOGIC MODAL DETAIL
|
// LOGIC MODAL DETAIL
|
||||||
$('#detailBukuModal').on('show.bs.modal', function(event) {
|
$('#detailBukuModal').on('show.bs.modal', function(event) {
|
||||||
const button = $(event.relatedTarget);
|
const button = $(event.relatedTarget);
|
||||||
const buku = button.data('buku');
|
|
||||||
|
|
||||||
$('#modalCover').attr('src', "{{ asset('') }}" + buku.cover);
|
$('#modalCover').attr('src', button.data('cover'));
|
||||||
$('#modalJudulContent').text(buku.judul);
|
$('#modalJudulContent').text(button.data('judul'));
|
||||||
$('#modalPenulis').text('Penulis: ' + buku.penulis);
|
$('#modalPenulis').text('Penulis: ' + button.data('penulis'));
|
||||||
$('#modalKategori').text(buku.kategori);
|
$('#modalKategori').text(button.data('kategori'));
|
||||||
$('#modalTahun').text(buku.tahun);
|
$('#modalTahun').text(button.data('tahun'));
|
||||||
$('#modalStok').text(buku.stok ? buku.stok + ' Buku' : '-');
|
$('#modalStok').text(button.data('stok') ? button.data('stok') + ' Buku' : '-');
|
||||||
|
|
||||||
if (buku.kode_buku) {
|
const kodeBuku = button.data('kode_buku');
|
||||||
|
if (kodeBuku) {
|
||||||
$('#rowKodeBuku').show();
|
$('#rowKodeBuku').show();
|
||||||
$('#modalKode').text(buku.kode_buku);
|
$('#modalKode').text(kodeBuku);
|
||||||
} else {
|
} else {
|
||||||
$('#rowKodeBuku').hide();
|
$('#rowKodeBuku').hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status Badge
|
// Status Badge
|
||||||
const statusBadge = $('#modalStatus');
|
const statusBadge = $('#modalStatus');
|
||||||
|
const status = button.data('status');
|
||||||
statusBadge.removeClass().addClass('badge');
|
statusBadge.removeClass().addClass('badge');
|
||||||
if (buku.status === 'Tersedia' || !buku.status) {
|
if (status === 'Tersedia' || status === 'Dapat Dibaca Online' || !status) {
|
||||||
statusBadge.addClass('bg-success-subtle text-success-emphasis').text('Tersedia / Online');
|
statusBadge.addClass('bg-success-subtle text-success-emphasis').text('Tersedia / Online');
|
||||||
} else {
|
} else {
|
||||||
statusBadge.addClass('bg-warning-subtle text-warning-emphasis').text('Dipinjam');
|
statusBadge.addClass('bg-warning-subtle text-warning-emphasis').text('Dipinjam');
|
||||||
|
|
@ -287,12 +305,12 @@ function updateTableNumbers() {
|
||||||
|
|
||||||
// LOGIC ARSIPKAN BUKU
|
// LOGIC ARSIPKAN BUKU
|
||||||
$(document).on('click', '.btn-arsipkan', function() {
|
$(document).on('click', '.btn-arsipkan', function() {
|
||||||
const row = $(this).closest('tr');
|
const button = $(this);
|
||||||
const judul = $(this).data('judul');
|
const row = button.closest('tr');
|
||||||
|
const id = button.data('id');
|
||||||
|
const judul = button.data('judul');
|
||||||
const tipe = row.data('tipe');
|
const tipe = row.data('tipe');
|
||||||
|
|
||||||
const rowData = row.prop('outerHTML');
|
|
||||||
|
|
||||||
modernSwal.fire({
|
modernSwal.fire({
|
||||||
title: 'Arsipkan Buku?',
|
title: 'Arsipkan Buku?',
|
||||||
text: `Buku "${judul}" akan dipindahkan ke tab Arsip.`,
|
text: `Buku "${judul}" akan dipindahkan ke tab Arsip.`,
|
||||||
|
|
@ -303,40 +321,53 @@ function updateTableNumbers() {
|
||||||
confirmButtonColor: '#ffc107',
|
confirmButtonColor: '#ffc107',
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
row.fadeOut(300, function() {
|
$.ajax({
|
||||||
const coverHtml = row.find('td:eq(1)').html();
|
url: "{{ route('admin.buku.arsip') }}",
|
||||||
const penulis = row.find('td:eq(3)').text();
|
type: "POST",
|
||||||
const badgeTipe = tipe === 'offline' ?
|
data: {
|
||||||
'<span class="badge bg-secondary"><i class="bi bi-book me-1"></i>Offline</span>' :
|
_token: "{{ csrf_token() }}",
|
||||||
'<span class="badge bg-info"><i class="bi bi-globe me-1"></i>Online</span>';
|
id: id
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
row.fadeOut(300, function() {
|
||||||
|
const coverHtml = row.find('td:eq(1)').html();
|
||||||
|
const penulis = button.data('penulis');
|
||||||
|
const badgeTipe = tipe === 'offline' ?
|
||||||
|
'<span class="badge bg-secondary"><i class="bi bi-book me-1"></i>Offline</span>' :
|
||||||
|
'<span class="badge bg-info"><i class="bi bi-globe me-1"></i>Online</span>';
|
||||||
|
|
||||||
const originalDataEncoded = encodeURIComponent(rowData);
|
const arsipRow = `
|
||||||
|
<tr data-id="${id}" data-tipe="${tipe}">
|
||||||
|
<td class="row-number"></td>
|
||||||
|
<td>${coverHtml}</td>
|
||||||
|
<td class="fw-bold text-muted">${judul}</td>
|
||||||
|
<td class="text-muted">${penulis}</td>
|
||||||
|
<td>${badgeTipe}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-outline-success btn-pulihkan"
|
||||||
|
data-id="${id}" data-judul="${judul}" title="Kembalikan ke Daftar Aktif">
|
||||||
|
<i class="bi bi-arrow-counterclockwise me-1"></i>Kembalikan
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
|
||||||
const arsipRow = `
|
$('#tableArsip tbody').append(arsipRow);
|
||||||
<tr data-origin="${originalDataEncoded}">
|
if ($('.empty-row-arsip').length) $('.empty-row-arsip').hide();
|
||||||
<td class="row-number"></td>
|
|
||||||
<td>${coverHtml}</td>
|
row.remove();
|
||||||
<td class="fw-bold text-muted">${judul}</td>
|
updateTableNumbers();
|
||||||
<td class="text-muted">${penulis}</td>
|
|
||||||
<td>${badgeTipe}</td>
|
Toast.fire({
|
||||||
<td class="text-center">
|
icon: 'success',
|
||||||
<button class="btn btn-sm btn-outline-success btn-pulihkan"
|
title: 'Diarsipkan',
|
||||||
data-judul="${judul}" title="Kembalikan ke Daftar Aktif">
|
text: response.message
|
||||||
<i class="bi bi-arrow-counterclockwise me-1"></i>Kembalikan
|
});
|
||||||
</button>
|
});
|
||||||
</td>
|
},
|
||||||
</tr>
|
error: function(xhr) {
|
||||||
`;
|
modernSwal.fire('Error', 'Gagal mengarsipkan buku.', 'error');
|
||||||
|
}
|
||||||
$('#tableArsip tbody').append(arsipRow);
|
|
||||||
$(this).remove();
|
|
||||||
|
|
||||||
updateTableNumbers();
|
|
||||||
Toast.fire({
|
|
||||||
icon: 'success',
|
|
||||||
title: 'Diarsipkan',
|
|
||||||
text: `Buku "${judul}" berhasil diarsipkan.`
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -344,9 +375,11 @@ function updateTableNumbers() {
|
||||||
|
|
||||||
// LOGIC PULIHKAN BUKU
|
// LOGIC PULIHKAN BUKU
|
||||||
$(document).on('click', '.btn-pulihkan', function() {
|
$(document).on('click', '.btn-pulihkan', function() {
|
||||||
const row = $(this).closest('tr');
|
const button = $(this);
|
||||||
const judul = $(this).data('judul');
|
const row = button.closest('tr');
|
||||||
const originalDataEncoded = row.data('origin');
|
const id = button.data('id');
|
||||||
|
const judul = button.data('judul');
|
||||||
|
const tipe = row.data('tipe');
|
||||||
|
|
||||||
modernSwal.fire({
|
modernSwal.fire({
|
||||||
title: 'Kembalikan Buku?',
|
title: 'Kembalikan Buku?',
|
||||||
|
|
@ -358,24 +391,37 @@ function updateTableNumbers() {
|
||||||
confirmButtonColor: '#198754',
|
confirmButtonColor: '#198754',
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
row.fadeOut(300, function() {
|
$.ajax({
|
||||||
const originalRowHtml = decodeURIComponent(originalDataEncoded);
|
url: "{{ route('admin.buku.pulihkan') }}",
|
||||||
const $originalRow = $(originalRowHtml);
|
type: "POST",
|
||||||
|
data: {
|
||||||
const tipe = $originalRow.data('tipe');
|
_token: "{{ csrf_token() }}",
|
||||||
const targetTable = tipe === 'offline' ? '#tableOffline' : '#tableOnline';
|
id: id
|
||||||
|
},
|
||||||
$originalRow.removeAttr('style');
|
success: function(response) {
|
||||||
|
row.fadeOut(300, function() {
|
||||||
$(targetTable + ' tbody').append($originalRow);
|
const targetTable = tipe === 'offline' ? '#tableOffline' : '#tableOnline';
|
||||||
$(this).remove();
|
|
||||||
|
// Because we don't have the original row data stored locally in a perfect way,
|
||||||
updateTableNumbers();
|
// the easiest way is to reload or just fade out and tell user it's back.
|
||||||
Toast.fire({
|
// For better UX without reload, we could hide it and notify.
|
||||||
icon: 'success',
|
|
||||||
title: 'Dipulihkan',
|
row.remove();
|
||||||
text: `Buku "${judul}" aktif kembali.`
|
updateTableNumbers();
|
||||||
});
|
|
||||||
|
Toast.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Dipulihkan',
|
||||||
|
text: response.message + ' Silakan refresh untuk melihat di daftar aktif.'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optionally reload to show it in the right place with all data
|
||||||
|
setTimeout(() => location.reload(), 1500);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
modernSwal.fire('Error', 'Gagal memulihkan buku.', 'error');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -107,21 +107,23 @@ class="btn btn-sm btn-success text-white" title="Tagih via WhatsApp">
|
||||||
<i class="bi bi-whatsapp"></i>
|
<i class="bi bi-whatsapp"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{{-- LOGIC TOMBOL AKTIFKAN / SANKSI --}}
|
{{-- LOGIC TOMBOL AKTIFKAN / SANKSI (Hanya untuk Siswa) --}}
|
||||||
@if ($item['is_banned'])
|
@if (!$item['is_guru'])
|
||||||
{{-- Jika sudah dibekukan (Otomatis/Manual), muncul tombol AKTIFKAN --}}
|
@if ($item['is_banned'])
|
||||||
<button class="btn btn-sm btn-outline-success btn-aktifkan"
|
{{-- Jika sudah dibekukan (Otomatis/Manual), muncul tombol AKTIFKAN --}}
|
||||||
data-user-id="{{ $item['user_id'] }}"
|
<button class="btn btn-sm btn-outline-success btn-aktifkan"
|
||||||
data-nama="{{ $item['peminjam'] }}" title="Aktifkan Kembali Akun">
|
data-user-id="{{ $item['user_id'] }}"
|
||||||
<i class="bi bi-shield-check"></i> Aktifkan
|
data-nama="{{ $item['peminjam'] }}" title="Aktifkan Kembali Akun">
|
||||||
</button>
|
<i class="bi bi-shield-check"></i> Aktifkan
|
||||||
@else
|
</button>
|
||||||
{{-- Jika belum dibekukan, muncul tombol SANKSI (Manual) --}}
|
@else
|
||||||
<button class="btn btn-sm btn-outline-danger btn-sanksi"
|
{{-- Jika belum dibekukan, muncul tombol SANKSI (Manual) --}}
|
||||||
data-user-id="{{ $item['user_id'] }}"
|
<button class="btn btn-sm btn-outline-danger btn-sanksi"
|
||||||
data-nama="{{ $item['peminjam'] }}" title="Berikan Sanksi">
|
data-user-id="{{ $item['user_id'] }}"
|
||||||
<i class="bi bi-slash-circle"></i> Sanksi
|
data-nama="{{ $item['peminjam'] }}" title="Berikan Sanksi">
|
||||||
</button>
|
<i class="bi bi-slash-circle"></i> Sanksi
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -146,8 +148,14 @@ class="btn btn-sm btn-success text-white" title="Tagih via WhatsApp">
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var table = $('#dendaTable').DataTable({
|
var table = $('#dendaTable').DataTable({
|
||||||
order: [
|
order: [[3, 'desc']],
|
||||||
[3, 'desc']
|
columns: [
|
||||||
|
{ title: "NO", searchable: false, orderable: false },
|
||||||
|
{ title: "NAMA" },
|
||||||
|
{ title: "BUKU TERLAMBAT" },
|
||||||
|
{ title: "STATUS" },
|
||||||
|
{ title: "DENDA" },
|
||||||
|
{ title: "AKSI", searchable: false, orderable: false }
|
||||||
],
|
],
|
||||||
|
|
||||||
columnDefs: [{
|
columnDefs: [{
|
||||||
|
|
@ -265,7 +273,7 @@ function(settings, data, dataIndex) {
|
||||||
});
|
});
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{{ route('admin.denda.sanksi ') }}",
|
url: "{{ route('admin.denda.sanksi') }}",
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
_token: '{{ csrf_token() }}',
|
_token: '{{ csrf_token() }}',
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,14 @@
|
||||||
<p class="text-muted mb-0">Daftar ini hanya menampilkan buku yang masih berstatus "Dipinjam".</p>
|
<p class="text-muted mb-0">Daftar ini hanya menampilkan buku yang masih berstatus "Dipinjam".</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<form action="#" method="GET" class="d-flex m-0 p-0 bg-white border rounded p-1" onsubmit="alert('Fitur download Excel sedang disiapkan tim Backend'); return false;">
|
<form action="{{ route('admin.peminjaman.export') }}" method="GET" class="d-flex m-0 p-0 bg-white border rounded p-1">
|
||||||
<input type="month" name="bulan_laporan" class="form-control form-control-sm border-0 me-1" required>
|
<div class="input-group input-group-sm">
|
||||||
<button type="submit" class="btn btn-sm btn-success text-nowrap">
|
<span class="input-group-text bg-transparent border-0 text-muted small">Filter Bulan:</span>
|
||||||
<i class="bi bi-file-earmark-excel-fill me-1"></i>Excel
|
<input type="date" name="bulan_laporan" class="form-control border-0" title="Pilih tanggal untuk filter bulan (Opsional)">
|
||||||
</button>
|
<button type="submit" class="btn btn-success text-nowrap">
|
||||||
|
<i class="bi bi-file-earmark-excel-fill me-1"></i>Excel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<a href="{{ route('admin.peminjaman.create') }}" class="btn btn-primary text-nowrap">
|
<a href="{{ route('admin.peminjaman.create') }}" class="btn btn-primary text-nowrap">
|
||||||
|
|
@ -166,9 +169,8 @@
|
||||||
$isGuru = ($transaksi['role'] ?? '') === 'guru';
|
$isGuru = ($transaksi['role'] ?? '') === 'guru';
|
||||||
$dendaTelat = 0;
|
$dendaTelat = 0;
|
||||||
if ($isTerlambat && !$isGuru) {
|
if ($isTerlambat && !$isGuru) {
|
||||||
if ($isTerlambat && !$isGuru) {
|
$hari = $tenggat->startOfDay()->diffInDays($now->startOfDay());
|
||||||
$hari = $tenggat->startOfDay()->diffInDays($now->startOfDay());
|
$dendaTelat = $hari * 1000;
|
||||||
$dendaTelat = $hari * 1000;
|
|
||||||
}
|
}
|
||||||
@endphp
|
@endphp
|
||||||
{{-- Data Attribute untuk JS --}}
|
{{-- Data Attribute untuk JS --}}
|
||||||
|
|
@ -233,8 +235,16 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#peminjamanTable').DataTable({
|
$('#peminjamanTable').DataTable({
|
||||||
pageLength: 10,
|
pageLength: 10,
|
||||||
order: [
|
order: [[0, 'asc']],
|
||||||
[0, 'asc']
|
columns: [
|
||||||
|
{ title: "NO" },
|
||||||
|
{ title: "ID PEMINJAMAN" },
|
||||||
|
{ title: "PEMINJAM (JABATAN/KELAS)" },
|
||||||
|
{ title: "JUDUL BUKU" },
|
||||||
|
{ title: "TGL. PINJAM" },
|
||||||
|
{ title: "TENGGAT KEMBALI" },
|
||||||
|
{ title: "STATUS" },
|
||||||
|
{ title: "AKSI" }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -382,7 +392,7 @@ function hitungTotalDenda(modal) {
|
||||||
|
|
||||||
function finishTransaction(returnsData, userId, waLink) {
|
function finishTransaction(returnsData, userId, waLink) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{{ route('admin.peminjaman.kembali ') }}",
|
url: "{{ route('admin.peminjaman.kembali') }}",
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
_token: '{{ csrf_token() }}',
|
_token: '{{ csrf_token() }}',
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="no_hp" class="form-label">No. Handphone</label>
|
<label for="no_hp" class="form-label">No. Handphone</label>
|
||||||
<input type="text" class="form-control" id="no_hp" name="no_hp"
|
<input type="text" class="form-control" id="no_hp" name="phone"
|
||||||
value="{{ old('no_hp') }}" placeholder="Contoh: 08123456789">
|
value="{{ old('phone') }}" placeholder="Contoh: 08123456789">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -171,13 +171,13 @@ function toggleFields() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@if(session('success'))
|
<?php if(session('success')): ?>
|
||||||
Toast.fire({
|
Toast.fire({
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
title: 'Berhasil',
|
title: 'Berhasil',
|
||||||
text: '{{ session("success") }}'
|
text: '<?php echo session("success"); ?>'
|
||||||
});
|
});
|
||||||
@endif
|
<?php endif; ?>
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@php $oldNomorInduk = old('nomor_induk', $pengguna->role == 'siswa' ? $pengguna->nisn : $pengguna->nip); @endphp
|
@php $oldNomorInduk = old('nomor_induk', $pengguna->nomor_induk); @endphp
|
||||||
<label for="nomor_induk" class="form-label" id="label_nomor_induk">NISN / NIP</label>
|
<label for="nomor_induk" class="form-label" id="label_nomor_induk">NISN / NIP</label>
|
||||||
<input type="number" class="form-control @error('nomor_induk') is-invalid @enderror"
|
<input type="number" class="form-control @error('nomor_induk') is-invalid @enderror"
|
||||||
id="nomor_induk" name="nomor_induk" value="{{ $oldNomorInduk }}" placeholder="Masukkan Nomor Induk">
|
id="nomor_induk" name="nomor_induk" value="{{ $oldNomorInduk }}" placeholder="Masukkan Nomor Induk">
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="no_hp" class="form-label">No. Handphone</label>
|
<label for="no_hp" class="form-label">No. Handphone</label>
|
||||||
<input type="text" class="form-control" id="no_hp" name="no_hp" value="{{ old('no_hp', $pengguna->no_hp) }}">
|
<input type="text" class="form-control" id="phone" name="phone" value="{{ old('phone', $pengguna->phone) }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -120,13 +120,13 @@ function toggleFields() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@if(session('success'))
|
<?php if(session('success')): ?>
|
||||||
Toast.fire({
|
Toast.fire({
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
title: 'Berhasil',
|
title: 'Berhasil',
|
||||||
text: '{{ session("success") }}'
|
text: '<?php echo session("success"); ?>'
|
||||||
});
|
});
|
||||||
@endif
|
<?php endif; ?>
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered align-middle" width="100%" cellspacing="0">
|
<table class="table table-bordered align-middle" cellspacing="0" style="min-width: 900px;">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th>No</th>
|
<th>No</th>
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
<td class="fw-bold">{{ $user->name }}</td>
|
<td class="fw-bold">{{ $user->name }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div>{{ $user->email }}</div>
|
<div>{{ $user->email }}</div>
|
||||||
<div class="small text-muted"><i class="bi bi-telephone me-1"></i>{{ $user->no_hp ??
|
<div class="small text-muted"><i class="bi bi-telephone me-1"></i>{{ $user->phone ??
|
||||||
'-' }}</div>
|
'-' }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="font-monospace text-primary">
|
<td class="font-monospace text-primary">
|
||||||
{{ $user->nisn ?? ($user->nip ?? '-') }}
|
{{ $user->nomor_induk ?? '-' }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if($user->role == 'siswa')
|
@if($user->role == 'siswa')
|
||||||
|
|
@ -78,7 +78,7 @@ class="btn btn-sm btn-warning" title="Edit Pengguna">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button type="button" class="btn btn-sm btn-secondary btn-reset-password"
|
<button type="button" class="btn btn-sm btn-secondary btn-reset-password"
|
||||||
data-nama="{{ $user->name }}" title="Reset Password (OTP)">
|
data-id="{{ $user->id }}" data-nama="{{ $user->name }}" title="Reset Password (OTP)">
|
||||||
<i class="bi bi-key-fill"></i>
|
<i class="bi bi-key-fill"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -295,19 +295,17 @@ class="form-delete-whitelist" data-induk="{{ $item->nomor_induk }}">
|
||||||
});
|
});
|
||||||
|
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
Toast.fire({
|
||||||
Toast.fire({
|
icon: 'success',
|
||||||
icon: 'success',
|
title: 'Berhasil',
|
||||||
title: 'Berhasil',
|
text: '{{ session("success") }}'
|
||||||
text: '{{ session("success") }}'
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
// GENERATE OTP & LINK RESET
|
// GENERATE OTP & LINK RESET
|
||||||
$(document).on('click', '.btn-reset-password', function() {
|
$(document).on('click', '.btn-reset-password', function() {
|
||||||
const nama = $(this).data('nama');
|
const nama = $(this).data('nama');
|
||||||
const otpCode = "678901";
|
const userId = $(this).data('id');
|
||||||
const linkReset = "{{ route('reset.password-request') }}";
|
const linkReset = "{{ route('reset.password-request') }}";
|
||||||
|
|
||||||
modernSwal.fire({
|
modernSwal.fire({
|
||||||
|
|
@ -322,27 +320,44 @@ class="form-delete-whitelist" data-induk="{{ $item->nomor_induk }}">
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
modernSwal.fire({
|
modernSwal.fire({
|
||||||
title: 'Generating...',
|
title: 'Generating...',
|
||||||
timer: 1000,
|
didOpen: () => Swal.showLoading(),
|
||||||
didOpen: () => Swal.showLoading()
|
allowOutsideClick: false
|
||||||
}).then(() => {
|
});
|
||||||
modernSwal.fire({
|
|
||||||
title: 'OTP Berhasil Dibuat!',
|
$.ajax({
|
||||||
html: `
|
url: "{{ route('reset.generate-otp') }}",
|
||||||
<div class="text-start bg-light p-3 rounded border">
|
method: 'POST',
|
||||||
<p class="mb-1 small text-muted">Kode OTP:</p>
|
data: {
|
||||||
<h3 class="text-primary fw-bold letter-spacing-1 mb-3">${otpCode}</h3>
|
_token: '{{ csrf_token() }}',
|
||||||
<p class="mb-1 small text-muted">Link Reset:</p>
|
user_id: userId
|
||||||
<div class="input-group">
|
},
|
||||||
<input type="text" class="form-control form-control-sm" value="${linkReset}" readonly>
|
success: function(response) {
|
||||||
|
if (response.status === 'success') {
|
||||||
|
modernSwal.fire({
|
||||||
|
title: 'OTP Berhasil Dibuat!',
|
||||||
|
html: `
|
||||||
|
<div class="text-start bg-light p-3 rounded border">
|
||||||
|
<p class="mb-1 small text-muted">Kode OTP:</p>
|
||||||
|
<h3 class="text-primary fw-bold letter-spacing-1 mb-3">${response.otp}</h3>
|
||||||
|
<p class="mb-1 small text-muted">Link Reset:</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control form-control-sm" value="${linkReset}" readonly>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="mt-3 small text-muted">
|
||||||
<div class="mt-3 small text-muted">
|
Salin Kode OTP & Link lalu kirim ke WhatsApp pengguna.
|
||||||
Silakan salin Kode OTP & Link di atas lalu kirim ke WhatsApp user.
|
</div>
|
||||||
</div>
|
`,
|
||||||
`,
|
icon: 'success',
|
||||||
icon: 'success',
|
confirmButtonText: 'Selesai'
|
||||||
confirmButtonText: 'Selesai'
|
});
|
||||||
});
|
} else {
|
||||||
|
modernSwal.fire('Gagal', response.message || 'Gagal membuat OTP.', 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
modernSwal.fire('Error', 'Terjadi kesalahan saat membuat OTP.', 'error');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
{{-- BANTUAN / LUPA PASSWORD --}}
|
{{-- BANTUAN / LUPA PASSWORD --}}
|
||||||
<div class="bg-light p-3 rounded-3 text-center border border-dashed">
|
<div class="bg-light p-3 rounded-3 text-center border border-dashed">
|
||||||
<p class="text-muted small mb-1">Mengalami kendala login?</p>
|
<p class="text-muted small mb-1">Mengalami kendala login?</p>
|
||||||
<a href="https://wa.me/6281234567890?text=Halo%20Admin,%20saya%20lupa%20kata%20sandi%20akun%20saya."
|
<a href="https://wa.me/62895618643811?text=Halo%20Admin,%20saya%20lupa%20kata%20sandi%20akun%20saya."
|
||||||
target="_blank" class="text-decoration-none fw-bold text-success d-inline-flex align-items-center">
|
target="_blank" class="text-decoration-none fw-bold text-success d-inline-flex align-items-center">
|
||||||
<i class="bi bi-whatsapp me-1"></i> Hubungi Petugas via WA
|
<i class="bi bi-whatsapp me-1"></i> Hubungi Petugas via WA
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,15 @@
|
||||||
<div id="step-otp">
|
<div id="step-otp">
|
||||||
<div class="mb-4 text-center">
|
<div class="mb-4 text-center">
|
||||||
<h4 class="fw-bold text-dark">Verifikasi Kode OTP</h4>
|
<h4 class="fw-bold text-dark">Verifikasi Kode OTP</h4>
|
||||||
<p class="text-muted small">Masukkan 6 digit kode yang diberikan Admin via WhatsApp.</p>
|
<p class="text-muted small">Masukkan email Anda dan 6 digit kode yang diberikan Admin via WhatsApp.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="formVerifyOtp" onsubmit="verifikasiOTP(event)">
|
<form id="formVerifyOtp" onsubmit="verifikasiOTP(event)">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Email Anda</label>
|
||||||
|
<input class="form-control" type="email" id="inputEmail"
|
||||||
|
placeholder="email@contoh.com" required autocomplete="email">
|
||||||
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label fw-bold">Kode OTP</label>
|
<label class="form-label fw-bold">Kode OTP</label>
|
||||||
<input class="form-control text-center fs-3 letter-spacing-2 py-2" type="text" id="inputOtp"
|
<input class="form-control text-center fs-3 letter-spacing-2 py-2" type="text" id="inputOtp"
|
||||||
|
|
@ -35,6 +40,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="formResetPassword" onsubmit="simpanPassword(event)">
|
<form id="formResetPassword" onsubmit="simpanPassword(event)">
|
||||||
|
{{-- Hidden email passed from OTP step --}}
|
||||||
|
<input type="hidden" id="verifiedEmail">
|
||||||
|
|
||||||
{{-- Password Utama --}}
|
{{-- Password Utama --}}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Kata Sandi Baru</label>
|
<label class="form-label">Kata Sandi Baru</label>
|
||||||
|
|
@ -67,60 +75,86 @@
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
<script>
|
<script>
|
||||||
// Fungsi Toggle Lihat Password
|
|
||||||
function lihatPassword(targetId, btn) {
|
function lihatPassword(targetId, btn) {
|
||||||
const input = document.getElementById(targetId);
|
const input = document.getElementById(targetId);
|
||||||
const icon = btn.querySelector('i');
|
const icon = btn.querySelector('i');
|
||||||
|
|
||||||
if (input.type === 'password') {
|
if (input.type === 'password') {
|
||||||
input.type = 'text';
|
input.type = 'text';
|
||||||
icon.classList.remove('bi-eye-slash-fill');
|
icon.classList.replace('bi-eye-slash-fill', 'bi-eye-fill');
|
||||||
icon.classList.add('bi-eye-fill');
|
|
||||||
} else {
|
} else {
|
||||||
input.type = 'password';
|
input.type = 'password';
|
||||||
icon.classList.remove('bi-eye-fill');
|
icon.classList.replace('bi-eye-fill', 'bi-eye-slash-fill');
|
||||||
icon.classList.add('bi-eye-slash-fill');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fungsi Verifikasi OTP
|
// Fungsi Verifikasi OTP (real backend call)
|
||||||
function verifikasiOTP(e) {
|
function verifikasiOTP(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const inputOtp = document.getElementById('inputOtp').value;
|
const email = document.getElementById('inputEmail').value;
|
||||||
|
const otp = document.getElementById('inputOtp').value;
|
||||||
|
|
||||||
if(inputOtp !== '678901') {
|
Swal.fire({ title: 'Memverifikasi...', didOpen: () => Swal.showLoading(), allowOutsideClick: false });
|
||||||
Swal.fire({ icon: 'error', title: 'Gagal', text: 'Kode OTP salah! Coba cek lagi WA Admin.' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Swal.fire({ title: 'Memverifikasi...', timer: 800, didOpen: () => Swal.showLoading() }).then(() => {
|
fetch('{{ route("reset.verify-otp") }}', {
|
||||||
document.getElementById('step-otp').classList.add('d-none');
|
method: 'POST',
|
||||||
document.getElementById('step-password').classList.remove('d-none');
|
headers: {
|
||||||
document.getElementById('password').focus();
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000 });
|
},
|
||||||
Toast.fire({ icon: 'success', title: 'Kode OTP Valid' });
|
body: JSON.stringify({ email, otp })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
// Pass verified email to the password step
|
||||||
|
document.getElementById('verifiedEmail').value = data.email;
|
||||||
|
document.getElementById('step-otp').classList.add('d-none');
|
||||||
|
document.getElementById('step-password').classList.remove('d-none');
|
||||||
|
document.getElementById('password').focus();
|
||||||
|
const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000 });
|
||||||
|
Toast.fire({ icon: 'success', title: 'Kode OTP Valid' });
|
||||||
|
} else {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Gagal', text: data.message || 'Kode OTP salah.' });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Error', text: 'Terjadi kesalahan. Silakan coba lagi.' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fungsi Simpan Password
|
// Fungsi Simpan Password (real backend call)
|
||||||
function simpanPassword(e) {
|
function simpanPassword(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const email = document.getElementById('verifiedEmail').value;
|
||||||
const pass = document.getElementById('password').value;
|
const pass = document.getElementById('password').value;
|
||||||
const conf = document.getElementById('password_confirmation').value;
|
const conf = document.getElementById('password_confirmation').value;
|
||||||
|
|
||||||
if(pass.length < 8) {
|
if (pass.length < 8) { Swal.fire({ icon: 'warning', text: 'Kata sandi minimal 8 karakter!' }); return; }
|
||||||
Swal.fire({ icon: 'warning', text: 'Kata sandi minimal 8 karakter!' }); return;
|
if (pass !== conf) { Swal.fire({ icon: 'warning', text: 'Konfirmasi kata sandi tidak cocok!' }); return; }
|
||||||
}
|
|
||||||
if(pass !== conf) {
|
|
||||||
Swal.fire({ icon: 'warning', text: 'Konfirmasi kata sandi tidak cocok!' }); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Swal.fire({ title: 'Menyimpan...', timer: 1500, didOpen: () => Swal.showLoading() }).then(() => {
|
Swal.fire({ title: 'Menyimpan...', didOpen: () => Swal.showLoading(), allowOutsideClick: false });
|
||||||
Swal.fire({
|
|
||||||
icon: 'success', title: 'Berhasil!', text: 'Kata sandi Anda telah diperbarui. Silakan login.',
|
fetch('{{ route("reset.update-password") }}', {
|
||||||
confirmButtonText: 'Ke Halaman Login', allowOutsideClick: false
|
method: 'POST',
|
||||||
}).then(() => { window.location.href = "/login"; });
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email, password: pass, password_confirmation: conf })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success', title: 'Berhasil!', text: 'Kata sandi Anda telah diperbarui. Silakan login.',
|
||||||
|
confirmButtonText: 'Ke Halaman Login', allowOutsideClick: false
|
||||||
|
}).then(() => { window.location.href = '/login'; });
|
||||||
|
} else {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Gagal', text: data.message || 'Gagal menyimpan password.' });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Error', text: 'Terjadi kesalahan. Silakan coba lagi.' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -88,50 +88,36 @@ class="btn btn-light text-start py-3 d-flex align-items-center">
|
||||||
<div class="col-lg-8 d-flex flex-column">
|
<div class="col-lg-8 d-flex flex-column">
|
||||||
<div class="card border-0 mb-3 mb-md-4">
|
<div class="card border-0 mb-3 mb-md-4">
|
||||||
<div class="card-body p-3 p-md-4">
|
<div class="card-body p-3 p-md-4">
|
||||||
<div class="d-flex flex-column flex-sm-row align-items-center text-center text-sm-start">
|
<div class="d-flex flex-column flex-sm-row align-items-center text-center text-sm-start mb-4">
|
||||||
<img src="https://ui-avatars.com/api/?name={{ urlencode($user->name) }}&background=198754&color=fff&size=80&rounded=true"
|
<img src="https://ui-avatars.com/api/?name={{ urlencode($user->name) }}&background=198754&color=fff&size=80&rounded=true"
|
||||||
alt="Foto Profil" class="rounded-circle profile-avatar-lg mb-3 mb-sm-0">
|
alt="Foto Profil" class="rounded-circle profile-avatar-lg mb-3 mb-sm-0">
|
||||||
<div class="ms-sm-4 mb-3 mb-sm-0">
|
<div class="ms-sm-4 mb-3 mb-sm-0">
|
||||||
<h4 class="fw-bold mb-1">{{ $user->name }}</h4>
|
<h4 class="fw-bold mb-1">{{ $user->name }}</h4>
|
||||||
<span class="badge rounded-pill bg-success-subtle text-success-emphasis">{{
|
<span class="badge rounded-pill bg-success-subtle text-success-emphasis">
|
||||||
Str::title($user->role) }}</span>
|
{{ Str::title($user->role) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="ms-sm-auto">
|
<div class="ms-sm-auto">
|
||||||
<a href="{{ route('profile.edit') }}"
|
<a href="{{ route('profile.edit') }}"
|
||||||
class="btn btn-outline-primary rounded-pill ms-sm-auto w-100 w-sm-auto">
|
class="btn btn-outline-primary rounded-pill w-100 w-sm-auto">
|
||||||
<i class="bi bi-pencil-square me-2"></i>Edit Profil
|
<i class="bi bi-pencil-square me-2"></i>Edit Profil
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-3 my-md-4">
|
|
||||||
<h5 class="fw-bold mb-3 px-4">Informasi Personal</h5>
|
|
||||||
<div class="row g-3 px-4 pb-4">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<small class="text-muted d-block mb-1">NIP / NUPTK</small>
|
|
||||||
<p class="fw-semibold mb-0">{{ $user->nuptk ?? ($user->nomor_induk ?? 'N/A') }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<small class="text-muted d-block mb-1">Email</small>
|
|
||||||
<p class="fw-semibold mb-0 text-break">{{ $user->email }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<small class="text-muted d-block mb-1">Nomor HP</small>
|
|
||||||
<p class="fw-semibold mb-0">{{ $user->phone ?? 'N/A' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="my-3 my-md-4">
|
<hr class="my-4">
|
||||||
|
|
||||||
<h5 class="fw-bold mb-3">Informasi Personal</h5>
|
<h5 class="fw-bold mb-3">Informasi Personal</h5>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6 col-md-4">
|
||||||
<small class="text-muted d-block mb-1">NIP/NIK</small>
|
<small class="text-muted d-block mb-1">NIP / NIK / NUPTK</small>
|
||||||
<p class="fw-semibold mb-0">{{ $user->nip ?? $user->nisn ?? '-' }}</p>
|
<p class="fw-semibold mb-0">{{ $user->nomor_induk ?? '-' }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6 col-md-4">
|
||||||
<small class="text-muted d-block mb-1">Email</small>
|
<small class="text-muted d-block mb-1">Email</small>
|
||||||
<p class="fw-semibold mb-0 text-break">{{ $user->email }}</p>
|
<p class="fw-semibold mb-0 text-break">{{ $user->email }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6 col-md-4">
|
||||||
<small class="text-muted d-block mb-1">Nomor HP</small>
|
<small class="text-muted d-block mb-1">Nomor HP</small>
|
||||||
<p class="fw-semibold mb-0">{{ $user->no_hp ?? '-' }}</p>
|
<p class="fw-semibold mb-0">{{ $user->no_hp ?? '-' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -195,39 +181,42 @@ class="list-group-item px-0 py-3 d-flex justify-content-between align-items-cent
|
||||||
<div class="col-lg-8 d-flex flex-column">
|
<div class="col-lg-8 d-flex flex-column">
|
||||||
<div class="card border-0 mb-3 mb-md-4">
|
<div class="card border-0 mb-3 mb-md-4">
|
||||||
<div class="card-body p-3 p-md-4">
|
<div class="card-body p-3 p-md-4">
|
||||||
<div class="d-flex flex-column flex-sm-row align-items-center text-center text-sm-start">
|
<div class="d-flex flex-column flex-sm-row align-items-center text-center text-sm-start mb-4">
|
||||||
<img src="https://ui-avatars.com/api/?name={{ urlencode($user->name) }}&background=435ebe&color=fff&size=80&rounded=true"
|
<img src="https://ui-avatars.com/api/?name={{ urlencode($user->name) }}&background=435ebe&color=fff&size=80&rounded=true"
|
||||||
alt="Foto Profil" class="rounded-circle profile-avatar-lg mb-3 mb-sm-0">
|
alt="Foto Profil" class="rounded-circle profile-avatar-lg mb-3 mb-sm-0">
|
||||||
<div class="ms-sm-4 mb-3 mb-sm-0">
|
<div class="ms-sm-4 mb-3 mb-sm-0">
|
||||||
<h4 class="fw-bold mb-1">{{ $user->name }}</h4>
|
<h4 class="fw-bold mb-1">{{ $user->name }}</h4>
|
||||||
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis">{{
|
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis">
|
||||||
Str::title($user->role) }}</span>
|
{{ Str::title($user->role) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="ms-sm-auto">
|
<div class="ms-sm-auto">
|
||||||
<a href="{{ route('profile.edit') }}"
|
<a href="{{ route('profile.edit') }}"
|
||||||
class="btn btn-outline-primary rounded-pill ms-md-auto">
|
class="btn btn-outline-primary rounded-pill w-100 w-sm-auto">
|
||||||
<i class="bi bi-pencil-square me-2"></i>Edit Profil
|
<i class="bi bi-pencil-square me-2"></i>Edit Profil
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-3 my-md-4">
|
</div>
|
||||||
<h5 class="fw-bold mb-3">Informasi Personal</h5>
|
|
||||||
<div class="row g-3">
|
<hr class="my-4">
|
||||||
<div class="col-sm-6">
|
|
||||||
<small class="text-muted d-block mb-1">NISN</small>
|
<h5 class="fw-bold mb-3">Informasi Personal</h5>
|
||||||
<p class="fw-semibold mb-0">{{ $user->nomor_induk ?? 'N/A' }}</p>
|
<div class="row g-3">
|
||||||
</div>
|
<div class="col-sm-6 col-md-3">
|
||||||
<div class="col-sm-6">
|
<small class="text-muted d-block mb-1">NISN</small>
|
||||||
<small class="text-muted d-block mb-1">Email</small>
|
<p class="fw-semibold mb-0">{{ $user->nomor_induk ?? '-' }}</p>
|
||||||
<p class="fw-semibold mb-0 text-break">{{ $user->email }}</p>
|
</div>
|
||||||
</div>
|
<div class="col-sm-6 col-md-3">
|
||||||
<div class="col-sm-6">
|
<small class="text-muted d-block mb-1">Email</small>
|
||||||
<small class="text-muted d-block mb-1">Nomor HP</small>
|
<p class="fw-semibold mb-0 text-break">{{ $user->email }}</p>
|
||||||
<p class="fw-semibold mb-0">{{ $user->phone ?? 'N/A' }}</p>
|
</div>
|
||||||
</div>
|
<div class="col-sm-6 col-md-3">
|
||||||
<div class="col-sm-6">
|
<small class="text-muted d-block mb-1">Nomor HP</small>
|
||||||
<small class="text-muted d-block mb-1">Kelas</small>
|
<p class="fw-semibold mb-0">{{ $user->no_hp ?? '-' }}</p>
|
||||||
<p class="fw-semibold mb-0">{{ $user->kelas ?? 'N/A' }}</p>
|
</div>
|
||||||
</div>
|
<div class="col-sm-6 col-md-3">
|
||||||
|
<small class="text-muted d-block mb-1">Kelas</small>
|
||||||
|
<p class="fw-semibold mb-0">{{ $user->kelas ?? '-' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
// Auth Controllers
|
// Auth Controllers
|
||||||
use App\Http\Controllers\Auth\AdminLoginController;
|
use App\Http\Controllers\Auth\AdminLoginController;
|
||||||
|
use App\Http\Controllers\Auth\ResetPasswordController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
@ -87,6 +88,8 @@
|
||||||
Route::get('/buku/tambah', [AdminBookController::class, 'create'])->name('buku.create');
|
Route::get('/buku/tambah', [AdminBookController::class, 'create'])->name('buku.create');
|
||||||
Route::post('/buku', [AdminBookController::class, 'store'])->name('buku.store');
|
Route::post('/buku', [AdminBookController::class, 'store'])->name('buku.store');
|
||||||
Route::get('/buku/{id}/edit', [AdminBookController::class, 'edit'])->name('buku.edit');
|
Route::get('/buku/{id}/edit', [AdminBookController::class, 'edit'])->name('buku.edit');
|
||||||
|
Route::post('/buku/arsip', [AdminBookController::class, 'arsip'])->name('buku.arsip');
|
||||||
|
Route::post('/buku/pulihkan', [AdminBookController::class, 'pulihkan'])->name('buku.pulihkan');
|
||||||
|
|
||||||
Route::get('/pengguna', [AdminUserController::class, 'index'])->name('pengguna.index');
|
Route::get('/pengguna', [AdminUserController::class, 'index'])->name('pengguna.index');
|
||||||
Route::get('/pengguna/tambah', [AdminUserController::class, 'create'])->name('pengguna.create');
|
Route::get('/pengguna/tambah', [AdminUserController::class, 'create'])->name('pengguna.create');
|
||||||
|
|
@ -115,7 +118,9 @@
|
||||||
|
|
||||||
Route::get('/peminjaman', [AdminPeminjamanController::class, 'index'])->name('peminjaman.index');
|
Route::get('/peminjaman', [AdminPeminjamanController::class, 'index'])->name('peminjaman.index');
|
||||||
Route::get('/peminjaman/tambah', [AdminPeminjamanController::class, 'create'])->name('peminjaman.create');
|
Route::get('/peminjaman/tambah', [AdminPeminjamanController::class, 'create'])->name('peminjaman.create');
|
||||||
|
Route::post('/peminjaman', [AdminPeminjamanController::class, 'store'])->name('peminjaman.store');
|
||||||
|
Route::post('/peminjaman/kembali', [AdminPeminjamanController::class, 'kembalikan'])->name('peminjaman.kembali');
|
||||||
|
Route::get('/peminjaman/export', [AdminPeminjamanController::class, 'export'])->name('peminjaman.export');
|
||||||
Route::get('/denda', [AdminPeminjamanController::class, 'dendaIndex'])->name('denda.index');
|
Route::get('/denda', [AdminPeminjamanController::class, 'dendaIndex'])->name('denda.index');
|
||||||
Route::post('/denda/sanksi', [AdminPeminjamanController::class, 'berikanSanksi'])->name('denda.sanksi');
|
Route::post('/denda/sanksi', [AdminPeminjamanController::class, 'berikanSanksi'])->name('denda.sanksi');
|
||||||
|
|
||||||
|
|
@ -134,4 +139,9 @@
|
||||||
return view('auth.reset-password-request');
|
return view('auth.reset-password-request');
|
||||||
})->name('reset.password-request');
|
})->name('reset.password-request');
|
||||||
|
|
||||||
|
// OTP-based password reset API endpoints
|
||||||
|
Route::post('/reset-password/generate-otp', [ResetPasswordController::class, 'generateOtp'])->name('reset.generate-otp')->middleware('auth');
|
||||||
|
Route::post('/reset-password/verify-otp', [ResetPasswordController::class, 'verifyOtp'])->name('reset.verify-otp');
|
||||||
|
Route::post('/reset-password/update', [ResetPasswordController::class, 'updatePassword'])->name('reset.update-password');
|
||||||
|
|
||||||
require __DIR__ . '/auth.php';
|
require __DIR__ . '/auth.php';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue