416 lines
20 KiB
JavaScript
416 lines
20 KiB
JavaScript
let allData = [];
|
|
|
|
document.addEventListener('auth-ready', function(e) {
|
|
const user = e.detail;
|
|
document.body.classList.add('auth-loaded', 'role-admin');
|
|
document.getElementById('user-display-name').textContent = user.username;
|
|
document.getElementById('user-display-role').textContent = 'Admin';
|
|
loadData();
|
|
});
|
|
|
|
async function loadData() {
|
|
try {
|
|
const res = await fetch('api/manajemen_akun.php');
|
|
const data = await res.json();
|
|
if (data.status !== 'success') throw new Error(data.message);
|
|
allData = data.data || [];
|
|
renderCards(allData);
|
|
} catch (err) {
|
|
document.getElementById('ri-grid').innerHTML = `
|
|
<div class="empty-state" style="grid-column:1/-1">
|
|
<div class="empty-state-icon">⚠️</div>
|
|
<h3>Gagal memuat data</h3>
|
|
<p>${esc(err.message)}</p>
|
|
</div>`;
|
|
}
|
|
}
|
|
|
|
function renderCards(data) {
|
|
const grid = document.getElementById('ri-grid');
|
|
document.getElementById('filter-count').textContent = `${data.length} rumah ibadah`;
|
|
|
|
if (!data.length) {
|
|
grid.innerHTML = `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">🏛️</div>
|
|
<h3>Belum ada rumah ibadah</h3>
|
|
<p>Tambahkan rumah ibadah melalui halaman Peta Interaktif.</p>
|
|
</div>`;
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = data.map(r => {
|
|
const hasAccount = !!r.username;
|
|
const rpFormatted = new Intl.NumberFormat('id-ID', { notation: 'compact', maximumFractionDigits: 1 }).format(r.total_nilai_bantuan);
|
|
return `
|
|
<div class="ri-card" onclick="openDrawer(${r.id})" data-jenis="${esc(r.jenis)}" data-account="${hasAccount ? 'active' : 'inactive'}" data-nama="${esc(r.nama.toLowerCase())}">
|
|
<span class="ri-account-status ${hasAccount ? 'active' : 'inactive'}">${hasAccount ? '● Akun Aktif' : '○ Tanpa Akun'}</span>
|
|
<div class="ri-card-header">
|
|
<div class="ri-avatar">${riEmoji(r.jenis)}</div>
|
|
<div class="ri-card-meta">
|
|
<div class="ri-card-name">${esc(r.nama)}</div>
|
|
<span class="ri-card-type">${esc(r.jenis)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ri-card-stats">
|
|
<div class="stat-mini">
|
|
<div class="stat-mini-val">${r.pm_dalam_radius}</div>
|
|
<div class="stat-mini-label">Warga Binaan</div>
|
|
</div>
|
|
<div class="stat-mini">
|
|
<div class="stat-mini-val">${r.total_penyaluran}</div>
|
|
<div class="stat-mini-label">Penyaluran</div>
|
|
</div>
|
|
<div class="stat-mini">
|
|
<div class="stat-mini-val">${r.total_nilai_bantuan > 0 ? rpFormatted : '—'}</div>
|
|
<div class="stat-mini-label">Total Nilai</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ri-card-account">
|
|
${hasAccount
|
|
? `<span>👤</span> <span class="username">${esc(r.username)}</span>`
|
|
: `<span class="no-account">Belum ada akun terdaftar</span>`}
|
|
</div>
|
|
|
|
<div class="ri-card-footer">
|
|
<div class="ri-card-alamat">📍 ${r.alamat ? esc(r.alamat) : 'Alamat belum diisi'}</div>
|
|
<div class="ri-card-actions" onclick="event.stopPropagation()">
|
|
<button class="btn-icon" title="Edit Rumah Ibadah" onclick="goToEdit(${r.id})">✏️</button>
|
|
${!hasAccount
|
|
? `<button class="btn-icon" title="Buat Akun" onclick="openBuatAkun(${r.id}, '${esc(r.nama)}')">🔑</button>`
|
|
: `<button class="btn-icon" title="Reset Password" onclick="openResetPassword(${r.id}, '${esc(r.username)}')">🔐</button>`}
|
|
${hasAccount ? `<button class="btn-icon danger" title="Hapus Akun" onclick="hapusAkun(${r.id}, '${esc(r.nama)}')">🗑️</button>` : ''}
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
function filterCards() {
|
|
const query = document.getElementById('search-input').value.toLowerCase().trim();
|
|
const jenis = document.getElementById('filter-jenis').value;
|
|
const akun = document.getElementById('filter-akun').value;
|
|
|
|
const filtered = allData.filter(r => {
|
|
const matchQuery = !query || r.nama.toLowerCase().includes(query) || r.jenis.toLowerCase().includes(query) || (r.alamat && r.alamat.toLowerCase().includes(query));
|
|
const matchJenis = !jenis || r.jenis === jenis;
|
|
const matchAkun = !akun || (akun === 'active' ? !!r.username : !r.username);
|
|
return matchQuery && matchJenis && matchAkun;
|
|
});
|
|
renderCards(filtered);
|
|
}
|
|
|
|
async function openDrawer(id) {
|
|
pwVisible = false; // Reset password visibility on each open
|
|
document.getElementById('detail-overlay').classList.add('open');
|
|
document.getElementById('drawer-body').innerHTML = `
|
|
<div class="loading-spinner-wrap">
|
|
<span class="spinner" style="width:20px;height:20px;border-width:3px;"></span>
|
|
<p style="margin-top:12px">Memuat detail…</p>
|
|
</div>`;
|
|
|
|
try {
|
|
const res = await fetch(`api/manajemen_akun.php?id=${id}`);
|
|
const data = await res.json();
|
|
if (data.status !== 'success') throw new Error(data.message);
|
|
renderDrawer(data.data);
|
|
} catch (err) {
|
|
document.getElementById('drawer-body').innerHTML = `<p style="color:#ef4444">Gagal memuat: ${esc(err.message)}</p>`;
|
|
}
|
|
}
|
|
|
|
function renderDrawer(r) {
|
|
document.getElementById('drawer-title').textContent = r.nama;
|
|
const rp = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(r.total_nilai_bantuan);
|
|
|
|
document.getElementById('drawer-body').innerHTML = `
|
|
<!-- KPI boxes -->
|
|
<div class="kpi-row">
|
|
<div class="kpi-box">
|
|
<div class="kpi-val green">${r.pm_dalam_radius}</div>
|
|
<div class="kpi-label">Warga Binaan</div>
|
|
</div>
|
|
<div class="kpi-box">
|
|
<div class="kpi-val accent">${r.total_penyaluran}</div>
|
|
<div class="kpi-label">Total Penyaluran</div>
|
|
</div>
|
|
<div class="kpi-box">
|
|
<div class="kpi-val">${r.jumlah_penerima_unik}</div>
|
|
<div class="kpi-label">Penerima Unik</div>
|
|
</div>
|
|
<div class="kpi-box">
|
|
<div class="kpi-val" style="font-size:15px">${rp}</div>
|
|
<div class="kpi-label">Total Nilai</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info dasar -->
|
|
<div class="section-heading">Informasi Rumah Ibadah</div>
|
|
<div>
|
|
<div class="info-row"><span class="info-label">Jenis</span><span class="info-val">${riEmoji(r.jenis)} ${esc(r.jenis)}</span></div>
|
|
<div class="info-row"><span class="info-label">Radius Jangkauan</span><span class="info-val">${r.radius_meter} meter</span></div>
|
|
<div class="info-row"><span class="info-label">Alamat</span><span class="info-val" style="max-width:260px">${r.alamat ? esc(r.alamat) : '—'}</span></div>
|
|
<div class="info-row"><span class="info-label">No. WhatsApp</span><span class="info-val">${r.no_wa ? esc(r.no_wa) : '—'}</span></div>
|
|
<div class="info-row"><span class="info-label">Koordinat</span><span class="info-val" style="font-family:monospace;font-size:11px">${r.latitude.toFixed(6)}, ${r.longitude.toFixed(6)}</span></div>
|
|
<div class="info-row"><span class="info-label">Bantuan Pemberdayaan</span><span class="info-val">📚 ${r.bantuan_pemberdayaan} kali</span></div>
|
|
<div class="info-row"><span class="info-label">Bantuan Konsumtif</span><span class="info-val">🛍️ ${r.bantuan_konsumtif} kali</span></div>
|
|
<div class="info-row"><span class="info-label">Penyaluran Terakhir</span><span class="info-val">${r.tanggal_bantuan_terakhir || '—'}</span></div>
|
|
<div class="info-row"><span class="info-label">Terdaftar Sejak</span><span class="info-val">${new Date(r.created_at).toLocaleDateString('id-ID', {day:'numeric',month:'long',year:'numeric'})}</span></div>
|
|
</div>
|
|
|
|
<!-- Info akun -->
|
|
<div class="section-heading">Informasi Akun</div>
|
|
<div>
|
|
${r.username ? `
|
|
<div class="info-row"><span class="info-label">Username</span><span class="info-val" style="font-family:monospace">${esc(r.username)}</span></div>
|
|
<div class="info-row"><span class="info-label">Password</span>
|
|
<span class="info-val" style="display:flex;align-items:center;gap:8px">
|
|
<span id="pw-display" style="font-family:monospace;letter-spacing:.1em">${r.password_plain ? '••••••••' : '<em style=color:#94a3b8>Tidak diketahui</em>'}</span>
|
|
${r.password_plain ? `<button onclick="togglePw('${esc(r.password_plain)}')" id="pw-toggle-btn" style="font-size:11px;padding:3px 8px;border:1px solid #e2e8f0;border-radius:6px;background:#f8fafc;cursor:pointer;color:#475569;transition:all .15s" title="Tampilkan/Sembunyikan Password">👁 Lihat</button>` : ''}
|
|
</span>
|
|
</div>
|
|
<div class="info-row"><span class="info-label">Status</span><span class="info-val" style="color:#15803d">● Aktif</span></div>
|
|
<div class="info-row"><span class="info-label">Akun Dibuat</span><span class="info-val">${r.akun_dibuat ? new Date(r.akun_dibuat).toLocaleDateString('id-ID',{day:'numeric',month:'long',year:'numeric'}) : '—'}</span></div>
|
|
<div style="margin-top:12px;display:flex;gap:8px">
|
|
<button class="btn btn-ghost btn-sm" onclick="openResetPassword(${r.id}, '${esc(r.username)}')">🔐 Reset Password</button>
|
|
<button class="btn btn-danger btn-sm" onclick="hapusAkun(${r.id}, '${esc(r.nama)}')">🗑️ Hapus Akun</button>
|
|
</div>
|
|
` : `
|
|
<div style="padding:16px;background:#f8fafc;border-radius:12px;text-align:center;color:#94a3b8;font-size:13px">
|
|
Belum ada akun terdaftar untuk rumah ibadah ini.
|
|
</div>
|
|
<div style="margin-top:12px">
|
|
<button class="btn btn-primary btn-sm" onclick="openBuatAkun(${r.id}, '${esc(r.nama)}')">🔑 Buat Akun</button>
|
|
</div>
|
|
`}
|
|
</div>
|
|
|
|
<!-- Riwayat bantuan terbaru -->
|
|
${r.riwayat_bantuan && r.riwayat_bantuan.length ? `
|
|
<div class="section-heading">Riwayat Penyaluran Terbaru</div>
|
|
<div class="log-list">
|
|
${r.riwayat_bantuan.map(l => `
|
|
<div class="log-entry">
|
|
<div class="log-type-dot ${l.tipe_bantuan === 'Pemberdayaan' ? 'pem' : 'kon'}"></div>
|
|
<div class="log-entry-info">
|
|
<div class="log-entry-name">${esc(l.nama_penerima)}</div>
|
|
<div class="log-entry-detail">${l.tipe_bantuan === 'Pemberdayaan' ? '📚' : '🛍️'} ${esc(l.tipe_bantuan)}${l.sub_kategori ? ' · ' + esc(l.sub_kategori) : ''}${l.nilai_bantuan ? ' · Rp' + new Intl.NumberFormat('id-ID').format(l.nilai_bantuan) : ''}</div>
|
|
</div>
|
|
<div class="log-entry-date">${l.tanggal_bantuan}</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
` : ''}
|
|
`;
|
|
}
|
|
|
|
function closeDrawer(e) {
|
|
if (e && e.target !== document.getElementById('detail-overlay')) return;
|
|
document.getElementById('detail-overlay').classList.remove('open');
|
|
}
|
|
|
|
let pwVisible = false;
|
|
function togglePw(plainPassword) {
|
|
const display = document.getElementById('pw-display');
|
|
const btn = document.getElementById('pw-toggle-btn');
|
|
if (!display || !btn) return;
|
|
pwVisible = !pwVisible;
|
|
if (pwVisible) {
|
|
display.textContent = plainPassword;
|
|
display.style.color = '#0f172a';
|
|
btn.textContent = '🙈 Sembunyikan';
|
|
btn.style.background = '#f0fdf4';
|
|
btn.style.borderColor = '#bbf7d0';
|
|
btn.style.color = '#15803d';
|
|
} else {
|
|
display.textContent = '••••••••';
|
|
display.style.color = '';
|
|
btn.textContent = '👁 Lihat';
|
|
btn.style.background = '#f8fafc';
|
|
btn.style.borderColor = '#e2e8f0';
|
|
btn.style.color = '#475569';
|
|
}
|
|
}
|
|
|
|
function openResetPassword(id, username) {
|
|
openModal('Reset Password Akun', `
|
|
<div class="form-group">
|
|
<label class="form-label">Username</label>
|
|
<input class="form-input" value="${esc(username)}" readonly style="background:#f8fafc;color:#64748b">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Password Baru *</label>
|
|
<input class="form-input" id="new-password" type="password" placeholder="Min. 6 karakter" autocomplete="new-password">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Konfirmasi Password *</label>
|
|
<input class="form-input" id="confirm-password" type="password" placeholder="Ulangi password baru" autocomplete="new-password">
|
|
</div>
|
|
`, `
|
|
<button class="btn btn-ghost" onclick="closeModal()">Batal</button>
|
|
<button class="btn btn-primary" onclick="doResetPassword(${id})">🔐 Simpan Password</button>
|
|
`);
|
|
}
|
|
|
|
async function doResetPassword(riId) {
|
|
const pw = document.getElementById('new-password').value;
|
|
const conf = document.getElementById('confirm-password').value;
|
|
if (!pw || pw.length < 6) return toast('Password minimal 6 karakter', 'error');
|
|
if (pw !== conf) return toast('Konfirmasi password tidak cocok', 'error');
|
|
|
|
// Find the data — cast ID to number to avoid string vs number mismatch
|
|
const ri = allData.find(r => Number(r.id) === Number(riId));
|
|
if (!ri) {
|
|
toast('Data rumah ibadah tidak ditemukan di cache, coba refresh halaman', 'error');
|
|
return;
|
|
}
|
|
|
|
const btn = document.querySelector('#modal-footer .btn-primary');
|
|
if (btn) { btn.disabled = true; btn.textContent = 'Menyimpan…'; }
|
|
|
|
try {
|
|
const res = await fetch(`api/rumah_ibadah.php?id=${riId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
nama: ri.nama, jenis: ri.jenis, alamat: ri.alamat,
|
|
no_wa: ri.no_wa, latitude: ri.latitude, longitude: ri.longitude,
|
|
radius_meter: ri.radius_meter, username: ri.username, password: pw
|
|
})
|
|
});
|
|
const data = await res.json();
|
|
if (data.status === 'success') {
|
|
toast('Password berhasil direset!', 'success');
|
|
closeModal();
|
|
await loadData(); // Refresh allData
|
|
openDrawer(riId); // Reopen drawer with fresh data
|
|
} else {
|
|
toast(data.message || 'Gagal reset password', 'error');
|
|
}
|
|
} catch (err) {
|
|
toast('Terjadi kesalahan: ' + err.message, 'error');
|
|
} finally {
|
|
if (btn) { btn.disabled = false; btn.textContent = '🔐 Simpan Password'; }
|
|
}
|
|
}
|
|
|
|
function openBuatAkun(id, namaRI) {
|
|
const sanitized = namaRI.toLowerCase().replace(/[^a-z0-9]/g, '_').replace(/_+/g, '_').replace(/^_+|_+$/g, '');
|
|
openModal('Buat Akun Rumah Ibadah', `
|
|
<div class="form-group">
|
|
<label class="form-label">Nama Rumah Ibadah</label>
|
|
<input class="form-input" value="${esc(namaRI)}" readonly style="background:#f8fafc;color:#64748b">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Username (otomatis)</label>
|
|
<input class="form-input" id="buat-username" value="${esc(sanitized)}" readonly style="background:#f1f5f9;color:#64748b;font-family:monospace">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Password *</label>
|
|
<input class="form-input" id="buat-password" type="password" placeholder="Min. 6 karakter" autocomplete="new-password">
|
|
</div>
|
|
`, `
|
|
<button class="btn btn-ghost" onclick="closeModal()">Batal</button>
|
|
<button class="btn btn-primary" onclick="doBuatAkun(${id})">🔑 Buat Akun</button>
|
|
`);
|
|
}
|
|
|
|
async function doBuatAkun(riId) {
|
|
const username = document.getElementById('buat-username').value;
|
|
const password = document.getElementById('buat-password').value;
|
|
if (!password || password.length < 6) return toast('Password minimal 6 karakter', 'error');
|
|
|
|
// Cast ID to number to avoid string vs number mismatch from PHP JSON
|
|
const ri = allData.find(r => Number(r.id) === Number(riId));
|
|
if (!ri) {
|
|
toast('Data rumah ibadah tidak ditemukan di cache, coba refresh halaman', 'error');
|
|
return;
|
|
}
|
|
|
|
const btn = document.querySelector('#modal-footer .btn-primary');
|
|
if (btn) { btn.disabled = true; btn.textContent = 'Membuat…'; }
|
|
|
|
try {
|
|
const res = await fetch(`api/rumah_ibadah.php?id=${riId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
nama: ri.nama, jenis: ri.jenis, alamat: ri.alamat,
|
|
no_wa: ri.no_wa, latitude: ri.latitude, longitude: ri.longitude,
|
|
radius_meter: ri.radius_meter, username, password
|
|
})
|
|
});
|
|
const data = await res.json();
|
|
if (data.status === 'success') {
|
|
toast('Akun berhasil dibuat!', 'success');
|
|
closeModal();
|
|
await loadData(); // Refresh allData
|
|
openDrawer(riId); // Reopen drawer with new account info
|
|
} else {
|
|
toast(data.message || 'Gagal membuat akun', 'error');
|
|
}
|
|
} catch (err) {
|
|
toast('Terjadi kesalahan: ' + err.message, 'error');
|
|
} finally {
|
|
if (btn) { btn.disabled = false; btn.textContent = '🔑 Buat Akun'; }
|
|
}
|
|
}
|
|
|
|
async function hapusAkun(id, nama) {
|
|
if (!confirm(`Hapus akun login untuk "${nama}"?\nRumah ibadah tidak akan dihapus, hanya akun loginnya.`)) return;
|
|
const res = await fetch(`api/manajemen_akun.php?id=${id}`, { method: 'DELETE' });
|
|
const data = await res.json();
|
|
if (data.status === 'success') {
|
|
toast(data.message, 'success');
|
|
document.getElementById('detail-overlay').classList.remove('open');
|
|
await loadData();
|
|
} else {
|
|
toast(data.message || 'Gagal menghapus akun', 'error');
|
|
}
|
|
}
|
|
|
|
function goToEdit(id) {
|
|
window.location.href = `?page=map`;
|
|
}
|
|
|
|
function riEmoji(jenis) {
|
|
const map = { 'Masjid': '🕌', 'Gereja': '⛪', 'Pura': '🛕', 'Vihara': '🏯', 'Klenteng': '🏮', 'Lainnya': '🏛️' };
|
|
return map[jenis] || '🏛️';
|
|
}
|
|
|
|
function esc(s) {
|
|
if (s == null) return '';
|
|
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
function toast(msg, type = 'info') {
|
|
const t = document.createElement('div');
|
|
t.className = `toast ${type}`;
|
|
t.textContent = msg;
|
|
document.getElementById('toast-container').appendChild(t);
|
|
setTimeout(() => t.remove(), 3500);
|
|
}
|
|
|
|
function openModal(title, bodyHTML, footerHTML) {
|
|
document.getElementById('modal-title').textContent = title;
|
|
document.getElementById('modal-body').innerHTML = bodyHTML;
|
|
document.getElementById('modal-footer').innerHTML = footerHTML;
|
|
document.getElementById('modal-overlay').classList.add('open');
|
|
}
|
|
function closeModal() {
|
|
document.getElementById('modal-overlay').classList.remove('open');
|
|
}
|
|
document.getElementById('modal-overlay').addEventListener('click', e => {
|
|
if (e.target.id === 'modal-overlay') closeModal();
|
|
});
|
|
|
|
async function handleLogout() {
|
|
if (!confirm('Apakah Anda yakin ingin keluar?')) return;
|
|
try {
|
|
const response = await fetch('api/logout.php');
|
|
const data = await response.json();
|
|
if (data.status === 'success') window.location.href = '?page=login';
|
|
} catch { alert('Gagal logout.'); }
|
|
} |