diff --git a/api/log_bantuan/create.php b/api/log_bantuan/create.php new file mode 100644 index 0000000..2abdc03 --- /dev/null +++ b/api/log_bantuan/create.php @@ -0,0 +1,27 @@ + 'error', 'message' => 'Data tidak lengkap']); + exit; +} + +$sql = "INSERT INTO log_bantuan (miskin_id, ibadah_id, tipe_bantuan, tanggal, keterangan) VALUES (?, ?, ?, ?, ?)"; +$stmt = $conn->prepare($sql); +$stmt->bind_param('iisss', $miskin_id, $ibadah_id, $tipe_bantuan, $tanggal, $keterangan); + +if ($stmt->execute()) { + echo json_encode(['status' => 'success', 'id' => $stmt->insert_id]); +} else { + echo json_encode(['status' => 'error', 'message' => $conn->error]); +} +$conn->close(); diff --git a/api/log_bantuan/read.php b/api/log_bantuan/read.php new file mode 100644 index 0000000..4cbc5c2 --- /dev/null +++ b/api/log_bantuan/read.php @@ -0,0 +1,37 @@ + 'error', 'message' => 'miskin_id diperlukan']); + exit; +} + +$sql = " + SELECT + lb.id, + lb.tipe_bantuan, + lb.tanggal, + lb.keterangan, + ri.nama AS nama_ibadah, + ri.jenis AS jenis_ibadah + FROM log_bantuan lb + JOIN rumah_ibadah ri ON ri.id = lb.ibadah_id + WHERE lb.miskin_id = ? + ORDER BY lb.tanggal DESC, lb.id DESC +"; + +$stmt = $conn->prepare($sql); +$stmt->bind_param('i', $miskin_id); +$stmt->execute(); +$result = $stmt->get_result(); + +$data = []; +while ($row = $result->fetch_assoc()) { + $data[] = $row; +} + +echo json_encode(['status' => 'success', 'data' => $data]); +$conn->close(); diff --git a/api/penduduk_miskin/create.php b/api/penduduk_miskin/create.php index 0e05e2f..c5a2945 100644 --- a/api/penduduk_miskin/create.php +++ b/api/penduduk_miskin/create.php @@ -4,15 +4,17 @@ include_once '../db_connect.php'; $data = json_decode(file_get_contents("php://input")); -if(!empty($data->lat) && !empty($data->lng)) { - $nama = $conn->real_escape_string($data->nama ?? ''); - $kategori = $conn->real_escape_string($data->kategori_bantuan ?? 'Makan'); - $lat = (float)$data->lat; - $lng = (float)$data->lng; +if (!empty($data->lat) && !empty($data->lng)) { + $nama = $conn->real_escape_string($data->nama ?? ''); + $kategori = $conn->real_escape_string($data->kategori_bantuan ?? 'Makan'); + $jumlah_jiwa = (int)($data->jumlah_jiwa ?? 1); + $lat = (float)$data->lat; + $lng = (float)$data->lng; - $query = "INSERT INTO penduduk_miskin (nama, kategori_bantuan, lat, lng) VALUES ('$nama', '$kategori', $lat, $lng)"; + $query = "INSERT INTO penduduk_miskin (nama, kategori_bantuan, jumlah_jiwa, lat, lng) + VALUES ('$nama', '$kategori', $jumlah_jiwa, $lat, $lng)"; - if($conn->query($query)) { + if ($conn->query($query)) { echo json_encode(["status" => "success", "id" => $conn->insert_id, "message" => "Berhasil ditambahkan."]); } else { echo json_encode(["status" => "error", "message" => "Gagal: " . $conn->error]); diff --git a/api/penduduk_miskin/update.php b/api/penduduk_miskin/update.php index ffa4d9f..2d05a2d 100644 --- a/api/penduduk_miskin/update.php +++ b/api/penduduk_miskin/update.php @@ -4,16 +4,19 @@ include_once '../db_connect.php'; $data = json_decode(file_get_contents("php://input")); -if(!empty($data->id)) { - $id = (int)$data->id; - $nama = $conn->real_escape_string($data->nama ?? ''); - $kategori = $conn->real_escape_string($data->kategori_bantuan ?? 'Makan'); - $lat = (float)$data->lat; - $lng = (float)$data->lng; +if (!empty($data->id)) { + $id = (int)$data->id; + $nama = $conn->real_escape_string($data->nama ?? ''); + $kategori = $conn->real_escape_string($data->kategori_bantuan ?? 'Makan'); + $jumlah_jiwa = (int)($data->jumlah_jiwa ?? 1); + $lat = (float)$data->lat; + $lng = (float)$data->lng; - $query = "UPDATE penduduk_miskin SET nama='$nama', kategori_bantuan='$kategori', lat=$lat, lng=$lng WHERE id=$id"; + $query = "UPDATE penduduk_miskin + SET nama='$nama', kategori_bantuan='$kategori', jumlah_jiwa=$jumlah_jiwa, lat=$lat, lng=$lng + WHERE id=$id"; - if($conn->query($query)) { + if ($conn->query($query)) { echo json_encode(["status" => "success", "message" => "Update berhasil."]); } else { echo json_encode(["status" => "error", "message" => "Gagal update: " . $conn->error]); diff --git a/api/rumah_ibadah/create.php b/api/rumah_ibadah/create.php index 307c6d1..4f77d6a 100644 --- a/api/rumah_ibadah/create.php +++ b/api/rumah_ibadah/create.php @@ -4,16 +4,18 @@ include_once '../db_connect.php'; $data = json_decode(file_get_contents("php://input")); -if(!empty($data->lat) && !empty($data->lng)) { - $nama = $conn->real_escape_string($data->nama ?? ''); +if (!empty($data->lat) && !empty($data->lng)) { + $nama = $conn->real_escape_string($data->nama ?? ''); + $jenis = $conn->real_escape_string($data->jenis ?? 'Masjid'); $alamat = $conn->real_escape_string($data->alamat ?? ''); - $radius = (float)($data->radius ?? 100); - $lat = (float)$data->lat; - $lng = (float)$data->lng; + $radius = (float)($data->radius ?? 500); + $lat = (float)$data->lat; + $lng = (float)$data->lng; - $query = "INSERT INTO rumah_ibadah (nama, alamat, radius, lat, lng) VALUES ('$nama', '$alamat', $radius, $lat, $lng)"; + $query = "INSERT INTO rumah_ibadah (nama, jenis, alamat, radius, lat, lng) + VALUES ('$nama', '$jenis', '$alamat', $radius, $lat, $lng)"; - if($conn->query($query)) { + if ($conn->query($query)) { echo json_encode(["status" => "success", "id" => $conn->insert_id, "message" => "Rumah Ibadah berhasil ditambahkan."]); } else { echo json_encode(["status" => "error", "message" => "Gagal menambahkan: " . $conn->error]); diff --git a/api/rumah_ibadah/update.php b/api/rumah_ibadah/update.php index 1b19cde..a40abb9 100644 --- a/api/rumah_ibadah/update.php +++ b/api/rumah_ibadah/update.php @@ -4,17 +4,20 @@ include_once '../db_connect.php'; $data = json_decode(file_get_contents("php://input")); -if(!empty($data->id)) { - $id = (int)$data->id; - $nama = $conn->real_escape_string($data->nama ?? ''); +if (!empty($data->id)) { + $id = (int)$data->id; + $nama = $conn->real_escape_string($data->nama ?? ''); + $jenis = $conn->real_escape_string($data->jenis ?? 'Masjid'); $alamat = $conn->real_escape_string($data->alamat ?? ''); - $radius = (float)($data->radius ?? 100); - $lat = (float)$data->lat; - $lng = (float)$data->lng; + $radius = (float)($data->radius ?? 500); + $lat = (float)$data->lat; + $lng = (float)$data->lng; - $query = "UPDATE rumah_ibadah SET nama='$nama', alamat='$alamat', radius=$radius, lat=$lat, lng=$lng WHERE id=$id"; + $query = "UPDATE rumah_ibadah + SET nama='$nama', jenis='$jenis', alamat='$alamat', radius=$radius, lat=$lat, lng=$lng + WHERE id=$id"; - if($conn->query($query)) { + if ($conn->query($query)) { echo json_encode(["status" => "success", "message" => "Update berhasil."]); } else { echo json_encode(["status" => "error", "message" => "Gagal update: " . $conn->error]); diff --git a/assets/css/style.css b/assets/css/style.css index f28612a..d7d40e7 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -15,7 +15,7 @@ body, html { #map { width: 100vw; height: 100vh; - z-index: 1; /* Di bawah UI element */ + z-index: 1; } /* Custom UI Container over Map */ @@ -75,7 +75,7 @@ body, html { display: flex; align-items: center; justify-content: center; - visibility: hidden; /* Muncul jika ada input */ + visibility: hidden; } .search-clear:hover { @@ -115,9 +115,10 @@ body, html { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); padding: 15px; width: 250px; - display: none; /* Default tertutup */ + display: none; } + .custom-layer-panel h3 { margin-bottom: 10px; font-size: 16px; @@ -133,6 +134,436 @@ body, html { font-size: 14px; } +/* ===== Sidebar Toggle Button ===== */ +.sidebar-toggle-btn { + position: absolute; + left: 20px; + top: 90px; + z-index: 1001; + width: 36px; + height: 36px; + background: white; + border: none; + border-radius: 10px; + box-shadow: 0 3px 10px rgba(0,0,0,0.15); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: #6366f1; + font-size: 13px; + transition: all 0.2s; +} + +.sidebar-toggle-btn:hover { + background: #6366f1; + color: white; + box-shadow: 0 4px 14px rgba(99,102,241,0.4); +} + +.sidebar-toggle-btn.open { + background: #6366f1; + color: white; +} + +/* ===== Left Sidebar ===== */ +.left-sidebar { + position: absolute; + left: 20px; + top: 136px; /* di bawah toggle button */ + z-index: 1000; + display: flex; + flex-direction: column; + gap: 8px; +} + +.sidebar-btn { + width: 60px; + height: 60px; + background: white; + border: none; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0,0,0,0.12); + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + transition: all 0.2s ease; + padding: 6px 4px; +} + +.sidebar-btn:hover { + transform: translateX(3px); + box-shadow: 0 6px 18px rgba(0,0,0,0.18); + background: #f8f9fa; +} + +.sidebar-btn.active { + background: linear-gradient(135deg, #6366f1, #4f46e5); + color: white; + box-shadow: 0 6px 18px rgba(99,102,241,0.4); + transform: translateX(4px); +} + +.sidebar-emoji { + font-size: 22px; + line-height: 1; +} + +.sidebar-label { + font-size: 9px; + font-weight: 600; + letter-spacing: 0.3px; + color: #555; + text-transform: uppercase; +} + +.sidebar-btn.active .sidebar-label { + color: rgba(255,255,255,0.85); +} + +/* ===== Right Panel ===== */ +.right-panel { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 320px; + background: #f8f9fb; + z-index: 999; + box-shadow: -4px 0 20px rgba(0,0,0,0.12); + display: flex; + flex-direction: column; + transform: translateX(100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + /* shift layer control when panel open */ +} + +.right-panel.open { + transform: translateX(0); +} + +.right-panel-header { + padding: 16px 16px 12px; + background: white; + border-bottom: 1px solid #eee; + display: flex; + align-items: center; + justify-content: space-between; + flex-shrink: 0; + padding-top: 26px; +} + +.right-panel-header h3 { + font-size: 15px; + font-weight: 700; + color: #1e1e2e; +} + +.right-panel-close { + width: 30px; + height: 30px; + border: none; + background: #f3f4f6; + border-radius: 8px; + cursor: pointer; + color: #555; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + transition: background 0.2s; +} + +.right-panel-close:hover { + background: #e5e7eb; + color: #333; +} + +.right-panel-actions { + padding: 12px 14px; + background: white; + border-bottom: 1px solid #eee; + display: flex; + gap: 8px; + flex-shrink: 0; +} + +.right-panel-actions button { + flex: 1; + padding: 9px 10px; + border: none; + border-radius: 8px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + transition: all 0.2s; +} + +.btn-panel-add { + background: linear-gradient(135deg, #6366f1, #4f46e5); + color: white; + box-shadow: 0 3px 10px rgba(99,102,241,0.3); +} + +.btn-panel-add:hover { + box-shadow: 0 5px 14px rgba(99,102,241,0.45); + transform: translateY(-1px); +} + +.btn-panel-import { + background: #f0fdf4; + color: #16a34a; + border: 1px solid #bbf7d0 !important; +} + +.btn-panel-import:hover { + background: #dcfce7; +} + +.right-panel-body { + flex: 1; + overflow-y: auto; + padding: 12px; + display: flex; + flex-direction: column; + gap: 8px; +} + +/* Panel Section Heading */ +.panel-section-title { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #9ca3af; + padding: 4px 2px 6px; + border-bottom: 1px solid #e5e7eb; + margin-top: 4px; +} + +/* Panel Empty State */ +.panel-empty { + text-align: center; + color: #aaa; + font-size: 13px; + padding: 40px 10px; +} + +/* ===== Data Card ===== */ +.data-card { + background: white; + border-radius: 12px; + padding: 12px 12px 10px; + box-shadow: 0 1px 4px rgba(0,0,0,0.07); + transition: box-shadow 0.2s; + cursor: pointer; +} + +.data-card:hover { + box-shadow: 0 3px 12px rgba(0,0,0,0.12); +} + +.data-card-main { + display: flex; + align-items: center; + gap: 10px; +} + +.data-card-icon { + width: 40px; + height: 40px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + flex-shrink: 0; +} + +.card-icon-spbu { background: #f0fdf4; } +.card-icon-jalan { background: #eff6ff; } +.card-icon-parsil { background: #fefce8; } +.card-icon-ibadah { background: #fff7ed; } +.card-icon-miskin-out { background: #f0fdf4; } +.card-icon-miskin-in { background: #fef2f2; } + +.data-card-info { + flex: 1; + min-width: 0; +} + +.data-card-name { + font-size: 13px; + font-weight: 600; + color: #1e1e2e; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.data-card-sub { + font-size: 11px; + color: #6b7280; + margin-top: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.data-card-badge { + font-size: 10px; + font-weight: 600; + padding: 2px 7px; + border-radius: 99px; + margin-top: 4px; + display: inline-block; +} + +.badge-green { background: #dcfce7; color: #16a34a; } +.badge-red { background: #fee2e2; color: #dc2626; } +.badge-blue { background: #dbeafe; color: #2563eb; } +.badge-orange { background: #ffedd5; color: #ea580c; } +.badge-yellow { background: #fef9c3; color: #ca8a04; } + +.data-card-actions { + display: flex; + gap: 6px; + flex-shrink: 0; + align-items: center; +} + +.btn-card-action { + width: 30px; + height: 30px; + border: none; + border-radius: 7px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + transition: all 0.15s; +} + +.btn-card-edit { + background: #eff6ff; + color: #3b82f6; +} + +.btn-card-edit:hover { + background: #3b82f6; + color: white; +} + +.btn-card-delete { + background: #fef2f2; + color: #ef4444; +} + +.btn-card-delete:hover { + background: #ef4444; + color: white; +} + +/* Expandable card (miskin) */ +.data-card-expand { + overflow: hidden; + max-height: 0; + transition: max-height 0.3s ease, padding 0.2s; + border-top: 0px solid #f3f4f6; +} + +.data-card.expanded .data-card-expand { + max-height: 400px; + border-top: 1px solid #f3f4f6; + margin-top: 10px; + padding-top: 10px; +} + +.expand-row { + display: flex; + justify-content: space-between; + font-size: 12px; + color: #6b7280; + padding: 3px 0; +} + +.expand-row span:first-child { + font-weight: 500; + color: #374151; +} + +.btn-log-bantuan { + margin-top: 10px; + width: 100%; + padding: 8px; + background: linear-gradient(135deg, #6366f1, #4f46e5); + color: white; + border: none; + border-radius: 8px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + transition: all 0.2s; +} + +.btn-log-bantuan:hover { + box-shadow: 0 4px 12px rgba(99,102,241,0.4); +} + +/* Log Bantuan Item */ +.log-item { + background: #f8f9fb; + border-radius: 8px; + padding: 10px 12px; + display: flex; + gap: 10px; + align-items: flex-start; +} + +.log-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #6366f1; + flex-shrink: 0; + margin-top: 5px; +} + +.log-info { + flex: 1; +} + +.log-type { + font-size: 12px; + font-weight: 600; + color: #1e1e2e; +} + +.log-meta { + font-size: 11px; + color: #9ca3af; + margin-top: 2px; +} + +.log-keterangan { + font-size: 11px; + color: #6b7280; + margin-top: 3px; + font-style: italic; +} + /* Popup Form */ .popup-form { display: flex; @@ -178,51 +609,6 @@ body, html { opacity: 0.9; } -/* Action Menu (Kiri) */ -.action-menu { - position: absolute; - z-index: 1000; - background: transparent; - box-shadow: none; - padding: 0; - display: flex; - flex-direction: column; - gap: 10px; - width: 200px; -} - -.action-menu button { - padding: 10px 15px; - background-color: white; - border: none; - border-radius: 25px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - cursor: pointer; - text-align: left; - font-size: 13px; - font-weight: 500; - transition: all 0.2s; - display: flex; - align-items: center; - gap: 8px; -} - -.action-menu button i { - width: 16px; - text-align: center; -} - -.action-menu button:hover { - background-color: #f8f9fa; - transform: translateX(5px); -} - -.action-menu button.active { - background-color: #007bff; - color: white; - box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3); -} - /* Unified Modal */ .unified-modal { display: none; @@ -243,10 +629,10 @@ body, html { .modal-content { background-color: white; - border-radius: 8px; + border-radius: 12px; width: 400px; max-width: 90%; - box-shadow: 0 4px 20px rgba(0,0,0,0.2); + box-shadow: 0 8px 30px rgba(0,0,0,0.2); display: flex; flex-direction: column; } @@ -290,13 +676,15 @@ body, html { .modal-body input[type="text"], .modal-body input[type="number"], +.modal-body input[type="date"], .modal-body select, .modal-body textarea { width: 100%; padding: 8px; border: 1px solid #ccc; - border-radius: 4px; + border-radius: 6px; font-size: 14px; + font-family: inherit; } .modal-footer { @@ -308,20 +696,23 @@ body, html { } .btn-save { - background-color: #007bff; + background: linear-gradient(135deg, #6366f1, #4f46e5); color: white; border: none; - padding: 8px 16px; - border-radius: 4px; + padding: 8px 18px; + border-radius: 7px; cursor: pointer; + font-weight: 600; + font-size: 13px; } .btn-cancel { background-color: #f8f9fa; border: 1px solid #ddd; - padding: 8px 16px; - border-radius: 4px; + padding: 8px 18px; + border-radius: 7px; cursor: pointer; + font-size: 13px; } .form-group { @@ -340,7 +731,7 @@ body, html { border-radius: 4px; font-size: 13px; font-weight: 500; - pointer-events: none; /* Supaya tidak menghalangi klik ke map */ + pointer-events: none; z-index: 9999; white-space: nowrap; box-shadow: 0 2px 6px rgba(0,0,0,0.2); @@ -432,8 +823,8 @@ body, html { } /* Warna bubble per tipe */ -.emoji-marker .bubble.spbu-24 { background: linear-gradient(135deg, #22c55e, #16a34a); } -.emoji-marker .bubble.spbu-not24 { background: linear-gradient(135deg, #ef4444, #dc2626); } -.emoji-marker .bubble.ibadah { background: linear-gradient(135deg, #f97316, #ea580c); } -.emoji-marker .bubble.miskin-in { background: linear-gradient(135deg, #ef4444, #dc2626); } -.emoji-marker .bubble.miskin-out { background: linear-gradient(135deg, #22c55e, #16a34a); } +.emoji-marker .bubble.spbu-24 { background: linear-gradient(135deg, #22c55e, #16a34a); } +.emoji-marker .bubble.spbu-not24 { background: linear-gradient(135deg, #ef4444, #dc2626); } +.emoji-marker .bubble.ibadah { background: linear-gradient(135deg, #f97316, #ea580c); } +.emoji-marker .bubble.miskin-in { background: linear-gradient(135deg, #ef4444, #dc2626); } +.emoji-marker .bubble.miskin-out { background: linear-gradient(135deg, #22c55e, #16a34a); } diff --git a/assets/js/features/jalan.js b/assets/js/features/jalan.js index ccd8541..733b72e 100644 --- a/assets/js/features/jalan.js +++ b/assets/js/features/jalan.js @@ -16,6 +16,7 @@ function loadJalan() { addJalanToMap(item); }); } + if (window.refreshActivePanel) window.refreshActivePanel(); }); } diff --git a/assets/js/features/kemiskinan.js b/assets/js/features/kemiskinan.js index 91acce0..1f9fa55 100644 --- a/assets/js/features/kemiskinan.js +++ b/assets/js/features/kemiskinan.js @@ -1,10 +1,20 @@ // --- Fitur Pemetaan Kemiskinan --- +// Emoji per jenis ibadah +const IBADAH_EMOJI_MAP = { + 'Masjid': '🕌', + 'Gereja': '⛪', + 'Pura': '🛕', + 'Vihara': '🪷', + 'Kelenteng': '🏮', +}; + // Emoji Bubble Icon builder -function makeIbadahIcon() { +function makeIbadahIcon(jenis) { + const emot = IBADAH_EMOJI_MAP[jenis] || '🕌'; return L.divIcon({ className: '', - html: `
Alamat: ${d.alamat}
+Jenis: ${d.jenis || 'Masjid'}
+Alamat: ${d.alamat}
Radius: ${d.radius} m
Bantuan: ${d.kategori_bantuan}
-Bantuan: ${d.kategori_bantuan}
+Jiwa: ${d.jumlah_jiwa || '-'}
+