Files

312 lines
15 KiB
PHP

<?php
// ================================================================
// Admin: Manajemen User
// ================================================================
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
requireAdmin(); // Hanya admin
$pageTitle = 'Manajemen Pengguna';
require_once __DIR__ . '/../includes/header.php';
$db = getDB();
$current = getCurrentUser();
$users = $db->query("SELECT id, nama, email, role, created_at FROM users ORDER BY role DESC, nama ASC")->fetchAll();
?>
<!-- ── Page Header ─────────────────────────────────────────── -->
<div class="page-header">
<div class="page-title-block">
<h1><i class="bi bi-person-gear"></i> Manajemen Pengguna</h1>
<div class="page-subtitle">Total <?= count($users) ?> pengguna terdaftar</div>
</div>
<div class="page-actions">
<button class="btn btn-primary btn-sm" onclick="openUserModal()">
<i class="bi bi-person-plus-fill"></i> Tambah Pengguna
</button>
</div>
</div>
<!-- ── Users Table ─────────────────────────────────────────── -->
<div class="card">
<div class="table-responsive">
<table class="admin-table">
<thead>
<tr>
<th>#</th>
<th>Nama</th>
<th>Email</th>
<th>Role</th>
<th>Terdaftar</th>
<th class="td-actions">Aksi</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $i => $u): ?>
<tr>
<td style="color:var(--text3);font-size:11px;"><?= $i + 1 ?></td>
<td>
<div style="display:flex;align-items:center;gap:10px;">
<div style="width:34px;height:34px;border-radius:50%;background:<?= $u['role'] === 'admin' ? '#5b7fff' : '#22c55e' ?>;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:14px;flex-shrink:0;">
<?= strtoupper(substr($u['nama'], 0, 1)) ?>
</div>
<div>
<div style="font-weight:700;"><?= htmlspecialchars($u['nama']) ?></div>
<?php if ($u['id'] == $current['id']): ?>
<div style="font-size:10px;color:var(--accent);">Anda</div>
<?php endif; ?>
</div>
</div>
</td>
<td style="font-size:12px;color:var(--text2);"><?= htmlspecialchars($u['email']) ?></td>
<td>
<span class="badge-status" style="background:<?= $u['role'] === 'admin' ? 'rgba(91,127,255,0.15)' : 'rgba(34,197,94,0.15)' ?>;color:<?= $u['role'] === 'admin' ? 'var(--accent)' : 'var(--green)' ?>;">
<i class="bi bi-<?= $u['role'] === 'admin' ? 'shield-fill' : 'person-fill' ?>"></i>
<?= ucfirst($u['role']) ?>
</span>
</td>
<td style="font-size:11px;color:var(--text3);"><?= formatTanggal($u['created_at']) ?></td>
<td class="td-actions">
<button class="btn btn-ghost btn-sm" onclick='openEditUser(<?= json_encode($u) ?>)' title="Edit">
<i class="bi bi-pencil-fill"></i>
</button>
<button class="btn btn-ghost btn-sm" onclick="openResetPwd(<?= $u['id'] ?>, '<?= addslashes($u['nama']) ?>')" title="Reset Password">
<i class="bi bi-key-fill"></i>
</button>
<?php if ($u['id'] != $current['id']): ?>
<button class="btn btn-danger btn-sm" onclick="deleteUser(<?= $u['id'] ?>, '<?= addslashes($u['nama']) ?>')" title="Hapus">
<i class="bi bi-trash-fill"></i>
</button>
<?php else: ?>
<button class="btn btn-ghost btn-sm" disabled title="Tidak bisa hapus akun sendiri">
<i class="bi bi-trash" style="opacity:.3"></i>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- ── Info Card ───────────────────────────────────────────── -->
<div class="card" style="margin-top:20px;">
<div class="card-body" style="padding:16px;">
<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:13px;">
<div style="display:flex;align-items:center;gap:8px;">
<span style="width:12px;height:12px;background:#5b7fff;border-radius:50%;display:inline-block;"></span>
<strong>Admin</strong>: Akses penuh ke semua fitur termasuk manajemen user, import/export
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span style="width:12px;height:12px;background:#22c55e;border-radius:50%;display:inline-block;"></span>
<strong>Operator</strong>: Akses CRUD rumah ibadah & penduduk miskin
</div>
</div>
</div>
</div>
<!-- ── Modal Tambah/Edit User ─────────────────────────────── -->
<div class="modal fade" id="modal-user" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="user-modal-title">👤 Tambah Pengguna</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="u-id">
<div class="form-group">
<label class="form-label">Nama Lengkap <span style="color:var(--red)">*</span></label>
<input class="form-control" id="u-nama" placeholder="Nama lengkap pengguna">
</div>
<div class="form-group">
<label class="form-label">Email <span style="color:var(--red)">*</span></label>
<input class="form-control" id="u-email" type="email" placeholder="email@domain.com">
</div>
<div class="form-group" id="u-pwd-group">
<label class="form-label">Password <span style="color:var(--red)" id="u-pwd-req">*</span></label>
<div style="position:relative;">
<input class="form-control" id="u-password" type="password" placeholder="Min. 6 karakter" style="padding-right:40px;">
<button type="button" onclick="togglePwd()" style="position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--text2);cursor:pointer;">
<i class="bi bi-eye" id="pwd-eye"></i>
</button>
</div>
<div class="form-hint" id="u-pwd-hint">Biarkan kosong jika tidak ingin mengubah password</div>
</div>
<div class="form-group">
<label class="form-label">Role <span style="color:var(--red)">*</span></label>
<select class="form-control" id="u-role">
<option value="operator">Operator</option>
<option value="admin">Admin</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" data-bs-dismiss="modal"><i class="bi bi-x-lg"></i> Batal</button>
<button type="button" class="btn btn-primary" id="btn-save-user"><i class="bi bi-floppy2-fill"></i> Simpan</button>
</div>
</div>
</div>
</div>
<!-- ── Modal Reset Password ───────────────────────────────── -->
<div class="modal fade" id="modal-reset-pwd" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">🔑 Reset Password</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="rp-id">
<div style="margin-bottom:16px;padding:12px;background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.3);border-radius:8px;font-size:13px;">
<i class="bi bi-exclamation-triangle-fill" style="color:#f59e0b"></i>
Reset password untuk: <strong id="rp-nama"></strong>
</div>
<div class="form-group">
<label class="form-label">Password Baru <span style="color:var(--red)">*</span></label>
<input class="form-control" id="rp-pwd" type="password" placeholder="Min. 6 karakter">
</div>
<div class="form-group">
<label class="form-label">Konfirmasi Password <span style="color:var(--red)">*</span></label>
<input class="form-control" id="rp-confirm" type="password" placeholder="Ulangi password baru">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" data-bs-dismiss="modal"><i class="bi bi-x-lg"></i> Batal</button>
<button type="button" class="btn btn-warning" id="btn-reset-pwd"><i class="bi bi-key-fill"></i> Reset Password</button>
</div>
</div>
</div>
</div>
<script>
const BASE = '<?= BASE_URL ?>';
let userModalBs, resetPwdModalBs;
document.addEventListener('DOMContentLoaded', () => {
userModalBs = new bootstrap.Modal(document.getElementById('modal-user'));
resetPwdModalBs = new bootstrap.Modal(document.getElementById('modal-reset-pwd'));
});
function openUserModal() {
document.getElementById('user-modal-title').textContent = '👤 Tambah Pengguna';
document.getElementById('u-id').value = '';
document.getElementById('u-nama').value = '';
document.getElementById('u-email').value = '';
document.getElementById('u-password').value = '';
document.getElementById('u-role').value = 'operator';
document.getElementById('u-pwd-req').style.display = 'inline';
document.getElementById('u-pwd-hint').style.display = 'none';
userModalBs.show();
}
function openEditUser(data) {
document.getElementById('user-modal-title').textContent = '✏️ Edit Pengguna';
document.getElementById('u-id').value = data.id;
document.getElementById('u-nama').value = data.nama;
document.getElementById('u-email').value = data.email;
document.getElementById('u-password').value = '';
document.getElementById('u-role').value = data.role;
document.getElementById('u-pwd-req').style.display = 'none';
document.getElementById('u-pwd-hint').style.display = 'block';
userModalBs.show();
}
function openResetPwd(id, nama) {
document.getElementById('rp-id').value = id;
document.getElementById('rp-nama').textContent = nama;
document.getElementById('rp-pwd').value = '';
document.getElementById('rp-confirm').value = '';
resetPwdModalBs.show();
}
function togglePwd() {
const inp = document.getElementById('u-password');
const eye = document.getElementById('pwd-eye');
if (inp.type === 'password') {
inp.type = 'text'; eye.className = 'bi bi-eye-slash';
} else {
inp.type = 'password'; eye.className = 'bi bi-eye';
}
}
// ── Save User ─────────────────────────────────────────────────
document.getElementById('btn-save-user').addEventListener('click', async function() {
const nama = document.getElementById('u-nama').value.trim();
const email = document.getElementById('u-email').value.trim();
const pwd = document.getElementById('u-password').value;
const role = document.getElementById('u-role').value;
const editId = document.getElementById('u-id').value;
if (!nama || !email) { Swal.fire('Error', 'Nama dan email wajib diisi!', 'error'); return; }
if (!editId && !pwd) { Swal.fire('Error', 'Password wajib diisi untuk pengguna baru!', 'error'); return; }
if (pwd && pwd.length < 6) { Swal.fire('Error', 'Password minimal 6 karakter!', 'error'); return; }
const payload = { nama, email, role };
if (pwd) payload.password = pwd;
this.disabled = true; this.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
try {
const url = editId ? `${BASE}/api/users.php?id=${editId}` : `${BASE}/api/users.php`;
const resp = await fetch(url, { method: editId ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
const json = await resp.json();
if (json.success) {
Swal.fire({ title: 'Berhasil!', icon: 'success', timer: 1500, background: '#161b27', color: '#e8ecf4' })
.then(() => location.reload());
} else { Swal.fire('Error', json.message, 'error'); }
} catch { Swal.fire('Error', 'Gagal menyimpan data', 'error'); }
finally { this.disabled = false; this.innerHTML = '<i class="bi bi-floppy2-fill"></i> Simpan'; }
});
// ── Reset Password ─────────────────────────────────────────────
document.getElementById('btn-reset-pwd').addEventListener('click', async function() {
const id = document.getElementById('rp-id').value;
const pwd = document.getElementById('rp-pwd').value;
const confirm = document.getElementById('rp-confirm').value;
if (!pwd || pwd.length < 6) { Swal.fire('Error', 'Password minimal 6 karakter!', 'error'); return; }
if (pwd !== confirm) { Swal.fire('Error', 'Konfirmasi password tidak cocok!', 'error'); return; }
this.disabled = true;
try {
const resp = await fetch(`${BASE}/api/users.php?id=${id}`, {
method: 'PUT', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: pwd })
});
const json = await resp.json();
if (json.success) {
Swal.fire({ title: 'Password Direset!', icon: 'success', timer: 1500, background: '#161b27', color: '#e8ecf4' })
.then(() => location.reload());
} else { Swal.fire('Error', json.message, 'error'); }
} catch { Swal.fire('Error', 'Gagal reset password', 'error'); }
finally { this.disabled = false; }
});
// ── Delete User ───────────────────────────────────────────────
async function deleteUser(id, nama) {
const res = await Swal.fire({
title: 'Hapus Pengguna?',
html: `Pengguna <b>${nama}</b> akan dihapus permanen.`,
icon: 'warning', showCancelButton: true,
confirmButtonText: 'Ya, Hapus!', cancelButtonText: 'Batal',
confirmButtonColor: '#ef4444', background: '#161b27', color: '#e8ecf4'
});
if (!res.isConfirmed) return;
try {
const r = await fetch(`${BASE}/api/users.php?id=${id}`, { method: 'DELETE' });
const js = await r.json();
if (js.success) {
Swal.fire({ title: 'Terhapus!', icon: 'success', timer: 1200, background: '#161b27', color: '#e8ecf4' })
.then(() => location.reload());
} else { Swal.fire('Error', js.message, 'error'); }
} catch { Swal.fire('Error', 'Gagal menghapus', 'error'); }
}
</script>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>