feat: implement form validation, localization, and enhanced input handling across admin modules

This commit is contained in:
cukiprit 2026-04-12 23:08:46 +07:00
parent ff16d4e53c
commit d076baff24
20 changed files with 544 additions and 348 deletions

View File

@ -145,7 +145,7 @@ public function store(Request $request)
public function export(Request $request)
{
$request->validate([
'bulan_laporan' => 'nullable|date',
'bulan_laporan' => 'required|date|before_or_equal:today',
]);
$query = Loan::with(['user', 'book'])->orderBy('borrowed_at', 'asc');

View File

@ -58,8 +58,8 @@ public function edit($id)
public function store(Request $request)
{
$validated = $request->validate([
'judul' => 'required|string|max:255',
'kategori' => 'required|string|max:100',
'judul' => 'required|string|min:3|max:50',
'kategori' => 'required|string|min:3|max:50',
'youtube_link' => 'required|url',
'deskripsi' => 'required|string',
]);
@ -75,8 +75,8 @@ public function update(Request $request, $id)
$rekomendasi = Recommendation::findOrFail($id);
$validated = $request->validate([
'judul' => 'required|string|max:255',
'kategori' => 'required|string|max:100',
'judul' => 'required|string|min:3|max:50',
'kategori' => 'required|string|min:3|max:50',
'youtube_link' => 'required|url',
'deskripsi' => 'required|string',
]);

View File

@ -80,15 +80,15 @@ public function edit($id)
public function store(Request $request)
{
$validated = $request->validate([
'judul' => 'required|string|min:3|max:255',
'penulis' => 'required|string|min:3|max:100',
'judul' => 'required|string|min:3|max:50',
'penulis' => 'required|string|min:3|max:50|regex:/^[a-zA-Z\s.,\']+$/',
'category_id' => 'required|exists:categories,id',
'tahun' => 'required|integer',
'kode_buku' => 'nullable|string|max:10',
'tahun' => 'required|integer|min:1900|max:2026',
'kode_buku' => 'required|numeric|max_digits:10',
'stok' => 'required|integer|min:0|max:99',
'tipe_akses' => 'required|array',
'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
'file_pdf' => 'nullable|mimes:pdf|max:10240',
'cover' => 'required|image|mimes:jpeg,png,jpg|max:2048',
'file_pdf' => 'required|mimes:pdf|max:10240',
]);
if ($request->hasFile('cover')) {
@ -111,11 +111,11 @@ public function update(Request $request, $id)
$buku = Book::findOrFail($id);
$validated = $request->validate([
'judul' => 'required|string|min:3|max:255',
'penulis' => 'required|string|min:3|max:100',
'judul' => 'required|string|min:3|max:50',
'penulis' => 'required|string|min:3|max:50|regex:/^[a-zA-Z\s.,\']+$/',
'category_id' => 'required|exists:categories,id',
'tahun' => 'required|integer',
'kode_buku' => 'nullable|string|max:10',
'tahun' => 'required|integer|min:1900|max:2026',
'kode_buku' => 'nullable|numeric|max_digits:10',
'stok' => 'required|integer|min:0|max:99',
'tipe_akses' => 'required|array',
'cover' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',

View File

@ -12,9 +12,9 @@ class MasterIndukController extends Controller
public function store(Request $request)
{
$request->validate([
'nomor_induk' => 'required|unique:master_induks,nomor_induk',
'nomor_induk' => 'required|string|max:15|unique:master_induks,nomor_induk',
'role' => 'required|in:siswa,guru',
'nama_pemilik' => 'required|string',
'nama_pemilik' => 'required|string|min:3|max:50|regex:/^[a-zA-Z\s.,\']+$/',
]);
MasterInduk::create(array_merge($request->all(), ['user_id' => auth()->id()]));

View File

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

View File

@ -54,10 +54,10 @@ public function edit($id)
public function store(Request $request)
{
$validated = $request->validate([
'nama_lengkap' => 'required|string|max:255',
'nama_lengkap' => 'required|string|min:3|max:50|regex:/^[a-zA-Z\s.,\']+$/',
'email' => 'required|email|unique:users,email',
'nomor_induk' => 'required|string|max:50|unique:users,nomor_induk',
'phone' => 'nullable|string|max:20',
'nomor_induk' => 'required|string|max:15|unique:users,nomor_induk',
'phone' => 'nullable|string|min:13|max:16|regex:/^\+?[0-9]+$/',
'role' => 'required|in:siswa,guru,penjaga perpus',
'kelas' => 'nullable|string|max:50',
'golongan' => 'nullable|string|max:50',
@ -88,10 +88,10 @@ public function update(Request $request, $id)
$pengguna = User::findOrFail($id);
$validated = $request->validate([
'nama_lengkap' => 'required|string|max:255',
'nama_lengkap' => 'required|string|min:3|max:50|regex:/^[a-zA-Z\s.,\']+$/',
'email' => 'required|email|unique:users,email,' . $id,
'nomor_induk' => 'nullable|string|max:50',
'phone' => 'nullable|string|max:20',
'nomor_induk' => 'nullable|string|max:15',
'phone' => 'nullable|string|min:13|max:16|regex:/^\+?[0-9]+$/',
'role' => 'required|in:siswa,guru,penjaga perpus',
'kelas' => 'nullable|string|max:50',
'golongan' => 'nullable|string|max:50',

View File

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

178
lang/id/validation.php Normal file
View File

@ -0,0 +1,178 @@
<?php
return [
'accepted' => ':attribute harus diterima.',
'accepted_if' => ':attribute harus diterima ketika :other adalah :value.',
'active_url' => ':attribute bukan URL yang valid.',
'after' => ':attribute harus berisi tanggal setelah :date.',
'after_or_equal' => ':attribute harus berisi tanggal setelah atau sama dengan :date.',
'alpha' => ':attribute hanya boleh berisi huruf.',
'alpha_dash' => ':attribute hanya boleh berisi huruf, angka, strip, dan garis bawah.',
'alpha_num' => ':attribute hanya boleh berisi huruf dan angka.',
'array' => ':attribute harus berisi sebuah array.',
'ascii' => ':attribute hanya boleh berisi karakter alfanumerik dan simbol satu bait.',
'before' => ':attribute harus berisi tanggal sebelum :date.',
'before_or_equal' => ':attribute harus berisi tanggal sebelum atau sama dengan :date.',
'between' => [
'array' => ':attribute harus memiliki antara :min dan :max anggota.',
'file' => ':attribute harus berukuran antara :min dan :max kilobita.',
'numeric' => ':attribute harus bernilai antara :min dan :max.',
'string' => ':attribute harus berisi antara :min dan :max karakter.',
],
'boolean' => ':attribute harus bernilai true atau false.',
'can' => ':attribute berisi nilai yang tidak sah.',
'confirmed' => 'Konfirmasi :attribute tidak cocok.',
'contains' => ':attribute tidak memiliki nilai yang dibutuhkan.',
'current_password' => 'Kata sandi salah.',
'date' => ':attribute bukan tanggal yang valid.',
'date_equals' => ':attribute harus berisi tanggal yang sama dengan :date.',
'date_format' => ':attribute tidak cocok dengan format :format.',
'decimal' => ':attribute harus memiliki :decimal tempat desimal.',
'declined' => ':attribute harus ditolak.',
'declined_if' => ':attribute harus ditolak ketika :other adalah :value.',
'different' => ':attribute dan :other harus berbeda.',
'digits' => ':attribute harus terdiri dari :digits angka.',
'digits_between' => ':attribute harus terdiri dari antara :min dan :max angka.',
'dimensions' => ':attribute tidak memiliki dimensi gambar yang valid.',
'distinct' => ':attribute memiliki nilai yang duplikat.',
'doesnt_end_with' => ':attribute tidak boleh diakhiri dengan salah satu dari: :values.',
'doesnt_start_with' => ':attribute tidak boleh diawali dengan salah satu dari: :values.',
'email' => ':attribute harus berupa alamat surel yang valid.',
'ends_with' => ':attribute harus diakhiri dengan salah satu dari: :values.',
'enum' => ':attribute yang dipilih tidak valid.',
'exists' => ':attribute yang dipilih tidak valid.',
'extensions' => ':attribute harus memiliki salah satu ekstensi berikut: :values.',
'file' => ':attribute harus berupa sebuah berkas.',
'filled' => ':attribute harus memiliki nilai.',
'gt' => [
'array' => ':attribute harus memiliki lebih dari :value anggota.',
'file' => ':attribute harus berukuran lebih dari :value kilobita.',
'numeric' => ':attribute harus bernilai lebih dari :value.',
'string' => ':attribute harus berisi lebih dari :value karakter.',
],
'gte' => [
'array' => ':attribute harus memiliki :value anggota atau lebih.',
'file' => ':attribute harus berukuran lebih dari atau sama dengan :value kilobita.',
'numeric' => ':attribute harus bernilai lebih dari atau sama dengan :value.',
'string' => ':attribute harus berisi lebih dari atau sama dengan :value karakter.',
],
'hex_color' => ':attribute harus berupa warna heksadesimal yang valid.',
'image' => ':attribute harus berupa gambar.',
'in' => ':attribute yang dipilih tidak valid.',
'in_array' => ':attribute tidak ada di dalam :other.',
'integer' => ':attribute harus berupa bilangan bulat.',
'ip' => ':attribute harus berupa alamat IP yang valid.',
'ipv4' => ':attribute harus berupa alamat IPv4 yang valid.',
'ipv6' => ':attribute harus berupa alamat IPv6 yang valid.',
'json' => ':attribute harus berupa string JSON yang valid.',
'list' => ':attribute harus berupa daftar.',
'lowercase' => ':attribute harus berupa huruf kecil.',
'lt' => [
'array' => ':attribute harus memiliki kurang dari :value anggota.',
'file' => ':attribute harus berukuran kurang dari :value kilobita.',
'numeric' => ':attribute harus bernilai kurang dari :value.',
'string' => ':attribute harus berisi kurang dari :value karakter.',
],
'lte' => [
'array' => ':attribute tidak boleh memiliki lebih dari :value anggota.',
'file' => ':attribute harus berukuran kurang dari atau sama dengan :value kilobita.',
'numeric' => ':attribute harus bernilai kurang dari atau sama dengan :value.',
'string' => ':attribute harus berisi kurang dari atau sama dengan :value karakter.',
],
'mac_address' => ':attribute harus berupa alamat MAC yang valid.',
'max' => [
'array' => ':attribute maksimal terdiri dari :max anggota.',
'file' => ':attribute maksimal berukuran :max kilobita.',
'numeric' => ':attribute maksimal bernilai :max.',
'string' => ':attribute maksimal berisi :max karakter.',
],
'max_digits' => ':attribute tidak boleh memiliki lebih dari :max digit.',
'mimes' => ':attribute harus berupa berkas berjenis: :values.',
'mimetypes' => ':attribute harus berupa berkas berjenis: :values.',
'min' => [
'array' => ':attribute minimal terdiri dari :min anggota.',
'file' => ':attribute minimal berukuran :min kilobita.',
'numeric' => ':attribute minimal bernilai :min.',
'string' => ':attribute minimal berisi :min karakter.',
],
'min_digits' => ':attribute harus memiliki setidaknya :min digit.',
'missing' => ':attribute harus tidak ada.',
'missing_if' => ':attribute harus tidak ada ketika :other adalah :value.',
'missing_unless' => ':attribute harus tidak ada kecuali :other adalah :value.',
'missing_with' => ':attribute harus tidak ada ketika :values ada.',
'missing_with_all' => ':attribute harus tidak ada ketika :values ada.',
'multiple_of' => ':attribute harus merupakan kelipatan dari :value.',
'not_in' => ':attribute yang dipilih tidak valid.',
'not_regex' => 'Format :attribute tidak valid.',
'numeric' => ':attribute harus berupa angka.',
'password' => [
'letters' => ':attribute harus berisi setidaknya satu huruf.',
'mixed' => ':attribute harus berisi setidaknya satu huruf besar dan satu huruf kecil.',
'numbers' => ':attribute harus berisi setidaknya satu angka.',
'symbols' => ':attribute harus berisi setidaknya satu simbol.',
'uncompromised' => ':attribute yang diberikan telah muncul dalam kebocoran data. Silakan pilih :attribute yang berbeda.',
],
'present' => ':attribute harus ada.',
'present_if' => ':attribute harus ada ketika :other adalah :value.',
'present_unless' => ':attribute harus ada kecuali :other adalah :value.',
'present_with' => ':attribute harus ada ketika :values ada.',
'present_with_all' => ':attribute harus ada ketika :values ada.',
'prohibited' => ':attribute dilarang.',
'prohibited_if' => ':attribute dilarang ketika :other adalah :value.',
'prohibited_unless' => ':attribute dilarang kecuali :other adalah :value.',
'prohibits' => ':attribute melarang :other dari ada.',
'regex' => 'Format :attribute tidak valid.',
'required' => ':attribute wajib diisi.',
'required_array_keys' => ':attribute harus memiliki entri untuk: :values.',
'required_if' => ':attribute wajib diisi ketika :other adalah :value.',
'required_if_accepted' => ':attribute wajib diisi ketika :other diterima.',
'required_if_declined' => ':attribute wajib diisi ketika :other ditolak.',
'required_unless' => ':attribute wajib diisi kecuali :other memiliki nilai :values.',
'required_with' => ':attribute wajib diisi ketika :values ada.',
'required_with_all' => ':attribute wajib diisi ketika :values ada.',
'required_without' => ':attribute wajib diisi ketika :values tidak ada.',
'required_without_all' => ':attribute wajib diisi ketika tidak ada :values yang ada.',
'same' => ':attribute dan :other harus sama.',
'size' => [
'array' => ':attribute harus mengandung :size anggota.',
'file' => ':attribute harus berukuran :size kilobita.',
'numeric' => ':attribute harus berukuran :size.',
'string' => ':attribute harus berukuran :size karakter.',
],
'starts_with' => ':attribute harus diawali dengan salah satu dari: :values.',
'string' => ':attribute harus berupa string.',
'timezone' => ':attribute harus berisi zona waktu yang valid.',
'unique' => ':attribute sudah ada sebelumnya.',
'uploaded' => ':attribute gagal diunggah.',
'uppercase' => ':attribute harus berupa huruf besar.',
'url' => ':attribute harus berupa URL yang valid.',
'ulid' => ':attribute harus berupa ULID yang valid.',
'uuid' => ':attribute harus berupa UUID yang valid.',
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
'attributes' => [
'judul' => 'Judul Buku',
'penulis' => 'Penulis',
'category_id' => 'Kategori',
'tahun' => 'Tahun Terbit',
'kode_buku' => 'Kode Buku',
'stok' => 'Stok',
'tipe_akses' => 'Tipe Akses',
'cover' => 'Cover Buku',
'file_pdf' => 'File PDF',
'nama_lengkap' => 'Nama Lengkap',
'nama_pemilik' => 'Nama Pemilik',
'nomor_induk' => 'Nomor Induk',
'phone' => 'Nomor Telepon',
'title' => 'Judul',
'content' => 'Konten',
'type' => 'Tipe',
'icon' => 'Ikon',
'kategori' => 'Kategori',
],
];

View File

@ -18,52 +18,59 @@
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label for="judul" class="form-label">Judul Buku</label>
<label for="judul" class="form-label">Judul Buku <span class="text-danger">*</span></label>
<input type="text" name="judul" class="form-control @error('judul') is-invalid @enderror" id="judul"
placeholder="Masukkan judul buku" value="{{ old('judul') }}" minlength="3" maxlength="255" required>
placeholder="Masukkan judul buku" value="{{ old('judul') }}" required minlength="3" maxlength="50">
@error('judul')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="penulis" class="form-label">Penulis</label>
<label for="penulis" class="form-label">Penulis <span class="text-danger">*</span></label>
<input type="text" name="penulis" class="form-control @error('penulis') is-invalid @enderror" id="penulis"
placeholder="Masukkan nama penulis" value="{{ old('penulis') }}" minlength="3" maxlength="100" required>
placeholder="Masukkan nama penulis" value="{{ old('penulis') }}" required minlength="3" maxlength="50" pattern="[a-zA-Z\s.,'\']+">
@error('penulis')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="row">
<div class="col-md-3 mb-3">
<label for="category_id" class="form-label">Kategori</label>
<select name="category_id" class="form-select" id="category_id" required>
<label for="category_id" class="form-label">Kategori <span class="text-danger">*</span></label>
<select name="category_id" class="form-select @error('category_id') is-invalid @enderror" id="category_id" required>
<option value="" disabled selected>Pilih Kategori</option>
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
<option value="{{ $category->id }}" {{ old('category_id') == $category->id ? 'selected' : '' }}>{{ $category->name }}</option>
@endforeach
</select>
@error('category_id')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-3 mb-3">
<label for="tahun" class="form-label">Tahun Terbit</label>
<select name="tahun" class="form-select" id="tahun" required>
<label for="tahun" class="form-label">Tahun Terbit <span class="text-danger">*</span></label>
<select name="tahun" class="form-select @error('tahun') is-invalid @enderror" id="tahun" required>
<option value="" disabled selected>Pilih Tahun</option>
@for ($year = date('Y'); $year >= 1500; $year--)
<option value="{{ $year }}">{{ $year }}</option>
@for ($year = date('Y') + 1; $year >= 1500; $year--)
<option value="{{ $year }}" {{ old('tahun') == $year ? 'selected' : '' }}>{{ $year }}</option>
@endfor
</select>
@error('tahun')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-3 mb-3">
<label for="kode_buku" class="form-label">Kode Buku</label>
<input type="text" name="kode_buku" class="form-control @error('kode_buku') is-invalid @enderror" id="kode_buku"
value="{{ old('kode_buku') }}" placeholder="Contoh: 330" maxlength="10">
value="{{ old('kode_buku') }}" placeholder="Contoh: 330" maxlength="10"
inputmode="numeric" oninput="this.value = this.value.replace(/[^0-9]/g, '')">
@error('kode_buku')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-3 mb-3">
<label for="stok" class="form-label">Stok</label>
<input type="text" name="stok" class="form-control @error('stok') is-invalid @enderror" id="stok"
value="{{ old('stok', 1) }}" min="0" max="99" maxlength="2" required oninput="this.value = this.value.replace(/[^0-9]/g, '')">
<label for="stok" class="form-label">Stok <span class="text-danger">*</span></label>
<input type="number" name="stok" class="form-control @error('stok') is-invalid @enderror" id="stok"
value="{{ old('stok', 1) }}" required min="0" max="99" oninput="if(this.value.length > 2) this.value = this.value.slice(0, 2); this.value = this.value.replace(/[^0-9]/g, '')">
@error('stok')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
@ -76,24 +83,33 @@
<label class="form-label">Tipe Akses</label>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="tipe_akses[]" value="offline" id="tipe_offline">
<input class="form-check-input @error('tipe_akses') is-invalid @enderror" type="checkbox" name="tipe_akses[]" value="offline" id="tipe_offline" {{ is_array(old('tipe_akses')) && in_array('offline', old('tipe_akses')) ? 'checked' : '' }}>
<label class="form-check-label" for="tipe_offline">Peminjaman
Offline</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="tipe_akses[]" value="online" id="tipe_online">
<input class="form-check-input @error('tipe_akses') is-invalid @enderror" type="checkbox" name="tipe_akses[]" value="online" id="tipe_online" {{ is_array(old('tipe_akses')) && in_array('online', old('tipe_akses')) ? 'checked' : '' }}>
<label class="form-check-label" for="tipe_online">Baca Online</label>
</div>
</div>
@error('tipe_akses')
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="cover" class="form-label">Cover Buku</label>
<input type="file" name="cover" class="form-control" id="cover" accept="image/*">
<input type="file" name="cover" class="form-control @error('cover') is-invalid @enderror" id="cover" accept="image/*">
@error('cover')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="file_pdf" class="form-label">File PDF (untuk buku online)</label>
<input type="file" name="file_pdf" class="form-control" id="file_pdf" accept=".pdf">
<input type="file" name="file_pdf" class="form-control @error('file_pdf') is-invalid @enderror" id="file_pdf" accept=".pdf">
@error('file_pdf')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
@ -113,24 +129,7 @@
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// LOGIC SIMPAN
document.getElementById('formTambahBuku').addEventListener('submit', function(e) {
e.preventDefault();
modernSwal.fire({
title: 'Menyimpan...',
timer: 1500,
didOpen: () => Swal.showLoading()
}).then(() => {
Toast.fire({
icon: 'success',
title: 'Berhasil',
text: 'Buku baru berhasil ditambahkan.'
});
setTimeout(() => {
window.location.href = "{{ route('admin.buku.index') }}";
}, 1500);
});
});
// Script removed to allow standard server-side validation
});
</script>
@endpush

View File

@ -19,46 +19,52 @@
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label for="judul" class="form-label">Judul Buku</label>
<label for="judul" class="form-label">Judul Buku <span class="text-danger">*</span></label>
<input type="text" name="judul" class="form-control @error('judul') is-invalid @enderror" id="judul"
value="{{ old('judul', $buku->judul) }}" minlength="3" maxlength="255" required>
value="{{ old('judul', $buku->judul) }}" required minlength="3" maxlength="50">
@error('judul')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="penulis" class="form-label">Penulis</label>
<label for="penulis" class="form-label">Penulis <span class="text-danger">*</span></label>
<input type="text" name="penulis" class="form-control @error('penulis') is-invalid @enderror" id="penulis"
value="{{ old('penulis', $buku->penulis) }}" minlength="3" maxlength="100" required>
value="{{ old('penulis', $buku->penulis) }}" required minlength="3" maxlength="50" pattern="[a-zA-Z\s.,'\']+">
@error('penulis')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="category_id" class="form-label">Kategori</label>
<select name="category_id" class="form-select" id="category_id" required>
<label for="category_id" class="form-label">Kategori <span class="text-danger">*</span></label>
<select name="category_id" class="form-select @error('category_id') is-invalid @enderror" id="category_id" required>
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ old('category_id', $buku->category_id) == $category->id ? 'selected' : '' }}>
{{ $category->name }}
</option>
@endforeach
</select>
@error('category_id')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4 mb-3">
<label for="tahun" class="form-label">Tahun Terbit</label>
<select name="tahun" class="form-select" id="tahun" required>
@for ($year = date('Y'); $year >= 1500; $year--)
<label for="tahun" class="form-label">Tahun Terbit <span class="text-danger">*</span></label>
<select name="tahun" class="form-select @error('tahun') is-invalid @enderror" id="tahun" required>
@for ($year = date('Y') + 1; $year >= 1500; $year--)
<option value="{{ $year }}" {{ old('tahun', $buku->tahun) == $year ? 'selected' : '' }}>
{{ $year }}
</option>
@endfor
</select>
@error('tahun')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4 mb-3">
<label for="stok" class="form-label">Stok</label>
<input type="text" name="stok" class="form-control @error('stok') is-invalid @enderror" id="stok"
value="{{ old('stok', $buku->stok ?? 1) }}" min="0" max="99" maxlength="2" required oninput="this.value = this.value.replace(/[^0-9]/g, '')">
<label for="stok" class="form-label">Stok <span class="text-danger">*</span></label>
<input type="number" name="stok" class="form-control @error('stok') is-invalid @enderror" id="stok"
value="{{ old('stok', $buku->stok ?? 1) }}" required min="0" max="99" oninput="if(this.value.length > 2) this.value = this.value.slice(0, 2); this.value = this.value.replace(/[^0-9]/g, '')">
@error('stok')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
@ -76,7 +82,8 @@
<div class="mb-3" id="kodeBukuWrapper">
<label for="kode_buku" class="form-label">Kode Buku</label>
<input type="text" name="kode_buku" class="form-control @error('kode_buku') is-invalid @enderror" id="kode_buku"
value="{{ old('kode_buku', $buku->kode_buku) }}" placeholder="Masukkan kode buku" maxlength="10">
value="{{ old('kode_buku', $buku->kode_buku) }}" placeholder="Masukkan kode buku" maxlength="10"
inputmode="numeric" oninput="this.value = this.value.replace(/[^0-9]/g, '')">
@error('kode_buku')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
@ -85,29 +92,38 @@
<div class="mb-3">
<label class="form-label">Tipe Akses</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="tipe_akses[]" value="offline" id="tipe_offline"
@if (in_array('offline', $tipe_akses)) checked @endif>
<input class="form-check-input @error('tipe_akses') is-invalid @enderror" type="checkbox" name="tipe_akses[]" value="offline" id="tipe_offline"
@if (in_array('offline', old('tipe_akses', $tipe_akses))) checked @endif>
<label class="form-check-label" for="tipe_offline">Peminjaman
Offline</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="tipe_akses[]" value="online" id="tipe_online"
@if (in_array('online', $tipe_akses)) checked @endif>
<input class="form-check-input @error('tipe_akses') is-invalid @enderror" type="checkbox" name="tipe_akses[]" value="online" id="tipe_online"
@if (in_array('online', old('tipe_akses', $tipe_akses))) checked @endif>
<label class="form-check-label" for="tipe_online">Baca Online</label>
</div>
@error('tipe_akses')
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="cover" class="form-label">Cover Buku</label>
<input type="file" name="cover" class="form-control" id="cover" accept="image/*">
<input type="file" name="cover" class="form-control @error('cover') is-invalid @enderror" id="cover" accept="image/*">
@error('cover')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<img src="{{ asset($buku->cover) }}" alt="Cover saat ini"
class="img-thumbnail mt-2" width="150">
</div>
<div class="mb-3">
<label for="file_pdf" class="form-label">Update File PDF (Opsional)</label>
<input type="file" name="file_pdf" class="form-control" id="file_pdf" accept=".pdf">
<input type="file" name="file_pdf" class="form-control @error('file_pdf') is-invalid @enderror" id="file_pdf" accept=".pdf">
@error('file_pdf')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
@ -124,24 +140,8 @@ class="img-thumbnail mt-2" width="150">
@push('scripts')
<script>
document.getElementById('formEditBuku').addEventListener('submit', function(e) {
e.preventDefault();
modernSwal.fire({
title: 'Menyimpan Perubahan...',
timer: 800,
didOpen: () => Swal.showLoading()
}).then(() => {
Toast.fire({
icon: 'success',
title: 'Berhasil',
text: 'Data buku berhasil diperbarui.'
});
setTimeout(() => {
window.location.href = "{{ route('admin.buku.index') }}";
}, 1500);
});
document.addEventListener('DOMContentLoaded', function() {
// Script removed to allow standard server-side validation
});
</script>
@endpush

View File

@ -9,17 +9,6 @@
</div>
<form action="{{ route('admin.peminjaman.store') }}" method="POST" id="formPeminjaman" autocomplete="off"> @csrf
@if ($errors->any())
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<strong>Terjadi kesalahan:</strong>
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
<div class="row g-4">
<div class="col-lg-7">
@ -28,25 +17,26 @@
<h5 class="fw-bold mb-3">Data Peminjaman</h5>
<div class="mb-3">
<label for="peminjam_id" class="form-label">Pilih Peminjam (Siswa/Guru)</label>
<select class="form-control" id="peminjam_id" name="peminjam_id" placeholder="Cari nama atau email...">
<label for="peminjam_id" class="form-label">Pilih Peminjam (Siswa/Guru) <span class="text-danger">*</span></label>
<select class="form-control @error('peminjam_id') is-invalid @enderror" id="peminjam_id" name="peminjam_id" placeholder="Cari nama atau email...">
<option value="">Pilih...</option>
@foreach ($groupedUsers as $role => $users)
<optgroup label="{{ ucfirst($role) }}">
@foreach ($users as $user)
<option value="{{ $user['id'] }}"
data-role="{{ strtolower($role) }}"
data-identitas="{{ $user['nip'] ?? $user['nisn'] ?? '-' }}"
data-kontak="{{ $user['no_hp'] ?? '-' }}"
data-kontak="{{ $user['phone'] ?? '-' }}"
data-info="{{ strtolower($role) == 'guru' ? ($user['jabatan'] ?? 'Guru') : ($user['kelas'] ?? 'Siswa') }}"
{{ $user['disabled'] ? 'disabled' : '' }}>
{{ $user['nama_lengkap'] }} {{ $user['status_text'] }}
{{ $user['disabled'] ? 'disabled' : '' }}
{{ old('peminjam_id') == $user['id'] ? 'selected' : '' }}>
{{ $user['name'] }} {{ $user['status_text'] }}
</option>
@endforeach
</optgroup>
@endforeach
</select>
@error('peminjam_id') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div id="detailPeminjamCard" class="alert alert-secondary d-none mb-3">
@ -61,12 +51,18 @@
<div class="row g-3">
<div class="col-md-6">
<label for="tanggal_pinjam" class="form-label">Tanggal Pinjam</label>
<input type="text" class="form-control" id="tanggal_pinjam" name="tanggal_pinjam" placeholder="Pilih tanggal pinjam">
<label for="tanggal_pinjam" class="form-label">Tanggal Pinjam <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('tanggal_pinjam') is-invalid @enderror"
id="tanggal_pinjam" name="tanggal_pinjam" value="{{ old('tanggal_pinjam') }}"
placeholder="Pilih tanggal pinjam" required readonly>
@error('tanggal_pinjam') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="col-md-6">
<label for="tanggal_kembali" class="form-label">Tenggat Kembali</label>
<input type="text" class="form-control" id="tanggal_kembali" name="tanggal_kembali" placeholder="Pilih tenggat kembali">
<label for="tanggal_kembali" class="form-label">Tenggat Kembali <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('tanggal_kembali') is-invalid @enderror"
id="tanggal_kembali" name="tanggal_kembali" value="{{ old('tanggal_kembali') }}"
placeholder="Pilih tenggat kembali" required readonly>
@error('tanggal_kembali') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
</div>
@ -77,6 +73,12 @@
<span class="badge bg-primary-soft" id="counterBuku">0 Buku</span>
</div>
@error('buku_ids')
<div class="alert alert-danger py-2 small">
<i class="bi bi-exclamation-triangle me-1"></i> {{ $message }}
</div>
@enderror
<div id="daftarBukuPinjam" class="mb-3">
<div id="emptyStateBuku" class="text-center text-muted py-4">
<i class="bi bi-collection fs-1" style="opacity: 0.25;"></i>
@ -145,7 +147,6 @@
create: false,
sortField: { field: "text", direction: "asc" },
onChange: function(value) {
// Munculkan Kartu Detail Peminjam saat Dropdown Berubah
const selectedOption = this.options[value];
const detailCard = document.getElementById('detailPeminjamCard');
@ -162,14 +163,14 @@
});
const tglPinjam = flatpickr("#tanggal_pinjam", {
dateFormat: "Y-m-d", altInput: true, altFormat: "d/m/Y", defaultDate: "today", locale: "id",
dateFormat: "Y-m-d", altInput: true, altFormat: "d/m/Y", defaultDate: "{{ old('tanggal_pinjam', date('Y-m-d')) }}", locale: "id",
onChange: function(selectedDates, dateStr) {
if (selectedDates.length > 0) tglKembali.set("minDate", new Date(selectedDates[0]).fp_incr(1));
}
});
const tglKembali = flatpickr("#tanggal_kembali", {
dateFormat: "Y-m-d", altInput: true, altFormat: "d/m/Y", defaultDate: new Date().fp_incr(7), locale: "id", minDate: new Date().fp_incr(1)
dateFormat: "Y-m-d", altInput: true, altFormat: "d/m/Y", defaultDate: "{{ old('tanggal_kembali', date('Y-m-d', strtotime('+7 days'))) }}", locale: "id", minDate: new Date().fp_incr(1)
});
const MAX_BOOKS_SISWA = 2;
@ -209,7 +210,6 @@ function renderSelectedBooks() {
counterBukuEl.textContent = `${selectedBookIds.size} Buku`;
}
// Global function untuk toggle selection
window.toggleBookSelection = function(itemElement, id) {
const selectEl = document.getElementById('peminjam_id');
if(selectEl.value === "") {
@ -250,7 +250,6 @@ function renderSelectedBooks() {
renderSelectedBooks();
}
// Pencarian
document.getElementById('searchBuku').addEventListener('keyup', function() {
const searchTerm = this.value.toLowerCase();
document.querySelectorAll('.book-option').forEach(el => {
@ -260,55 +259,26 @@ function renderSelectedBooks() {
});
});
document.getElementById('formPeminjaman').addEventListener('submit', function(e) {
e.preventDefault();
const peminjam = document.getElementById('peminjam_id').value;
const jumlahBuku = selectedBookIds.size;
// Validasi
if (!peminjam) {
Toast.fire({ icon: 'warning', title: 'Perhatian', text: 'Pilih peminjam terlebih dahulu!' });
return;
}
if (jumlahBuku === 0) {
Toast.fire({ icon: 'warning', title: 'Perhatian', text: 'Pilih minimal 1 buku!' });
return;
}
document.getElementById('formPeminjaman').addEventListener('submit', function() {
modernSwal.fire({
title: 'Proses Peminjaman?',
text: `Anda akan memproses ${jumlahBuku} buku.`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Ya, Proses',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
// Loading
modernSwal.fire({
title: 'Memproses...',
timer: 1000,
didOpen: () => Swal.showLoading()
}).then(() => {
Toast.fire({
icon: 'success',
title: 'Berhasil',
text: 'Transaksi peminjaman berhasil disimpan.'
});
setTimeout(() => {
window.location.href = "{{ route('admin.peminjaman.index') }}";
}, 1500);
});
}
title: 'Memproses...',
allowOutsideClick: false,
didOpen: () => Swal.showLoading()
});
});
document.getElementById('formPeminjaman').reset();
tomSelect.clear();
selectedBookIds.clear();
renderSelectedBooks();
// Restore old state if any
@if(old('buku_ids'))
@foreach(old('buku_ids') as $oldId)
selectedBookIds.add("{{ $oldId }}");
const oldItem = document.querySelector(`.book-option[data-book-id="{{ $oldId }}"] .book-item-list`);
if(oldItem) {
oldItem.querySelector('.book-checkbox').checked = true;
oldItem.style.background = 'rgba(var(--bs-primary-rgb), 0.05)';
}
@endforeach
renderSelectedBooks();
@endif
});
</script>
@endpush

View File

@ -10,7 +10,9 @@
<form action="{{ route('admin.peminjaman.export') }}" method="GET" class="d-flex m-0 p-0 bg-white border rounded p-1">
<div class="input-group input-group-sm">
<span class="input-group-text bg-transparent border-0 text-muted small">Filter Bulan:</span>
<input type="date" name="bulan_laporan" class="form-control border-0" title="Pilih tanggal untuk filter bulan (Opsional)">
<input type="date" name="bulan_laporan" class="form-control border-0 @error('bulan_laporan') is-invalid @enderror"
title="Pilih tanggal untuk filter bulan" required
min="1900-01-01" max="{{ date('Y-m-d') }}">
<button type="submit" class="btn btn-success text-nowrap">
<i class="bi bi-file-earmark-excel-fill me-1"></i>Excel
</button>

View File

@ -27,44 +27,47 @@
<label for="role" class="form-label">Role <span class="text-danger">*</span></label>
@if($prefilledData)
<input type="hidden" name="role" value="{{ $prefilledData->role }}">
<select class="form-select" id="role" disabled>
<select class="form-select @error('role') is-invalid @enderror" id="role" disabled>
<option value="siswa" {{ $prefilledData->role == 'siswa' ? 'selected' : '' }}>Siswa</option>
<option value="guru" {{ $prefilledData->role == 'guru' ? 'selected' : '' }}>Guru</option>
<option value="penjaga perpus" {{ $prefilledData->role == 'penjaga perpus' ? 'selected' : '' }}>Penjaga Perpustakaan</option>
</select>
@else
<select class="form-select @error('role') is-invalid @enderror" id="role" name="role"
required onchange="toggleFields()">
onchange="toggleFields()">
<option value="" selected disabled>Pilih role terlebih dahulu...</option>
<option value="siswa" {{ old('role')=='siswa' ? 'selected' : '' }}>Siswa</option>
<option value="guru" {{ old('role')=='guru' ? 'selected' : '' }}>Guru</option>
<option value="penjaga perpus" {{ old('role')=='penjaga perpus' ? 'selected' : '' }}>
Penjaga Perpustakaan</option>
</select>
@endif
@error('role')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
@endif
</div>
{{-- Bagian Form Dinamis --}}
<div id="dynamic-form" class="{{ $prefilledData ? '' : 'd-none' }}">
<div id="dynamic-form" class="{{ ($prefilledData || old('role')) ? '' : 'd-none' }}">
<div class="mb-3">
<label for="nama_lengkap" class="form-label">Nama Lengkap</label>
<label for="nama_lengkap" class="form-label">Nama Lengkap <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('nama_lengkap') is-invalid @enderror"
id="nama_lengkap" name="nama_lengkap" value="{{ old('nama_lengkap', $prefilledData->nama_pemilik ?? '') }}" required
{{ $prefilledData ? 'readonly' : '' }}>
id="nama_lengkap" name="nama_lengkap" value="{{ old('nama_lengkap', $prefilledData->nama_pemilik ?? '') }}"
{{ $prefilledData ? 'readonly' : '' }} required minlength="3" maxlength="50" pattern="[a-zA-Z\s.,'\']+">
@error('nama_lengkap')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<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 <span class="text-danger">*</span></label>
@if($prefilledData)
<input type="hidden" name="nomor_induk" value="{{ $prefilledData->nomor_induk }}">
@endif
<input type="number" class="form-control @error('nomor_induk') is-invalid @enderror"
<input type="text" class="form-control @error('nomor_induk') is-invalid @enderror"
id="nomor_induk" name="{{ $prefilledData ? '' : 'nomor_induk' }}" value="{{ old('nomor_induk', $prefilledData->nomor_induk ?? '') }}"
placeholder="Masukkan Nomor Induk" {{ $prefilledData ? 'readonly' : '' }}>
placeholder="Masukkan Nomor Induk" {{ $prefilledData ? 'readonly' : '' }} required maxlength="15">
@error('nomor_induk')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
@ -72,7 +75,7 @@
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email</label>
<label for="email" class="form-label">Email <span class="text-danger">*</span></label>
<input type="email" class="form-control @error('email') is-invalid @enderror"
id="email" name="email" value="{{ old('email') }}" required>
@error('email')
@ -80,22 +83,32 @@
@enderror
</div>
<div class="col-md-6 mb-3">
<label for="no_hp" class="form-label">No. Handphone</label>
<input type="text" class="form-control" id="no_hp" name="phone"
value="{{ old('phone') }}" placeholder="Contoh: 08123456789">
<label for="no_hp" class="form-label">Nomor Telepon</label>
<input type="text" class="form-control @error('phone') is-invalid @enderror" id="no_hp" name="phone"
value="{{ old('phone') }}" placeholder="Contoh: 08123456789"
minlength="13" maxlength="15" pattern="\+?[0-9]+">
@error('phone')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row" id="field_siswa_only" style="{{ ($prefilledData && $prefilledData->role == 'siswa') || old('role') == 'siswa' ? 'display: flex;' : 'display: none;' }}">
<div class="col-md-6 mb-3">
<label for="kelas" class="form-label">Kelas</label>
<input type="text" class="form-control" id="kelas" name="kelas"
value="{{ old('kelas') }}" placeholder="Contoh: XII RPL 1">
<input type="text" class="form-control @error('kelas') is-invalid @enderror" id="kelas" name="kelas"
value="{{ old('kelas') }}" placeholder="Contoh: XII RPL 1" maxlength="50">
@error('kelas')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label for="golongan" class="form-label">Golongan</label>
<input type="text" class="form-control" id="golongan" name="golongan"
value="{{ old('golongan') }}" placeholder="Contoh: A/B">
<input type="text" class="form-control @error('golongan') is-invalid @enderror" id="golongan" name="golongan"
value="{{ old('golongan') }}" placeholder="Contoh: A/B" maxlength="50">
@error('golongan')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
@ -103,11 +116,11 @@
<div class="row">
<div class="col-md-6 mb-3">
<label for="password" class="form-label">Password</label>
<label for="password" class="form-label">Password <span class="text-danger">*</span></label>
<div class="input-group has-validation">
<input type="password"
class="form-control @error('password') is-invalid @enderror" id="password"
name="password">
name="password" required minlength="8">
<button class="btn btn-outline-secondary toggle-password" type="button"
data-target="password">
<i class="bi bi-eye" id="icon-password"></i>
@ -119,10 +132,10 @@ class="form-control @error('password') is-invalid @enderror" id="password"
</div>
<div class="col-md-6 mb-3">
<label for="password_confirmation" class="form-label">Konfirmasi Password</label>
<label for="password_confirmation" class="form-label">Konfirmasi Password <span class="text-danger">*</span></label>
<div class="input-group">
<input type="password" class="form-control" id="password_confirmation"
name="password_confirmation">
name="password_confirmation" required minlength="8">
<button class="btn btn-outline-secondary toggle-password" type="button"
data-target="password_confirmation">
<i class="bi bi-eye" id="icon-password_confirmation"></i>
@ -188,21 +201,7 @@ function toggleFields() {
});
});
document.getElementById('formPengguna').addEventListener('submit', function() {
modernSwal.fire({
title: 'Menyimpan Data...',
allowOutsideClick: false,
didOpen: () => Swal.showLoading()
});
});
<?php if(session('success')): ?>
Toast.fire({
icon: 'success',
title: 'Berhasil',
text: '<?php echo session("success"); ?>'
});
<?php endif; ?>
// Form submission listener removed to allow server-side validation
</script>
@endpush
</x-app-layout>

View File

@ -17,7 +17,7 @@
<div class="mb-3">
<label for="role" class="form-label">Role <span class="text-danger">*</span></label>
<select class="form-select @error('role') is-invalid @enderror" id="role" name="role" required onchange="toggleFields()">
<select class="form-select @error('role') is-invalid @enderror" id="role" name="role" onchange="toggleFields()">
<option value="siswa" {{ old('role', $pengguna->role) == 'siswa' ? 'selected' : '' }}>Siswa</option>
<option value="guru" {{ old('role', $pengguna->role) == 'guru' ? 'selected' : '' }}>Guru</option>
<option value="penjaga perpus" {{ old('role', $pengguna->role) == 'penjaga perpus' ? 'selected' : '' }}>Penjaga Perpustakaan</option>
@ -27,40 +27,47 @@
<div id="dynamic-form">
<div class="mb-3">
<label for="nama_lengkap" class="form-label">Nama Lengkap</label>
<label for="nama_lengkap" class="form-label">Nama Lengkap <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('nama_lengkap') is-invalid @enderror"
id="nama_lengkap" name="nama_lengkap" value="{{ old('nama_lengkap', $pengguna->name) }}" required>
id="nama_lengkap" name="nama_lengkap" value="{{ old('nama_lengkap', $pengguna->name) }}"
required minlength="3" maxlength="50" pattern="[a-zA-Z\s.,'\']+">
@error('nama_lengkap') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="mb-3">
@php $oldNomorInduk = old('nomor_induk', $pengguna->nomor_induk); @endphp
<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"
id="nomor_induk" name="nomor_induk" value="{{ $oldNomorInduk }}" placeholder="Masukkan Nomor Induk">
@error('nomor_induk') <div class="invalid-feedback fw-bold">{{ $message }}</div> @enderror
<label for="nomor_induk" class="form-label" id="label_nomor_induk">NISN / NIP <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('nomor_induk') is-invalid @enderror"
id="nomor_induk" name="nomor_induk" value="{{ old('nomor_induk', $pengguna->nomor_induk) }}"
placeholder="Masukkan Nomor Induk" required maxlength="15">
@error('nomor_induk') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email</label>
<label for="email" class="form-label">Email <span class="text-danger">*</span></label>
<input type="email" class="form-control @error('email') is-invalid @enderror"
id="email" name="email" value="{{ old('email', $pengguna->email) }}" required>
@error('email') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="col-md-6 mb-3">
<label for="no_hp" class="form-label">No. Handphone</label>
<input type="text" class="form-control" id="phone" name="phone" value="{{ old('phone', $pengguna->phone) }}">
<input type="text" class="form-control @error('phone') is-invalid @enderror" id="phone" name="phone"
value="{{ old('phone', $pengguna->phone) }}"
minlength="13" maxlength="15" pattern="\+?[0-9]+">
@error('phone') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
</div>
<div class="row" id="field_siswa_only" style="display: none;">
<div class="col-md-6 mb-3">
<label for="kelas" class="form-label">Kelas</label>
<input type="text" class="form-control" id="kelas" name="kelas" value="{{ old('kelas', $pengguna->kelas) }}">
<input type="text" class="form-control @error('kelas') is-invalid @enderror" id="kelas" name="kelas" value="{{ old('kelas', $pengguna->kelas) }}" maxlength="50">
@error('kelas') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="col-md-6 mb-3">
<label for="golongan" class="form-label">Golongan</label>
<input type="text" class="form-control" id="golongan" name="golongan" value="{{ old('golongan', $pengguna->golongan) }}">
<input type="text" class="form-control @error('golongan') is-invalid @enderror" id="golongan" name="golongan" value="{{ old('golongan', $pengguna->golongan) }}" maxlength="50">
@error('golongan') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
</div>
@ -69,12 +76,22 @@
<div class="row">
<div class="col-md-6 mb-3">
<label for="password" class="form-label">Password Baru <span class="small text-muted">(opsional)</span></label>
<input type="password" class="form-control @error('password') is-invalid @enderror" id="password" name="password" placeholder="Kosongkan jika tidak diubah">
@error('password') <div class="invalid-feedback">{{ $message }}</div> @enderror
<div class="input-group">
<input type="password" class="form-control @error('password') is-invalid @enderror" id="password" name="password" placeholder="Kosongkan jika tidak diubah" minlength="8">
<button class="btn btn-outline-secondary toggle-password" type="button" data-target="password">
<i class="bi bi-eye" id="icon-password"></i>
</button>
@error('password') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
</div>
<div class="col-md-6 mb-3">
<label for="password_confirmation" class="form-label">Konfirmasi Password Baru</label>
<input type="password" class="form-control" id="password_confirmation" name="password_confirmation" placeholder="Ulangi password baru">
<div class="input-group">
<input type="password" class="form-control" id="password_confirmation" name="password_confirmation" placeholder="Ulangi password baru" minlength="8">
<button class="btn btn-outline-secondary toggle-password" type="button" data-target="password_confirmation">
<i class="bi bi-eye" id="icon-password_confirmation"></i>
</button>
</div>
</div>
</div>
@ -112,21 +129,23 @@ function toggleFields() {
}
document.addEventListener('DOMContentLoaded', toggleFields);
document.getElementById('formEditPengguna').addEventListener('submit', function() {
modernSwal.fire({
title: 'Menyimpan Perubahan...',
allowOutsideClick: false,
didOpen: () => Swal.showLoading()
document.querySelectorAll('.toggle-password').forEach(function(button) {
button.addEventListener('click', function() {
const targetId = this.getAttribute('data-target');
const input = document.getElementById(targetId);
const icon = document.getElementById('icon-' + targetId);
if (input.type === 'password') {
input.type = 'text';
icon.classList.replace('bi-eye', 'bi-eye-slash');
} else {
input.type = 'password';
icon.classList.replace('bi-eye-slash', 'bi-eye');
}
});
});
<?php if(session('success')): ?>
Toast.fire({
icon: 'success',
title: 'Berhasil',
text: '<?php echo session("success"); ?>'
});
<?php endif; ?>
// Form submission listener removed to allow server-side validation
</script>
@endpush
</x-app-layout>

View File

@ -53,10 +53,10 @@
@endphp
@if ($isRegistered)
<span class="badge bg-success text-white"><i
class="bi bi-check-circle-fill me-1"></i>Terdaftar</span>
class="bi bi-check-circle-fill me-1"></i>Terdaftar</span>
@else
<span class="badge bg-warning text-dark"><i
class="bi bi-hourglass-split me-1"></i>Belum Daftar</span>
class="bi bi-hourglass-split me-1"></i>Belum Daftar</span>
@endif
</td>
<td class="text-center">
@ -200,7 +200,7 @@ class="form-delete-user" data-nama="{{ $user->name }}">
</div>
{{-- MODAL TAMBAH DATA INDUK --}}
<div class="modal fade" id="modalMasterInduk" tabindex="-1">
<div class="modal fade {{ $errors->hasAny(['role', 'nomor_induk', 'nama_pemilik']) ? 'show' : '' }}" id="modalMasterInduk" tabindex="-1" style="{{ $errors->hasAny(['role', 'nomor_induk', 'nama_pemilik']) ? 'display: block;' : '' }}">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@ -216,23 +216,26 @@ class="form-delete-user" data-nama="{{ $user->name }}">
Masukkan data siswa/guru yang valid agar mereka bisa mendaftar.
</div>
<div class="mb-3">
<label class="form-label">Role</label>
<select name="role" class="form-select" required>
<option value="siswa">Siswa</option>
<option value="guru">Guru</option>
<option value="penjaga perpus">Petugas Perpustakaan</option>
<label class="form-label">Role <span class="text-danger">*</span></label>
<select name="role" class="form-select @error('role') is-invalid @enderror" required>
<option value="siswa" {{ old('role') == 'siswa' ? 'selected' : '' }}>Siswa</option>
<option value="guru" {{ old('role') == 'guru' ? 'selected' : '' }}>Guru</option>
<option value="penjaga perpus" {{ old('role') == 'penjaga perpus' ? 'selected' : '' }}>Petugas Perpustakaan</option>
</select>
@error('role') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="mb-3">
<label class="form-label">NIP / NISN / NIK</label>
<input type="text" name="nomor_induk" class="form-control"
placeholder="Contoh: 1234567890" maxlength="16"
oninput="this.value = this.value.replace(/[^0-9]/g, '')" required>
<label class="form-label">NIP / NISN / NIK <span class="text-danger">*</span></label>
<input type="text" name="nomor_induk" class="form-control @error('nomor_induk') is-invalid @enderror"
placeholder="Contoh: 1234567890" maxlength="15" required
value="{{ old('nomor_induk') }}">
@error('nomor_induk') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="mb-3">
<label class="form-label">Nama Pemilik</label>
<input type="text" name="nama_pemilik" class="form-control" placeholder="Nama Siswa/Guru..."
required>
<label class="form-label">Nama Pemilik <span class="text-danger">*</span></label>
<input type="text" name="nama_pemilik" class="form-control @error('nama_pemilik') is-invalid @enderror"
placeholder="Nama Siswa/Guru..." value="{{ old('nama_pemilik') }}" required minlength="3" maxlength="50" pattern="[a-zA-Z\s.,'\']+">
@error('nama_pemilik') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
</div>
<div class="modal-footer">
@ -243,6 +246,9 @@ class="form-delete-user" data-nama="{{ $user->name }}">
</div>
</div>
</div>
@if($errors->hasAny(['role', 'nomor_induk', 'nama_pemilik']))
<div class="modal-backdrop fade show"></div>
@endif
@push('scripts')
<script>
@ -297,13 +303,7 @@ class="form-delete-user" data-nama="{{ $user->name }}">
});
});
$('#formWhitelist').on('submit', function() {
modernSwal.fire({
title: 'Menyimpan...',
allowOutsideClick: false,
didOpen: () => Swal.showLoading()
});
});
// Form submission listener removed to allow server-side validation
});
@if(session('success'))

View File

@ -15,27 +15,36 @@
<form action="{{ route('admin.pengumuman.store') }}" method="POST">
@csrf
<div class="mb-3">
<label for="title" class="form-label">Judul Pengumuman</label>
<input type="text" name="title" class="form-control" id="title"
placeholder="Masukkan judul pengumuman" required>
<label for="title" class="form-label">Judul Pengumuman <span class="text-danger">*</span></label>
<input type="text" name="title" class="form-control @error('title') is-invalid @enderror" id="title"
placeholder="Masukkan judul pengumuman" value="{{ old('title') }}" required minlength="3" maxlength="50">
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="type" class="form-label">Tipe Pengumuman</label>
<select name="type" class="form-select" id="type" required>
<option value="">Pilih tipe...</option>
<option value="info">Info</option>
<option value="success">Success</option>
<option value="warning">Warning</option>
<option value="danger">Danger</option>
<label for="type" class="form-label">Tipe Pengumuman <span class="text-danger">*</span></label>
<select name="type" class="form-select @error('type') is-invalid @enderror" id="type" required>
<option value="" disabled selected>Pilih tipe...</option>
<option value="info" {{ old('type') == 'info' ? 'selected' : '' }}>Info</option>
<option value="success" {{ old('type') == 'success' ? 'selected' : '' }}>Success</option>
<option value="warning" {{ old('type') == 'warning' ? 'selected' : '' }}>Warning</option>
<option value="danger" {{ old('type') == 'danger' ? 'selected' : '' }}>Danger</option>
</select>
@error('type')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="content" class="form-label">Isi Pengumuman</label>
<textarea name="content" class="form-control" id="content" rows="5" placeholder="Tulis isi pengumuman di sini..." required></textarea>
<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..." required minlength="3">{{ old('content') }}</textarea>
@error('content')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<hr>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Simpan Pengumuman</button>
<button type="submit" class="btn btn-primary px-4 fw-bold">Simpan Pengumuman</button>
</div>
</form>
</div>
@ -46,24 +55,8 @@
@push('scripts')
<script>
document.getElementById('formTambahPengumuman').addEventListener('submit', function(e) {
e.preventDefault();
modernSwal.fire({
title: 'Menyimpan...',
timer: 800,
didOpen: () => Swal.showLoading()
}).then(() => {
Toast.fire({
icon: 'success',
title: 'Berhasil',
text: 'Pengumuman baru berhasil ditambahkan.'
});
setTimeout(() => {
window.location.href = "{{ route('admin.pengumuman.index') }}";
}, 1500);
});
document.addEventListener('DOMContentLoaded', function() {
// Script removed to allow standard server-side validation
});
</script>
@endpush

View File

@ -16,31 +16,36 @@
@csrf
@method('PUT')
<div class="mb-3">
<label for="title" class="form-label">Judul Pengumuman</label>
<input type="text" name="title" class="form-control" id="title"
value="{{ old('title', $pengumuman->title) }}" required>
<label for="title" class="form-label">Judul Pengumuman <span class="text-danger">*</span></label>
<input type="text" name="title" class="form-control @error('title') is-invalid @enderror" id="title"
value="{{ old('title', $pengumuman->title) }}" required minlength="3" maxlength="50">
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="type" class="form-label">Tipe Pengumuman</label>
<select name="type" class="form-select" id="type" required>
<option value="">Pilih tipe...</option>
<option value="info" @if (old('type', $pengumuman->type) == 'info') selected @endif>Info
</option>
<option value="success" @if (old('type', $pengumuman->type) == 'success') selected @endif>Success
</option>
<option value="warning" @if (old('type', $pengumuman->type) == 'warning') selected @endif>Warning
</option>
<option value="danger" @if (old('type', $pengumuman->type) == 'danger') selected @endif>Danger
</option>
<label for="type" class="form-label">Tipe Pengumuman <span class="text-danger">*</span></label>
<select name="type" class="form-select @error('type') is-invalid @enderror" id="type" required>
<option value="" disabled>Pilih tipe...</option>
<option value="info" @if (old('type', $pengumuman->type) == 'info') selected @endif>Info</option>
<option value="success" @if (old('type', $pengumuman->type) == 'success') selected @endif>Success</option>
<option value="warning" @if (old('type', $pengumuman->type) == 'warning') selected @endif>Warning</option>
<option value="danger" @if (old('type', $pengumuman->type) == 'danger') selected @endif>Danger</option>
</select>
@error('type')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="content" class="form-label">Isi Pengumuman</label>
<textarea name="content" class="form-control" id="content" rows="5" required>{{ old('content', $pengumuman->content) }}</textarea>
<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" required minlength="3">{{ old('content', $pengumuman->content) }}</textarea>
@error('content')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<hr>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Simpan Perubahan</button>
<button type="submit" class="btn btn-primary px-4 fw-bold">Simpan Perubahan</button>
</div>
</form>
</div>
@ -51,25 +56,8 @@
@push('scripts')
<script>
document.getElementById('formEditPengumuman').addEventListener('submit', function(e) {
e.preventDefault();
modernSwal.fire({
title: 'Menyimpan Perubahan...',
timer: 800,
didOpen: () => Swal.showLoading()
}).then(() => {
Toast.fire({
icon: 'success',
title: 'Berhasil',
text: 'Pengumuman berhasil diperbarui.'
});
// Redirect setelah 1.5 detik
setTimeout(() => {
window.location.href = "{{ route('admin.pengumuman.index') }}";
}, 1500);
});
document.addEventListener('DOMContentLoaded', function() {
// Script removed to allow standard server-side validation
});
</script>
@endpush

View File

@ -14,18 +14,41 @@
<div class="card-body">
<form action="{{ route('admin.rekomendasi.store') }}" method="POST">
@csrf
<div class="mb-3"><label class="form-label">Judul</label><input type="text"
name="judul" class="form-control" required></div>
<div class="mb-3"><label class="form-label">Kategori</label><input type="text"
name="kategori" class="form-control" placeholder="Contoh: Teknologi, Sains, Biologi" required></div>
<div class="mb-3"><label class="form-label">Link YouTube</label><input type="url"
name="youtube_link" class="form-control" placeholder="https://www.youtube.com/watch?v=xxxxxx" required></div>
<div class="mb-3"><label class="form-label">Deskripsi</label>
<textarea name="deskripsi" id="editor" required></textarea>
<div class="mb-3">
<label class="form-label">Judul <span class="text-danger">*</span></label>
<input type="text" name="judul" class="form-control @error('judul') is-invalid @enderror"
value="{{ old('judul') }}" required minlength="3" maxlength="50">
@error('judul')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label class="form-label">Kategori <span class="text-danger">*</span></label>
<input type="text" name="kategori" class="form-control @error('kategori') is-invalid @enderror"
value="{{ old('kategori') }}" placeholder="Contoh: Teknologi, Sains, Biologi" required minlength="3" maxlength="50">
@error('kategori')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label class="form-label">Link YouTube <span class="text-danger">*</span></label>
<input type="url" name="youtube_link" class="form-control @error('youtube_link') is-invalid @enderror"
value="{{ old('youtube_link') }}" placeholder="https://www.youtube.com/watch?v=xxxxxx" required>
@error('youtube_link')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label class="form-label">Deskripsi <span class="text-danger">*</span></label>
<textarea name="deskripsi" id="editor" class="@error('deskripsi') is-invalid @enderror" required minlength="3">{{ old('deskripsi') }}</textarea>
@error('deskripsi')
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
</div>
<hr>
<div class="d-flex justify-content-end"><button type="submit"
class="btn btn-primary">Simpan</button></div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary px-4 fw-bold">Simpan</button>
</div>
</form>
</div>
</div>

View File

@ -15,18 +15,41 @@
<form action="{{ route('admin.rekomendasi.update', $rekomendasi->id) }}" method="POST">
@csrf
@method('PUT')
<div class="mb-3"><label class="form-label">Judul</label><input type="text"
name="judul" class="form-control" value="{{ old('judul', $rekomendasi->judul) }}" required></div>
<div class="mb-3"><label class="form-label">Kategori</label><input type="text"
name="kategori" class="form-control" value="{{ old('kategori', $rekomendasi->kategori) }}" required></div>
<div class="mb-3"><label class="form-label">Link YouTube</label><input type="url"
name="youtube_link" class="form-control" value="{{ old('youtube_link', $rekomendasi->youtube_link) }}" required></div>
<div class="mb-3"><label class="form-label">Deskripsi</label>
<textarea name="deskripsi" id="editor" required>{{ old('deskripsi', $rekomendasi->deskripsi) }}</textarea>
<div class="mb-3">
<label class="form-label">Judul <span class="text-danger">*</span></label>
<input type="text" name="judul" class="form-control @error('judul') is-invalid @enderror"
value="{{ old('judul', $rekomendasi->judul) }}" required minlength="3" maxlength="50">
@error('judul')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label class="form-label">Kategori <span class="text-danger">*</span></label>
<input type="text" name="kategori" class="form-control @error('kategori') is-invalid @enderror"
value="{{ old('kategori', $rekomendasi->kategori) }}" required minlength="3" maxlength="50">
@error('kategori')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label class="form-label">Link YouTube <span class="text-danger">*</span></label>
<input type="url" name="youtube_link" class="form-control @error('youtube_link') is-invalid @enderror"
value="{{ old('youtube_link', $rekomendasi->youtube_link) }}" required>
@error('youtube_link')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label class="form-label">Deskripsi <span class="text-danger">*</span></label>
<textarea name="deskripsi" id="editor" class="@error('deskripsi') is-invalid @enderror" required minlength="3">{{ old('deskripsi', $rekomendasi->deskripsi) }}</textarea>
@error('deskripsi')
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
</div>
<hr>
<div class="d-flex justify-content-end"><button type="submit"
class="btn btn-primary">Simpan Perubahan</button></div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary px-4 fw-bold">Simpan Perubahan</button>
</div>
</form>
</div>
</div>

View File

@ -21,7 +21,8 @@
<div class="mb-3">
<label for="name" class="form-label">{{ __('Nama Lengkap') }}</label>
<input id="name" name="name" type="text" class="form-control @error('name') is-invalid @enderror"
value="{{ old('name', $user->name) }}" required autofocus autocomplete="name">
value="{{ old('name', $user->name) }}" required autofocus autocomplete="name"
minlength="3" maxlength="50">
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
@ -58,7 +59,8 @@ class="form-control @error('email') is-invalid @enderror" value="{{ old('email',
<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>
value="{{ old('phone', $user->phone) }}" required
minlength="13" maxlength="15" pattern="\d*">
@error('phone')
<div class="invalid-feedback">{{ $message }}</div>
@enderror