fix: bug that happen on the list
This commit is contained in:
parent
22d0c2f645
commit
1a356692dd
|
|
@ -31,6 +31,7 @@ public function index(Request $request)
|
|||
'tanggal_pinjam' => $firstLoan->borrowed_at,
|
||||
'tenggat_kembali' => $firstLoan->due_at,
|
||||
'status' => $firstLoan->status,
|
||||
'role' => $user->role,
|
||||
'books' => $userLoans->map(fn($l) => [
|
||||
'id' => $l->book->id,
|
||||
'judul' => $l->book->judul,
|
||||
|
|
@ -148,26 +149,36 @@ public function dendaIndex()
|
|||
|
||||
$tenggat = Carbon::parse($firstLoan->due_at);
|
||||
$hariTelat = $now->greaterThan($tenggat) ? (int) $tenggat->diffInDays($now) : 0;
|
||||
$totalDenda = $hariTelat * 1000;
|
||||
|
||||
$isGuru = $user->role === 'guru';
|
||||
$totalDenda = $isGuru ? 0 : ($hariTelat * 1000);
|
||||
|
||||
// Link WA
|
||||
$hp = $user->phone ?? '';
|
||||
$waLink = '#';
|
||||
if ($hp) {
|
||||
if (substr($hp, 0, 1) == '0') $hp = '62' . substr($hp, 1);
|
||||
$pesan = $hariTelat > 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.";
|
||||
|
||||
if ($isGuru && $hariTelat > 0) {
|
||||
$pesan = "Halo Bapak/Ibu {$user->nama_lengkap}, anda terlambat pengembalian buku selama {$hariTelat} hari. Mohon segera dikembalikan ke perpustakaan. Terima kasih.";
|
||||
} else {
|
||||
$pesan = $hariTelat > 0
|
||||
? "Halo {$user->nama_lengkap}, anda terlambat pengembalian buku. Total Denda: Rp " . number_format($totalDenda, 0, ',', '.')
|
||||
: "Halo {$user->nama_lengkap}, akun anda sedang dinonaktifkan sementara. Mohon hubungi petugas.";
|
||||
}
|
||||
|
||||
$waLink = "https://wa.me/{$hp}?text=" . urlencode($pesan);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $firstLoan->id,
|
||||
'user_id' => $userId,
|
||||
'peminjam' => $user->nama_lengkap,
|
||||
'nomor_hp' => $user->phone ?? '-',
|
||||
'kelas' => $user->kelas ?? 'Guru',
|
||||
'hari_terlambat' => $hariTelat,
|
||||
'total_denda' => $totalDenda,
|
||||
'is_guru' => $isGuru,
|
||||
'wa_link' => $waLink,
|
||||
'is_banned' => $user->is_banned,
|
||||
'tenggat_kembali' => $firstLoan->due_at,
|
||||
|
|
@ -189,7 +200,61 @@ public function dendaIndex()
|
|||
|
||||
public function berikanSanksi(Request $request)
|
||||
{
|
||||
// Actually implement banning logic here if needed
|
||||
return response()->json(['status' => 'success']);
|
||||
$validated = $request->validate([
|
||||
'user_id' => 'required|exists:users,id',
|
||||
'action' => 'required|in:ban,unban',
|
||||
]);
|
||||
|
||||
$user = User::findOrFail($validated['user_id']);
|
||||
$user->is_banned = ($validated['action'] === 'ban');
|
||||
$user->save();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => $user->is_banned ? "Akun {$user->nama_lengkap} berhasil dibekukan." : "Akun {$user->nama_lengkap} telah diaktifkan kembali."
|
||||
]);
|
||||
}
|
||||
|
||||
public function kembalikan(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'user_id' => 'required|exists:users,id',
|
||||
'returns' => 'required|array',
|
||||
'returns.*.book_id' => 'required|exists:books,id',
|
||||
'returns.*.condition' => 'required|string',
|
||||
'returns.*.fine_damage' => 'required|integer',
|
||||
'returns.*.fine_overdue' => 'required|integer',
|
||||
'returns.*.notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
\DB::beginTransaction();
|
||||
try {
|
||||
foreach ($validated['returns'] as $item) {
|
||||
$loan = Loan::where('user_id', $validated['user_id'])
|
||||
->where('book_id', $item['book_id'])
|
||||
->whereIn('status', ['Dipinjam', 'Terlambat'])
|
||||
->first();
|
||||
|
||||
if ($loan) {
|
||||
$loan->update([
|
||||
'status' => 'Dikembalikan',
|
||||
'returned_at' => now(),
|
||||
'condition' => $item['condition'],
|
||||
'fine_damage' => $item['fine_damage'],
|
||||
'fine_overdue' => $item['fine_overdue'],
|
||||
'return_notes' => $item['notes'],
|
||||
]);
|
||||
|
||||
// Update book status
|
||||
$loan->book->update(['status' => 'Tersedia']);
|
||||
}
|
||||
}
|
||||
|
||||
\DB::commit();
|
||||
return response()->json(['status' => 'success', 'message' => 'Buku berhasil dikembalikan.']);
|
||||
} catch (\Exception $e) {
|
||||
\DB::rollBack();
|
||||
return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,10 +21,13 @@ public function index(Request $request)
|
|||
|
||||
$semuaBuku = $query->latest()->get();
|
||||
|
||||
// Memisahkan buku menjadi dua koleksi: online dan offline
|
||||
[$bukuOnline, $bukuOffline] = $semuaBuku->partition(function ($buku) {
|
||||
$tipe = $buku->tipe_akses;
|
||||
return in_array('online', $tipe ?? []);
|
||||
// Memisahkan buku menjadi dua koleksi: online dan offline secara independen
|
||||
$bukuOnline = $semuaBuku->filter(function ($buku) {
|
||||
return in_array('online', $buku->tipe_akses ?? []);
|
||||
});
|
||||
|
||||
$bukuOffline = $semuaBuku->filter(function ($buku) {
|
||||
return in_array('offline', $buku->tipe_akses ?? []);
|
||||
});
|
||||
|
||||
return view('admin.buku.index', [
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public function index()
|
|||
['label' => 'Total Buku', 'value' => $allBooks, 'icon' => 'bi-journal-bookmark-fill', 'color' => 'primary'],
|
||||
['label' => 'Total Anggota', 'value' => $allUsers, 'icon' => 'bi-people-fill', 'color' => 'success'],
|
||||
['label' => 'Buku Dipinjam', 'value' => $bukuDipinjam, 'icon' => 'bi-arrow-up-right-circle-fill', 'color' => 'warning'],
|
||||
['label' => 'Denda Menunggu', 'value' => Loan::where('status', 'Terlambat')->count(), 'icon' => 'bi-cash-coin', 'color' => 'danger'],
|
||||
['label' => 'Total Denda', 'value' => 'Rp ' . number_format(Loan::where('status', 'Terlambat')->sum('fine_overdue'), 0, ',', '.'), 'icon' => 'bi-cash-coin', 'color' => 'danger'],
|
||||
];
|
||||
|
||||
// Monthly stats (last 7 months)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
|
@ -134,15 +135,15 @@ public function streamPdf(int $id): BinaryFileResponse|Response
|
|||
abort(403, 'Akses Ditolak.');
|
||||
}
|
||||
$book = Book::findOrFail($id);
|
||||
$filePath = 'books/' . ($book->file_pdf ?? 'sample.pdf');
|
||||
$absolutePath = storage_path('app/' . $filePath);
|
||||
|
||||
// For demo purposes, if file doesn't exist, we might want to use a placeholder or handle it gracefully
|
||||
if (!file_exists($absolutePath)) {
|
||||
// Create a dummy file for testing if it doesn't exist? (Optional, maybe just abort)
|
||||
$fileName = $book->file_pdf ?? 'sample.pdf';
|
||||
$filePath = 'books/' . $fileName;
|
||||
|
||||
if (!Storage::disk('local')->exists($filePath)) {
|
||||
abort(404, 'File PDF tidak ditemukan di server.');
|
||||
}
|
||||
|
||||
$absolutePath = Storage::disk('local')->path($filePath);
|
||||
|
||||
return response()->file($absolutePath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,10 +57,28 @@ public function index()
|
|||
['type' => 'info', 'icon' => 'bi-bell-fill', 'title' => 'Selamat Datang', 'content' => 'Selamat datang di perpustakaan digital SMKN 1.', 'badge' => 'Baru']
|
||||
]);
|
||||
|
||||
$progressMembaca = ['selesai' => 70, 'sisa' => 30]; // Still dummy as we don't track pages yet
|
||||
// Dynamic reading progress based on returned vs total books
|
||||
$totalUserLoans = Loan::where('user_id', $user->id)->count();
|
||||
$returnedLoans = Loan::where('user_id', $user->id)->where('status', 'Dikembalikan')->count();
|
||||
$progressSelesai = $totalUserLoans > 0 ? round(($returnedLoans / $totalUserLoans) * 100) : 0;
|
||||
|
||||
$progressMembaca = ['selesai' => $progressSelesai, 'sisa' => 100 - $progressSelesai];
|
||||
|
||||
// Dynamic monthly stats for the last 7 months
|
||||
$labels = [];
|
||||
$data = [];
|
||||
for ($i = 6; $i >= 0; $i--) {
|
||||
$month = Carbon::now()->subMonths($i);
|
||||
$labels[] = $month->translatedFormat('M');
|
||||
$data[] = Loan::where('user_id', $user->id)
|
||||
->whereMonth('borrowed_at', $month->month)
|
||||
->whereYear('borrowed_at', $month->year)
|
||||
->count();
|
||||
}
|
||||
|
||||
$statistikBulanan = [
|
||||
'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul'],
|
||||
'data' => [10, 15, 8, 20, 18, 25, 22],
|
||||
'labels' => $labels,
|
||||
'data' => $data,
|
||||
];
|
||||
|
||||
// Online books (books with 'online' in tipe_akses)
|
||||
|
|
|
|||
|
|
@ -112,13 +112,15 @@ public function edit(Request $request): View
|
|||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
$user = $request->user();
|
||||
$user->fill($request->validated());
|
||||
$user->name = $user->nama_lengkap; // Sync for compatibility
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
if ($user->isDirty('email')) {
|
||||
$user->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
$user->save();
|
||||
|
||||
return Redirect::route('profile.edit')->with('status', 'profile-updated');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class ProfileUpdateRequest extends FormRequest
|
|||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'nama_lengkap' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
|
|
@ -25,6 +25,7 @@ public function rules(): array
|
|||
'max:255',
|
||||
Rule::unique(User::class)->ignore($this->user()->id),
|
||||
],
|
||||
'phone' => ['required', 'string', 'max:20'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class Book extends Model
|
|||
{
|
||||
protected $fillable = [
|
||||
'judul', 'penulis', 'cover', 'kode_buku',
|
||||
'category_id', 'tahun', 'status', 'is_new', 'tipe_akses'
|
||||
'category_id', 'tahun', 'status', 'is_new', 'tipe_akses', 'file_pdf'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ class Loan extends Model
|
|||
{
|
||||
protected $fillable = [
|
||||
'user_id', 'book_id', 'loan_code',
|
||||
'borrowed_at', 'due_at', 'returned_at', 'status'
|
||||
'borrowed_at', 'due_at', 'returned_at', 'status',
|
||||
'fine_overdue', 'fine_damage', 'condition', 'return_notes'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
|
|
|||
|
|
@ -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->string('file_pdf')->nullable()->after('tipe_akses');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('books', function (Blueprint $table) {
|
||||
$table->dropColumn('file_pdf');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?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('loans', function (Blueprint $table) {
|
||||
$table->integer('fine_overdue')->default(0)->after('status');
|
||||
$table->integer('fine_damage')->default(0)->after('fine_overdue');
|
||||
$table->string('condition')->nullable()->after('fine_damage');
|
||||
$table->text('return_notes')->nullable()->after('condition');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('loans', function (Blueprint $table) {
|
||||
$table->dropColumn(['fine_overdue', 'fine_damage', 'condition', 'return_notes']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
<div class="col-md-4 mb-3">
|
||||
<label for="tahun" class="form-label">Tahun Terbit</label>
|
||||
<input type="number" name="tahun" class="form-control" id="tahun"
|
||||
placeholder="Contoh: 2024" required>
|
||||
placeholder="Contoh: 2024" min="0" required>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="kode_buku" class="form-label">Kode Buku</label>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class="badge bg-warning-subtle text-warning-emphasis">Dipinjam</span>
|
|||
data-judul="{{ $buku['judul'] }}"
|
||||
data-kode_buku="{{ $buku['kode_buku'] }}"
|
||||
data-penulis="{{ $buku['penulis'] }}"
|
||||
data-kategori="{{ $buku['kategori'] }}"
|
||||
data-kategori="{{ $buku->category->name ?? '-' }}"
|
||||
data-tahun="{{ $buku['tahun'] }}" data-status="{{ $buku['status'] }}">
|
||||
<i class="bi bi-eye-fill"></i> Detail
|
||||
</button>
|
||||
|
|
@ -109,7 +109,7 @@ class="badge bg-info-subtle text-info-emphasis">{{ $buku['file_pdf'] ?? 'N/A' }}
|
|||
data-cover="{{ asset($buku['cover']) }}"
|
||||
data-judul="{{ $buku['judul'] }}"
|
||||
data-penulis="{{ $buku['penulis'] }}"
|
||||
data-kategori="{{ $buku['kategori'] }}"
|
||||
data-kategori="{{ $buku->category->name ?? '-' }}"
|
||||
data-tahun="{{ $buku['tahun'] }}" data-status="Dapat Dibaca Online">
|
||||
<i class="bi bi-eye-fill"></i> Detail
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -82,9 +82,14 @@ class="badge bg-danger-subtle text-danger border border-danger-subtle rounded-pi
|
|||
</td>
|
||||
|
||||
<td data-order="{{ $item['total_denda'] }}">
|
||||
<div class="fw-bold text-danger">Rp
|
||||
{{ number_format($item['total_denda'], 0, ',', '.') }}</div>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Rp 1.000/hari</small>
|
||||
@if($item['is_guru'])
|
||||
<div class="fw-bold text-success">Bebas Denda</div>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Kebijakan Guru</small>
|
||||
@else
|
||||
<div class="fw-bold text-danger">Rp
|
||||
{{ number_format($item['total_denda'], 0, ',', '.') }}</div>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Rp 1.000/hari</small>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex gap-2">
|
||||
|
|
@ -98,12 +103,14 @@ class="btn btn-sm btn-success text-white" title="Tagih via WhatsApp">
|
|||
@if ($item['is_banned'])
|
||||
{{-- Jika sudah dibekukan (Otomatis/Manual), muncul tombol AKTIFKAN --}}
|
||||
<button class="btn btn-sm btn-outline-success btn-aktifkan"
|
||||
data-user-id="{{ $item['user_id'] }}"
|
||||
data-nama="{{ $item['peminjam'] }}" title="Aktifkan Kembali Akun">
|
||||
<i class="bi bi-shield-check"></i> Aktifkan
|
||||
</button>
|
||||
@else
|
||||
{{-- Jika belum dibekukan, muncul tombol SANKSI (Manual) --}}
|
||||
<button class="btn btn-sm btn-outline-danger btn-sanksi"
|
||||
data-user-id="{{ $item['user_id'] }}"
|
||||
data-nama="{{ $item['peminjam'] }}" title="Berikan Sanksi">
|
||||
<i class="bi bi-slash-circle"></i> Sanksi
|
||||
</button>
|
||||
|
|
@ -183,11 +190,12 @@ function(settings, data, dataIndex) {
|
|||
});
|
||||
});
|
||||
|
||||
// --- LOGIC TOMBOL SANKSI ---
|
||||
$(document).on('click', '.btn-sanksi', function() {
|
||||
const nama = $(this).data('nama');
|
||||
const userId = $(this).data('user-id');
|
||||
|
||||
modernSwal.fire({
|
||||
title: 'Nonaktifkan Guru?',
|
||||
title: 'Bekukan Akun?',
|
||||
text: `Apakah Anda yakin ingin memberikan sanksi pembekuan akun kepada ${nama}?`,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
|
|
@ -198,23 +206,40 @@ function(settings, data, dataIndex) {
|
|||
if (result.isConfirmed) {
|
||||
modernSwal.fire({
|
||||
title: 'Memproses...',
|
||||
timer: 1000,
|
||||
didOpen: () => Swal.showLoading()
|
||||
})
|
||||
.then(() => {
|
||||
Toast.fire({
|
||||
icon: 'success',
|
||||
title: 'Sanksi Diterapkan',
|
||||
text: `Akun ${nama} berhasil dibekukan.`
|
||||
});
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route('admin.denda.sanksi') }}',
|
||||
method: 'POST',
|
||||
data: {
|
||||
_token: '{{ csrf_token() }}',
|
||||
user_id: userId,
|
||||
action: 'ban'
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
Toast.fire({
|
||||
icon: 'success',
|
||||
title: 'Sanksi Diterapkan',
|
||||
text: response.message
|
||||
});
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} else {
|
||||
modernSwal.fire('Gagal', response.message, 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
modernSwal.fire('Gagal', 'Terjadi kesalahan saat memproses data.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Logic Tombol Aktifkan (Unban)
|
||||
$(document).on('click', '.btn-aktifkan', function() {
|
||||
const nama = $(this).data('nama');
|
||||
const userId = $(this).data('user-id');
|
||||
|
||||
modernSwal.fire({
|
||||
title: 'Aktifkan Akun?',
|
||||
|
|
@ -226,20 +251,35 @@ function(settings, data, dataIndex) {
|
|||
cancelButtonText: 'Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// Simulasi Loading & Sukses
|
||||
modernSwal.fire({
|
||||
title: 'Memproses...',
|
||||
timer: 1000,
|
||||
didOpen: () => Swal.showLoading()
|
||||
})
|
||||
.then(() => {
|
||||
Toast.fire({
|
||||
icon: 'success',
|
||||
title: 'Akun Diaktifkan',
|
||||
text: `Status sanksi pada ${nama} telah dicabut.`
|
||||
});
|
||||
$(this).closest('td').html('<span class="badge bg-success">Aktif</span>');
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route('admin.denda.sanksi') }}',
|
||||
method: 'POST',
|
||||
data: {
|
||||
_token: '{{ csrf_token() }}',
|
||||
user_id: userId,
|
||||
action: 'unban'
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
Toast.fire({
|
||||
icon: 'success',
|
||||
title: 'Akun Diaktifkan',
|
||||
text: response.message
|
||||
});
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} else {
|
||||
modernSwal.fire('Gagal', response.message, 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
modernSwal.fire('Gagal', 'Terjadi kesalahan saat memproses data.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class="badge bg-primary-subtle text-primary fw-bold">{{ $transaksi['id_peminjama
|
|||
<h6 class="fw-bold mb-3">Daftar Buku yang Dikembalikan:</h6>
|
||||
|
||||
@foreach ($transaksi['books'] as $buku)
|
||||
<div class="card mb-3 border bg-light">
|
||||
<div class="card mb-3 border bg-light book-return-item" data-book-id="{{ $buku['id'] }}">
|
||||
<div class="card-body p-3">
|
||||
<h6 class="fw-bold text-primary mb-2">{{ $buku['judul'] }}</h6>
|
||||
<div class="row">
|
||||
|
|
@ -159,17 +159,25 @@ class="form-control form-control-sm denda-rusak-input"
|
|||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<span>Denda Keterlambatan ({{ $statusText }})</span>
|
||||
@php
|
||||
$isGuru = ($transaksi['role'] ?? '') === 'guru';
|
||||
$dendaTelat = 0;
|
||||
if ($isTerlambat) {
|
||||
if ($isTerlambat && !$isGuru) {
|
||||
$hari = $tenggat->startOfDay()->diffInDays($now->startOfDay());
|
||||
$dendaTelat = $hari * 1000;
|
||||
}
|
||||
@endphp
|
||||
{{-- Data Attribute untuk JS --}}
|
||||
<strong class="text-danger denda-keterlambatan-display"
|
||||
data-denda-keterlambatan="{{ $dendaTelat }}">
|
||||
Rp {{ number_format($dendaTelat, 0, ',', '.') }}
|
||||
</strong>
|
||||
@if($isGuru && $isTerlambat)
|
||||
<strong class="text-success denda-keterlambatan-display"
|
||||
data-denda-keterlambatan="0">
|
||||
Bebas Denda
|
||||
</strong>
|
||||
@else
|
||||
<strong class="text-danger denda-keterlambatan-display"
|
||||
data-denda-keterlambatan="{{ $dendaTelat }}">
|
||||
Rp {{ number_format($dendaTelat, 0, ',', '.') }}
|
||||
</strong>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -203,7 +211,8 @@ class="d-flex justify-content-between align-items-center mb-3 bg-danger bg-opaci
|
|||
data-bs-dismiss="modal">Batal</button>
|
||||
<button type="button" class="btn btn-primary btn-konfirmasi-kembali"
|
||||
data-nama-peminjam="{{ $transaksi['peminjam'] }}"
|
||||
data-nomor-hp="{{ $transaksi['nomor_hp'] }}">
|
||||
data-nomor-hp="{{ $transaksi['nomor_hp'] }}"
|
||||
data-user-id="{{ $transaksi['user_id'] }}">
|
||||
Konfirmasi & Selesai
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -291,9 +300,11 @@ function hitungTotalDenda(modal) {
|
|||
|
||||
$(document).on('click', '.btn-konfirmasi-kembali', function() {
|
||||
const nama = $(this).data('nama-peminjam');
|
||||
const hp = $(this).data('nomor-hp');
|
||||
const modalEl = $(this).closest('.modal');
|
||||
const modalInstance = bootstrap.Modal.getInstance(modalEl[0]);
|
||||
const isEmailChecked = modalEl.find('.email-toggle').is(':checked');
|
||||
const isWaChecked = modalEl.find('input[role="switch"][id^="waStrukToggle"]').is(':checked');
|
||||
|
||||
modalInstance.hide();
|
||||
|
||||
|
|
@ -313,10 +324,58 @@ function hitungTotalDenda(modal) {
|
|||
timerProgressBar: true,
|
||||
didOpen: () => Swal.showLoading()
|
||||
}).then(() => {
|
||||
// Loading Kirim Email (Jika dicentang)
|
||||
// Prepare Request Data
|
||||
const returnsData = [];
|
||||
const booksList = [];
|
||||
const dendaOverdueTotal = parseInt(modalEl.find('.denda-keterlambatan-display').data('denda-keterlambatan'));
|
||||
const dendaOverduePerBook = dendaOverdueTotal / modalEl.find('.book-return-item').length;
|
||||
|
||||
let totalDendaRusak = 0;
|
||||
modalEl.find('.book-return-item').each(function() {
|
||||
const bookId = $(this).data('book-id');
|
||||
const judul = $(this).find('h6').text();
|
||||
const condition = $(this).find('.radio-kondisi:checked').val();
|
||||
const fineDamage = parseInt($(this).find('.denda-rusak-input').val()) || 0;
|
||||
const notes = $(this).find('textarea').val();
|
||||
|
||||
totalDendaRusak += fineDamage;
|
||||
booksList.push(judul);
|
||||
returnsData.push({
|
||||
book_id: bookId,
|
||||
condition: condition,
|
||||
fine_damage: fineDamage,
|
||||
fine_overdue: Math.round(dendaOverduePerBook),
|
||||
notes: notes
|
||||
});
|
||||
});
|
||||
|
||||
const userId = modalEl.find('.btn-konfirmasi-kembali').data('user-id');
|
||||
const totalDenda = dendaOverdueTotal + totalDendaRusak;
|
||||
|
||||
// Construct WA Message
|
||||
let waLink = null;
|
||||
if (isWaChecked && hp && hp !== '-') {
|
||||
let phone = hp;
|
||||
if (phone.startsWith('0')) phone = '62' + phone.substring(1);
|
||||
|
||||
let message = `*BUKTI PENGEMBALIAN BUKU*\n\n`;
|
||||
message += `Halo ${nama},\n`;
|
||||
message += `Terima kasih telah mengembalikan buku:\n`;
|
||||
booksList.forEach(b => message += `- ${b}\n`);
|
||||
message += `\n*Status:* Berhasil Dikembalikan\n`;
|
||||
if (totalDenda > 0) {
|
||||
message += `*Total Denda:* Rp ${new Intl.NumberFormat('id-ID').format(totalDenda)}\n`;
|
||||
} else {
|
||||
message += `*Denda:* Bebas Denda\n`;
|
||||
}
|
||||
message += `\n_Simpan pesan ini sebagai bukti pengembalian._`;
|
||||
|
||||
waLink = `https://wa.me/${phone}?text=${encodeURIComponent(message)}`;
|
||||
}
|
||||
|
||||
// Loading Kirim Email flow
|
||||
if (isEmailChecked) {
|
||||
const dummyEmail = nama.replace(/\s+/g, '.').toLowerCase() +
|
||||
'@sekolah.sch.id';
|
||||
const dummyEmail = nama.replace(/\s+/g, '.').toLowerCase() + '@sekolah.sch.id';
|
||||
modernSwal.fire({
|
||||
title: 'Mengirim Email...',
|
||||
html: `Mengirim nota ke: <b>${dummyEmail}</b>`,
|
||||
|
|
@ -324,10 +383,10 @@ function hitungTotalDenda(modal) {
|
|||
timerProgressBar: true,
|
||||
didOpen: () => Swal.showLoading()
|
||||
}).then(() => {
|
||||
finishTransaction(true);
|
||||
finishTransaction(returnsData, userId, waLink);
|
||||
});
|
||||
} else {
|
||||
finishTransaction(false);
|
||||
finishTransaction(returnsData, userId, waLink);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
@ -335,13 +394,35 @@ function hitungTotalDenda(modal) {
|
|||
}
|
||||
});
|
||||
|
||||
function finishTransaction(withEmail) {
|
||||
Toast.fire({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
text: withEmail ? 'Buku kembali & Email terkirim.' : 'Buku berhasil dikembalikan.'
|
||||
function finishTransaction(returnsData, userId, waLink) {
|
||||
$.ajax({
|
||||
url: '{{ route('admin.peminjaman.kembali') }}',
|
||||
method: 'POST',
|
||||
data: {
|
||||
_token: '{{ csrf_token() }}',
|
||||
user_id: userId,
|
||||
returns: returnsData
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
if (waLink) {
|
||||
window.open(waLink, '_blank');
|
||||
}
|
||||
|
||||
Toast.fire({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
text: 'Buku berhasil dikembalikan.'
|
||||
});
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} else {
|
||||
modernSwal.fire('Gagal', response.message, 'error');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
modernSwal.fire('Gagal', 'Terjadi kesalahan saat memproses data.', 'error');
|
||||
}
|
||||
});
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -22,19 +22,12 @@
|
|||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" id="email"
|
||||
placeholder="Masukkan alamat email" required>
|
||||
placeholder="Masukkan alamat email" required pattern="[^@\s]+@[^@\s]+\.[^@\s]+" title="Masukkan alamat email yang valid">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="nomor_induk" class="form-label">Nomor Induk (NISN/NUPTK)</label>
|
||||
<input type="text" name="nomor_induk" class="form-control" id="nomor_induk" placeholder="Masukkan NISN atau NUPTK">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Nomor HP</label>
|
||||
<input type="text" name="phone" class="form-control" id="phone" placeholder="Masukkan nomor HP">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="kelas" class="form-label">Kelas (jika siswa)</label>
|
||||
<input type="text" name="kelas" class="form-control" id="kelas" placeholder="Contoh: X IPA 1">
|
||||
<input type="text" name="nomor_induk" class="form-control numeric-only" id="nomor_induk"
|
||||
placeholder="Masukkan NISN atau NUPTK" inputmode="numeric">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Role</label>
|
||||
|
|
@ -45,6 +38,14 @@
|
|||
<option value="penjaga perpus">Penjaga Perpus</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3" id="kelas-container" style="display: none;">
|
||||
<label for="kelas" class="form-label">Kelas (jika siswa)</label>
|
||||
<input type="text" name="kelas" class="form-control" id="kelas" placeholder="Contoh: X IPA 1">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Nomor HP</label>
|
||||
<input type="text" name="phone" class="form-control numeric-only" id="phone" placeholder="Masukkan nomor HP" inputmode="numeric">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
|
|
@ -60,6 +61,33 @@
|
|||
<button type="submit" class="btn btn-primary">Simpan Pengguna</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Numeric only input
|
||||
document.querySelectorAll('.numeric-only').forEach(function(input) {
|
||||
input.addEventListener('input', function(e) {
|
||||
this.value = this.value.replace(/[^0-9]/g, '');
|
||||
});
|
||||
});
|
||||
|
||||
// Dynamic Kelas Input
|
||||
const roleSelect = document.getElementById('role');
|
||||
const kelasContainer = document.getElementById('kelas-container');
|
||||
|
||||
function toggleKelas() {
|
||||
if (roleSelect.value === 'siswa') {
|
||||
kelasContainer.style.display = 'block';
|
||||
} else {
|
||||
kelasContainer.style.display = 'none';
|
||||
document.getElementById('kelas').value = ''; // Reset value
|
||||
}
|
||||
}
|
||||
|
||||
roleSelect.addEventListener('change', toggleKelas);
|
||||
toggleKelas(); // Run on load
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,22 +23,12 @@
|
|||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" id="email"
|
||||
value="{{ old('email', $pengguna->email) }}" required>
|
||||
value="{{ old('email', $pengguna->email) }}" required pattern="[^@\s]+@[^@\s]+\.[^@\s]+" title="Masukkan alamat email yang valid">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="nomor_induk" class="form-label">Nomor Induk (NISN/NUPTK)</label>
|
||||
<input type="text" name="nomor_induk" class="form-control" id="nomor_induk"
|
||||
value="{{ old('nomor_induk', $pengguna->nomor_induk) }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Nomor HP</label>
|
||||
<input type="text" name="phone" class="form-control" id="phone"
|
||||
value="{{ old('phone', $pengguna->phone) }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="kelas" class="form-label">Kelas (jika siswa)</label>
|
||||
<input type="text" name="kelas" class="form-control" id="kelas"
|
||||
value="{{ old('kelas', $pengguna->kelas) }}">
|
||||
<input type="text" name="nomor_induk" class="form-control numeric-only" id="nomor_induk"
|
||||
value="{{ old('nomor_induk', $pengguna->nomor_induk) }}" inputmode="numeric">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Role</label>
|
||||
|
|
@ -52,6 +42,16 @@
|
|||
Penjaga Perpus</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3" id="kelas-container" style="display: none;">
|
||||
<label for="kelas" class="form-label">Kelas (jika siswa)</label>
|
||||
<input type="text" name="kelas" class="form-control" id="kelas"
|
||||
value="{{ old('kelas', $pengguna->kelas) }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Nomor HP</label>
|
||||
<input type="text" name="phone" class="form-control numeric-only" id="phone"
|
||||
value="{{ old('phone', $pengguna->phone) }}" inputmode="numeric">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="password" class="form-label">Password Baru</label>
|
||||
|
|
@ -68,6 +68,44 @@
|
|||
<button type="submit" class="btn btn-primary">Simpan Perubahan</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Numeric only input
|
||||
document.querySelectorAll('.numeric-only').forEach(function(input) {
|
||||
input.addEventListener('input', function(e) {
|
||||
this.value = this.value.replace(/[^0-9]/g, '');
|
||||
});
|
||||
});
|
||||
|
||||
// Dynamic Kelas Input
|
||||
const roleSelect = document.getElementById('role');
|
||||
const kelasContainer = document.getElementById('kelas-container');
|
||||
|
||||
function toggleKelas() {
|
||||
if (roleSelect.value === 'siswa') {
|
||||
kelasContainer.style.display = 'block';
|
||||
} else {
|
||||
kelasContainer.style.display = 'none';
|
||||
|
||||
// Optional: Clear value only if changing to non-student to prevent accidental data loss on edit?
|
||||
// For edit, maybe better NOT to clear immediately unless user saves?
|
||||
// But per request "hide input", clearing might be expected behavior if they change role.
|
||||
// I'll keep it simple: just hide. If they change role to Teacher, they probably want to remove class.
|
||||
// But resetting value on edit might exist data. Let's just hide for now.
|
||||
// Actually, if they SUBMIT, and it is hidden, logic might be needed to clear it in backend or here.
|
||||
// For now, mirroring create behavior (reset logic mainly).
|
||||
// Wait, on Edit, if I load as Guru, it hides. If I change to Siswa, it shows (empty or old value).
|
||||
// If I change Siswa -> Guru, it hides.
|
||||
// For safety in Edit, I won't force clear value on toggle to avoid losing data if they accidentally switch.
|
||||
// Just hide.
|
||||
}
|
||||
}
|
||||
|
||||
roleSelect.addEventListener('change', toggleKelas);
|
||||
toggleKelas(); // Run on load
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
data-nama="{{ $siswa['nama_lengkap'] }}"
|
||||
data-email="{{ $siswa['email'] }}"
|
||||
data-role="{{ Str::title($siswa['role']) }}"
|
||||
data-nisn="{{ $siswa['nisn'] ?? 'N/A' }}">
|
||||
data-nisn="{{ $siswa['nomor_induk'] ?? 'N/A' }}">
|
||||
<i class="bi bi-eye-fill"></i> Detail
|
||||
</button>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -32,12 +32,20 @@
|
|||
{{ $item['content'] }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ route('admin.pengumuman.edit', $item['id']) }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger" disabled>
|
||||
<i class="bi bi-trash3-fill"></i>
|
||||
</button>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ route('admin.pengumuman.edit', $item['id']) }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger btn-delete"
|
||||
data-id="{{ $item['id'] }}"
|
||||
data-title="{{ $item['title'] }}">
|
||||
<i class="bi bi-trash3-fill"></i>
|
||||
</button>
|
||||
<form id="delete-form-{{ $item['id'] }}" action="{{ route('admin.pengumuman.destroy', $item['id']) }}" method="POST" style="display: none;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
|
|
@ -52,4 +60,27 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
$(document).on('click', '.btn-delete', function() {
|
||||
const id = $(this).data('id');
|
||||
const title = $(this).data('title');
|
||||
|
||||
modernSwal.fire({
|
||||
title: 'Hapus Pengumuman?',
|
||||
text: `Apakah Anda yakin ingin menghapus pengumuman "${title}"?`,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Ya, Hapus',
|
||||
confirmButtonColor: '#dc3545',
|
||||
cancelButtonText: 'Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
$(`#delete-form-${id}`).submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-app-layout>
|
||||
|
|
@ -17,7 +17,20 @@
|
|||
<td>{{ $item['judul'] }}</td>
|
||||
<td>{{ $item['kategori'] }}</td>
|
||||
<td>
|
||||
<a href="{{ route('admin.rekomendasi.edit', $item['id']) }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-pencil-fill"></i></a>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ route('admin.rekomendasi.edit', $item['id']) }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger btn-delete"
|
||||
data-id="{{ $item['id'] }}"
|
||||
data-title="{{ $item['judul'] }}">
|
||||
<i class="bi bi-trash3-fill"></i>
|
||||
</button>
|
||||
<form id="delete-form-{{ $item['id'] }}" action="{{ route('admin.rekomendasi.destroy', $item['id']) }}" method="POST" style="display: none;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
|
@ -26,4 +39,27 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
$(document).on('click', '.btn-delete', function() {
|
||||
const id = $(this).data('id');
|
||||
const title = $(this).data('title');
|
||||
|
||||
modernSwal.fire({
|
||||
title: 'Hapus Rekomendasi?',
|
||||
text: `Apakah Anda yakin ingin menghapus rekomendasi "${title}"?`,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Ya, Hapus',
|
||||
confirmButtonColor: '#dc3545',
|
||||
cancelButtonText: 'Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
$(`#delete-form-${id}`).submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-app-layout>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<x-guest-layout>
|
||||
@if ($errors->has('forbidden'))
|
||||
<div class="alert alert-danger d-flex align-items-center" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
<div>{{ $errors->first('forbidden') }}</div>
|
||||
<div class="alert alert-danger d-flex align-items-center mb-4" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2 fs-5"></i>
|
||||
<div class="fw-bold">{{ $errors->first('forbidden') }}</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@
|
|||
<label for="name" class="form-label">Nama Lengkap</label>
|
||||
<input id="name" class="form-control bg-body-tertiary @error('name') is-invalid @enderror" type="text" name="name" value="{{ old('name') }}" required autofocus autocomplete="name" />
|
||||
@error('name')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
<div class="invalid-feedback fw-semibold">
|
||||
<i class="bi bi-exclamation-circle-fill me-1"></i>
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
|
@ -26,7 +29,10 @@
|
|||
<label for="nisn" class="form-label">Nomor Induk Siswa Nasional (NISN)</label>
|
||||
<input id="nisn" class="form-control bg-body-tertiary @error('nisn') is-invalid @enderror" type="text" name="nisn" value="{{ old('nisn') }}" required autocomplete="username" />
|
||||
@error('nisn')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
<div class="invalid-feedback fw-semibold">
|
||||
<i class="bi bi-exclamation-circle-fill me-1"></i>
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
@else
|
||||
|
|
@ -34,7 +40,10 @@
|
|||
<label for="nip" class="form-label">Nomor Induk Pegawai (NIP)</label>
|
||||
<input id="nip" class="form-control bg-body-tertiary @error('nip') is-invalid @enderror" type="text" name="nip" value="{{ old('nip') }}" required autocomplete="username" />
|
||||
@error('nip')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
<div class="invalid-feedback fw-semibold">
|
||||
<i class="bi bi-exclamation-circle-fill me-1"></i>
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -44,7 +53,10 @@
|
|||
<label for="password" class="form-label">Password</label>
|
||||
<input id="password" class="form-control bg-body-tertiary @error('password') is-invalid @enderror" type="password" name="password" required autocomplete="new-password" />
|
||||
@error('password')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
<div class="invalid-feedback fw-semibold">
|
||||
<i class="bi bi-exclamation-circle-fill me-1"></i>
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@
|
|||
<p class="small text-danger mt-1">Jangan bagikan kode ini kepada orang lain.</p>
|
||||
</div>
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger">
|
||||
{{ session('error') }}
|
||||
<div class="alert alert-danger border-0 shadow-sm rounded-3 d-flex align-items-center mb-4">
|
||||
<i class="bi bi-exclamation-circle-fill me-2 fs-5"></i>
|
||||
<div class="small fw-bold">{{ session('error') }}</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
@props(['status'])
|
||||
|
||||
@if ($status)
|
||||
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
|
||||
{{ $status }}
|
||||
<div {{ $attributes->merge(['class' => 'alert alert-success d-flex align-items-center mb-4']) }} role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>
|
||||
<div class="small fw-semibold">{{ $status }}</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
@props(['messages'])
|
||||
|
||||
@if ($messages)
|
||||
<ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
|
||||
<ul {{ $attributes->merge(['class' => 'list-unstyled mt-1 mb-0']) }}>
|
||||
@foreach ((array) $messages as $message)
|
||||
<li>{{ $message }}</li>
|
||||
<li class="text-danger small fw-semibold">
|
||||
<i class="bi bi-exclamation-circle-fill me-1"></i>
|
||||
{{ $message }}
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -113,11 +113,16 @@ class="btn btn-sm btn-primary w-100">
|
|||
</div>
|
||||
@empty
|
||||
<div class="col-12">
|
||||
<div class="alert alert-warning text-center">
|
||||
<h4 class="alert-heading">Tidak Ada Hasil</h4>
|
||||
<p>Tidak ada buku yang cocok dengan kriteria filter Anda. Coba reset atau ubah filter.</p>
|
||||
<hr>
|
||||
<a href="{{ route(request()->route()->getName()) }}" class="btn btn-primary">Reset Filter</a>
|
||||
<div class="alert alert-warning text-center border-0 shadow-sm rounded-4 py-5 mb-0">
|
||||
<div class="icon-circle bg-warning-subtle text-warning mx-auto mb-3" style="width: 80px; height: 80px; border-radius: 20px;">
|
||||
<i class="bi bi-search fs-1"></i>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-2" style="color: #856404;">Tidak Ada Hasil</h4>
|
||||
<p class="text-muted">Tidak ada buku yang cocok dengan kriteria filter Anda.<br>Coba reset atau ubah filter pencarian Anda.</p>
|
||||
<hr class="w-25 mx-auto my-4 opacity-10">
|
||||
<a href="{{ route(request()->route()->getName()) }}" class="btn btn-warning px-4 rounded-pill fw-bold">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i> Reset Filter
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
|
|
|
|||
|
|
@ -33,9 +33,40 @@
|
|||
@include('layouts.navigation')
|
||||
<main class="container-fluid py-4 px-4">
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger alert-dismissible fade show m-3" role="alert">
|
||||
<i class="bi bi-exclamation-octagon-fill me-2"></i>
|
||||
<strong>DITOLAK!</strong> {{ session('error') }}
|
||||
<div class="alert alert-danger alert-dismissible fade show m-3 border-0 shadow-sm rounded-4" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-exclamation-octagon-fill me-2 fs-4"></i>
|
||||
<div>
|
||||
<strong class="text-danger h6 mb-0 d-block">DITOLAK!</strong>
|
||||
<span class="small">{{ session('error') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('success'))
|
||||
<div class="alert alert-success alert-dismissible fade show m-3 border-0 shadow-sm rounded-4" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-check-circle-fill me-2 fs-4"></i>
|
||||
<div>
|
||||
<strong class="text-success h6 mb-0 d-block">BERHASIL!</strong>
|
||||
<span class="small">{{ session('success') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('warning'))
|
||||
<div class="alert alert-warning alert-dismissible fade show m-3 border-0 shadow-sm rounded-4" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2 fs-4"></i>
|
||||
<div>
|
||||
<strong class="h6 mb-0 d-block" style="color: #856404;">PERINGATAN!</strong>
|
||||
<span class="small">{{ session('warning') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small">Nomor Handphone</label>
|
||||
<p class="fw-semibold">{{ $user['nomor_hp'] }}</p>
|
||||
<p class="fw-semibold">{{ $user->phone ?? 'N/A' }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small">Kelas</label>
|
||||
|
|
@ -275,9 +275,9 @@ class="bi bi-star-fill me-1"></i>Buku Utama</span>
|
|||
{{-- <button type="button" class="btn btn-primary px-4" onclick="kirimForm()">Setuju</button> --}}
|
||||
|
||||
{{-- Button Setuju jika belum ada backend, akan ter direct ke peminjaman.index --}}
|
||||
<a href="{{ route('peminjaman.index') }}" class="btn btn-primary px-4">
|
||||
<button type="submit" form="formPeminjaman" class="btn btn-primary px-4">
|
||||
Setuju
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -286,7 +286,6 @@ class="bi bi-star-fill me-1"></i>Buku Utama</span>
|
|||
document.addEventListener("DOMContentLoaded", function() {
|
||||
|
||||
// (Default Hari Ini + 2 Hari)
|
||||
// Simpan instance-nya ke variabel
|
||||
const fpKembali = flatpickr("#tanggalKembali", {
|
||||
dateFormat: "d F Y",
|
||||
altInput: true,
|
||||
|
|
@ -306,7 +305,6 @@ class="bi bi-star-fill me-1"></i>Buku Utama</span>
|
|||
locale: "id",
|
||||
minDate: "today",
|
||||
|
||||
// LOGIC : Saat Tanggal Pinjam Berubah
|
||||
onChange: function(selectedDates, dateStr) {
|
||||
if (selectedDates.length > 0) {
|
||||
const tglMulai = selectedDates[0];
|
||||
|
|
@ -315,11 +313,117 @@ class="bi bi-star-fill me-1"></i>Buku Utama</span>
|
|||
|
||||
fpKembali.set("minDate", minDateBaru);
|
||||
fpKembali.set("maxDate", maxDateBaru);
|
||||
|
||||
fpKembali.setDate(maxDateBaru);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Inisialisasi data dari data-attributes
|
||||
const form = document.getElementById('formPeminjaman');
|
||||
const arrayBuku = JSON.parse(form.dataset.semuaBuku);
|
||||
const bukuUtama = JSON.parse(form.dataset.bukuAwal);
|
||||
|
||||
let selectedIds = [bukuUtama.id];
|
||||
const MAX_BOOKS = 2;
|
||||
|
||||
// Global functions
|
||||
window.toggleBookSelection = function(id) {
|
||||
const index = selectedIds.indexOf(id);
|
||||
const checkbox = document.getElementById('book' + id);
|
||||
|
||||
if (index > -1) {
|
||||
if (id === bukuUtama.id) return;
|
||||
selectedIds.splice(index, 1);
|
||||
if (checkbox) checkbox.checked = false;
|
||||
} else {
|
||||
if (selectedIds.length >= MAX_BOOKS) {
|
||||
alert('Maksimal peminjaman adalah ' + MAX_BOOKS + ' buku.');
|
||||
return;
|
||||
}
|
||||
selectedIds.push(id);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
}
|
||||
updateCounter();
|
||||
};
|
||||
|
||||
window.konfirmasiPilihanBuku = function() {
|
||||
renderDaftarBuku();
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('pilihBukuModal'));
|
||||
modal.hide();
|
||||
};
|
||||
|
||||
function updateCounter() {
|
||||
const counterEl = document.getElementById('counterBuku');
|
||||
const sisaSlotEl = document.getElementById('sisaSlot');
|
||||
if (counterEl) counterEl.textContent = selectedIds.length;
|
||||
if (sisaSlotEl) sisaSlotEl.textContent = MAX_BOOKS - selectedIds.length;
|
||||
}
|
||||
|
||||
function renderDaftarBuku() {
|
||||
const container = document.getElementById('daftarBukuPinjam');
|
||||
const hiddenInputs = document.getElementById('hiddenInputs');
|
||||
const ringkasan = document.getElementById('ringkasanBuku');
|
||||
|
||||
if (!container || !hiddenInputs || !ringkasan) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
hiddenInputs.innerHTML = '';
|
||||
ringkasan.innerHTML = '<ul class="mb-0 text-start">';
|
||||
|
||||
selectedIds.forEach(id => {
|
||||
const buku = arrayBuku.find(b => b.id == id);
|
||||
if (!buku) return;
|
||||
|
||||
const isUtama = id === (typeof bukuUtama.id === 'string' ? parseInt(bukuUtama.id) : bukuUtama.id);
|
||||
|
||||
const itemHtml = `
|
||||
<div class="book-item border rounded p-3 mb-3" data-book-id="${buku.id}">
|
||||
<div class="d-flex align-items-start text-start">
|
||||
<img src="/${buku.cover}" alt="Cover" class="rounded me-3 form-book-cover" style="width: 50px; height: 70px; object-fit: cover;">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="fw-bold mb-1">${buku.judul}</h6>
|
||||
<p class="text-muted small mb-1">${buku.penulis}</p>
|
||||
<span class="badge bg-info">${buku.kategori || 'Kategori'}</span>
|
||||
</div>
|
||||
${isUtama ? '<span class="badge bg-success">Buku Utama</span>' :
|
||||
'<button type="button" class="btn btn-sm btn-outline-danger" onclick="toggleSelectionAndRender(' + buku.id + ')"><i class="bi bi-trash"></i></button>'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.insertAdjacentHTML('beforeend', itemHtml);
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'buku_ids[]';
|
||||
input.value = buku.id;
|
||||
hiddenInputs.appendChild(input);
|
||||
|
||||
ringkasan.innerHTML += `<li>${buku.judul}</li>`;
|
||||
});
|
||||
|
||||
ringkasan.innerHTML += '</ul>';
|
||||
}
|
||||
|
||||
window.toggleSelectionAndRender = function(id) {
|
||||
window.toggleBookSelection(id);
|
||||
renderDaftarBuku();
|
||||
};
|
||||
|
||||
document.getElementById('searchBuku').addEventListener('input', function(e) {
|
||||
const keyword = e.target.value.toLowerCase();
|
||||
document.querySelectorAll('.book-option').forEach(el => {
|
||||
const title = el.dataset.bookTitle;
|
||||
const author = el.dataset.bookAuthor;
|
||||
if (title.includes(keyword) || author.includes(keyword)) {
|
||||
el.style.display = 'block';
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
renderDaftarBuku();
|
||||
updateCounter();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,22 @@ class="btn btn-outline-primary rounded-pill w-100 w-md-auto ms-md-auto">
|
|||
<i class="bi bi-pencil-square me-2"></i>Edit Profil
|
||||
</a>
|
||||
</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>
|
||||
|
||||
{{-- Ringkasan Laporan Minat Baca --}}
|
||||
|
|
@ -192,7 +208,7 @@ class="btn btn-outline-primary rounded-pill ms-md-auto">
|
|||
<div class="row g-3">
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">NISN</small>
|
||||
<p class="fw-semibold mb-0">{{ $user->nisn ?? 'N/A' }}</p>
|
||||
<p class="fw-semibold mb-0">{{ $user->nomor_induk ?? 'N/A' }}</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">Email</small>
|
||||
|
|
@ -200,7 +216,7 @@ class="btn btn-outline-primary rounded-pill ms-md-auto">
|
|||
</div>
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">Nomor HP</small>
|
||||
<p class="fw-semibold mb-0">{{ $user->nomor_hp ?? 'N/A' }}</p>
|
||||
<p class="fw-semibold mb-0">{{ $user->phone ?? 'N/A' }}</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">Kelas</small>
|
||||
|
|
|
|||
|
|
@ -55,43 +55,41 @@ class="form-control @error('email') is-invalid @enderror" value="{{ old('email',
|
|||
</div>
|
||||
|
||||
{{-- Bagian Nomor Telepon --}}
|
||||
@if ($user->nomor_hp)
|
||||
<div class="mb-3">
|
||||
<label for="nomor_hp" class="form-label">{{ __('Nomor Telepon (WA)') }}</label>
|
||||
<input id="nomor_hp" name="nomor_hp" type="text" class="form-control @error('nomor_hp') is-invalid @enderror"
|
||||
value="{{ old('nomor_hp', $user->nomor_hp) }}" required>
|
||||
@error('nomor_hp')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<small class="text-muted">*Nomor telepon wajib disi</small>
|
||||
</div>
|
||||
@endif
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">{{ __('Nomor Telepon (WA)') }}</label>
|
||||
<input id="phone" name="phone" type="text" class="form-control @error('phone') is-invalid @enderror"
|
||||
value="{{ old('phone', $user->phone) }}" required>
|
||||
@error('phone')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<small class="text-muted">*Nomor telepon wajib diisi</small>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
{{-- Bagian Info Spesifik Role (Tidak Dapat Diubah) --}}
|
||||
@if ($user->role == 'siswa')
|
||||
<div class="mb-3">
|
||||
<label for="nisn" class="form-label">NISN (Nomor Induk Siswa Nasional)</label>
|
||||
<input id="nisn" type="text" class="form-control" value="{{ $user->nisn }}" readonly disabled>
|
||||
<label for="nomor_induk" class="form-label">NISN (Nomor Induk Siswa Nasional)</label>
|
||||
<input id="nomor_induk" type="text" class="form-control" value="{{ $user->nomor_induk }}" readonly disabled>
|
||||
<small class="text-muted">NISN tidak dapat diubah.</small>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="kelas" class="form-label">Kelas</label>
|
||||
<input id="kelas" type="text" class="form-control" value="{{ $user->kelas }}">
|
||||
<input id="kelas" type="text" class="form-control" value="{{ $user->kelas }}" readonly disabled>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="golongan" class="form-label">Golongan</label>
|
||||
<input id="golongan" type="text" class="form-control" value="{{ $user->golongan }}" >
|
||||
<input id="golongan" type="text" class="form-control" value="{{ $user->golongan }}" readonly disabled>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
{{-- Untuk Guru atau Penjaga Perpus --}}
|
||||
<div class="mb-3">
|
||||
<label for="nip" class="form-label">NIP (Nomor Induk Pegawai)</label>
|
||||
<input id="nip" type="text" class="form-control" value="{{ $user->nip }}" readonly disabled>
|
||||
<small class="text-muted">NIP tidak dapat diubah.</small>
|
||||
<label for="nuptk" class="form-label">NIP / NUPTK</label>
|
||||
<input id="nuptk" type="text" class="form-control" value="{{ $user->nuptk }}" readonly disabled>
|
||||
<small class="text-muted">ID Kepegawaian tidak dapat diubah.</small>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@
|
|||
Route::get('/peminjaman', [AdminPeminjamanController::class, 'index'])->name('peminjaman.index');
|
||||
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('/denda', [AdminPeminjamanController::class, 'dendaIndex'])->name('denda.index');
|
||||
Route::post('/denda/sanksi', [AdminPeminjamanController::class, 'berikanSanksi'])->name('denda.sanksi');
|
||||
|
|
|
|||
Loading…
Reference in New Issue