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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user