From 7bb07f3335758ede040ecafecc9f6aa87acca389 Mon Sep 17 00:00:00 2001 From: pancaa Date: Wed, 10 Jun 2026 22:26:03 +0700 Subject: [PATCH] Update Project 3 files --- 03/admin_reset_password.php | 46 ++ 03/auth_check.php | 20 + 03/auth_login.php | 76 +++ 03/auth_logout.php | 18 + 03/auth_register.php | 69 +++ 03/get_laporan_user.php | 32 ++ 03/get_pending_users.php | 26 + 03/get_reset_requests.php | 32 ++ 03/{index.html => index.php} | 152 +++-- 03/koneksi.php | 11 +- 03/login.html | 1007 ++++++++++++++++++++++++++++++++++ 03/request_reset.php | 62 +++ 03/script.js | 388 ++++++++++++- 03/setup_users.php | 22 + 03/simpan_laporan.php | 11 +- 03/style.css | 235 +++++++- 03/verify_user.php | 41 ++ 17 files changed, 2169 insertions(+), 79 deletions(-) create mode 100644 03/admin_reset_password.php create mode 100644 03/auth_check.php create mode 100644 03/auth_login.php create mode 100644 03/auth_logout.php create mode 100644 03/auth_register.php create mode 100644 03/get_laporan_user.php create mode 100644 03/get_pending_users.php create mode 100644 03/get_reset_requests.php rename 03/{index.html => index.php} (77%) create mode 100644 03/login.html create mode 100644 03/request_reset.php create mode 100644 03/setup_users.php create mode 100644 03/verify_user.php diff --git a/03/admin_reset_password.php b/03/admin_reset_password.php new file mode 100644 index 0000000..0dcde10 --- /dev/null +++ b/03/admin_reset_password.php @@ -0,0 +1,46 @@ + false, 'message' => 'Akses ditolak']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + echo json_encode(['success' => false, 'message' => 'Hanya POST']); + exit; +} + +$request_id = (int)($_POST['request_id'] ?? 0); +$user_id = (int)($_POST['user_id'] ?? 0); +$new_password = $_POST['new_password'] ?? ''; + +if ($request_id <= 0 || $user_id <= 0 || strlen($new_password) < 6) { + echo json_encode(['success' => false, 'message' => 'Password minimal 6 karakter']); + exit; +} + +// Update password user +$hashed = password_hash($new_password, PASSWORD_DEFAULT); +$stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?"); +$stmt->bind_param("si", $hashed, $user_id); +if (!$stmt->execute()) { + echo json_encode(['success' => false, 'message' => 'Gagal update password']); + $stmt->close(); $conn->close(); exit; +} +$stmt->close(); + +// Tandai request sebagai done +$stmt2 = $conn->prepare("UPDATE password_resets SET status = 'done' WHERE id = ?"); +$stmt2->bind_param("i", $request_id); +$stmt2->execute(); +$stmt2->close(); + +echo json_encode(['success' => true, 'message' => 'Password berhasil direset. User bisa login dengan password baru.']); +$conn->close(); diff --git a/03/auth_check.php b/03/auth_check.php new file mode 100644 index 0000000..448c5a3 --- /dev/null +++ b/03/auth_check.php @@ -0,0 +1,20 @@ + true, + 'user' => [ + 'id' => $_SESSION['user_id'], + 'nama' => $_SESSION['user_nama'], + 'email' => $_SESSION['user_email'], + 'role' => $_SESSION['user_role'] + ] + ]); +} else { + echo json_encode(['logged_in' => false]); +} diff --git a/03/auth_login.php b/03/auth_login.php new file mode 100644 index 0000000..9086b40 --- /dev/null +++ b/03/auth_login.php @@ -0,0 +1,76 @@ + false, 'message' => 'Hanya POST']); + exit; +} + +$email = trim($_POST['email'] ?? ''); +$password = $_POST['password'] ?? ''; + +if (empty($email) || empty($password)) { + echo json_encode(['success' => false, 'message' => 'Email dan password wajib diisi']); + exit; +} + +$stmt = $conn->prepare("SELECT id, nama, email, password, role, status FROM users WHERE email = ?"); +$stmt->bind_param("s", $email); +$stmt->execute(); +$result = $stmt->get_result(); + +if ($result->num_rows === 0) { + echo json_encode(['success' => false, 'message' => 'Email tidak ditemukan']); + $stmt->close(); + $conn->close(); + exit; +} + +$user = $result->fetch_assoc(); + +if (!password_verify($password, $user['password'])) { + echo json_encode(['success' => false, 'message' => 'Password salah']); + $stmt->close(); + $conn->close(); + exit; +} + +// Cek status verifikasi akun +if ($user['status'] === 'pending') { + echo json_encode(['success' => false, 'message' => 'Akun Anda sedang menunggu verifikasi dari Admin. Harap bersabar.']); + $stmt->close(); + $conn->close(); + exit; +} +if ($user['status'] === 'rejected') { + echo json_encode(['success' => false, 'message' => 'Akun Anda ditolak oleh Admin.']); + $stmt->close(); + $conn->close(); + exit; +} + +// Set session +$_SESSION['user_id'] = (int)$user['id']; +$_SESSION['user_nama'] = $user['nama']; +$_SESSION['user_email']= $user['email']; +$_SESSION['user_role'] = $user['role']; + +echo json_encode([ + 'success' => true, + 'message' => 'Login berhasil!', + 'user' => [ + 'id' => (int)$user['id'], + 'nama' => $user['nama'], + 'email' => $user['email'], + 'role' => $user['role'] + ] +]); + +$stmt->close(); +$conn->close(); diff --git a/03/auth_logout.php b/03/auth_logout.php new file mode 100644 index 0000000..e60941c --- /dev/null +++ b/03/auth_logout.php @@ -0,0 +1,18 @@ + true]); +} else { + // If navigated directly, redirect to login + header('Location: login.html'); + exit; +} diff --git a/03/auth_register.php b/03/auth_register.php new file mode 100644 index 0000000..e04aff5 --- /dev/null +++ b/03/auth_register.php @@ -0,0 +1,69 @@ + false, 'message' => 'Hanya POST']); + exit; +} + +$nama = trim($_POST['nama'] ?? ''); +$email = trim($_POST['email'] ?? ''); +$password = $_POST['password'] ?? ''; +$role = $_POST['role'] ?? 'viewer'; + +// Validasi +if (empty($nama)) { + echo json_encode(['success' => false, 'message' => 'Nama wajib diisi']); + exit; +} +if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + echo json_encode(['success' => false, 'message' => 'Email tidak valid']); + exit; +} +if (strlen($password) < 6) { + echo json_encode(['success' => false, 'message' => 'Password minimal 6 karakter']); + exit; +} +// Validasi role (admin tidak boleh daftar sendiri) +if (!in_array($role, ['surveyer', 'viewer'])) { + echo json_encode(['success' => false, 'message' => 'Pendaftaran untuk role tersebut tidak diizinkan']); + exit; +} + +// Cek email sudah terdaftar +$stmt = $conn->prepare("SELECT id FROM users WHERE email = ?"); +$stmt->bind_param("s", $email); +$stmt->execute(); +if ($stmt->get_result()->num_rows > 0) { + echo json_encode(['success' => false, 'message' => 'Email sudah terdaftar']); + $stmt->close(); + $conn->close(); + exit; +} +$stmt->close(); + +// Hash password & simpan +$hashed = password_hash($password, PASSWORD_DEFAULT); +$stmt = $conn->prepare("INSERT INTO users (nama, email, password, role) VALUES (?, ?, ?, ?)"); +$stmt->bind_param("ssss", $nama, $email, $hashed, $role); + +if ($stmt->execute()) { + $userId = $conn->insert_id; + + // Jangan auto-login. Menunggu verifikasi admin. + echo json_encode([ + 'success' => true, + 'message' => 'Registrasi berhasil! Akun Anda sedang menunggu verifikasi dari Admin.' + ]); +} else { + echo json_encode(['success' => false, 'message' => 'Gagal menyimpan: ' . $conn->error]); +} + +$stmt->close(); +$conn->close(); diff --git a/03/get_laporan_user.php b/03/get_laporan_user.php new file mode 100644 index 0000000..2f24a46 --- /dev/null +++ b/03/get_laporan_user.php @@ -0,0 +1,32 @@ + false, 'message' => 'Belum login']); + exit; +} + +$response = ['success' => true, 'reports' => []]; + +$result = $conn->query("SELECT * FROM laporan ORDER BY created_at DESC LIMIT 100"); +if ($result) { + while ($row = $result->fetch_assoc()) { + $response['reports'][] = [ + 'id' => (int)$row['id'], + 'name' => $row['pelapor'] ?? 'Anonim', + 'text' => $row['deskripsi'], + 'imgBase64'=> $row['foto_base64'] ?? null, + 'time' => date('d/m/Y H:i', strtotime($row['created_at'])) + ]; + } + $result->free(); +} + +echo json_encode($response); +$conn->close(); diff --git a/03/get_pending_users.php b/03/get_pending_users.php new file mode 100644 index 0000000..8b83372 --- /dev/null +++ b/03/get_pending_users.php @@ -0,0 +1,26 @@ + false, 'message' => 'Akses ditolak. Hanya Admin.']); + exit; +} + +$stmt = $conn->prepare("SELECT id, nama, email, role, DATE_FORMAT(created_at, '%d %M %Y %H:%i') as waktu FROM users WHERE status = 'pending' ORDER BY created_at ASC"); +$stmt->execute(); +$result = $stmt->get_result(); + +$users = []; +while ($row = $result->fetch_assoc()) { + $users[] = $row; +} + +echo json_encode(['success' => true, 'data' => $users]); +$stmt->close(); +$conn->close(); diff --git a/03/get_reset_requests.php b/03/get_reset_requests.php new file mode 100644 index 0000000..30b3cd1 --- /dev/null +++ b/03/get_reset_requests.php @@ -0,0 +1,32 @@ + false, 'message' => 'Akses ditolak']); + exit; +} + +$stmt = $conn->prepare(" + SELECT pr.id, pr.user_id, pr.email, u.nama, u.role, + DATE_FORMAT(pr.created_at, '%d %M %Y %H:%i') as waktu + FROM password_resets pr + JOIN users u ON u.id = pr.user_id + WHERE pr.status = 'pending' + ORDER BY pr.created_at ASC +"); +$stmt->execute(); +$result = $stmt->get_result(); + +$requests = []; +while ($row = $result->fetch_assoc()) { + $requests[] = $row; +} + +echo json_encode(['success' => true, 'data' => $requests]); +$stmt->close(); +$conn->close(); diff --git a/03/index.html b/03/index.php similarity index 77% rename from 03/index.html rename to 03/index.php index c7261f8..6947341 100644 --- a/03/index.html +++ b/03/index.php @@ -1,3 +1,11 @@ + @@ -35,67 +43,36 @@ Pelaporan + + - + - - + + +
+
+
+
+
📜 History Pelaporan
+
Riwayat semua laporan yang telah dikirim oleh masyarakat
+
+
+
+ 0 + Total Laporan +
+
+ 0 + Baru +
+
+ 0 + Ditangani +
+
+ 0 + Selesai +
+
+
+
+ +
+
+
📭 Belum ada laporan.
+
+
+
+ + + diff --git a/03/koneksi.php b/03/koneksi.php index b786c1b..2fe16b8 100644 --- a/03/koneksi.php +++ b/03/koneksi.php @@ -1,11 +1,12 @@ connect_error) { + $conn = new mysqli('localhost', 'root', '', 'webgis_bansos'); +} if ($conn->connect_error) { http_response_code(500); diff --git a/03/login.html b/03/login.html new file mode 100644 index 0000000..58475b5 --- /dev/null +++ b/03/login.html @@ -0,0 +1,1007 @@ + + + + + + Login — BantSOSial GIS Pontianak + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/03/request_reset.php b/03/request_reset.php new file mode 100644 index 0000000..6d337c6 --- /dev/null +++ b/03/request_reset.php @@ -0,0 +1,62 @@ + false, 'message' => 'Hanya POST']); + exit; +} + +$email = trim($_POST['email'] ?? ''); + +if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + echo json_encode(['success' => false, 'message' => 'Email tidak valid']); + exit; +} + +// Cek apakah email terdaftar +$stmt = $conn->prepare("SELECT id, nama FROM users WHERE email = ? AND status = 'approved'"); +$stmt->bind_param("s", $email); +$stmt->execute(); +$result = $stmt->get_result(); + +if ($result->num_rows === 0) { + // Jangan reveal apakah email terdaftar atau tidak (keamanan) + echo json_encode(['success' => true, 'message' => 'Permintaan reset password Anda telah dikirim ke Admin.']); + $stmt->close(); + $conn->close(); + exit; +} + +$user = $result->fetch_assoc(); +$stmt->close(); + +// Cek apakah sudah ada request pending +$stmt2 = $conn->prepare("SELECT id FROM password_resets WHERE user_id = ? AND status = 'pending'"); +$stmt2->bind_param("i", $user['id']); +$stmt2->execute(); +if ($stmt2->get_result()->num_rows > 0) { + echo json_encode(['success' => true, 'message' => 'Permintaan reset sudah dikirim sebelumnya. Harap tunggu Admin memproses.']); + $stmt2->close(); + $conn->close(); + exit; +} +$stmt2->close(); + +// Simpan request reset +$stmt3 = $conn->prepare("INSERT INTO password_resets (user_id, email) VALUES (?, ?)"); +$stmt3->bind_param("is", $user['id'], $email); + +if ($stmt3->execute()) { + echo json_encode(['success' => true, 'message' => 'Permintaan reset password telah dikirim ke Admin. Harap tunggu konfirmasi.']); +} else { + echo json_encode(['success' => false, 'message' => 'Gagal mengirim permintaan: ' . $conn->error]); +} + +$stmt3->close(); +$conn->close(); diff --git a/03/script.js b/03/script.js index 07c00f0..44b2b60 100644 --- a/03/script.js +++ b/03/script.js @@ -34,22 +34,76 @@ const ROLE_CONFIG = { canReport: true, canViewReport: true, canViewDetail: true, canEditKas: false, canDragRadius: false }, viewer: { - label: 'Viewer', icon: '👁', + label: 'Masyarakat', icon: '👁', canAdd: false, canEdit: false, canDelete: false, canReset: false, canReport: true, canViewReport: false, canViewDetail: true, canEditKas: false, canDragRadius: false } }; +// User session data +let loggedInUser = null; + +// Auth check — redirect to login if not logged in +async function checkAuth() { + try { + const res = await fetch('auth_check.php'); + const data = await res.json(); + if (data.logged_in) { + loggedInUser = data.user; + currentRole = data.user.role; + updateUserUI(); + return true; + } else { + window.location.href = 'login.html'; + return false; + } + } catch (e) { + // Server not available — allow offline usage with default role + console.log('Auth server tidak tersedia, mode lokal.'); + return true; + } +} + +function updateUserUI() { + if (!loggedInUser) return; + const cfg = ROLE_CONFIG[loggedInUser.role] || ROLE_CONFIG.viewer; + + // Update role icon (label already set by PHP) + const roleIconEl = document.getElementById('roleIcon'); + if (roleIconEl) roleIconEl.textContent = cfg.icon; + + // Show history nav for viewer (masyarakat) + const navHistory = document.getElementById('navHistory'); + if (navHistory) { + navHistory.style.display = loggedInUser.role === 'viewer' ? 'flex' : 'none'; + } + + // Show verify nav for admin + const navVerify = document.getElementById('navVerify'); + if (navVerify) { + navVerify.style.display = loggedInUser.role === 'admin' ? 'flex' : 'none'; + } + + // Hide add buttons for viewer + applyRoleUI(); + + // Auto-fill report name if logged in + const reportNameEl = document.getElementById('reportName'); + if (reportNameEl && loggedInUser.nama) { + reportNameEl.value = loggedInUser.nama; + reportNameEl.readOnly = true; + reportNameEl.style.background = '#f0f4f8'; + } +} + + function openRoleModal() { - document.getElementById('roleModal').classList.remove('hidden'); - // Highlight active role card - document.querySelectorAll('.role-card').forEach(c => c.classList.remove('active-role')); - const activeCard = document.getElementById(`roleCard_${currentRole}`); - if (activeCard) activeCard.classList.add('active-role'); + // Role modal removed — role is from database/session + // No-op for backward compatibility } function closeRoleModal() { - document.getElementById('roleModal').classList.add('hidden'); + // No-op } function setRole(role) { @@ -60,13 +114,8 @@ function setRole(role) { document.getElementById('roleIcon').textContent = cfg.icon; document.getElementById('roleLabel').textContent = cfg.label; - // Role card highlight - document.querySelectorAll('.role-card').forEach(c => c.classList.remove('active-role')); - document.getElementById(`roleCard_${role}`)?.classList.add('active-role'); - // Apply UI restrictions applyRoleUI(); - closeRoleModal(); } function applyRoleUI() { @@ -147,24 +196,28 @@ function applyRoleUI() { function navigateTo(page) { const pagePeta = document.getElementById('pagePeta'); const pagePelaporan = document.getElementById('pagePelaporan'); + const pageHistory = document.getElementById('pageHistory'); const navHome = document.getElementById('navHome'); const navReport = document.getElementById('navReport'); + const navHistory = document.getElementById('navHistory'); + + // Hide all pages + [pagePeta, pagePelaporan, pageHistory].forEach(p => { if(p) p.classList.remove('active-page'); }); + [navHome, navReport, navHistory].forEach(n => { if(n) n.classList.remove('active'); }); if (page === 'map') { pagePeta.classList.add('active-page'); - pagePelaporan.classList.remove('active-page'); navHome.classList.add('active'); - navReport.classList.remove('active'); - // Fix map rendering when returning to map page setTimeout(() => map.invalidateSize(), 100); } else if (page === 'report') { - pagePeta.classList.remove('active-page'); pagePelaporan.classList.add('active-page'); - navHome.classList.remove('active'); navReport.classList.add('active'); - // Apply role UI for report page columns applyRoleUI(); renderReportList(); + } else if (page === 'history') { + pageHistory.classList.add('active-page'); + if (navHistory) navHistory.classList.add('active'); + loadHistory(); } } @@ -1625,10 +1678,307 @@ function highlightError(id, msg) { el.style.borderColor = '#dc2626'; el.placeholder = msg; el.focus(); } +// ============================================================ +// VERIFIKASI AKUN (Admin) +// ============================================================ +function openVerifyModal() { + document.getElementById('verifyModal').classList.remove('hidden'); + switchAdminTab('verify'); + loadPendingUsers(); + loadResetRequests(); +} + +function closeVerifyModal() { + document.getElementById('verifyModal').classList.add('hidden'); +} + +function switchAdminTab(tab) { + const panelVerify = document.getElementById('adminPanelVerify'); + const panelReset = document.getElementById('adminPanelReset'); + const tabVerify = document.getElementById('vTabVerify'); + const tabReset = document.getElementById('vTabReset'); + + if (tab === 'verify') { + panelVerify.style.display = 'block'; + panelReset.style.display = 'none'; + tabVerify.style.color = 'var(--primary)'; + tabVerify.style.borderBottom = '3px solid var(--primary)'; + tabVerify.style.fontWeight = '700'; + tabReset.style.color = 'var(--text-muted)'; + tabReset.style.borderBottom = '3px solid transparent'; + tabReset.style.fontWeight = '600'; + } else { + panelVerify.style.display = 'none'; + panelReset.style.display = 'block'; + tabReset.style.color = 'var(--primary)'; + tabReset.style.borderBottom = '3px solid var(--primary)'; + tabReset.style.fontWeight = '700'; + tabVerify.style.color = 'var(--text-muted)'; + tabVerify.style.borderBottom = '3px solid transparent'; + tabVerify.style.fontWeight = '600'; + } +} + +async function loadPendingUsers() { + const container = document.getElementById('verifyListContainer'); + container.innerHTML = '
Memuat data...
'; + try { + const res = await fetch('get_pending_users.php'); + const data = await res.json(); + if (data.success) { + if (data.data.length === 0) { + container.innerHTML = '
✅ Tidak ada akun yang menunggu verifikasi.
'; + } else { + let html = '
'; + data.data.forEach(u => { + const roleLabel = ROLE_CONFIG[u.role] ? ROLE_CONFIG[u.role].label : u.role; + html += ` +
+
+
${u.nama}
+
${u.email} • ${roleLabel}
+
🕒 Terdaftar: ${u.waktu}
+
+
+ + +
+
+ `; + }); + html += '
'; + container.innerHTML = html; + } + } else { + container.innerHTML = `
Gagal memuat: ${data.message}
`; + } + } catch (e) { + container.innerHTML = `
Error mengambil data.
`; + } +} + +async function verifyUserAction(userId, action) { + const actionText = action === 'approve' ? 'menyetujui' : 'menolak'; + if (!confirm(`Yakin ingin ${actionText} akun ini?`)) return; + + try { + const res = await fetch('verify_user.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `user_id=${userId}&action=${action}` + }); + const data = await res.json(); + if (data.success) { + alert(data.message); + loadPendingUsers(); + } else { + alert('Gagal: ' + data.message); + } + } catch (e) { + alert('Terjadi kesalahan koneksi.'); + } +} + +async function loadResetRequests() { + const container = document.getElementById('resetListContainer'); + if (!container) return; + container.innerHTML = '
Memuat data...
'; + try { + const res = await fetch('get_reset_requests.php'); + const data = await res.json(); + if (data.success) { + // Update badge + const badge = document.getElementById('resetBadge'); + if (badge) { + if (data.data.length > 0) { + badge.style.display = 'inline'; + badge.textContent = data.data.length; + } else { + badge.style.display = 'none'; + } + } + + if (data.data.length === 0) { + container.innerHTML = '
✅ Tidak ada permintaan reset password.
'; + } else { + let html = '
'; + data.data.forEach(r => { + const roleLabel = ROLE_CONFIG[r.role] ? ROLE_CONFIG[r.role].label : r.role; + html += ` +
+
+
+
🔑 ${r.nama}
+
${r.email} • ${roleLabel}
+
🕒 Diminta: ${r.waktu}
+
+
+
+
+ + +
+ +
+
+ `; + }); + html += '
'; + container.innerHTML = html; + } + } else { + container.innerHTML = `
Gagal: ${data.message}
`; + } + } catch (e) { + container.innerHTML = `
Error mengambil data.
`; + } +} + +function toggleAdminResetPassword(id) { + const input = document.getElementById(`newPw_${id}`); + const btn = document.getElementById(`togglePw_${id}`); + if (input.type === 'password') { + input.type = 'text'; + btn.textContent = '🔒'; + } else { + input.type = 'password'; + btn.textContent = '👁'; + } +} + +async function adminDoReset(requestId, userId) { + const newPw = document.getElementById(`newPw_${requestId}`)?.value || ''; + if (newPw.length < 6) { + alert('Password baru minimal 6 karakter!'); + return; + } + if (!confirm(`Yakin ingin mereset password untuk user ini?`)) return; + + try { + const res = await fetch('admin_reset_password.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `request_id=${requestId}&user_id=${userId}&new_password=${encodeURIComponent(newPw)}` + }); + const data = await res.json(); + if (data.success) { + alert('✅ ' + data.message); + loadResetRequests(); + } else { + alert('❌ Gagal: ' + data.message); + } + } catch (e) { + alert('Terjadi kesalahan koneksi.'); + } +} + + +// ============================================================ +// LOGOUT +// ============================================================ +function doLogout() { + if (!confirm('Yakin ingin keluar?')) return; + // Redirect langsung ke server — session pasti ter-destroy + window.location.href = 'auth_logout.php'; +} + + +// ============================================================ +// HISTORY PELAPORAN (Masyarakat — read-only) +// ============================================================ +let historyReports = []; + +async function loadHistory() { + try { + const res = await fetch('get_laporan_user.php'); + const data = await res.json(); + if (data.success && data.reports) { + historyReports = data.reports; + renderHistory(); + } + } catch(e) { + // If backend not available, show reports from memory + historyReports = [...reports]; + renderHistory(); + } +} + +function renderHistory() { + const container = document.getElementById('historyList'); + if (!container) return; + + // Update stats + const total = historyReports.length; + const baru = historyReports.filter(r => (r.status || 'baru') === 'baru').length; + const ditangani = historyReports.filter(r => r.status === 'ditangani').length; + const selesai = historyReports.filter(r => r.status === 'selesai').length; + + const elTotal = document.getElementById('historyTotal'); + const elBaru = document.getElementById('historyBaru'); + const elDitangani = document.getElementById('historyDitangani'); + const elSelesai = document.getElementById('historySelesai'); + if (elTotal) elTotal.textContent = total; + if (elBaru) elBaru.textContent = baru; + if (elDitangani) elDitangani.textContent = ditangani; + if (elSelesai) elSelesai.textContent = selesai; + + filterHistory(); +} + +function filterHistory() { + const q = (document.getElementById('searchHistory')?.value || '').toLowerCase(); + let filtered = [...historyReports]; + if (q) { + filtered = filtered.filter(r => + (r.name || '').toLowerCase().includes(q) || + (r.text || '').toLowerCase().includes(q) + ); + } + renderHistoryCards(filtered); +} + +function renderHistoryCards(list) { + const container = document.getElementById('historyList'); + if (!container) return; + + if (!list || list.length === 0) { + container.innerHTML = '
📭 Tidak ada laporan yang ditemukan.
'; + return; + } + + const statusLabels = { + baru: '🆕 Baru', + ditangani: '🔄 Ditangani', + selesai: '✅ Selesai' + }; + + container.innerHTML = list.map(r => { + const statusClass = r.status || 'baru'; + return ` +
+
+
👤 ${r.name || 'Anonim'}
+
🕐 ${r.time || ''}
+
+
${r.text || ''}
+ ${r.imgBase64 ? `Foto laporan` : ''} + +
`; + }).join(''); +} + // ============================================================ // INIT // ============================================================ -(function init() { +(async function init() { + const isAuth = await checkAuth(); + if (!isAuth) return; // Will redirect to login + + // Set role from session + setRole(currentRole); loadFromBackend(); updateStats(); applyRoleUI(); diff --git a/03/setup_users.php b/03/setup_users.php new file mode 100644 index 0000000..f73bab5 --- /dev/null +++ b/03/setup_users.php @@ -0,0 +1,22 @@ +query($sql)) { + echo json_encode(['success' => true, 'message' => 'Tabel users berhasil dibuat!']); +} else { + echo json_encode(['success' => false, 'message' => 'Gagal: ' . $conn->error]); +} + +$conn->close(); diff --git a/03/simpan_laporan.php b/03/simpan_laporan.php index 12e651e..c9cd126 100644 --- a/03/simpan_laporan.php +++ b/03/simpan_laporan.php @@ -3,13 +3,22 @@ * simpan_laporan.php — Simpan Laporan Masyarakat * POST: name, text, img (base64, opsional) */ +session_start(); require_once 'koneksi.php'; if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'message'=>'Hanya POST']); exit; } -$name = $conn->real_escape_string(strip_tags($_POST['name'] ?? 'Anonim')); +// Ambil nama dari session jika ada, fallback ke POST +$name = ''; +if (isset($_SESSION['user_nama']) && !empty($_SESSION['user_nama'])) { + $name = $_SESSION['user_nama']; +} else { + $name = strip_tags($_POST['name'] ?? 'Anonim'); +} +$name = $conn->real_escape_string($name); + $text = $conn->real_escape_string(strip_tags($_POST['text'] ?? '')); $img = $_POST['img'] ?? ''; diff --git a/03/style.css b/03/style.css index d6e81ff..d1f3c70 100644 --- a/03/style.css +++ b/03/style.css @@ -95,6 +95,22 @@ html, body { height: 100%; font-family: var(--font); background: var(--bg); over font-size: 12px; font-weight: 600; } +.user-name-pill { + display: flex; align-items: center; gap: 6px; + background: white; border: 1.5px solid var(--border); + border-radius: 20px; padding: 5px 12px; + font-size: 12px; font-weight: 700; color: var(--primary); +} + +.btn-logout { + padding: 6px 14px; background: #fee2e2; color: #dc2626; + border: 1px solid #fca5a5; border-radius: 20px; font-family: var(--font); + font-size: 12px; font-weight: 600; cursor: pointer; transition: all .2s; + text-decoration: none; display: inline-flex; align-items: center; +} +.btn-logout:hover { background: #dc2626; color: white; border-color: #dc2626; } + + #roleSwitchBtn { padding: 6px 14px; background: var(--primary); color: white; border: none; border-radius: 20px; font-family: var(--font); @@ -554,4 +570,221 @@ html, body { height: 100%; font-family: var(--font); background: var(--bg); over /* ============================================ ANIMATIONS ============================================ */ -@keyframes fadeIn { from{opacity:0;transform:translateX(-8px)} to{opacity:1;transform:translateX(0)} } \ No newline at end of file +@keyframes fadeIn { from{opacity:0;transform:translateX(-8px)} to{opacity:1;transform:translateX(0)} } + +/* ============================================ + USER NAME PILL & LOGOUT + ============================================ */ +.user-name-pill { + display: flex; align-items: center; gap: 6px; + background: var(--primary-light); border: 1.5px solid #99f6e4; + border-radius: 20px; padding: 5px 12px; + font-size: 12px; font-weight: 600; color: var(--primary); +} + +.btn-logout { + padding: 6px 14px; background: white; color: var(--danger); + border: 1.5px solid #fca5a5; border-radius: 20px; font-family: var(--font); + font-size: 11px; font-weight: 600; cursor: pointer; transition: all .2s; +} +.btn-logout:hover { background: var(--danger-light); border-color: var(--danger); } + +/* ============================================ + PAGE: HISTORY PELAPORAN + ============================================ */ +#pageHistory { display: none; background: var(--bg); } +#pageHistory.active-page { display: block; overflow-y: auto; } + +.history-page { + max-width: 900px; + margin: 0 auto; + padding: 0 20px; + min-height: calc(100vh - var(--nav-h)); +} + +.history-header { + background: white; + border-radius: 0 0 var(--radius-md) var(--radius-md); + padding: 24px 28px 20px; + border: 1px solid var(--border); + border-top: none; + margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + box-shadow: var(--shadow); +} + +.history-title { + font-size: 18px; + font-weight: 700; + margin-bottom: 3px; +} + +.history-sub { + font-size: 12px; + color: var(--text-muted); +} + +.history-stats { + display: flex; + gap: 12px; + flex-shrink: 0; +} + +.history-stat { + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 10px 16px; + text-align: center; + min-width: 80px; + transition: all .2s; +} + +.history-stat:hover { + transform: translateY(-1px); + box-shadow: var(--shadow); +} + +.history-stat-num { + display: block; + font-size: 20px; + font-weight: 700; + font-family: var(--mono); + color: var(--primary); + line-height: 1; +} + +.history-stat-label { + font-size: 9px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: .6px; + color: var(--text-muted); + margin-top: 3px; + display: block; +} + +.history-toolbar { + display: flex; + gap: 8px; + margin-bottom: 16px; +} + +.history-toolbar .search-input { + padding: 10px 14px; + font-size: 13px; + border-radius: var(--radius-md); + box-shadow: var(--shadow); +} + +.history-list { + display: flex; + flex-direction: column; + gap: 10px; + padding-bottom: 30px; +} + +/* History card — read-only version of report card */ +.history-card { + background: white; + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 16px 18px; + transition: all .2s; + animation: fadeIn .3s; + position: relative; +} + +.history-card:hover { + box-shadow: var(--shadow); + transform: translateY(-1px); + border-color: var(--primary); +} + +.history-card-top { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 8px; + gap: 10px; +} + +.history-card-name { + font-size: 13px; + font-weight: 700; +} + +.history-card-time { + font-size: 10px; + color: var(--text-muted); + font-family: var(--mono); + flex-shrink: 0; + background: var(--bg); + padding: 2px 8px; + border-radius: 12px; +} + +.history-card-text { + font-size: 13px; + color: var(--text); + line-height: 1.6; + margin-bottom: 10px; +} + +.history-card-img { + width: 100%; + max-height: 240px; + object-fit: cover; + border-radius: var(--radius-sm); + margin-bottom: 10px; + border: 1px solid var(--border); +} + +.history-card-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 10px; + border-top: 1px solid var(--border); +} + +.history-card-status { + font-size: 10px; + font-weight: 700; + padding: 3px 10px; + border-radius: 12px; +} + +.history-card-status.baru { background: #eff6ff; color: #1d4ed8; } +.history-card-status.ditangani { background: var(--warning-light); color: var(--warning); } +.history-card-status.selesai { background: var(--success-light); color: var(--success); } + +.history-card-readonly { + font-size: 10px; + color: var(--text-muted); + display: flex; + align-items: center; + gap: 4px; +} + +/* ============================================ + RESPONSIVE — History + ============================================ */ +@media (max-width: 768px) { + .history-header { + flex-direction: column; + align-items: flex-start; + } + .history-stats { + width: 100%; + justify-content: space-between; + } + .history-stat { + min-width: 0; + flex: 1; + padding: 8px 10px; + } +} \ No newline at end of file diff --git a/03/verify_user.php b/03/verify_user.php new file mode 100644 index 0000000..c58950d --- /dev/null +++ b/03/verify_user.php @@ -0,0 +1,41 @@ + false, 'message' => 'Akses ditolak. Hanya Admin.']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + echo json_encode(['success' => false, 'message' => 'Hanya POST']); + exit; +} + +$user_id = (int)($_POST['user_id'] ?? 0); +$action = $_POST['action'] ?? ''; + +if ($user_id <= 0 || !in_array($action, ['approve', 'reject'])) { + echo json_encode(['success' => false, 'message' => 'Parameter tidak valid']); + exit; +} + +$newStatus = ($action === 'approve') ? 'approved' : 'rejected'; + +$stmt = $conn->prepare("UPDATE users SET status = ? WHERE id = ?"); +$stmt->bind_param("si", $newStatus, $user_id); + +if ($stmt->execute()) { + echo json_encode(['success' => true, 'message' => "Akun berhasil di-" . $newStatus]); +} else { + echo json_encode(['success' => false, 'message' => 'Gagal memperbarui status: ' . $conn->error]); +} + +$stmt->close(); +$conn->close();