feat: add bulk delete for ibadah and miskin with checkboxes (admin-only)

This commit is contained in:
GuavaPopper
2026-06-03 20:59:08 +07:00
parent cb3933541d
commit 6d157a17c9
4 changed files with 104 additions and 2 deletions
+14
View File
@@ -72,4 +72,18 @@ class IbadahController extends Controller
ActivityLog::record('delete', "Menghapus rumah ibadah: {$ibadah->nama}", 'ibadah', $id);
return response()->json(['success' => true]);
}
public function bulkDestroy(Request $request)
{
$validated = $request->validate([
'ids' => 'required|array|min:1',
'ids.*' => 'integer',
]);
$count = RumahIbadah::whereIn('id', $validated['ids'])->count();
RumahIbadah::whereIn('id', $validated['ids'])->delete();
ActivityLog::record('bulk_delete', "Menghapus massal {$count} rumah ibadah", 'ibadah');
return response()->json(['success' => true, 'deleted' => $count]);
}
}
+14
View File
@@ -62,4 +62,18 @@ class MiskinController extends Controller
ActivityLog::record('delete', "Menghapus keluarga miskin: {$id_rumah}", 'miskin', $id_rumah);
return response()->json(['success' => true]);
}
public function bulkDestroy(Request $request)
{
$validated = $request->validate([
'ids' => 'required|array|min:1',
'ids.*' => 'string',
]);
$count = RumahMiskin::whereIn('id_rumah', $validated['ids'])->count();
RumahMiskin::whereIn('id_rumah', $validated['ids'])->delete();
ActivityLog::record('bulk_delete', "Menghapus massal {$count} keluarga miskin", 'miskin');
return response()->json(['success' => true, 'deleted' => $count]);
}
}
+73 -2
View File
@@ -132,10 +132,19 @@
<div role="tablist" class="tabs tabs-lifted tabs-lg mb-6">
<input type="radio" name="data_tabs" role="tab" class="tab font-bold" aria-label="🕌 Rumah Ibadah" checked />
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6 shadow-sm">
@if(auth()->user()->hasRole('administrator'))
<div id="bulkBar_ibadah" class="hidden alert alert-warning py-2 mb-2 flex items-center justify-between">
<span class="text-sm font-semibold"><span id="bulkCount_ibadah">0</span> item terpilih</span>
<button onclick="bulkDelete('ibadah')" class="btn btn-xs btn-error text-white"><i class="fa-solid fa-trash mr-1"></i>Hapus Terpilih</button>
</div>
@endif
<div class="overflow-x-auto">
<table class="table table-zebra w-full" id="tableIbadah">
<thead>
<tr class="text-xs font-bold uppercase opacity-50">
@if(auth()->user()->hasRole('administrator'))
<th><input type="checkbox" class="checkbox checkbox-xs" onchange="toggleSelectAll('ibadah', this.checked)"></th>
@endif
<th>#</th>
<th>Nama</th>
<th>Jenis</th>
@@ -152,10 +161,19 @@
<input type="radio" name="data_tabs" role="tab" class="tab font-bold" aria-label="🏠 Rumah Miskin" />
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6 shadow-sm">
@if(auth()->user()->hasRole('administrator'))
<div id="bulkBar_miskin" class="hidden alert alert-warning py-2 mb-2 flex items-center justify-between">
<span class="text-sm font-semibold"><span id="bulkCount_miskin">0</span> item terpilih</span>
<button onclick="bulkDelete('miskin')" class="btn btn-xs btn-error text-white"><i class="fa-solid fa-trash mr-1"></i>Hapus Terpilih</button>
</div>
@endif
<div class="overflow-x-auto">
<table class="table table-zebra w-full" id="tableMiskin">
<thead>
<tr class="text-xs font-bold uppercase opacity-50">
@if(auth()->user()->hasRole('administrator'))
<th><input type="checkbox" class="checkbox checkbox-xs" onchange="toggleSelectAll('miskin', this.checked)"></th>
@endif
<th>ID</th>
<th>Nama KK</th>
<th>KK/Jiwa</th>
@@ -308,6 +326,9 @@
const canEditIbadah = ['administrator', 'pengurus_ibadah'].includes(authUserRole);
const canEditMiskin = ['administrator', 'petugas_pendataan'].includes(authUserRole);
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const isAdmin = authUserRole === 'administrator';
let selectedIbadah = new Set();
let selectedMiskin = new Set();
let ibadahData = [], miskinData = [];
document.addEventListener('DOMContentLoaded', loadAllData);
@@ -340,6 +361,7 @@
const canEditThis = canEditIbadah && (authUserRole === 'administrator' || item.user_id === authUserId);
return `
<tr>
${isAdmin ? `<td><input type="checkbox" class="checkbox checkbox-xs" ${selectedIbadah.has(String(item.id)) ? 'checked' : ''} onchange="toggleSelect('ibadah', '${item.id}', this.checked)"></td>` : ''}
<td class="text-xs font-bold opacity-30">${i + 1}</td>
<td>
<div class="font-bold text-primary">${item.nama}</div>
@@ -359,12 +381,13 @@
</div>
</td>
</tr>`;
}).join('') : '<tr><td colspan="7" class="text-center py-10 opacity-30 italic">Belum ada data</td></tr>';
}).join('') : '<tr><td colspan="8" class="text-center py-10 opacity-30 italic">Belum ada data</td></tr>';
// Miskin Table
const bodyM = document.getElementById('bodyMiskin');
bodyM.innerHTML = dataM.length ? dataM.map(item => `
<tr>
${isAdmin ? `<td><input type="checkbox" class="checkbox checkbox-xs" ${selectedMiskin.has(String(item.id_rumah)) ? 'checked' : ''} onchange="toggleSelect('miskin', '${item.id_rumah}', this.checked)"></td>` : ''}
<td class="font-bold text-error">${item.id_rumah}</td>
<td>
<div class="font-bold">${item.nama_kk || '-'}</div>
@@ -384,7 +407,7 @@
</div>
</td>
</tr>
`).join('') : '<tr><td colspan="7" class="text-center py-10 opacity-30 italic">Belum ada data</td></tr>';
`).join('') : '<tr><td colspan="8" class="text-center py-10 opacity-30 italic">Belum ada data</td></tr>';
}
function filterData() {
@@ -532,6 +555,54 @@
}
}
// ── BULK DELETE ─────────────────────────────────────────────
function toggleSelect(type, id, checked) {
const set = type === 'ibadah' ? selectedIbadah : selectedMiskin;
if (checked) set.add(String(id)); else set.delete(String(id));
updateBulkBar(type);
}
function toggleSelectAll(type, checked) {
const set = type === 'ibadah' ? selectedIbadah : selectedMiskin;
set.clear();
if (checked) {
(type === 'ibadah' ? ibadahData : miskinData).forEach(it =>
set.add(String(type === 'ibadah' ? it.id : it.id_rumah)));
}
renderTables();
updateBulkBar(type);
}
function updateBulkBar(type) {
const set = type === 'ibadah' ? selectedIbadah : selectedMiskin;
const bar = document.getElementById(`bulkBar_${type}`);
if (!bar) return;
bar.classList.toggle('hidden', set.size === 0);
document.getElementById(`bulkCount_${type}`).textContent = set.size;
}
async function bulkDelete(type) {
const set = type === 'ibadah' ? selectedIbadah : selectedMiskin;
const ids = [...set];
if (!ids.length) return;
if (!confirm(`Hapus ${ids.length} data terpilih secara permanen?`)) return;
const res = await fetch(`{{ url('/api/admin') }}/${type}/bulk-delete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
body: JSON.stringify({ ids }),
});
const json = await res.json();
if (json.success) {
showToast(`${json.deleted} data dihapus`, 'success');
set.clear();
updateBulkBar(type);
loadAllData();
} else {
showToast('Gagal hapus massal', 'error');
}
}
let deleteTarget = null;
function prepDelete(type, id) {
deleteTarget = { type, id };
+3
View File
@@ -57,6 +57,9 @@ Route::middleware('auth')->group(function () {
Route::put('/users/{id}', [AdminUserController::class, 'update']);
Route::delete('/users/{id}', [AdminUserController::class, 'destroy']);
Route::put('/users/{id}/password', [AdminUserController::class, 'resetPassword']);
Route::post('/ibadah/bulk-delete', [IbadahController::class, 'bulkDestroy']);
Route::post('/miskin/bulk-delete', [MiskinController::class, 'bulkDestroy']);
});
});
});