refactor: update book stock management, refine validation constraints, and adjust dashboard statistics and UI components.

This commit is contained in:
cukiprit 2026-04-14 00:19:26 +07:00
parent 0c3369262f
commit 78df5b3a72
15 changed files with 33 additions and 25 deletions

View File

@ -126,9 +126,10 @@ public function store(Request $request)
]); ]);
// Update book status and decrement stock // Update book status and decrement stock
$newStok = $book->stok - 1;
$book->update([ $book->update([
'status' => 'Dipinjam', 'status' => $newStok <= 0 ? 'Dipinjam' : 'Tersedia',
'stok' => $book->stok - 1, 'stok' => $newStok,
]); ]);
} }

View File

@ -61,7 +61,7 @@ public function store(Request $request)
'judul' => 'required|string|min:3|max:50', 'judul' => 'required|string|min:3|max:50',
'kategori' => 'required|string|min:3|max:50', 'kategori' => 'required|string|min:3|max:50',
'youtube_link' => 'required|url', 'youtube_link' => 'required|url',
'deskripsi' => 'required|string', 'deskripsi' => 'required|string|max:255',
]); ]);
$validated['user_id'] = auth()->id(); $validated['user_id'] = auth()->id();
@ -78,7 +78,7 @@ public function update(Request $request, $id)
'judul' => 'required|string|min:3|max:50', 'judul' => 'required|string|min:3|max:50',
'kategori' => 'required|string|min:3|max:50', 'kategori' => 'required|string|min:3|max:50',
'youtube_link' => 'required|url', 'youtube_link' => 'required|url',
'deskripsi' => 'required|string', 'deskripsi' => 'required|string|max:255',
]); ]);
$rekomendasi->update($validated); $rekomendasi->update($validated);

View File

@ -101,6 +101,8 @@ public function store(Request $request)
$validated['file_pdf'] = basename($path); $validated['file_pdf'] = basename($path);
} }
$validated['status'] = $validated['stok'] <= 0 ? 'Dipinjam' : 'Tersedia';
Book::create($validated); Book::create($validated);
return redirect()->route('admin.buku.index')->with('success', 'Buku berhasil ditambahkan.'); return redirect()->route('admin.buku.index')->with('success', 'Buku berhasil ditambahkan.');
@ -132,6 +134,8 @@ public function update(Request $request, $id)
$validated['file_pdf'] = basename($path); $validated['file_pdf'] = basename($path);
} }
$validated['status'] = $validated['stok'] <= 0 ? 'Dipinjam' : 'Tersedia';
$buku->update($validated); $buku->update($validated);
return redirect()->route('admin.buku.index')->with('success', 'Buku berhasil diperbarui.'); return redirect()->route('admin.buku.index')->with('success', 'Buku berhasil diperbarui.');

View File

@ -22,7 +22,7 @@ public function index()
['label' => 'Total Buku', 'value' => $allBooks, 'icon' => 'bi-journal-bookmark-fill', 'color' => 'primary'], ['label' => 'Total Buku', 'value' => $allBooks, 'icon' => 'bi-journal-bookmark-fill', 'color' => 'primary'],
['label' => 'Total Anggota', 'value' => $allUsers, 'icon' => 'bi-people-fill', 'color' => 'success'], ['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' => 'Buku Dipinjam', 'value' => $bukuDipinjam, 'icon' => 'bi-arrow-up-right-circle-fill', 'color' => 'warning'],
['label' => 'Total Denda', 'value' => 'Rp ' . number_format(Loan::where('status', 'Terlambat')->sum('fine_overdue'), 0, ',', '.'), 'icon' => 'bi-cash-coin', 'color' => 'danger'], ['label' => 'Total Denda', 'value' => 'Rp ' . number_format(Loan::sum('fine_overdue') + Loan::sum('fine_damage'), 0, ',', '.'), 'icon' => 'bi-cash-coin', 'color' => 'danger'],
]; ];
// Monthly stats (last 7 months) // Monthly stats (last 7 months)

View File

