feat: add anggota keluarga and riwayat bantuan modals to data page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
GuavaPopper
2026-06-05 04:57:01 +07:00
parent 804618bb54
commit deefa7a126
+246 -1
View File
@@ -359,6 +359,127 @@
<form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
<!-- Modal Anggota Keluarga -->
<dialog id="modal_anggota" class="modal">
<div class="modal-box w-11/12 max-w-3xl">
<h3 class="font-bold text-lg mb-1 flex items-center gap-2"><i class="fa-solid fa-users text-info"></i> Anggota Keluarga</h3>
<p id="anggota_rumah_label" class="text-xs opacity-50 mb-4">-</p>
<div class="overflow-x-auto mb-4">
<table class="table table-sm w-full">
<thead><tr class="text-xs uppercase opacity-50"><th>Nama</th><th>Hubungan</th><th>L/P</th><th>Umur</th><th>Pendidikan</th><th>Status</th><th></th></tr></thead>
<tbody id="bodyAnggota"><tr><td colspan="7" class="text-center py-4 opacity-30 italic">Memuat...</td></tr></tbody>
</table>
</div>
@if(auth()->user()->hasRole('administrator') || auth()->user()->hasRole('petugas_pendataan'))
<div class="collapse collapse-arrow border border-base-300 rounded-lg">
<input type="checkbox" id="toggleFormAnggota">
<div class="collapse-title text-sm font-bold"><i class="fa-solid fa-plus mr-2 text-info"></i>Tambah Anggota</div>
<div class="collapse-content">
<div class="grid grid-cols-2 gap-3 mt-2">
<div class="form-control col-span-2">
<label class="label"><span class="label-text font-bold">Nama</span></label>
<input id="anggota_nama" type="text" class="input input-bordered w-full" placeholder="Nama lengkap">
</div>
<div class="form-control">
<label class="label"><span class="label-text font-bold">Hubungan</span></label>
<select id="anggota_hubungan" class="select select-bordered w-full">
<option value="kepala_keluarga">Kepala Keluarga</option>
<option value="istri">Istri</option>
<option value="anak">Anak</option>
<option value="orang_tua">Orang Tua</option>
<option value="lainnya" selected>Lainnya</option>
</select>
</div>
<div class="form-control">
<label class="label"><span class="label-text font-bold">Jenis Kelamin</span></label>
<select id="anggota_kelamin" class="select select-bordered w-full">
<option value="">-- pilih --</option>
<option value="laki_laki">Laki-laki</option>
<option value="perempuan">Perempuan</option>
</select>
</div>
<div class="form-control">
<label class="label"><span class="label-text font-bold">Tanggal Lahir</span></label>
<input id="anggota_tgl_lahir" type="date" class="input input-bordered w-full">
</div>
<div class="form-control">
<label class="label"><span class="label-text font-bold">Pendidikan</span></label>
<select id="anggota_pendidikan" class="select select-bordered w-full">
<option value="">-- pilih --</option>
<option value="tidak_sekolah">Tidak Sekolah</option>
<option value="sd">SD</option>
<option value="smp">SMP</option>
<option value="sma">SMA</option>
<option value="d3">D3</option>
<option value="s1_plus">S1+</option>
</select>
</div>
<div class="form-control col-span-2">
<label class="label"><span class="label-text font-bold">Status Hidup</span></label>
<select id="anggota_status" class="select select-bordered w-full">
<option value="hidup">Hidup</option>
<option value="meninggal">Meninggal</option>
</select>
</div>
</div>
<button onclick="simpanAnggota()" class="btn btn-info btn-sm mt-3 w-full"><i class="fa-solid fa-floppy-disk mr-1"></i>Simpan Anggota</button>
</div>
</div>
@endif
<div class="modal-action">
<button onclick="document.getElementById('modal_anggota').close()" class="btn btn-ghost">Tutup</button>
</div>
</div>
<form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
<!-- Modal Riwayat Bantuan -->
<dialog id="modal_bantuan" class="modal">
<div class="modal-box w-11/12 max-w-2xl">
<h3 class="font-bold text-lg mb-1 flex items-center gap-2"><i class="fa-solid fa-hand-holding-heart text-success"></i> Riwayat Bantuan</h3>
<p id="bantuan_rumah_label" class="text-xs opacity-50 mb-4">-</p>
<div class="overflow-x-auto mb-4">
<table class="table table-sm w-full">
<thead><tr class="text-xs uppercase opacity-50"><th>Tanggal</th><th>Jenis</th><th>Dari Ibadah</th><th>Dicatat oleh</th><th></th></tr></thead>
<tbody id="bodyBantuan"><tr><td colspan="5" class="text-center py-4 opacity-30 italic">Memuat...</td></tr></tbody>
</table>
</div>
@if(auth()->user()->hasRole('administrator') || auth()->user()->hasRole('petugas_pendataan') || auth()->user()->hasRole('pengurus_ibadah'))
<div class="collapse collapse-arrow border border-base-300 rounded-lg">
<input type="checkbox" id="toggleFormBantuan">
<div class="collapse-title text-sm font-bold"><i class="fa-solid fa-plus mr-2 text-success"></i>Catat Bantuan Baru</div>
<div class="collapse-content">
<div class="grid grid-cols-2 gap-3 mt-2">
<div class="form-control col-span-2">
<label class="label"><span class="label-text font-bold">Jenis Bantuan</span></label>
<input id="bantuan_jenis" type="text" class="input input-bordered w-full" placeholder="Contoh: sembako, uang, pakaian">
</div>
<div class="form-control">
<label class="label"><span class="label-text font-bold">Tanggal</span></label>
<input id="bantuan_tanggal" type="date" class="input input-bordered w-full">
</div>
<div class="form-control">
<label class="label"><span class="label-text font-bold">Dari Rumah Ibadah</span></label>
<select id="bantuan_ibadah" class="select select-bordered w-full">
<option value="">-- Pilih (opsional) --</option>
</select>
</div>
<div class="form-control col-span-2">
<label class="label"><span class="label-text font-bold">Keterangan</span></label>
<textarea id="bantuan_ket" class="textarea textarea-bordered w-full" rows="2" placeholder="Keterangan tambahan"></textarea>
</div>
</div>
<button onclick="simpanBantuan()" class="btn btn-success btn-sm mt-3 w-full"><i class="fa-solid fa-floppy-disk mr-1"></i>Simpan Bantuan</button>
</div>
</div>
@endif
<div class="modal-action">
<button onclick="document.getElementById('modal_bantuan').close()" class="btn btn-ghost">Tutup</button>
</div>
</div>
<form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
<!-- Toast Container -->
<div class="toast toast-end toast-bottom z-[9999]" id="toastBox"></div>
@@ -443,7 +564,9 @@
<td class="capitalize text-xs">${item.kondisi_rumah || '-'}</td>
<td class="font-mono text-[10px]">${parseFloat(item.latitude).toFixed(4)}, ${parseFloat(item.longitude).toFixed(4)}</td>
<td class="text-center">
<div class="flex gap-1 justify-center">
<div class="flex gap-1 justify-center flex-wrap">
<button class="btn btn-xs btn-info btn-outline" onclick="openAnggotaModal('${item.id_rumah}')" title="Anggota Keluarga"><i class="fa-solid fa-users"></i></button>
<button class="btn btn-xs btn-success btn-outline" onclick="openBantuanModal('${item.id_rumah}')" title="Riwayat Bantuan"><i class="fa-solid fa-hand-holding-heart"></i></button>
${canEditMiskin ? `<button class="btn btn-xs btn-warning btn-outline" onclick="openEditMiskinById('${item.id_rumah}')"><i class="fa-solid fa-pen"></i></button>` : ''}
${canEditMiskin ? `<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('miskin', '${item.id_rumah}')"><i class="fa-solid fa-trash"></i></button>` : ''}
</div>
@@ -711,6 +834,128 @@
setTimeout(() => t.remove(), 3000);
}
// ── ANGGOTA KELUARGA ───────────────────────────────────────────────────
let currentAnggotaRumah = null;
const canEditAnggota = {{ auth()->user()->hasRole('administrator') || auth()->user()->hasRole('petugas_pendataan') ? 'true' : 'false' }};
async function openAnggotaModal(idRumah) {
currentAnggotaRumah = idRumah;
const rm = miskinData.find(m => m.id_rumah === idRumah);
document.getElementById('anggota_rumah_label').textContent = `ID: ${idRumah} — ${rm?.nama_kk || '-'}`;
document.getElementById('modal_anggota').showModal();
await loadAnggota(idRumah);
}
async function loadAnggota(idRumah) {
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(idRumah)}/anggota`, { headers: { 'X-CSRF-TOKEN': csrfToken } });
const list = await res.json();
document.getElementById('bodyAnggota').innerHTML = list.length ? list.map(a => `
<tr>
<td class="font-semibold">${esc(a.nama)}</td>
<td class="capitalize text-xs">${esc(a.hubungan.replace('_', ' '))}</td>
<td class="text-xs">${a.jenis_kelamin === 'laki_laki' ? 'L' : a.jenis_kelamin === 'perempuan' ? 'P' : '-'}</td>
<td class="text-xs">${a.umur !== null ? esc(String(a.umur)) + ' th' : '-'}</td>
<td class="capitalize text-xs">${a.pendidikan ? esc(a.pendidikan.replace(/_/g, ' ')) : '-'}</td>
<td><span class="badge badge-xs ${a.status_hidup === 'hidup' ? 'badge-success' : 'badge-error'}">${esc(a.status_hidup)}</span></td>
<td>${canEditAnggota ? `<button onclick="deleteAnggota(${a.id})" class="btn btn-xs btn-ghost text-error"><i class="fa-solid fa-trash"></i></button>` : ''}</td>
</tr>`).join('')
: '<tr><td colspan="7" class="text-center py-4 opacity-30 italic">Belum ada anggota</td></tr>';
}
async function simpanAnggota() {
const body = {
nama: document.getElementById('anggota_nama').value.trim(),
hubungan: document.getElementById('anggota_hubungan').value,
jenis_kelamin: document.getElementById('anggota_kelamin').value || null,
tanggal_lahir: document.getElementById('anggota_tgl_lahir').value || null,
pendidikan: document.getElementById('anggota_pendidikan').value || null,
status_hidup: document.getElementById('anggota_status').value,
};
if (!body.nama) { showToast('Nama wajib diisi', 'error'); return; }
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentAnggotaRumah)}/anggota`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
body: JSON.stringify(body),
});
const json = await res.json();
if (json.success) { showToast('Anggota ditambahkan', 'success'); loadAnggota(currentAnggotaRumah); }
else showToast('Gagal menambah anggota', 'error');
}
async function deleteAnggota(id) {
if (!confirm('Hapus anggota ini?')) return;
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentAnggotaRumah)}/anggota/${id}`, {
method: 'DELETE', headers: { 'X-CSRF-TOKEN': csrfToken },
});
const json = await res.json();
if (json.success) { showToast('Anggota dihapus', 'success'); loadAnggota(currentAnggotaRumah); }
else showToast('Gagal hapus', 'error');
}
// ── RIWAYAT BANTUAN ────────────────────────────────────────────────────
let currentBantuanRumah = null;
const canDeleteBantuan = {{ auth()->user()->hasRole('administrator') ? 'true' : 'false' }};
async function openBantuanModal(idRumah) {
currentBantuanRumah = idRumah;
const rm = miskinData.find(m => m.id_rumah === idRumah);
document.getElementById('bantuan_rumah_label').textContent = `ID: ${idRumah} — ${rm?.nama_kk || '-'}`;
const sel = document.getElementById('bantuan_ibadah');
if (sel) {
sel.innerHTML = '<option value="">-- Pilih (opsional) --</option>' +
ibadahData.map(ib => `<option value="${ib.id}">${esc(ib.nama)}</option>`).join('');
}
const tgl = document.getElementById('bantuan_tanggal');
if (tgl) tgl.value = new Date().toISOString().split('T')[0];
document.getElementById('modal_bantuan').showModal();
await loadBantuan(idRumah);
}
async function loadBantuan(idRumah) {
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(idRumah)}/bantuan`, { headers: { 'X-CSRF-TOKEN': csrfToken } });
const list = await res.json();
document.getElementById('bodyBantuan').innerHTML = list.length ? list.map(b => `
<tr>
<td class="text-xs whitespace-nowrap">${esc(b.tanggal_bantuan)}</td>
<td class="font-semibold text-sm">${esc(b.jenis_bantuan)}</td>
<td class="text-xs">${esc(b.ibadah_nama || '-')}</td>
<td class="text-xs">${esc(b.pencatat_nama)}</td>
<td>${canDeleteBantuan ? `<button onclick="deleteBantuan(${b.id})" class="btn btn-xs btn-ghost text-error"><i class="fa-solid fa-trash"></i></button>` : ''}</td>
</tr>`).join('')
: '<tr><td colspan="5" class="text-center py-4 opacity-30 italic">Belum ada riwayat bantuan</td></tr>';
}
async function simpanBantuan() {
const body = {
jenis_bantuan: document.getElementById('bantuan_jenis').value.trim(),
tanggal_bantuan: document.getElementById('bantuan_tanggal').value,
keterangan: document.getElementById('bantuan_ket').value.trim() || null,
ibadah_id: document.getElementById('bantuan_ibadah').value || null,
};
if (!body.jenis_bantuan || !body.tanggal_bantuan) { showToast('Jenis bantuan dan tanggal wajib diisi', 'error'); return; }
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentBantuanRumah)}/bantuan`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
body: JSON.stringify(body),
});
const json = await res.json();
if (json.success) { showToast('Bantuan dicatat', 'success'); loadBantuan(currentBantuanRumah); }
else showToast('Gagal menyimpan bantuan', 'error');
}
async function deleteBantuan(id) {
if (!confirm('Hapus riwayat bantuan ini?')) return;
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentBantuanRumah)}/bantuan/${id}`, {
method: 'DELETE', headers: { 'X-CSRF-TOKEN': csrfToken },
});
const json = await res.json();
if (json.success) { showToast('Bantuan dihapus', 'success'); loadBantuan(currentBantuanRumah); }
else showToast('Gagal hapus', 'error');
}
// ── NOTIFIKASI PERUBAHAN DATA ────────────────────────────────────────
(function initNotifPolling() {
const POLL_MS = 30000;