Update Project 3 files
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* admin_reset_password.php — Admin mereset password user
|
||||
* POST: request_id, user_id, new_password
|
||||
*/
|
||||
session_start();
|
||||
require_once 'koneksi.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'admin') {
|
||||
echo json_encode(['success' => 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();
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* auth_check.php — Cek Session Aktif
|
||||
*/
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
echo json_encode([
|
||||
'logged_in' => 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]);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* auth_login.php — Login User
|
||||
* POST: email, password
|
||||
*/
|
||||
session_start();
|
||||
require_once 'koneksi.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => 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();
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* auth_logout.php — Logout User
|
||||
* Supports both AJAX (POST) and direct navigation (GET)
|
||||
*/
|
||||
session_start();
|
||||
session_unset();
|
||||
session_destroy();
|
||||
|
||||
// If called via AJAX (POST), return JSON
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
// If navigated directly, redirect to login
|
||||
header('Location: login.html');
|
||||
exit;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* auth_register.php — Daftar Akun Baru
|
||||
* POST: nama, email, password, role
|
||||
*/
|
||||
session_start();
|
||||
require_once 'koneksi.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => 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();
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* get_laporan_user.php — Ambil semua laporan (untuk history masyarakat)
|
||||
* Masyarakat bisa melihat semua laporan tapi read-only
|
||||
*/
|
||||
session_start();
|
||||
require_once 'koneksi.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => 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();
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* get_pending_users.php — Ambil daftar user yang statusnya 'pending'
|
||||
* Hanya admin yang boleh akses
|
||||
*/
|
||||
session_start();
|
||||
require_once 'koneksi.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'admin') {
|
||||
echo json_encode(['success' => 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();
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* get_reset_requests.php — Ambil daftar permintaan reset password (Admin only)
|
||||
*/
|
||||
session_start();
|
||||
require_once 'koneksi.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'admin') {
|
||||
echo json_encode(['success' => 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();
|
||||
@@ -1,3 +1,11 @@
|
||||
<?php
|
||||
session_start();
|
||||
// Cek jika belum login, arahkan langsung ke login.html untuk mencegah flicker
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.html');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
@@ -35,67 +43,36 @@
|
||||
<span class="nav-item-label">Pelaporan</span>
|
||||
<span class="nav-badge hidden" id="navReportBadge">0</span>
|
||||
</button>
|
||||
<button class="nav-item" id="navHistory" onclick="navigateTo('history')" style="display:none">
|
||||
<span class="nav-item-icon">📜</span>
|
||||
<span class="nav-item-label">History</span>
|
||||
</button>
|
||||
<button class="nav-item" id="navVerify" onclick="openVerifyModal()" style="display:none">
|
||||
<span class="nav-item-icon">🛡️</span>
|
||||
<span class="nav-item-label">Verifikasi Akun</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Kanan: Role switcher -->
|
||||
<!-- Kanan: User info — tampilkan data session dari PHP -->
|
||||
<div class="nav-right">
|
||||
<div id="roleIndicator" class="role-pill">
|
||||
<span id="roleIcon">👑</span>
|
||||
<span id="roleLabel">Admin</span>
|
||||
<span id="roleIcon"><?php
|
||||
$roleIcons = ['admin'=>'👑','surveyer'=>'📋','viewer'=>'👁'];
|
||||
echo $roleIcons[$_SESSION['user_role'] ?? ''] ?? '👤';
|
||||
?></span>
|
||||
<span id="roleLabel"><?php
|
||||
$roleLabels = ['admin'=>'Admin','surveyer'=>'Surveyer','viewer'=>'Masyarakat'];
|
||||
echo htmlspecialchars($roleLabels[$_SESSION['user_role'] ?? ''] ?? 'Guest');
|
||||
?></span>
|
||||
</div>
|
||||
<button id="roleSwitchBtn" onclick="openRoleModal()">Ganti Role ▾</button>
|
||||
<div id="userNamePill" class="user-name-pill">
|
||||
<span id="userNameDisplay"><?php echo htmlspecialchars($_SESSION['user_nama'] ?? ''); ?></span>
|
||||
</div>
|
||||
<a href="auth_logout.php" id="btnLogout" class="btn-logout" onclick="return confirm('Yakin ingin keluar?')">🚪 Keluar</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- =====================================================================
|
||||
ROLE MODAL
|
||||
===================================================================== -->
|
||||
<div id="roleModal" class="modal-overlay hidden">
|
||||
<div class="modal-box" style="max-width:420px">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">🔐 Pilih Role Pengguna</div>
|
||||
<button class="modal-close" onclick="closeRoleModal()">✕</button>
|
||||
</div>
|
||||
<div class="modal-body" style="padding:16px">
|
||||
<p style="font-size:12px;color:var(--text-muted);margin-bottom:14px">Pilih role sesuai hak akses Anda. Perubahan langsung berlaku.</p>
|
||||
<div class="role-cards">
|
||||
<div class="role-card" id="roleCard_admin" onclick="setRole('admin')">
|
||||
<div class="role-card-icon">👑</div>
|
||||
<div class="role-card-name">Admin</div>
|
||||
<div class="role-card-desc">Akses penuh — tambah, edit, hapus, lihat semua data dan laporan</div>
|
||||
<div class="role-card-perms">
|
||||
<span class="perm perm-green">✓ Tambah & Edit</span>
|
||||
<span class="perm perm-green">✓ Hapus & Reset</span>
|
||||
<span class="perm perm-green">✓ Lihat laporan</span>
|
||||
<span class="perm perm-green">✓ Kirim laporan</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="role-card" id="roleCard_surveyer" onclick="setRole('surveyer')">
|
||||
<div class="role-card-icon">📋</div>
|
||||
<div class="role-card-name">Surveyer</div>
|
||||
<div class="role-card-desc">Input data & kelola laporan — tidak bisa hapus atau reset</div>
|
||||
<div class="role-card-perms">
|
||||
<span class="perm perm-green">✓ Tambah & Edit</span>
|
||||
<span class="perm perm-red">✕ Hapus & Reset</span>
|
||||
<span class="perm perm-green">✓ Lihat laporan</span>
|
||||
<span class="perm perm-green">✓ Kirim laporan</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="role-card" id="roleCard_viewer" onclick="setRole('viewer')">
|
||||
<div class="role-card-icon">👁</div>
|
||||
<div class="role-card-name">Viewer</div>
|
||||
<div class="role-card-desc">Hanya lihat peta & kirim laporan masyarakat</div>
|
||||
<div class="role-card-perms">
|
||||
<span class="perm perm-green">✓ Lihat peta</span>
|
||||
<span class="perm perm-red">✕ Edit data</span>
|
||||
<span class="perm perm-red">✕ Lihat laporan</span>
|
||||
<span class="perm perm-green">✓ Kirim laporan</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Role modal removed — role is now from database/session -->
|
||||
|
||||
<!-- =====================================================================
|
||||
PAGE: PETA (default)
|
||||
@@ -309,6 +286,44 @@
|
||||
</div>
|
||||
</div><!-- end #pagePelaporan -->
|
||||
|
||||
<!-- =====================================================================
|
||||
PAGE: HISTORY PELAPORAN (Masyarakat — read-only)
|
||||
===================================================================== -->
|
||||
<div id="pageHistory" class="page">
|
||||
<div class="history-page">
|
||||
<div class="history-header">
|
||||
<div class="history-header-content">
|
||||
<div class="history-title">📜 History Pelaporan</div>
|
||||
<div class="history-sub">Riwayat semua laporan yang telah dikirim oleh masyarakat</div>
|
||||
</div>
|
||||
<div class="history-stats">
|
||||
<div class="history-stat">
|
||||
<span class="history-stat-num" id="historyTotal">0</span>
|
||||
<span class="history-stat-label">Total Laporan</span>
|
||||
</div>
|
||||
<div class="history-stat">
|
||||
<span class="history-stat-num" id="historyBaru">0</span>
|
||||
<span class="history-stat-label">Baru</span>
|
||||
</div>
|
||||
<div class="history-stat">
|
||||
<span class="history-stat-num" id="historyDitangani">0</span>
|
||||
<span class="history-stat-label">Ditangani</span>
|
||||
</div>
|
||||
<div class="history-stat">
|
||||
<span class="history-stat-num" id="historySelesai">0</span>
|
||||
<span class="history-stat-label">Selesai</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="history-toolbar">
|
||||
<input type="text" id="searchHistory" class="search-input" placeholder="🔍 Cari laporan..." oninput="filterHistory()" style="flex:1"/>
|
||||
</div>
|
||||
<div class="history-list" id="historyList">
|
||||
<div class="empty-state" style="margin-top:40px">📭 Belum ada laporan.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- end #pageHistory -->
|
||||
|
||||
|
||||
<!-- =====================================================================
|
||||
CUSTOM DATE PICKER
|
||||
@@ -404,6 +419,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =====================================================================
|
||||
MODAL — VERIFIKASI AKUN (Admin)
|
||||
===================================================================== -->
|
||||
<div id="verifyModal" class="modal-overlay hidden">
|
||||
<div class="modal-box modal-wide" style="max-width: 620px;">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">🛡️ Panel Admin</div>
|
||||
<button class="modal-close" onclick="closeVerifyModal()">✕</button>
|
||||
</div>
|
||||
<!-- Tabs -->
|
||||
<div style="display:flex; border-bottom:1px solid var(--border); background:#fafafa;">
|
||||
<button id="vTabVerify" onclick="switchAdminTab('verify')" style="flex:1;padding:11px;border:none;background:none;font-family:var(--font);font-size:13px;font-weight:700;cursor:pointer;color:var(--primary);border-bottom:3px solid var(--primary);">Verifikasi Akun</button>
|
||||
<button id="vTabReset" onclick="switchAdminTab('reset')" style="flex:1;padding:11px;border:none;background:none;font-family:var(--font);font-size:13px;font-weight:600;cursor:pointer;color:var(--text-muted);border-bottom:3px solid transparent;">🔑 Reset Password <span id="resetBadge" style="display:none;background:var(--orange);color:white;border-radius:10px;padding:1px 7px;font-size:10px;margin-left:4px;">0</span></button>
|
||||
</div>
|
||||
<div class="modal-body" style="max-height: 400px; overflow-y: auto;">
|
||||
<!-- Panel Verifikasi Akun -->
|
||||
<div id="adminPanelVerify">
|
||||
<div id="verifyListContainer">
|
||||
<div class="empty-state">Memuat data...</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Panel Reset Password -->
|
||||
<div id="adminPanelReset" style="display:none;">
|
||||
<div id="resetListContainer">
|
||||
<div class="empty-state">Memuat data...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
+6
-5
@@ -1,11 +1,12 @@
|
||||
<?php
|
||||
|
||||
define('DB_HOST', 'ys0kkgokgswgoc8ow0wgks0s');
|
||||
define('DB_USER', 'mysql');
|
||||
define('DB_PASS', '2DbtID7JC92R3imZjAvKFXYSISCLhsltWFROPQu6yZAlMZ3iXReXpJcygC3lnDZ2');
|
||||
define('DB_NAME', 'default');
|
||||
// Coba koneksi remote terlebih dahulu
|
||||
$conn = @new mysqli('ys0kkgokgswgoc8ow0wgks0s', 'mysql', '2DbtID7JC92R3imZjAvKFXYSISCLhsltWFROPQu6yZAlMZ3iXReXpJcygC3lnDZ2', 'default');
|
||||
|
||||
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
||||
// Fallback ke localhost (XAMPP) jika remote gagal
|
||||
if ($conn->connect_error) {
|
||||
$conn = new mysqli('localhost', 'root', '', 'webgis_bansos');
|
||||
}
|
||||
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
|
||||
+1007
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* request_reset.php — Kirim permintaan reset password ke Admin
|
||||
* POST: email
|
||||
*/
|
||||
session_start();
|
||||
require_once 'koneksi.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => 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();
|
||||
+369
-19
@@ -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 = '<div class="empty-state">Memuat data...</div>';
|
||||
try {
|
||||
const res = await fetch('get_pending_users.php');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (data.data.length === 0) {
|
||||
container.innerHTML = '<div class="empty-state">✅ Tidak ada akun yang menunggu verifikasi.</div>';
|
||||
} else {
|
||||
let html = '<div style="display:flex; flex-direction:column; gap:10px; padding:4px 0;">';
|
||||
data.data.forEach(u => {
|
||||
const roleLabel = ROLE_CONFIG[u.role] ? ROLE_CONFIG[u.role].label : u.role;
|
||||
html += `
|
||||
<div style="border:1px solid var(--border); border-radius:8px; padding:12px; display:flex; justify-content:space-between; align-items:center;">
|
||||
<div>
|
||||
<div style="font-weight:700; font-size:14px;">${u.nama}</div>
|
||||
<div style="font-size:12px; color:var(--text-muted);">${u.email} • <span style="font-weight:600; color:var(--primary);">${roleLabel}</span></div>
|
||||
<div style="font-size:10px; color:var(--text-muted); margin-top:4px;">🕒 Terdaftar: ${u.waktu}</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<button onclick="verifyUserAction(${u.id}, 'reject')" style="background:var(--danger-light); color:var(--danger); border:1px solid #fca5a5; padding:6px 12px; border-radius:6px; cursor:pointer; font-weight:600; font-size:12px;">Tolak</button>
|
||||
<button onclick="verifyUserAction(${u.id}, 'approve')" style="background:var(--success-light); color:var(--success); border:1px solid #86efac; padding:6px 12px; border-radius:6px; cursor:pointer; font-weight:600; font-size:12px;">Setujui</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = `<div class="empty-state" style="color:red;">Gagal memuat: ${data.message}</div>`;
|
||||
}
|
||||
} catch (e) {
|
||||
container.innerHTML = `<div class="empty-state" style="color:red;">Error mengambil data.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
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 = '<div class="empty-state">Memuat data...</div>';
|
||||
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 = '<div class="empty-state">✅ Tidak ada permintaan reset password.</div>';
|
||||
} else {
|
||||
let html = '<div style="display:flex; flex-direction:column; gap:12px; padding:4px 0;">';
|
||||
data.data.forEach(r => {
|
||||
const roleLabel = ROLE_CONFIG[r.role] ? ROLE_CONFIG[r.role].label : r.role;
|
||||
html += `
|
||||
<div style="border:1px solid #fde68a; border-radius:8px; padding:14px; background:#fffbeb;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:10px;">
|
||||
<div>
|
||||
<div style="font-weight:700; font-size:14px;">🔑 ${r.nama}</div>
|
||||
<div style="font-size:12px; color:var(--text-muted);">${r.email} • <span style="font-weight:600; color:var(--primary);">${roleLabel}</span></div>
|
||||
<div style="font-size:10px; color:var(--text-muted); margin-top:3px;">🕒 Diminta: ${r.waktu}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px; align-items:center;">
|
||||
<div style="position:relative; flex:1;">
|
||||
<input type="password" id="newPw_${r.id}" placeholder="Password baru (min. 6 karakter)" style="width:100%; padding:8px 30px 8px 10px; border:1.5px solid #d97706; border-radius:6px; font-family:inherit; font-size:12px; outline:none; box-sizing:border-box;"/>
|
||||
<button onclick="toggleAdminResetPassword(${r.id})" id="togglePw_${r.id}" style="position:absolute; right:8px; top:50%; transform:translateY(-50%); background:none; border:none; cursor:pointer; font-size:14px; color:var(--text-muted);">👁</button>
|
||||
</div>
|
||||
<button onclick="adminDoReset(${r.id}, ${r.user_id})" style="background:#d97706; color:white; border:none; padding:8px 14px; border-radius:6px; cursor:pointer; font-weight:700; font-size:12px; white-space:nowrap;">Reset Password</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = `<div class="empty-state" style="color:red;">Gagal: ${data.message}</div>`;
|
||||
}
|
||||
} catch (e) {
|
||||
container.innerHTML = `<div class="empty-state" style="color:red;">Error mengambil data.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
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 = '<div class="empty-state" style="margin-top:40px">📭 Tidak ada laporan yang ditemukan.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const statusLabels = {
|
||||
baru: '🆕 Baru',
|
||||
ditangani: '🔄 Ditangani',
|
||||
selesai: '✅ Selesai'
|
||||
};
|
||||
|
||||
container.innerHTML = list.map(r => {
|
||||
const statusClass = r.status || 'baru';
|
||||
return `
|
||||
<div class="history-card">
|
||||
<div class="history-card-top">
|
||||
<div class="history-card-name">👤 ${r.name || 'Anonim'}</div>
|
||||
<div class="history-card-time">🕐 ${r.time || ''}</div>
|
||||
</div>
|
||||
<div class="history-card-text">${r.text || ''}</div>
|
||||
${r.imgBase64 ? `<img class="history-card-img" src="${r.imgBase64}" alt="Foto laporan"/>` : ''}
|
||||
<div class="history-card-footer">
|
||||
<div class="history-card-status ${statusClass}">${statusLabels[statusClass] || '🆕 Baru'}</div>
|
||||
<div class="history-card-readonly">👁 Hanya Lihat</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}).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();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* setup_users.php — Buat tabel users (jalankan 1x saja)
|
||||
*/
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role ENUM('admin', 'surveyer', 'viewer') DEFAULT 'viewer',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo json_encode(['success' => true, 'message' => 'Tabel users berhasil dibuat!']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal: ' . $conn->error]);
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
+10
-1
@@ -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'] ?? '';
|
||||
|
||||
|
||||
+233
@@ -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);
|
||||
@@ -555,3 +571,220 @@ 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)} }
|
||||
|
||||
/* ============================================
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* verify_user.php — Approve atau Reject user yang pending
|
||||
* POST: user_id, action (approve/reject)
|
||||
* Hanya admin yang boleh akses
|
||||
*/
|
||||
session_start();
|
||||
require_once 'koneksi.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'admin') {
|
||||
echo json_encode(['success' => 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();
|
||||
Reference in New Issue
Block a user