@ -47,7 +47,7 @@ public function store(Request $request)
{ {
$validated = $request->validate([ $validated = $request->validate([
'title' => 'required|string|min:3|max:50', 'title' => 'required|string|min:3|max:50',
'content' => 'required|string', 'content' => 'required|string|max:255',
'type' => 'required|in:info,warning,success,danger', 'type' => 'required|in:info,warning,success,danger',
'icon' => 'nullable|string|max:50', 'icon' => 'nullable|string|max:50',
]); ]);
@ -69,7 +69,7 @@ public function update(Request $request, $id)
$validated = $request->validate([ $validated = $request->validate([
'title' => 'required|string|min:3|max:50', 'title' => 'required|string|min:3|max:50',
'content' => 'required|string', 'content' => 'required|string|max:255',
'type' => 'required|in:info,warning,success,danger', 'type' => 'required|in:info,warning,success,danger',
'icon' => 'nullable|string|max:50', 'icon' => 'nullable|string|max:50',
]); ]);

View File

@ -45,7 +45,7 @@ public function index()
// Stats calculation // Stats calculation
$stats = [ $stats = [
['label' => 'Buku yang dipinjam', 'value' => $loans->count(), 'icon' => 'bi-book-half', 'color' => 'primary'], ['label' => 'Buku yang dipinjam', 'value' => $loans->count(), 'icon' => 'bi-book-half', 'color' => 'primary'],
['label' => 'Tenggat Waktu', 'value' => $bukuPinjamOffline->where('sisa_hari', '<=', 3)->where('sisa_hari', '>=', 0)->count(), 'icon' => 'bi-clock-history', 'color' => 'danger'], ['label' => 'Tenggat Waktu', 'value' => $bukuPinjamOffline->where('sisa_hari', '<=', 2)->count(), 'icon' => 'bi-clock-history', 'color' => 'danger'],
['label' => 'Buku dikembalikan', 'value' => Loan::where('user_id', $user->id)->where('status', 'Dikembalikan')->count(), 'icon' => 'bi-check-circle', 'color' => 'success'], ['label' => 'Buku dikembalikan', 'value' => Loan::where('user_id', $user->id)->where('status', 'Dikembalikan')->count(), 'icon' => 'bi-check-circle', 'color' => 'success'],
['label' => 'History Baca', 'value' => Loan::where('user_id', $user->id)->count(), 'icon' => 'bi-hourglass-split', 'color' => 'warning'], ['label' => 'History Baca', 'value' => Loan::where('user_id', $user->id)->count(), 'icon' => 'bi-hourglass-split', 'color' => 'warning'],
]; ];

View File

@ -88,7 +88,7 @@ public function form($id)
$buku = Book::with('category')->findOrFail($id); $buku = Book::with('category')->findOrFail($id);
$semuaBuku = Book::whereJsonContains('tipe_akses', 'offline') $semuaBuku = Book::whereJsonContains('tipe_akses', 'offline')
->where('status', 'Tersedia') ->where('stok', '>', 0)
->get(); ->get();
return view('peminjaman.form', compact('user', 'buku', 'semuaBuku')); return view('peminjaman.form', compact('user', 'buku', 'semuaBuku'));
@ -113,7 +113,7 @@ public function store(Request $request)
foreach ($bukuIds as $bukuId) { foreach ($bukuIds as $bukuId) {
$book = Book::lockForUpdate()->find($bukuId); $book = Book::lockForUpdate()->find($bukuId);
if ($book->status !== 'Tersedia') { if ($book->stok <= 0) {
throw new \Exception("Buku '{$book->judul}' sudah tidak tersedia."); throw new \Exception("Buku '{$book->judul}' sudah tidak tersedia.");
} }
@ -126,7 +126,11 @@ public function store(Request $request)
'status' => 'Dipinjam', 'status' => 'Dipinjam',
]); ]);
$book->update(['status' => 'Dipinjam']); $newStok = $book->stok - 1;
$book->update([
'stok' => $newStok,
'status' => $newStok <= 0 ? 'Dipinjam' : 'Tersedia'
]);
} }
}); });

View File

@ -25,7 +25,7 @@ public function rules(): array
'max:255', 'max:255',
Rule::unique(User::class)->ignore($this->user()->id), Rule::unique(User::class)->ignore($this->user()->id),
], ],
'phone' => ['required', 'numeric', 'digits_between:13,15'], 'phone' => ['required', 'string', 'min:13', 'max:16'],
]; ];
} }
} }

View File

@ -37,7 +37,7 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="content" class="form-label">Isi Pengumuman <span class="text-danger">*</span></label> <label for="content" class="form-label">Isi Pengumuman <span class="text-danger">*</span></label>
<textarea name="content" class="form-control @error('content') is-invalid @enderror" id="content" rows="5" placeholder="Tulis isi pengumuman di sini...">{{ old('content') }}</textarea> <textarea name="content" class="form-control @error('content') is-invalid @enderror" id="content" rows="5" placeholder="Tulis isi pengumuman di sini..." maxlength="255">{{ old('content') }}</textarea>
@error('content') @error('content')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback">{{ $message }}</div>
@enderror @enderror

View File

