feat: add dashboard feature with modular API endpoints and UI integration
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
require_once '../auth_helper.php';
|
||||
requireLogin();
|
||||
include_once '../db_connect.php';
|
||||
|
||||
$user = getCurrentUser();
|
||||
$isPengelola = $user['role'] === 'pengelola';
|
||||
$ibadahId = $isPengelola ? intval($user['ibadah_id']) : null;
|
||||
|
||||
// ── Helper: Haversine distance in meters ──────────────────────────────────
|
||||
function haversine($lat1, $lng1, $lat2, $lng2) {
|
||||
$R = 6371000;
|
||||
$dLat = deg2rad($lat2 - $lat1);
|
||||
$dLng = deg2rad($lng2 - $lng1);
|
||||
$a = sin($dLat/2)*sin($dLat/2)
|
||||
+ cos(deg2rad($lat1))*cos(deg2rad($lat2))*sin($dLng/2)*sin($dLng/2);
|
||||
return $R * 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
}
|
||||
|
||||
// ── 1. Rumah Ibadah ───────────────────────────────────────────────────────
|
||||
if ($isPengelola && $ibadahId) {
|
||||
$riRes = $conn->query("SELECT * FROM rumah_ibadah WHERE id=$ibadahId");
|
||||
} else {
|
||||
$riRes = $conn->query("SELECT * FROM rumah_ibadah");
|
||||
}
|
||||
$ibadahList = [];
|
||||
while ($row = $riRes->fetch_assoc()) $ibadahList[] = $row;
|
||||
$totalIbadah = count($ibadahList);
|
||||
|
||||
// Distribusi per jenis
|
||||
$jenisDist = [];
|
||||
foreach ($ibadahList as $ib) {
|
||||
$j = $ib['jenis'] ?? 'Lainnya';
|
||||
$jenisDist[$j] = ($jenisDist[$j] ?? 0) + 1;
|
||||
}
|
||||
|
||||
// ── 2. Penduduk Miskin ────────────────────────────────────────────────────
|
||||
$pmRes = $conn->query("SELECT * FROM penduduk_miskin");
|
||||
$allMiskin = [];
|
||||
while ($row = $pmRes->fetch_assoc()) $allMiskin[] = $row;
|
||||
|
||||
// Filter: pengelola hanya lihat yang dalam radius ibadahnya
|
||||
if ($isPengelola && $ibadahId && !empty($ibadahList)) {
|
||||
$ib = $ibadahList[0];
|
||||
$filteredMiskin = array_filter($allMiskin, function($m) use ($ib) {
|
||||
return haversine((float)$ib['lat'], (float)$ib['lng'], (float)$m['lat'], (float)$m['lng']) <= (float)$ib['radius'];
|
||||
});
|
||||
$filteredMiskin = array_values($filteredMiskin);
|
||||
} else {
|
||||
$filteredMiskin = $allMiskin;
|
||||
}
|
||||
|
||||
$totalMiskin = count($filteredMiskin);
|
||||
$totalJiwa = array_sum(array_column($filteredMiskin, 'jumlah_jiwa'));
|
||||
|
||||
// Kategori bantuan
|
||||
$kategoriDist = [];
|
||||
foreach ($filteredMiskin as $m) {
|
||||
$k = $m['kategori_bantuan'] ?? 'Lainnya';
|
||||
$kategoriDist[$k] = ($kategoriDist[$k] ?? 0) + 1;
|
||||
}
|
||||
|
||||
// Terurus vs Tidak (dalam radius salah satu ibadah)
|
||||
$terurusCount = 0;
|
||||
$tidakTerurusCount = 0;
|
||||
foreach ($filteredMiskin as $m) {
|
||||
$inAnyRadius = false;
|
||||
foreach ($ibadahList as $ib) {
|
||||
if (haversine((float)$ib['lat'], (float)$ib['lng'], (float)$m['lat'], (float)$m['lng']) <= (float)$ib['radius']) {
|
||||
$inAnyRadius = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($inAnyRadius) $terurusCount++;
|
||||
else $tidakTerurusCount++;
|
||||
}
|
||||
|
||||
// Jika admin: hitung seluruh miskin dalam radius seluruh ibadah
|
||||
if (!$isPengelola) {
|
||||
$allIbadahRes = $conn->query("SELECT * FROM rumah_ibadah");
|
||||
$allIbadahList = [];
|
||||
while ($row = $allIbadahRes->fetch_assoc()) $allIbadahList[] = $row;
|
||||
|
||||
$terurusCount = 0;
|
||||
$tidakTerurusCount = 0;
|
||||
foreach ($allMiskin as $m) {
|
||||
$inAnyRadius = false;
|
||||
foreach ($allIbadahList as $ib) {
|
||||
if (haversine((float)$ib['lat'], (float)$ib['lng'], (float)$m['lat'], (float)$m['lng']) <= (float)$ib['radius']) {
|
||||
$inAnyRadius = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($inAnyRadius) $terurusCount++;
|
||||
else $tidakTerurusCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Log Bantuan ────────────────────────────────────────────────────────
|
||||
if ($isPengelola && $ibadahId) {
|
||||
$logRes = $conn->query("
|
||||
SELECT lb.*, ri.nama as nama_ibadah, ri.jenis as jenis_ibadah, pm.nama as nama_kk
|
||||
FROM log_bantuan lb
|
||||
JOIN rumah_ibadah ri ON ri.id = lb.ibadah_id
|
||||
JOIN penduduk_miskin pm ON pm.id = lb.miskin_id
|
||||
WHERE lb.ibadah_id = $ibadahId
|
||||
ORDER BY lb.tanggal DESC, lb.id DESC
|
||||
LIMIT 10
|
||||
");
|
||||
$totalLogRes = $conn->query("SELECT COUNT(*) as cnt FROM log_bantuan WHERE ibadah_id=$ibadahId");
|
||||
} else {
|
||||
$logRes = $conn->query("
|
||||
SELECT lb.*, ri.nama as nama_ibadah, ri.jenis as jenis_ibadah, pm.nama as nama_kk
|
||||
FROM log_bantuan lb
|
||||
JOIN rumah_ibadah ri ON ri.id = lb.ibadah_id
|
||||
JOIN penduduk_miskin pm ON pm.id = lb.miskin_id
|
||||
ORDER BY lb.tanggal DESC, lb.id DESC
|
||||
LIMIT 10
|
||||
");
|
||||
$totalLogRes = $conn->query("SELECT COUNT(*) as cnt FROM log_bantuan");
|
||||
}
|
||||
|
||||
$recentLogs = [];
|
||||
while ($row = $logRes->fetch_assoc()) $recentLogs[] = $row;
|
||||
|
||||
$totalLog = 0;
|
||||
if ($totalLogRes && $trow = $totalLogRes->fetch_assoc()) $totalLog = (int)$trow['cnt'];
|
||||
|
||||
// Distribusi tipe bantuan dari log
|
||||
$tipeDist = [];
|
||||
if ($isPengelola && $ibadahId) {
|
||||
$tipeRes = $conn->query("SELECT tipe_bantuan, COUNT(*) as cnt FROM log_bantuan WHERE ibadah_id=$ibadahId GROUP BY tipe_bantuan");
|
||||
} else {
|
||||
$tipeRes = $conn->query("SELECT tipe_bantuan, COUNT(*) as cnt FROM log_bantuan GROUP BY tipe_bantuan");
|
||||
}
|
||||
while ($row = $tipeRes->fetch_assoc()) $tipeDist[$row['tipe_bantuan']] = (int)$row['cnt'];
|
||||
|
||||
// Bantuan per bulan (6 bulan terakhir)
|
||||
if ($isPengelola && $ibadahId) {
|
||||
$bulanRes = $conn->query("
|
||||
SELECT DATE_FORMAT(tanggal, '%Y-%m') as bulan, COUNT(*) as cnt
|
||||
FROM log_bantuan
|
||||
WHERE ibadah_id=$ibadahId AND tanggal >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
|
||||
GROUP BY bulan ORDER BY bulan ASC
|
||||
");
|
||||
} else {
|
||||
$bulanRes = $conn->query("
|
||||
SELECT DATE_FORMAT(tanggal, '%Y-%m') as bulan, COUNT(*) as cnt
|
||||
FROM log_bantuan
|
||||
WHERE tanggal >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
|
||||
GROUP BY bulan ORDER BY bulan ASC
|
||||
");
|
||||
}
|
||||
$bantuanPerBulan = [];
|
||||
while ($row = $bulanRes->fetch_assoc()) $bantuanPerBulan[] = $row;
|
||||
|
||||
// ── 4. Ibadah Tipe (admin only: performa per ibadah) ─────────────────────
|
||||
$ibadahPerforma = [];
|
||||
if (!$isPengelola) {
|
||||
$perfRes = $conn->query("
|
||||
SELECT ri.id, ri.nama, ri.jenis, ri.radius,
|
||||
COUNT(DISTINCT lb.id) as total_log,
|
||||
COUNT(DISTINCT lb.miskin_id) as total_penerima
|
||||
FROM rumah_ibadah ri
|
||||
LEFT JOIN log_bantuan lb ON lb.ibadah_id = ri.id
|
||||
GROUP BY ri.id
|
||||
ORDER BY total_log DESC
|
||||
");
|
||||
while ($row = $perfRes->fetch_assoc()) $ibadahPerforma[] = $row;
|
||||
}
|
||||
|
||||
// ── Output ────────────────────────────────────────────────────────────────
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'role' => $user['role'],
|
||||
'ibadah_id' => $ibadahId,
|
||||
'stats' => [
|
||||
'total_ibadah' => $totalIbadah,
|
||||
'total_miskin' => $totalMiskin,
|
||||
'total_jiwa' => $totalJiwa,
|
||||
'total_log' => $totalLog,
|
||||
'terurus' => $terurusCount,
|
||||
'tidak_terurus' => $tidakTerurusCount,
|
||||
'pct_terurus' => $totalMiskin > 0 ? round($terurusCount / $totalMiskin * 100, 1) : 0,
|
||||
],
|
||||
'distribusi' => [
|
||||
'jenis_ibadah' => $jenisDist,
|
||||
'kategori' => $kategoriDist,
|
||||
'tipe_bantuan' => $tipeDist,
|
||||
'per_bulan' => $bantuanPerBulan,
|
||||
],
|
||||
'ibadah_list' => $ibadahList,
|
||||
'ibadah_performa' => $ibadahPerforma,
|
||||
'recent_logs' => $recentLogs,
|
||||
]);
|
||||
@@ -4,11 +4,29 @@ require_once '../auth_helper.php';
|
||||
requireAdminOrPengelola();
|
||||
include_once '../db_connect.php';
|
||||
|
||||
// Ambil info ibadah pengelola jika role pengelola
|
||||
$currentUser = getCurrentUser();
|
||||
$ibadahFilter = null; // null = admin (tidak dibatasi)
|
||||
if ($currentUser && $currentUser['role'] === 'pengelola') {
|
||||
$ibadah_id = $currentUser['ibadah_id'];
|
||||
if (!$ibadah_id) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Akun pengelola tidak terhubung ke rumah ibadah manapun.']);
|
||||
exit;
|
||||
}
|
||||
$res = $conn->query("SELECT lat, lng, radius FROM rumah_ibadah WHERE id=$ibadah_id");
|
||||
if (!$res || $res->num_rows === 0) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Rumah ibadah yang dikelola tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
$ibadahFilter = $res->fetch_assoc();
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (is_array($data)) {
|
||||
$conn->begin_transaction();
|
||||
$inserted = 0;
|
||||
$skipped = 0;
|
||||
$errors = [];
|
||||
foreach ($data as $item) {
|
||||
if (isset($item->lat) && isset($item->lng) && $item->lat !== '' && $item->lng !== '') {
|
||||
@@ -18,6 +36,21 @@ if (is_array($data)) {
|
||||
$lat = (float)$item->lat;
|
||||
$lng = (float)$item->lng;
|
||||
|
||||
// Jika pengelola, cek apakah item berada dalam radius ibadahnya
|
||||
if ($ibadahFilter !== null) {
|
||||
$R = 6371000;
|
||||
$lat1 = deg2rad((float)$ibadahFilter['lat']);
|
||||
$lat2 = deg2rad($lat);
|
||||
$dLat = $lat2 - $lat1;
|
||||
$dLng = deg2rad($lng - (float)$ibadahFilter['lng']);
|
||||
$a = sin($dLat/2)*sin($dLat/2) + cos($lat1)*cos($lat2)*sin($dLng/2)*sin($dLng/2);
|
||||
$dist = $R * 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
if ($dist > (float)$ibadahFilter['radius']) {
|
||||
$skipped++;
|
||||
continue; // Lewati item di luar radius
|
||||
}
|
||||
}
|
||||
|
||||
$query = "INSERT INTO penduduk_miskin (nama, kategori_bantuan, jumlah_jiwa, lat, lng)
|
||||
VALUES ('$nama', '$kategori', $jumlah_jiwa, $lat, $lng)";
|
||||
|
||||
@@ -31,10 +64,15 @@ if (is_array($data)) {
|
||||
|
||||
if (empty($errors) && $inserted > 0) {
|
||||
$conn->commit();
|
||||
echo json_encode(["status" => "success", "message" => "Berhasil mengimpor $inserted data penduduk miskin."]);
|
||||
$msg = "Berhasil mengimpor $inserted data penduduk miskin.";
|
||||
if ($skipped > 0) $msg .= " ($skipped data di luar radius diabaikan.)";
|
||||
echo json_encode(["status" => "success", "message" => $msg]);
|
||||
} else if ($inserted === 0) {
|
||||
$conn->rollback();
|
||||
echo json_encode(["status" => "error", "message" => "Tidak ada data valid yang diimpor."]);
|
||||
$msg = $skipped > 0
|
||||
? "Semua $skipped data berada di luar radius rumah ibadah Anda, tidak ada yang diimpor."
|
||||
: "Tidak ada data valid yang diimpor.";
|
||||
echo json_encode(["status" => "error", "message" => $msg]);
|
||||
} else {
|
||||
$conn->rollback();
|
||||
echo json_encode(["status" => "error", "message" => "Gagal mengimpor beberapa data. Transaksi dibatalkan.", "errors" => $errors]);
|
||||
|
||||
@@ -27,6 +27,38 @@ if (!empty($_POST)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Validasi pengelola: hanya boleh tambah jika lokasi berada dalam radius ibadah yang dikelolanya
|
||||
$currentUser = getCurrentUser();
|
||||
if ($currentUser && $currentUser['role'] === 'pengelola') {
|
||||
$ibadah_id = $currentUser['ibadah_id'];
|
||||
if (!$ibadah_id) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Akun pengelola tidak terhubung ke rumah ibadah manapun.']);
|
||||
exit;
|
||||
}
|
||||
// Ambil data ibadah (lat, lng, radius)
|
||||
$res = $conn->query("SELECT lat, lng, radius FROM rumah_ibadah WHERE id=$ibadah_id");
|
||||
if (!$res || $res->num_rows === 0) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Rumah ibadah yang dikelola tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
$ib = $res->fetch_assoc();
|
||||
// Hitung jarak menggunakan Haversine formula
|
||||
$R = 6371000; // meter
|
||||
$lat1 = deg2rad((float)$ib['lat']);
|
||||
$lat2 = deg2rad($lat);
|
||||
$dLat = $lat2 - $lat1;
|
||||
$dLng = deg2rad($lng - (float)$ib['lng']);
|
||||
$a = sin($dLat/2)*sin($dLat/2) + cos($lat1)*cos($lat2)*sin($dLng/2)*sin($dLng/2);
|
||||
$dist = $R * 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
if ($dist > (float)$ib['radius']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Lokasi penduduk di luar radius rumah ibadah Anda. Hanya boleh menambah penduduk dalam radius yang dikelola.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($lat != 0 && $lng != 0) {
|
||||
$upload_dir = "../../uploads/";
|
||||
if (!file_exists($upload_dir)) {
|
||||
|
||||
@@ -9,6 +9,49 @@ $data = json_decode(file_get_contents("php://input"));
|
||||
if(!empty($data->id)) {
|
||||
$id = (int)$data->id;
|
||||
|
||||
// Validasi pengelola: hanya boleh hapus penduduk dalam radius ibadah yang dikelolanya
|
||||
$currentUser = getCurrentUser();
|
||||
if ($currentUser && $currentUser['role'] === 'pengelola') {
|
||||
$ibadah_id = $currentUser['ibadah_id'];
|
||||
if (!$ibadah_id) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Akun pengelola tidak terhubung ke rumah ibadah manapun.']);
|
||||
exit;
|
||||
}
|
||||
// Ambil koordinat penduduk
|
||||
$mRes = $conn->query("SELECT lat, lng FROM penduduk_miskin WHERE id=$id");
|
||||
if (!$mRes || $mRes->num_rows === 0) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Data penduduk tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
$mRow = $mRes->fetch_assoc();
|
||||
$pLat = (float)$mRow['lat'];
|
||||
$pLng = (float)$mRow['lng'];
|
||||
|
||||
// Ambil data ibadah
|
||||
$iRes = $conn->query("SELECT lat, lng, radius FROM rumah_ibadah WHERE id=$ibadah_id");
|
||||
if (!$iRes || $iRes->num_rows === 0) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Rumah ibadah yang dikelola tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
$ib = $iRes->fetch_assoc();
|
||||
|
||||
// Hitung jarak Haversine
|
||||
$R = 6371000;
|
||||
$lat1 = deg2rad((float)$ib['lat']);
|
||||
$lat2 = deg2rad($pLat);
|
||||
$dLat = $lat2 - $lat1;
|
||||
$dLng = deg2rad($pLng - (float)$ib['lng']);
|
||||
$a = sin($dLat/2)*sin($dLat/2) + cos($lat1)*cos($lat2)*sin($dLng/2)*sin($dLng/2);
|
||||
$dist = $R * 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
if ($dist > (float)$ib['radius']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Penduduk ini berada di luar radius rumah ibadah Anda. Tidak diizinkan menghapus.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$query = "DELETE FROM penduduk_miskin WHERE id=$id";
|
||||
|
||||
if($conn->query($query)) {
|
||||
|
||||
@@ -30,6 +30,47 @@ if (!empty($_POST)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Validasi pengelola: hanya boleh edit penduduk yang lokasinya dalam radius ibadah yang dikelolanya
|
||||
$currentUser = getCurrentUser();
|
||||
if ($currentUser && $currentUser['role'] === 'pengelola') {
|
||||
$ibadah_id = $currentUser['ibadah_id'];
|
||||
if (!$ibadah_id) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Akun pengelola tidak terhubung ke rumah ibadah manapun.']);
|
||||
exit;
|
||||
}
|
||||
$res = $conn->query("SELECT lat, lng, radius FROM rumah_ibadah WHERE id=$ibadah_id");
|
||||
if (!$res || $res->num_rows === 0) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Rumah ibadah yang dikelola tidak ditemukan.']);
|
||||
exit;
|
||||
}
|
||||
$ib = $res->fetch_assoc();
|
||||
// Gunakan koordinat penduduk yang sedang diedit (dari DB jika lat/lng 0, atau dari input)
|
||||
$checkLat = $lat;
|
||||
$checkLng = $lng;
|
||||
if ($checkLat == 0 && $checkLng == 0 && $id > 0) {
|
||||
$mRes = $conn->query("SELECT lat, lng FROM penduduk_miskin WHERE id=$id");
|
||||
if ($mRes && $mRow = $mRes->fetch_assoc()) {
|
||||
$checkLat = (float)$mRow['lat'];
|
||||
$checkLng = (float)$mRow['lng'];
|
||||
}
|
||||
}
|
||||
$R = 6371000;
|
||||
$lat1 = deg2rad((float)$ib['lat']);
|
||||
$lat2 = deg2rad($checkLat);
|
||||
$dLat = $lat2 - $lat1;
|
||||
$dLng = deg2rad($checkLng - (float)$ib['lng']);
|
||||
$a = sin($dLat/2)*sin($dLat/2) + cos($lat1)*cos($lat2)*sin($dLng/2)*sin($dLng/2);
|
||||
$dist = $R * 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
if ($dist > (float)$ib['radius']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Penduduk ini berada di luar radius rumah ibadah Anda. Tidak diizinkan mengedit.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($id > 0) {
|
||||
// Get current files to delete them if updated
|
||||
$old_foto_rumah = null;
|
||||
|
||||
Reference in New Issue
Block a user