@ -38,7 +38,7 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="content" class="form-label">Isi Pengumuman <span class="text-danger">*</span></label> <label for="content" class="form-label">Isi Pengumuman <span class="text-danger">*</span></label>
<textarea name="content" class="form-control @error('content') is-invalid @enderror" id="content" rows="5">{{ old('content', $pengumuman->content) }}</textarea> <textarea name="content" class="form-control @error('content') is-invalid @enderror" id="content" rows="5" maxlength="255">{{ old('content', $pengumuman->content) }}</textarea>
@error('content') @error('content')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback">{{ $message }}</div>
@enderror @enderror

View File

@ -40,7 +40,7 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Deskripsi <span class="text-danger">*</span></label> <label class="form-label">Deskripsi <span class="text-danger">*</span></label>
<textarea name="deskripsi" id="editor" class="@error('deskripsi') is-invalid @enderror">{{ old('deskripsi') }}</textarea> <textarea name="deskripsi" id="editor" class="@error('deskripsi') is-invalid @enderror" maxlength="255">{{ old('deskripsi') }}</textarea>
@error('deskripsi') @error('deskripsi')
<div class="text-danger small mt-1">{{ $message }}</div> <div class="text-danger small mt-1">{{ $message }}</div>
@enderror @enderror

View File

@ -41,7 +41,7 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Deskripsi <span class="text-danger">*</span></label> <label class="form-label">Deskripsi <span class="text-danger">*</span></label>
<textarea name="deskripsi" id="editor" class="@error('deskripsi') is-invalid @enderror">{{ old('deskripsi', $rekomendasi->deskripsi) }}</textarea> <textarea name="deskripsi" id="editor" class="@error('deskripsi') is-invalid @enderror" maxlength="255">{{ old('deskripsi', $rekomendasi->deskripsi) }}</textarea>
@error('deskripsi') @error('deskripsi')
<div class="text-danger small mt-1">{{ $message }}</div> <div class="text-danger small mt-1">{{ $message }}</div>
@enderror @enderror

View File

@ -76,7 +76,7 @@ class="badge fw-normal {{ $buku['status'] == 'Tersedia' ? 'bg-success-subtle tex
$isOnlineAccess = $isOnlineAccess =
$buku['tipe_akses'] === 'online' || $buku['tipe_akses'] === 'online' ||
(is_array($buku['tipe_akses']) && in_array('online', $buku['tipe_akses'])); (is_array($buku['tipe_akses']) && in_array('online', $buku['tipe_akses']));
$stokHabis = $buku['status'] == 'Dipinjam'; $stokHabis = $buku['stok'] <= 0;
@endphp @endphp
{{-- TOMBOL PINJAM (OFFLINE) --}} {{-- TOMBOL PINJAM (OFFLINE) --}}

View File

@ -59,8 +59,8 @@ class="form-control @error('email') is-invalid @enderror" value="{{ old('email',
<div class="mb-3"> <div class="mb-3">
<label for="phone" class="form-label">{{ __('Nomor Telepon (WA)') }}</label> <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" <input id="phone" name="phone" type="text" class="form-control @error('phone') is-invalid @enderror"
value="{{ old('phone', $user->phone) }}" required value="{{ old('phone', $user->phone) }}"
minlength="13" maxlength="15" pattern="\d*"> maxlength="16" pattern="\+?[0-9]+" oninput="this.value = this.value.replace(/[^0-9+]/g, '')">
@error('phone') @error('phone')
<div class="invalid-feedback">{{ $message }}</div> <div class="invalid-feedback">{{ $message }}</div>
@enderror @enderror

View File

@ -19,7 +19,7 @@
</thead> </thead>
<tbody> <tbody>
@php $counter = 1; @endphp @php $counter = 1; @endphp
@forelse ($riwayatOnline as $transaksi) @foreach ($riwayatOnline as $transaksi)
@foreach ($transaksi['books'] as $buku) @foreach ($transaksi['books'] as $buku)
<tr> <tr>
<td>{{ $counter++ }}</td> <td>{{ $counter++ }}</td>
@ -38,11 +38,7 @@
</td> </td>
</tr> </tr>
@endforeach @endforeach
@empty @endforeach
<tr>
<td colspan="6" class="text-center">Tidak ada riwayat baca buku online.</td>
</tr>
@endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
@ -82,6 +78,9 @@ class="bi bi-book-half me-2 text-primary"></i>Detail Riwayat</h5>
searching: false, searching: false,
pageLength: 10, pageLength: 10,
order: [[0, 'asc']], order: [[0, 'asc']],
language: {
emptyTable: "Tidak ada riwayat baca buku online."
},
columnDefs: [ columnDefs: [
{ responsivePriority: 1, targets: 2 }, { responsivePriority: 1, targets: 2 },
{ responsivePriority: 2, targets: 5 } { responsivePriority: 2, targets: 5 }