First Commit
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
-- ============================================================
|
||||
-- WebGIS BantSOSial — Database Schema
|
||||
-- Database: sig_bansos
|
||||
-- ============================================================
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS sig_bansos
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE sig_bansos;
|
||||
|
||||
-- ============================================================
|
||||
-- Table: religious_centers
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS religious_centers (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address TEXT,
|
||||
kas DECIMAL(15,2) DEFAULT 0,
|
||||
latitude DOUBLE NOT NULL,
|
||||
longitude DOUBLE NOT NULL,
|
||||
radius INT DEFAULT 300,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- Table: houses
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS houses (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
latitude DOUBLE NOT NULL,
|
||||
longitude DOUBLE NOT NULL,
|
||||
address TEXT,
|
||||
rt VARCHAR(10) DEFAULT '',
|
||||
rw VARCHAR(10) DEFAULT '',
|
||||
kelurahan VARCHAR(100) DEFAULT '',
|
||||
status_miskin ENUM('sangat_miskin','miskin','tidak_miskin','') DEFAULT '',
|
||||
jumlah_anggota INT DEFAULT 0,
|
||||
anggota JSON,
|
||||
aid_status ENUM('helped','not_helped','outside') DEFAULT 'outside',
|
||||
has_data TINYINT(1) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- Table: laporan
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS laporan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
pelapor VARCHAR(150) DEFAULT 'Anonim',
|
||||
deskripsi TEXT NOT NULL,
|
||||
lokasi VARCHAR(255) DEFAULT '',
|
||||
foto_base64 MEDIUMTEXT,
|
||||
status ENUM('baru','ditangani','selesai') DEFAULT 'baru',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- Table: aid_logs
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS aid_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
house_id INT NOT NULL,
|
||||
religious_center_id INT DEFAULT 0,
|
||||
status ENUM('helped','reverted') NOT NULL,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (house_id) REFERENCES houses(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$response = ['success' => true, 'centers' => [], 'houses' => [], 'reports' => []];
|
||||
|
||||
$result = $conn->query("SELECT * FROM religious_centers ORDER BY id ASC");
|
||||
if ($result) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$response['centers'][] = [
|
||||
'id' => (int)$row['id'],
|
||||
'name' => $row['name'],
|
||||
'address' => $row['address'],
|
||||
'kas' => (float)($row['kas'] ?? 0),
|
||||
'latitude' => (float)$row['latitude'],
|
||||
'longitude' => (float)$row['longitude'],
|
||||
'radius' => (int)$row['radius']
|
||||
];
|
||||
}
|
||||
$result->free();
|
||||
}
|
||||
|
||||
$result = $conn->query("SELECT * FROM houses ORDER BY id ASC");
|
||||
if ($result) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$anggota = [];
|
||||
if (!empty($row['anggota'])) {
|
||||
$decoded = json_decode($row['anggota'], true);
|
||||
if (is_array($decoded)) $anggota = $decoded;
|
||||
}
|
||||
$response['houses'][] = [
|
||||
'id' => (int)$row['id'],
|
||||
'latitude' => (float)$row['latitude'],
|
||||
'longitude' => (float)$row['longitude'],
|
||||
'address' => $row['address'] ?? '',
|
||||
'rt' => $row['rt'] ?? '',
|
||||
'rw' => $row['rw'] ?? '',
|
||||
'kelurahan' => $row['kelurahan'] ?? '',
|
||||
'status_miskin' => $row['status_miskin'] ?? '',
|
||||
'jumlah_anggota' => (int)($row['jumlah_anggota'] ?? 0),
|
||||
'anggota' => $anggota,
|
||||
'aid_status' => $row['aid_status'] ?? 'outside',
|
||||
'has_data' => (int)($row['has_data'] ?? 0)
|
||||
];
|
||||
}
|
||||
$result->free();
|
||||
}
|
||||
|
||||
$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'],
|
||||
'lokasi' => $row['lokasi'] ?? '',
|
||||
'imgBase64' => $row['foto_base64'] ?? null,
|
||||
'status' => $row['status'] ?? 'baru',
|
||||
'time' => date('d/m/Y H:i', strtotime($row['created_at']))
|
||||
];
|
||||
}
|
||||
$result->free();
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
$conn->close();
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
if ($id < 1) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); exit; }
|
||||
|
||||
if ($conn->query("DELETE FROM laporan WHERE id=$id")) {
|
||||
echo json_encode(['success' => true, 'id' => $id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $conn->error]);
|
||||
}
|
||||
$conn->close();
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
if ($id < 1) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); exit; }
|
||||
|
||||
$conn->query("DELETE FROM aid_logs WHERE religious_center_id=$id");
|
||||
|
||||
if ($conn->query("DELETE FROM religious_centers WHERE id=$id")) {
|
||||
echo json_encode(['success' => true, 'id' => $id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $conn->error]);
|
||||
}
|
||||
$conn->close();
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
if ($id < 1) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); exit; }
|
||||
|
||||
if ($conn->query("DELETE FROM houses WHERE id=$id")) {
|
||||
echo json_encode(['success' => true, 'id' => $id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $conn->error]);
|
||||
}
|
||||
$conn->close();
|
||||
@@ -0,0 +1,381 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BantSOSial GIS — Distribusi Bantuan Sosial</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- =====================================================================
|
||||
NAVBAR
|
||||
===================================================================== -->
|
||||
<nav id="topNav">
|
||||
<div class="nav-brand">
|
||||
<span class="nav-brand-icon">🕌</span>
|
||||
<div>
|
||||
<div class="nav-brand-title">BantSOSial GIS</div>
|
||||
<div class="nav-brand-sub">Distribusi Bantuan Sosial</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
<button class="nav-item active" id="navHome" onclick="navigateTo('map')">
|
||||
<span class="nav-item-icon">🗺</span>
|
||||
<span class="nav-item-label">Peta</span>
|
||||
</button>
|
||||
<button class="nav-item" id="navReport" onclick="navigateTo('report')">
|
||||
<span class="nav-item-icon">📢</span>
|
||||
<span class="nav-item-label">Pelaporan</span>
|
||||
<span class="nav-badge hidden" id="navReportBadge">0</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
<div id="roleIndicator" class="role-pill">
|
||||
<span id="roleIcon">👑</span>
|
||||
<span id="roleLabel">Admin</span>
|
||||
</div>
|
||||
<button id="roleSwitchBtn" onclick="openRoleModal()">Ganti Role ▾</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- =====================================================================
|
||||
ROLE MODAL
|
||||
===================================================================== -->
|
||||
<div id="roleModal" class="modal-overlay hidden">
|
||||
<div class="modal-box" style="max-width:440px">
|
||||
<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, dan kelola semua data</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 & lihat 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>
|
||||
|
||||
<!-- =====================================================================
|
||||
PAGE: PETA
|
||||
===================================================================== -->
|
||||
<div id="pagePeta" class="page active-page">
|
||||
|
||||
<div id="sidebar">
|
||||
<div id="modeIndicator" class="mode-badge hidden">
|
||||
<span class="pulse-dot"></span>
|
||||
<span id="modeIndicatorText">Klik peta untuk menempatkan titik</span>
|
||||
</div>
|
||||
|
||||
<div id="viewerNotice" style="display:none;margin:10px 14px 0;padding:10px 12px;background:#f3e8ff;border:1px solid #c4b5fd;border-radius:8px;font-size:11px;color:#5b21b6;font-weight:500;text-align:center;">
|
||||
👁 Mode Viewer — Anda hanya dapat melihat data
|
||||
</div>
|
||||
|
||||
<div class="btn-group" id="actionBtnGroup">
|
||||
<button id="btnAddCenter" onclick="startAddingCenter()">🕌 Tambah Rumah Ibadah</button>
|
||||
<button id="btnAddHouse" onclick="startAddingHouse()">🏠 Tambah Rumah Miskin</button>
|
||||
</div>
|
||||
|
||||
<div class="stat-panel">
|
||||
<div class="stat-title">📊 Statistik Bantuan</div>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-num" id="statTotal">0</div>
|
||||
<div class="stat-label">Total Rumah</div>
|
||||
</div>
|
||||
<div class="stat-card red">
|
||||
<div class="stat-num" id="statInside">0</div>
|
||||
<div class="stat-label">Dalam Radius</div>
|
||||
</div>
|
||||
<div class="stat-card yellow">
|
||||
<div class="stat-num" id="statHelped">0</div>
|
||||
<div class="stat-label">Sudah Dibantu</div>
|
||||
</div>
|
||||
<div class="stat-card green">
|
||||
<div class="stat-num" id="statOutside">0</div>
|
||||
<div class="stat-label">Luar Radius</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
🕌 Rumah Ibadah
|
||||
<span id="centerCount" class="badge badge-teal">0</span>
|
||||
</div>
|
||||
<div class="search-wrap">
|
||||
<input type="text" id="searchCenter" class="search-input" placeholder="🔍 Cari nama..." oninput="filterCenters()"/>
|
||||
</div>
|
||||
<select id="sortCenter" class="filter-select" onchange="filterCenters()">
|
||||
<option value="">— Urutkan —</option>
|
||||
<option value="kas_desc">💰 Kas terbanyak</option>
|
||||
<option value="kas_asc">💰 Kas terkecil</option>
|
||||
<option value="tanggungan_desc">👨👩👧 Tanggungan terbanyak</option>
|
||||
<option value="tanggungan_asc">👨👩👧 Tanggungan terkecil</option>
|
||||
</select>
|
||||
<div id="centerList"><div class="empty-state">Belum ada rumah ibadah.</div></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
🏠 Data Rumah Miskin
|
||||
<span id="houseCount" class="badge badge-orange">0</span>
|
||||
</div>
|
||||
<div class="search-wrap">
|
||||
<input type="text" id="searchHouse" class="search-input" placeholder="🔍 Cari nama KK..." oninput="filterHouses()"/>
|
||||
</div>
|
||||
<div class="filter-row">
|
||||
<select id="filterStatus" class="filter-select" onchange="filterHouses()">
|
||||
<option value="">Semua Status</option>
|
||||
<option value="helped">✅ Sudah dibantu</option>
|
||||
<option value="not_helped">⏳ Belum dibantu</option>
|
||||
<option value="outside">🟢 Luar radius</option>
|
||||
</select>
|
||||
<select id="filterMiskin" class="filter-select" onchange="filterHouses()">
|
||||
<option value="">Semua Kemiskinan</option>
|
||||
<option value="sangat_miskin">😢 Sangat Miskin</option>
|
||||
<option value="miskin">😟 Miskin</option>
|
||||
<option value="tidak_miskin">😊 Tidak Miskin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="houseList"><div class="empty-state">Belum ada data rumah miskin.</div></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">🗺 Legenda</div>
|
||||
<div class="legend-list">
|
||||
<div class="legend-item"><span style="font-size:13px">🕌</span> Masjid / Musholla</div>
|
||||
<div class="legend-item"><span style="font-size:13px">⛪</span> Gereja Katolik</div>
|
||||
<div class="legend-item"><span style="font-size:13px">✝️</span> Gereja Protestan</div>
|
||||
<div class="legend-item"><span style="font-size:13px">🛕</span> Vihara / Klenteng / Pura</div>
|
||||
<div class="legend-item"><span class="legend-dot green"></span> Luar radius</div>
|
||||
<div class="legend-item"><span class="legend-dot red"></span> Dalam radius (belum dibantu)</div>
|
||||
<div class="legend-item"><span class="legend-dot yellow"></span> Sudah dibantu</div>
|
||||
<div class="legend-item"><span class="legend-circle"></span> Area jangkauan</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="btnReset" class="btn-danger" onclick="confirmReset()">🗑 Reset Semua Data</button>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
<div id="radiusTooltip" class="radius-tooltip hidden"></div>
|
||||
</div>
|
||||
|
||||
<!-- =====================================================================
|
||||
PAGE: PELAPORAN
|
||||
===================================================================== -->
|
||||
<div id="pagePelaporan" class="page">
|
||||
<div class="report-page">
|
||||
|
||||
<div class="report-col report-col-form">
|
||||
<div class="report-col-header">
|
||||
<div class="report-col-title">📢 Kirim Laporan</div>
|
||||
<div class="report-col-sub">Laporkan kondisi kemiskinan yang belum terdata</div>
|
||||
</div>
|
||||
<div class="report-form-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Deskripsi Laporan *</label>
|
||||
<textarea id="reportText" class="form-input form-textarea" rows="5"
|
||||
placeholder="Contoh: Ada keluarga miskin di Jl. Merdeka No.5 yang belum terdata..."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Nama Pelapor (opsional)</label>
|
||||
<input type="text" id="reportName" class="form-input" placeholder="Nama Anda..."/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Lokasi / Alamat (opsional)</label>
|
||||
<input type="text" id="reportLocation" class="form-input" placeholder="Jl. Contoh No.1, Kelurahan..."/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Foto / Bukti (opsional)</label>
|
||||
<div class="upload-area" id="uploadArea" onclick="document.getElementById('reportImg').click()">
|
||||
<div class="upload-icon">📷</div>
|
||||
<div class="upload-text">Klik untuk upload foto</div>
|
||||
<div class="upload-sub">JPG, PNG, maks 5MB</div>
|
||||
<input type="file" id="reportImg" accept="image/*" style="display:none" onchange="previewImage(event)"/>
|
||||
</div>
|
||||
<div id="imgPreview" style="display:none;margin-top:8px;">
|
||||
<img id="previewImg" style="width:100%;border-radius:8px;max-height:200px;object-fit:cover;"/>
|
||||
<button class="remove-img-btn" onclick="removeImage()">✕ Hapus foto</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="form-submit" onclick="submitReport()" style="margin-top:4px;padding:12px;font-size:13px;">
|
||||
📤 Kirim Laporan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-col report-col-list" id="reportColList">
|
||||
<div class="report-col-header">
|
||||
<div class="report-col-title">
|
||||
📋 Laporan Masuk
|
||||
<span id="reportCount" class="badge badge-orange" style="margin-left:8px">0</span>
|
||||
</div>
|
||||
<div class="report-col-sub">Daftar laporan dari masyarakat</div>
|
||||
</div>
|
||||
<div class="report-list-toolbar">
|
||||
<input type="text" id="searchReport" class="search-input" placeholder="🔍 Cari laporan..." oninput="filterReports()" style="flex:1"/>
|
||||
<select id="filterReportStatus" class="filter-select" onchange="filterReports()" style="width:auto;margin:0">
|
||||
<option value="">Semua</option>
|
||||
<option value="baru">🆕 Baru</option>
|
||||
<option value="ditangani">🔄 Ditangani</option>
|
||||
<option value="selesai">✅ Selesai</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="reportList" class="report-list-container">
|
||||
<div class="empty-state" style="margin-top:20px">Belum ada laporan masuk.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-col report-col-viewer" id="reportColViewer" style="display:none">
|
||||
<div class="report-col-header">
|
||||
<div class="report-col-title">👁 Mode Viewer</div>
|
||||
<div class="report-col-sub">Daftar laporan hanya untuk Admin dan Surveyer</div>
|
||||
</div>
|
||||
<div style="padding:40px 20px;text-align:center">
|
||||
<div style="font-size:48px;margin-bottom:16px">🔒</div>
|
||||
<div style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:8px">Akses Terbatas</div>
|
||||
<div style="font-size:12px;color:var(--text-muted);line-height:1.6">
|
||||
Sebagai Viewer, Anda dapat mengirim laporan tapi tidak dapat melihat daftar laporan yang masuk.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =====================================================================
|
||||
MODAL — FORM DATA RUMAH
|
||||
===================================================================== -->
|
||||
<div id="houseModal" class="modal-overlay hidden">
|
||||
<div class="modal-box">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">🏠 Data Kemiskinan</div>
|
||||
<button class="modal-close" onclick="closeHouseModal()">✕</button>
|
||||
</div>
|
||||
<div class="modal-body" id="houseModalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =====================================================================
|
||||
MODAL — DETAIL RUMAH
|
||||
===================================================================== -->
|
||||
<div id="detailModal" class="modal-overlay hidden">
|
||||
<div class="modal-box modal-wide">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">📋 Detail Data Rumah</div>
|
||||
<button class="modal-close" onclick="closeDetailModal()">✕</button>
|
||||
</div>
|
||||
<div class="modal-body" id="detailModalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =====================================================================
|
||||
MODAL — EDIT RUMAH IBADAH
|
||||
===================================================================== -->
|
||||
<div id="centerEditModal" class="modal-overlay hidden">
|
||||
<div class="modal-box" style="max-width:460px">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">🕌 Edit Rumah Ibadah</div>
|
||||
<button class="modal-close" onclick="closeCenterEditModal()">✕</button>
|
||||
</div>
|
||||
<div class="modal-body" id="centerEditModalBody">
|
||||
<input type="hidden" id="ce_id"/>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Jenis Rumah Ibadah</label>
|
||||
<select class="form-input" id="ce_type">
|
||||
<option value="masjid">🕌 Masjid / Musholla</option>
|
||||
<option value="gereja katedral">⛪ Gereja Katolik / Katedral</option>
|
||||
<option value="gereja protestan">✝️ Gereja Protestan / Kapel</option>
|
||||
<option value="vihara">🛕 Vihara / Klenteng / Pura</option>
|
||||
<option value="sinagog">✡️ Sinagog</option>
|
||||
<option value="default">🏛️ Lainnya</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Nama Rumah Ibadah *</label>
|
||||
<input class="form-input" id="ce_name" type="text" placeholder="Nama rumah ibadah..."/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Alamat</label>
|
||||
<input class="form-input" id="ce_address" type="text" placeholder="Alamat..."/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group" style="flex:1">
|
||||
<label class="form-label">Kas / Dana (Rp)</label>
|
||||
<input class="form-input" id="ce_kas" type="number" min="0" step="1000" placeholder="0"/>
|
||||
</div>
|
||||
<div class="form-group" style="flex:1">
|
||||
<label class="form-label">Radius (meter)</label>
|
||||
<input class="form-input" id="ce_radius" type="number" min="50" step="10" placeholder="300"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-modal-cancel" onclick="closeCenterEditModal()">Batal</button>
|
||||
<button class="btn-modal-save" onclick="saveCenterEdit()">💾 Simpan Perubahan</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =====================================================================
|
||||
MODAL — DETAIL LAPORAN
|
||||
===================================================================== -->
|
||||
<div id="reportDetailModal" class="modal-overlay hidden">
|
||||
<div class="modal-box" style="max-width:500px">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">📋 Detail Laporan</div>
|
||||
<button class="modal-close" onclick="closeReportDetail()">✕</button>
|
||||
</div>
|
||||
<div class="modal-body" id="reportDetailBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =====================================================================
|
||||
CUSTOM DATE PICKER
|
||||
===================================================================== -->
|
||||
<div id="datepickerOverlay" class="datepicker-overlay hidden" onclick="closeDatepickerIfOutside(event)">
|
||||
<div class="datepicker-box" id="datepickerBox"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_USER', 'root');
|
||||
define('DB_PASS', '');
|
||||
define('DB_NAME', 'sig_bansos');
|
||||
|
||||
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
||||
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
die(json_encode(['success' => false, 'message' => 'Koneksi database gagal: ' . $conn->connect_error]));
|
||||
}
|
||||
|
||||
$conn->set_charset('utf8mb4');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Hanya menerima POST']); exit;
|
||||
}
|
||||
|
||||
$conn->query("SET FOREIGN_KEY_CHECKS=0");
|
||||
$conn->query("TRUNCATE TABLE aid_logs");
|
||||
$conn->query("TRUNCATE TABLE houses");
|
||||
$conn->query("TRUNCATE TABLE religious_centers");
|
||||
$conn->query("TRUNCATE TABLE laporan");
|
||||
$conn->query("SET FOREIGN_KEY_CHECKS=1");
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Semua data berhasil direset']);
|
||||
$conn->close();
|
||||
+1462
File diff suppressed because it is too large
Load Diff
+115
@@ -0,0 +1,115 @@
|
||||
USE sig_bansos;
|
||||
|
||||
-- ============================================================
|
||||
-- Seed: religious_centers (5 titik di Pontianak)
|
||||
-- ============================================================
|
||||
INSERT INTO religious_centers (name, address, kas, latitude, longitude, radius) VALUES
|
||||
('Masjid Jami Pontianak', 'Jl. Tanjungpura No.1, Pontianak Selatan', 12500000, -0.0294, 109.3225, 400),
|
||||
('Masjid Mujahidin', 'Jl. Ahmad Yani, Pontianak Barat', 8750000, -0.0510, 109.3100, 350),
|
||||
('Gereja Katedral Santo Yosef','Jl. Rahadi Usman No.2, Pontianak Kota', 6200000, -0.0245, 109.3344, 300),
|
||||
('Vihara Bodhisattva Karaniya','Jl. Diponegoro No.47, Pontianak Kota', 4300000, -0.0348, 109.3278, 280),
|
||||
('Masjid Al-Falah', 'Jl. Pahlawan, Pontianak Timur', 5100000, -0.0600, 109.3600, 320);
|
||||
|
||||
-- ============================================================
|
||||
-- Seed: houses (15 rumah miskin)
|
||||
-- ============================================================
|
||||
INSERT INTO houses (latitude, longitude, address, rt, rw, kelurahan, status_miskin, jumlah_anggota, anggota, aid_status, has_data) VALUES
|
||||
|
||||
-- Cluster sekitar Masjid Jami (radius 400m)
|
||||
(-0.0301, 109.3240, 'Jl. Tanjungpura Gang Melati No.3, Pontianak Selatan', '002', '001', 'Dalam Bugis',
|
||||
'sangat_miskin', 4,
|
||||
'[{"nama":"Suryanto","statusAnggota":"Kepala Keluarga","tglLahir":"1978-04-12","pekerjaan":"Buruh/Karyawan","gaji":900000},{"nama":"Dewi Suryanto","statusAnggota":"Istri/Suami","tglLahir":"1982-07-20","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Rian Suryanto","statusAnggota":"Anak","tglLahir":"2008-01-15","pekerjaan":"Pelajar/Mahasiswa","gaji":0},{"nama":"Sari Suryanto","statusAnggota":"Anak","tglLahir":"2011-09-03","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'helped', 1),
|
||||
|
||||
(-0.0310, 109.3255, 'Jl. Tanjungpura Gang Anggrek No.7, Pontianak Selatan', '003', '001', 'Dalam Bugis',
|
||||
'miskin', 3,
|
||||
'[{"nama":"Hendra Wijaya","statusAnggota":"Kepala Keluarga","tglLahir":"1985-06-28","pekerjaan":"Petani/Nelayan","gaji":1200000},{"nama":"Yuli Wijaya","statusAnggota":"Istri/Suami","tglLahir":"1988-11-14","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Budi Wijaya","statusAnggota":"Anak","tglLahir":"2014-03-22","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'not_helped', 1),
|
||||
|
||||
(-0.0285, 109.3210, 'Jl. Gajah Mada Gg. Sejahtera No.12, Pontianak Selatan', '001', '002', 'Benua Melayu Darat',
|
||||
'sangat_miskin', 5,
|
||||
'[{"nama":"Ahmad Fauzi","statusAnggota":"Kepala Keluarga","tglLahir":"1970-02-10","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Siti Aminah","statusAnggota":"Istri/Suami","tglLahir":"1974-08-30","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Rizky Fauzi","statusAnggota":"Anak","tglLahir":"1998-05-17","pekerjaan":"Buruh/Karyawan","gaji":800000},{"nama":"Nurul Fauzi","statusAnggota":"Anak","tglLahir":"2002-12-01","pekerjaan":"Pelajar/Mahasiswa","gaji":0},{"nama":"Kakek Hamid","statusAnggota":"Orang Tua","tglLahir":"1945-01-01","pekerjaan":"Tidak Bekerja","gaji":0}]',
|
||||
'helped', 1),
|
||||
|
||||
-- Cluster sekitar Masjid Mujahidin (radius 350m)
|
||||
(-0.0490, 109.3085, 'Jl. Ahmad Yani Gang Damai No.4, Pontianak Barat', '005', '003', 'Akcaya',
|
||||
'miskin', 4,
|
||||
'[{"nama":"Bambang Santoso","statusAnggota":"Kepala Keluarga","tglLahir":"1975-09-05","pekerjaan":"Wiraswasta","gaji":1500000},{"nama":"Rahayu Santoso","statusAnggota":"Istri/Suami","tglLahir":"1979-04-18","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Dika Santoso","statusAnggota":"Anak","tglLahir":"2005-07-11","pekerjaan":"Pelajar/Mahasiswa","gaji":0},{"nama":"Tika Santoso","statusAnggota":"Anak","tglLahir":"2009-02-25","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'not_helped', 1),
|
||||
|
||||
(-0.0530, 109.3115, 'Jl. Kom. Yos Sudarso Gg. Mawar No.9, Pontianak Barat', '007', '004', 'Akcaya',
|
||||
'sangat_miskin', 2,
|
||||
'[{"nama":"Pak Idrus","statusAnggota":"Kepala Keluarga","tglLahir":"1952-03-14","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Bu Rohani","statusAnggota":"Istri/Suami","tglLahir":"1956-11-08","pekerjaan":"Tidak Bekerja","gaji":0}]',
|
||||
'helped', 1),
|
||||
|
||||
(-0.0480, 109.3070, 'Jl. Purnama Gang Murni No.2, Pontianak Barat', '004', '002', 'Sungai Beliung',
|
||||
'miskin', 3,
|
||||
'[{"nama":"Eko Prasetyo","statusAnggota":"Kepala Keluarga","tglLahir":"1983-12-20","pekerjaan":"Buruh/Karyawan","gaji":1100000},{"nama":"Fitri Prasetyo","statusAnggota":"Istri/Suami","tglLahir":"1986-05-30","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Kevin Prasetyo","statusAnggota":"Anak","tglLahir":"2012-08-14","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'not_helped', 1),
|
||||
|
||||
-- Cluster sekitar Gereja Katedral (radius 300m)
|
||||
(-0.0260, 109.3360, 'Jl. Rahadi Usman Gg. Teratai No.6, Pontianak Kota', '001', '001', 'Darat Sekip',
|
||||
'tidak_miskin', 4,
|
||||
'[{"nama":"Antonius Liong","statusAnggota":"Kepala Keluarga","tglLahir":"1972-07-22","pekerjaan":"Wiraswasta","gaji":3500000},{"nama":"Maria Liong","statusAnggota":"Istri/Suami","tglLahir":"1975-10-09","pekerjaan":"Buruh/Karyawan","gaji":1800000},{"nama":"Felix Liong","statusAnggota":"Anak","tglLahir":"2003-04-05","pekerjaan":"Pelajar/Mahasiswa","gaji":0},{"nama":"Clara Liong","statusAnggota":"Anak","tglLahir":"2007-01-17","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'outside', 1),
|
||||
|
||||
(-0.0235, 109.3330, 'Jl. Tanjung Raya Gg. Bakti No.11, Pontianak Kota', '002', '001', 'Darat Sekip',
|
||||
'miskin', 3,
|
||||
'[{"nama":"Yohanes Budi","statusAnggota":"Kepala Keluarga","tglLahir":"1980-08-16","pekerjaan":"Petani/Nelayan","gaji":900000},{"nama":"Elisabeth Budi","statusAnggota":"Istri/Suami","tglLahir":"1984-03-27","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Theresia Budi","statusAnggota":"Anak","tglLahir":"2010-06-13","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'not_helped', 1),
|
||||
|
||||
-- Cluster sekitar Vihara (radius 280m)
|
||||
(-0.0365, 109.3290, 'Jl. Diponegoro Gg. Lestari No.5, Pontianak Kota', '003', '002', 'Mariana',
|
||||
'miskin', 4,
|
||||
'[{"nama":"Lim Ah Kow","statusAnggota":"Kepala Keluarga","tglLahir":"1968-05-10","pekerjaan":"Wiraswasta","gaji":1200000},{"nama":"Tan Siu Lan","statusAnggota":"Istri/Suami","tglLahir":"1972-09-24","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Lim Wei","statusAnggota":"Anak","tglLahir":"2000-11-29","pekerjaan":"Buruh/Karyawan","gaji":700000},{"nama":"Lim Hui","statusAnggota":"Anak","tglLahir":"2006-02-08","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'helped', 1),
|
||||
|
||||
(-0.0340, 109.3265, 'Jl. Veteran Gg. Rukun No.8, Pontianak Kota', '006', '003', 'Mariana',
|
||||
'sangat_miskin', 2,
|
||||
'[{"nama":"Wang Cheng","statusAnggota":"Kepala Keluarga","tglLahir":"1960-04-04","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Liu Fang","statusAnggota":"Istri/Suami","tglLahir":"1963-12-18","pekerjaan":"Tidak Bekerja","gaji":0}]',
|
||||
'not_helped', 1),
|
||||
|
||||
-- Cluster sekitar Masjid Al-Falah (radius 320m)
|
||||
(-0.0580, 109.3580, 'Jl. Pahlawan Gg. Bersatu No.3, Pontianak Timur', '001', '001', 'Saigon',
|
||||
'sangat_miskin', 5,
|
||||
'[{"nama":"Syaiful Anwar","statusAnggota":"Kepala Keluarga","tglLahir":"1973-01-25","pekerjaan":"Buruh/Karyawan","gaji":750000},{"nama":"Nurhayati","statusAnggota":"Istri/Suami","tglLahir":"1977-06-14","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Fajar Anwar","statusAnggota":"Anak","tglLahir":"1999-10-07","pekerjaan":"Buruh/Karyawan","gaji":600000},{"nama":"Dewi Anwar","statusAnggota":"Anak","tglLahir":"2004-03-19","pekerjaan":"Pelajar/Mahasiswa","gaji":0},{"nama":"Si Kecil","statusAnggota":"Anak","tglLahir":"2016-07-30","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'helped', 1),
|
||||
|
||||
(-0.0620, 109.3615, 'Jl. Sultan Hamid Gg. Damai No.10, Pontianak Timur', '002', '002', 'Saigon',
|
||||
'miskin', 3,
|
||||
'[{"nama":"Mukhtar Halim","statusAnggota":"Kepala Keluarga","tglLahir":"1981-11-03","pekerjaan":"Petani/Nelayan","gaji":1000000},{"nama":"Salmah Halim","statusAnggota":"Istri/Suami","tglLahir":"1984-08-22","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Hafiz Halim","statusAnggota":"Anak","tglLahir":"2013-05-16","pekerjaan":"Pelajar/Mahasiswa","gaji":0}]',
|
||||
'not_helped', 1),
|
||||
|
||||
(-0.0565, 109.3560, 'Jl. DR. Wahidin Gg. Sempurna No.15, Pontianak Timur', '004', '003', 'Banjar Serasan',
|
||||
'miskin', 4,
|
||||
'[{"nama":"Roslan Idris","statusAnggota":"Kepala Keluarga","tglLahir":"1976-02-17","pekerjaan":"Wiraswasta","gaji":1300000},{"nama":"Yanti Idris","statusAnggota":"Istri/Suami","tglLahir":"1979-07-05","pekerjaan":"Tidak Bekerja","gaji":0},{"nama":"Reza Idris","statusAnggota":"Anak","tglLahir":"2007-09-28","pekerjaan":"Pelajar/Mahasiswa","gaji":0},{"nama":"Nenek Maimunah","statusAnggota":"Orang Tua","tglLahir":"1950-06-12","pekerjaan":"Tidak Bekerja","gaji":0}]',
|
||||
'not_helped', 1),
|
||||
|
||||
-- Pin tanpa data (has_data=0)
|
||||
(-0.0330, 109.3310, 'Jl. Nusa Indah, Pontianak Kota', '005', '002', 'Mariana',
|
||||
'', 0, '[]', 'outside', 0),
|
||||
|
||||
(-0.0520, 109.3090, 'Jl. Ahmad Yani, Pontianak Barat', '008', '005', 'Akcaya',
|
||||
'', 0, '[]', 'outside', 0);
|
||||
|
||||
-- ============================================================
|
||||
-- Seed: laporan
|
||||
-- ============================================================
|
||||
INSERT INTO laporan (pelapor, deskripsi, lokasi, status) VALUES
|
||||
('Budi Santoso', 'Ada keluarga lansia di Gang Flamboyan RT 007 yang tinggal sendiri, rumahnya hampir roboh. Perlu perhatian segera.', 'Gang Flamboyan RT 007, Pontianak Barat', 'baru'),
|
||||
('Siti Rahayu', 'Keluarga dengan 6 anak di Jl. Pahlawan belum pernah menerima bantuan apapun. Kondisi rumah sangat tidak layak.', 'Jl. Pahlawan, Pontianak Timur', 'ditangani'),
|
||||
('Anonim', 'Ada balita yang kekurangan gizi di RT 003 RW 001, orang tuanya pengangguran. Butuh bantuan pangan segera.', 'RT 003/001, Dalam Bugis, Pontianak Selatan', 'baru'),
|
||||
('Pak RT Hamzah', 'Terdapat 3 kepala keluarga di gang belakang pasar yang belum terdata. Mereka tinggal di bantaran sungai.', 'Bantaran Sungai Kapuas, Pontianak Kota', 'selesai'),
|
||||
('Marlina Dewi', 'Ibu tunggal dengan 4 anak di Jl. Veteran, suami meninggal tahun lalu. Sangat membutuhkan bantuan sosial.', 'Jl. Veteran, Pontianak Kota', 'baru');
|
||||
|
||||
-- ============================================================
|
||||
-- Seed: aid_logs (riwayat bantuan)
|
||||
-- ============================================================
|
||||
INSERT INTO aid_logs (house_id, religious_center_id, status, timestamp) VALUES
|
||||
(1, 1, 'helped', '2026-03-10 09:00:00'),
|
||||
(3, 1, 'helped', '2026-03-12 10:30:00'),
|
||||
(5, 2, 'helped', '2026-03-15 08:45:00'),
|
||||
(9, 4, 'helped', '2026-04-01 11:00:00'),
|
||||
(11, 5, 'helped', '2026-04-05 09:30:00'),
|
||||
(2, 1, 'reverted', '2026-02-20 14:00:00'),
|
||||
(4, 2, 'reverted', '2026-02-25 13:00:00');
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Hanya POST']); exit;
|
||||
}
|
||||
|
||||
$name = strip_tags(trim($_POST['name'] ?? 'Anonim'));
|
||||
$text = strip_tags(trim($_POST['text'] ?? ''));
|
||||
$lokasi = strip_tags(trim($_POST['lokasi'] ?? ''));
|
||||
$img = $_POST['img'] ?? '';
|
||||
|
||||
if (empty($text)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Deskripsi laporan tidak boleh kosong']); exit;
|
||||
}
|
||||
if (strlen($img) > 7_000_000) {
|
||||
echo json_encode(['success' => false, 'message' => 'Ukuran foto terlalu besar (max 5MB)']); exit;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("INSERT INTO laporan (pelapor, deskripsi, lokasi, foto_base64) VALUES (?, ?, ?, ?)");
|
||||
$stmt->bind_param('ssss', $name, $text, $lokasi, $img);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'id' => $conn->insert_id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Hanya menerima POST']); exit;
|
||||
}
|
||||
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$name = $conn->real_escape_string(strip_tags(trim($_POST['name'] ?? '')));
|
||||
$address = $conn->real_escape_string(strip_tags(trim($_POST['address'] ?? '')));
|
||||
$kas = floatval($_POST['kas'] ?? 0);
|
||||
$lat = floatval($_POST['lat'] ?? 0);
|
||||
$lng = floatval($_POST['lng'] ?? 0);
|
||||
$radius = intval($_POST['radius'] ?? 300);
|
||||
$isUpd = isset($_POST['update']) && $_POST['update'] === '1';
|
||||
|
||||
if (empty($name)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Nama wajib diisi']); exit;
|
||||
}
|
||||
if ($radius < 50) $radius = 50;
|
||||
|
||||
if ($id > 0 || $isUpd) {
|
||||
$stmt = $conn->prepare("UPDATE religious_centers SET name=?, address=?, kas=?, latitude=?, longitude=?, radius=?, updated_at=NOW() WHERE id=?");
|
||||
$stmt->bind_param('ssdddii', $name, $address, $kas, $lat, $lng, $radius, $id);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'id' => $id, 'action' => 'updated']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
$stmt = $conn->prepare("INSERT INTO religious_centers (name, address, kas, latitude, longitude, radius) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param('ssdddi', $name, $address, $kas, $lat, $lng, $radius);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'id' => $conn->insert_id, 'action' => 'inserted']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
$conn->close();
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Hanya POST']); exit;
|
||||
}
|
||||
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$lat = floatval($_POST['lat'] ?? 0);
|
||||
$lng = floatval($_POST['lng'] ?? 0);
|
||||
$address = $conn->real_escape_string(strip_tags(trim($_POST['address'] ?? '')));
|
||||
$rt = $conn->real_escape_string(strip_tags(trim($_POST['rt'] ?? '')));
|
||||
$rw = $conn->real_escape_string(strip_tags(trim($_POST['rw'] ?? '')));
|
||||
$kelurahan = $conn->real_escape_string(strip_tags(trim($_POST['kelurahan'] ?? '')));
|
||||
$statusMiskin = $_POST['status_miskin'] ?? '';
|
||||
$jumlahAnggota = intval($_POST['jumlah_anggota'] ?? 0);
|
||||
$anggotaRaw = $_POST['anggota'] ?? '[]';
|
||||
$aidStatus = $_POST['aid_status'] ?? 'outside';
|
||||
$hasData = intval($_POST['has_data'] ?? 0);
|
||||
|
||||
$validStatuses = ['sangat_miskin', 'miskin', 'tidak_miskin', ''];
|
||||
if (!in_array($statusMiskin, $validStatuses)) $statusMiskin = '';
|
||||
$validAid = ['helped', 'not_helped', 'outside'];
|
||||
if (!in_array($aidStatus, $validAid)) $aidStatus = 'outside';
|
||||
|
||||
$anggotaDecoded = json_decode($anggotaRaw, true);
|
||||
if (!is_array($anggotaDecoded)) $anggotaDecoded = [];
|
||||
$anggotaJson = json_encode($anggotaDecoded, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
if ($id > 0) {
|
||||
$stmt = $conn->prepare("UPDATE houses SET latitude=?, longitude=?, address=?, rt=?, rw=?, kelurahan=?, status_miskin=?, jumlah_anggota=?, anggota=?, aid_status=?, has_data=?, updated_at=NOW() WHERE id=?");
|
||||
$stmt->bind_param('ddsssssiisii', $lat, $lng, $address, $rt, $rw, $kelurahan, $statusMiskin, $jumlahAnggota, $anggotaJson, $aidStatus, $hasData, $id);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'id' => $id, 'action' => 'updated']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
$stmt = $conn->prepare("INSERT INTO houses (latitude, longitude, address, rt, rw, kelurahan, status_miskin, jumlah_anggota, anggota, aid_status, has_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param('ddsssssissi', $lat, $lng, $address, $rt, $rw, $kelurahan, $statusMiskin, $jumlahAnggota, $anggotaJson, $aidStatus, $hasData);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'id' => $conn->insert_id, 'action' => 'inserted']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
$conn->close();
|
||||
@@ -0,0 +1,649 @@
|
||||
/* ===================================================================
|
||||
BantSOSial GIS — style.css
|
||||
=================================================================== */
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--primary: #0d9488;
|
||||
--primary-light: #ccfbf1;
|
||||
--primary-dark: #0f766e;
|
||||
--orange: #ea580c;
|
||||
--orange-light: #fff7ed;
|
||||
--danger: #dc2626;
|
||||
--danger-light: #fef2f2;
|
||||
--yellow: #d97706;
|
||||
--yellow-light: #fffbeb;
|
||||
--green: #16a34a;
|
||||
--green-light: #f0fdf4;
|
||||
--purple: #7c3aed;
|
||||
--purple-light: #f5f3ff;
|
||||
--bg: #f8fafc;
|
||||
--surface: #ffffff;
|
||||
--border: #e2e8f0;
|
||||
--border-hover: #cbd5e1;
|
||||
--text: #0f172a;
|
||||
--text-muted: #64748b;
|
||||
--text-dim: #94a3b8;
|
||||
--nav-h: 56px;
|
||||
--sidebar-w: 300px;
|
||||
--font: 'DM Sans', -apple-system, system-ui, sans-serif;
|
||||
--mono: 'DM Mono', monospace;
|
||||
--radius: 10px;
|
||||
--shadow: 0 2px 12px rgba(15,23,42,.07);
|
||||
--shadow-md: 0 8px 30px rgba(15,23,42,.10);
|
||||
}
|
||||
|
||||
html, body { height: 100%; overflow: hidden; font-family: var(--font); color: var(--text); background: var(--bg); }
|
||||
|
||||
/* ===================================================================
|
||||
NAVBAR
|
||||
=================================================================== */
|
||||
#topNav {
|
||||
position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
|
||||
height: var(--nav-h);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.nav-brand { display: flex; align-items: center; gap: 10px; }
|
||||
.nav-brand-icon { font-size: 22px; line-height: 1; }
|
||||
.nav-brand-title { font-size: 14px; font-weight: 700; letter-spacing: -.3px; }
|
||||
.nav-brand-sub { font-size: 10px; color: var(--text-muted); }
|
||||
|
||||
.nav-menu { display: flex; gap: 4px; }
|
||||
.nav-item {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 6px 14px; border: none; border-radius: 8px;
|
||||
background: transparent; cursor: pointer;
|
||||
font-family: var(--font); font-size: 13px; font-weight: 500;
|
||||
color: var(--text-muted); transition: all .2s; position: relative;
|
||||
}
|
||||
.nav-item:hover { background: var(--bg); color: var(--text); }
|
||||
.nav-item.active { background: var(--primary-light); color: var(--primary-dark); font-weight: 600; }
|
||||
.nav-item-icon { font-size: 14px; }
|
||||
.nav-badge {
|
||||
position: absolute; top: 4px; right: 6px;
|
||||
min-width: 16px; height: 16px; padding: 0 4px;
|
||||
background: var(--danger); color: #fff;
|
||||
border-radius: 10px; font-size: 9px; font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.nav-badge.hidden { display: none; }
|
||||
|
||||
.nav-right { display: flex; align-items: center; gap: 8px; }
|
||||
.role-pill {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 5px 12px; border-radius: 20px;
|
||||
background: var(--primary-light); color: var(--primary-dark);
|
||||
font-size: 12px; font-weight: 700;
|
||||
}
|
||||
#roleSwitchBtn {
|
||||
padding: 6px 12px; border: 1px solid var(--border); border-radius: 8px;
|
||||
background: var(--surface); cursor: pointer;
|
||||
font-family: var(--font); font-size: 12px; font-weight: 500;
|
||||
color: var(--text-muted); transition: all .2s;
|
||||
}
|
||||
#roleSwitchBtn:hover { border-color: var(--border-hover); color: var(--text); }
|
||||
|
||||
/* ===================================================================
|
||||
PAGE LAYOUT
|
||||
=================================================================== */
|
||||
.page { display: none; position: fixed; inset: 0; top: var(--nav-h); }
|
||||
.page.active-page { display: flex; }
|
||||
|
||||
/* ===================================================================
|
||||
SIDEBAR
|
||||
=================================================================== */
|
||||
#sidebar {
|
||||
width: var(--sidebar-w); min-width: var(--sidebar-w);
|
||||
height: 100%; overflow-y: auto;
|
||||
background: var(--surface); border-right: 1px solid var(--border);
|
||||
display: flex; flex-direction: column; gap: 0;
|
||||
scrollbar-width: thin; scrollbar-color: var(--border) transparent;
|
||||
}
|
||||
#sidebar::-webkit-scrollbar { width: 4px; }
|
||||
#sidebar::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
||||
|
||||
/* ===================================================================
|
||||
MAP
|
||||
=================================================================== */
|
||||
#map { flex: 1; height: 100%; z-index: 0; }
|
||||
#map.adding-mode { cursor: crosshair !important; }
|
||||
|
||||
/* ===================================================================
|
||||
MODE BADGE
|
||||
=================================================================== */
|
||||
.mode-badge {
|
||||
margin: 10px 14px 0;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--primary-light); border: 1px solid var(--primary);
|
||||
border-radius: 8px; font-size: 11px; font-weight: 600; color: var(--primary-dark);
|
||||
}
|
||||
.mode-badge.house-mode { background: #fff7ed; border-color: var(--orange); color: var(--orange); }
|
||||
.mode-badge.hidden { display: none !important; }
|
||||
.pulse-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
background: var(--primary); flex-shrink: 0;
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
.mode-badge.house-mode .pulse-dot { background: var(--orange); }
|
||||
@keyframes pulse { 0%,100% { opacity:1; transform:scale(1); } 50% { opacity:.5; transform:scale(1.4); } }
|
||||
|
||||
/* ===================================================================
|
||||
BUTTONS
|
||||
=================================================================== */
|
||||
.btn-group {
|
||||
display: flex; flex-direction: column; gap: 6px;
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.btn-group button {
|
||||
padding: 9px 14px; border: 1px solid var(--border); border-radius: 9px;
|
||||
background: var(--surface); cursor: pointer;
|
||||
font-family: var(--font); font-size: 12px; font-weight: 600;
|
||||
color: var(--text); text-align: left; transition: all .2s;
|
||||
}
|
||||
.btn-group button:hover { border-color: var(--primary); color: var(--primary); background: var(--primary-light); }
|
||||
.btn-group button.active { background: var(--primary); color: #fff; border-color: var(--primary-dark); }
|
||||
.btn-danger {
|
||||
margin: 8px 14px 14px; padding: 9px 14px;
|
||||
border: 1px solid #fca5a5; border-radius: 9px;
|
||||
background: var(--danger-light); cursor: pointer;
|
||||
font-family: var(--font); font-size: 12px; font-weight: 600;
|
||||
color: var(--danger); transition: all .2s;
|
||||
}
|
||||
.btn-danger:hover { background: #fee2e2; }
|
||||
|
||||
/* ===================================================================
|
||||
STATS
|
||||
=================================================================== */
|
||||
.stat-panel { padding: 12px 14px; border-bottom: 1px solid var(--border); }
|
||||
.stat-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .8px; color: var(--text-muted); margin-bottom: 8px; }
|
||||
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
|
||||
.stat-card {
|
||||
padding: 8px 10px; border-radius: 8px;
|
||||
background: var(--bg); border: 1px solid var(--border); text-align: center;
|
||||
}
|
||||
.stat-card.red { background: #fef2f2; border-color: #fca5a5; }
|
||||
.stat-card.yellow { background: #fffbeb; border-color: #fcd34d; }
|
||||
.stat-card.green { background: #f0fdf4; border-color: #86efac; }
|
||||
.stat-num { font-size: 22px; font-weight: 800; font-family: var(--mono); color: var(--text); }
|
||||
.stat-card.red .stat-num { color: var(--danger); }
|
||||
.stat-card.yellow .stat-num { color: var(--yellow); }
|
||||
.stat-card.green .stat-num { color: var(--green); }
|
||||
.stat-label { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: .5px; color: var(--text-muted); margin-top: 2px; }
|
||||
|
||||
/* ===================================================================
|
||||
SECTIONS
|
||||
=================================================================== */
|
||||
.section { padding: 12px 14px; border-bottom: 1px solid var(--border); }
|
||||
.section-title {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-size: 12px; font-weight: 700; color: var(--text); margin-bottom: 8px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
min-width: 20px; height: 18px; padding: 0 6px;
|
||||
border-radius: 10px; font-size: 10px; font-weight: 700;
|
||||
}
|
||||
.badge-teal { background: var(--primary-light); color: var(--primary-dark); }
|
||||
.badge-orange { background: #fff7ed; color: var(--orange); }
|
||||
|
||||
.search-wrap { margin-bottom: 6px; }
|
||||
.search-input {
|
||||
width: 100%; padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px;
|
||||
font-family: var(--font); font-size: 12px; color: var(--text);
|
||||
background: var(--bg); outline: none; transition: border-color .2s;
|
||||
}
|
||||
.search-input:focus { border-color: var(--primary); }
|
||||
.filter-row { display: flex; gap: 6px; margin-bottom: 8px; }
|
||||
.filter-select {
|
||||
width: 100%; padding: 6px 8px; border: 1px solid var(--border); border-radius: 8px;
|
||||
font-family: var(--font); font-size: 11px; color: var(--text);
|
||||
background: var(--bg); outline: none; cursor: pointer;
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
CENTER LIST ITEMS
|
||||
=================================================================== */
|
||||
.center-item {
|
||||
display: flex; align-items: flex-start; gap: 8px;
|
||||
padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border);
|
||||
margin-bottom: 6px; cursor: pointer; transition: all .2s;
|
||||
background: var(--surface);
|
||||
}
|
||||
.center-item:hover { border-color: var(--primary); background: var(--primary-light); }
|
||||
.center-item-icon { font-size: 18px; line-height: 1.2; flex-shrink: 0; }
|
||||
.center-item-body { flex: 1; min-width: 0; }
|
||||
.center-item-name { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.center-item-addr { font-size: 10px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.center-item-meta { display: flex; gap: 6px; margin-top: 3px; flex-wrap: wrap; }
|
||||
.meta-tag {
|
||||
padding: 1px 6px; border-radius: 4px; font-size: 9px; font-weight: 600;
|
||||
background: var(--bg); border: 1px solid var(--border); color: var(--text-muted);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
.center-item-del {
|
||||
padding: 3px 7px; border: 1px solid #fca5a5; border-radius: 6px;
|
||||
background: var(--danger-light); color: var(--danger);
|
||||
font-size: 10px; cursor: pointer; flex-shrink: 0; transition: all .2s;
|
||||
}
|
||||
.center-item-del:hover { background: #fee2e2; }
|
||||
|
||||
/* ===================================================================
|
||||
HOUSE LIST ITEMS
|
||||
=================================================================== */
|
||||
.house-item {
|
||||
display: flex; align-items: flex-start; gap: 8px;
|
||||
padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border);
|
||||
margin-bottom: 6px; cursor: pointer; transition: all .2s;
|
||||
background: var(--surface);
|
||||
}
|
||||
.house-item:hover { border-color: var(--orange); background: var(--orange-light); }
|
||||
.house-item.status-helped { border-left: 3px solid var(--yellow); }
|
||||
.house-item.status-not_helped { border-left: 3px solid var(--danger); }
|
||||
.house-item.status-outside { border-left: 3px solid var(--green); }
|
||||
.house-item-icon { font-size: 16px; line-height: 1.3; flex-shrink: 0; }
|
||||
.house-item-body { flex: 1; min-width: 0; }
|
||||
.house-item-name { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.house-item-addr { font-size: 10px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.house-item-tags { display: flex; gap: 4px; margin-top: 3px; flex-wrap: wrap; }
|
||||
.tag {
|
||||
padding: 1px 6px; border-radius: 4px; font-size: 9px; font-weight: 700;
|
||||
text-transform: uppercase; letter-spacing: .3px;
|
||||
}
|
||||
.tag-helped { background: #fef9c3; color: #854d0e; }
|
||||
.tag-not_helped { background: #fef2f2; color: var(--danger); }
|
||||
.tag-outside { background: var(--green-light); color: var(--green); }
|
||||
.tag-nodata { background: var(--bg); color: var(--text-muted); border: 1px solid var(--border); }
|
||||
.tag-sangat_miskin { background: #fef2f2; color: #991b1b; }
|
||||
.tag-miskin { background: #fff7ed; color: #9a3412; }
|
||||
.tag-tidak_miskin { background: var(--green-light); color: #166534; }
|
||||
.house-item-del {
|
||||
padding: 3px 7px; border: 1px solid #fca5a5; border-radius: 6px;
|
||||
background: var(--danger-light); color: var(--danger);
|
||||
font-size: 10px; cursor: pointer; flex-shrink: 0; transition: all .2s;
|
||||
}
|
||||
.house-item-del:hover { background: #fee2e2; }
|
||||
|
||||
/* ===================================================================
|
||||
LEGEND
|
||||
=================================================================== */
|
||||
.legend-list { display: flex; flex-direction: column; gap: 6px; }
|
||||
.legend-item { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text-muted); }
|
||||
.legend-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||
.legend-dot.green { background: #16a34a; }
|
||||
.legend-dot.red { background: #dc2626; }
|
||||
.legend-dot.yellow { background: #d97706; }
|
||||
.legend-circle {
|
||||
width: 14px; height: 14px; border-radius: 50%; flex-shrink: 0;
|
||||
border: 2px dashed #3b82f6; background: rgba(59,130,246,.08);
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
EMPTY STATE
|
||||
=================================================================== */
|
||||
.empty-state { text-align: center; font-size: 11px; color: var(--text-dim); padding: 12px 0; }
|
||||
|
||||
/* ===================================================================
|
||||
RADIUS TOOLTIP
|
||||
=================================================================== */
|
||||
.radius-tooltip {
|
||||
position: absolute; bottom: 24px; left: 50%; transform: translateX(-50%);
|
||||
z-index: 800; padding: 6px 14px;
|
||||
background: rgba(15,23,42,.85); color: #fff; border-radius: 20px;
|
||||
font-size: 12px; font-weight: 700; font-family: var(--mono);
|
||||
pointer-events: none;
|
||||
}
|
||||
.radius-tooltip.hidden { display: none; }
|
||||
|
||||
/* ===================================================================
|
||||
MAP POPUPS
|
||||
=================================================================== */
|
||||
.leaflet-popup-content-wrapper { padding: 0 !important; border-radius: 12px !important; overflow: hidden; box-shadow: var(--shadow-md) !important; }
|
||||
.leaflet-popup-content { margin: 0 !important; width: 240px !important; }
|
||||
.leaflet-popup-tip { background: var(--surface) !important; }
|
||||
|
||||
.map-popup { padding: 12px 14px 8px; }
|
||||
.map-popup-badge { font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 10px; display: inline-block; margin-bottom: 6px; }
|
||||
.map-popup-badge.helped { background: #fef9c3; color: #854d0e; }
|
||||
.map-popup-badge.not_helped { background: #fef2f2; color: var(--danger); }
|
||||
.map-popup-badge.outside { background: var(--green-light); color: var(--green); }
|
||||
.map-popup-badge.nodata { background: var(--bg); color: var(--text-muted); }
|
||||
.map-popup-title { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 3px; }
|
||||
.map-popup-addr { font-size: 11px; color: var(--text-muted); line-height: 1.5; }
|
||||
.map-popup-actions { display: flex; gap: 5px; padding: 8px 14px 12px; border-top: 1px solid var(--border); }
|
||||
.popup-btn {
|
||||
flex: 1; padding: 6px 8px; border-radius: 7px; border: 1px solid var(--border);
|
||||
background: var(--bg); cursor: pointer; font-family: var(--font);
|
||||
font-size: 10px; font-weight: 600; color: var(--text); transition: all .2s;
|
||||
}
|
||||
.popup-btn:hover { border-color: var(--border-hover); }
|
||||
.popup-btn.primary { background: var(--primary-light); border-color: var(--primary); color: var(--primary-dark); }
|
||||
.popup-btn.orange { background: #fff7ed; border-color: var(--orange); color: var(--orange); }
|
||||
|
||||
.center-popup { padding: 12px 13px 8px; }
|
||||
.center-popup-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
||||
.center-popup-icon {
|
||||
width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 16px; background: var(--primary-light); flex-shrink: 0;
|
||||
}
|
||||
.center-popup-title { font-size: 13px; font-weight: 700; color: var(--text); line-height: 1.3; }
|
||||
.center-popup-addr { font-size: 11px; color: var(--text-muted); margin-bottom: 8px; }
|
||||
.kas-badge {
|
||||
display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
|
||||
padding: 5px 8px; background: var(--green-light); border: 1px solid #86efac; border-radius: 7px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.kas-label { color: var(--green); font-weight: 600; flex: 1; }
|
||||
.kas-value { font-family: var(--mono); font-size: 12px; font-weight: 700; color: var(--green); }
|
||||
.radius-info-box {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 5px 8px; background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 7px;
|
||||
font-size: 11px; color: #1e40af; margin-bottom: 5px;
|
||||
}
|
||||
.radius-val { font-family: var(--mono); font-weight: 700; }
|
||||
.radius-drag-hint { font-size: 10px; color: var(--text-dim); text-align: center; padding: 3px 0 5px; }
|
||||
|
||||
.form-popup { padding: 12px; }
|
||||
.form-popup h3 { font-size: 13px; font-weight: 700; margin-bottom: 10px; color: var(--text); }
|
||||
|
||||
/* ===================================================================
|
||||
MODALS
|
||||
=================================================================== */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; z-index: 2000;
|
||||
background: rgba(15,23,42,.45);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 20px;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.modal-overlay.hidden { display: none !important; }
|
||||
.modal-box {
|
||||
background: var(--surface); border-radius: 16px;
|
||||
width: 100%; max-width: 580px; max-height: 88vh;
|
||||
display: flex; flex-direction: column;
|
||||
box-shadow: 0 24px 60px rgba(15,23,42,.18);
|
||||
overflow: hidden;
|
||||
}
|
||||
.modal-wide { max-width: 700px; }
|
||||
.modal-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 16px 20px; border-bottom: 1px solid var(--border);
|
||||
background: linear-gradient(135deg, #f0fdfa, #e0f2fe); flex-shrink: 0;
|
||||
}
|
||||
.modal-title { font-size: 15px; font-weight: 700; color: var(--text); }
|
||||
.modal-close {
|
||||
width: 28px; height: 28px; border: none; border-radius: 8px;
|
||||
background: rgba(15,23,42,.06); cursor: pointer;
|
||||
font-size: 13px; color: var(--text-muted); transition: all .2s;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.modal-close:hover { background: rgba(15,23,42,.12); color: var(--text); }
|
||||
.modal-body { overflow-y: auto; flex: 1; padding: 20px; }
|
||||
.modal-actions {
|
||||
display: flex; justify-content: flex-end; gap: 8px;
|
||||
padding: 14px 20px; border-top: 1px solid var(--border); flex-shrink: 0;
|
||||
}
|
||||
.btn-modal-cancel {
|
||||
padding: 8px 18px; border: 1px solid var(--border); border-radius: 8px;
|
||||
background: var(--surface); cursor: pointer; font-family: var(--font);
|
||||
font-size: 13px; font-weight: 500; color: var(--text-muted); transition: all .2s;
|
||||
}
|
||||
.btn-modal-cancel:hover { border-color: var(--border-hover); color: var(--text); }
|
||||
.btn-modal-save {
|
||||
padding: 8px 18px; border: none; border-radius: 8px;
|
||||
background: var(--primary); cursor: pointer; font-family: var(--font);
|
||||
font-size: 13px; font-weight: 700; color: #fff; transition: all .2s;
|
||||
}
|
||||
.btn-modal-save:hover { background: var(--primary-dark); }
|
||||
|
||||
/* ===================================================================
|
||||
FORMS (modal)
|
||||
=================================================================== */
|
||||
.modal-section { margin-bottom: 20px; }
|
||||
.modal-section:last-child { margin-bottom: 0; }
|
||||
.modal-section-title {
|
||||
font-size: 11px; font-weight: 700; text-transform: uppercase;
|
||||
letter-spacing: .8px; color: var(--text-muted); margin-bottom: 10px;
|
||||
padding-bottom: 6px; border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.form-group { margin-bottom: 12px; }
|
||||
.form-group:last-child { margin-bottom: 0; }
|
||||
.form-row { display: flex; gap: 10px; }
|
||||
.form-row .form-group { flex: 1; margin-bottom: 0; }
|
||||
.form-label { display: block; font-size: 11px; font-weight: 600; color: var(--text-muted); margin-bottom: 5px; }
|
||||
.form-input {
|
||||
width: 100%; padding: 8px 10px; border: 1.5px solid var(--border); border-radius: 8px;
|
||||
font-family: var(--font); font-size: 13px; color: var(--text);
|
||||
background: var(--bg); outline: none; transition: border-color .2s;
|
||||
}
|
||||
.form-input:focus { border-color: var(--primary); background: var(--surface); }
|
||||
.form-textarea { resize: vertical; min-height: 80px; }
|
||||
.form-submit {
|
||||
width: 100%; padding: 10px; border: none; border-radius: 9px;
|
||||
background: var(--primary); color: #fff; cursor: pointer;
|
||||
font-family: var(--font); font-size: 13px; font-weight: 700; transition: all .2s;
|
||||
}
|
||||
.form-submit:hover { background: var(--primary-dark); }
|
||||
|
||||
.info-box { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; margin-bottom: 10px; }
|
||||
.info-box-row { display: flex; justify-content: space-between; padding: 7px 12px; border-bottom: 1px solid var(--border); font-size: 12px; }
|
||||
.info-box-row:last-child { border-bottom: none; }
|
||||
.info-box-label { color: var(--text-muted); font-weight: 500; }
|
||||
.info-box-val { font-weight: 600; color: var(--text); text-align: right; max-width: 60%; }
|
||||
|
||||
/* ===================================================================
|
||||
STATUS PILLS
|
||||
=================================================================== */
|
||||
.status-pills { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.status-pill {
|
||||
flex: 1; min-width: 100px; padding: 8px 10px; border-radius: 9px;
|
||||
border: 2px solid var(--border); cursor: pointer;
|
||||
font-size: 12px; font-weight: 600; text-align: center;
|
||||
background: var(--surface); transition: all .2s; color: var(--text-muted);
|
||||
}
|
||||
.status-pill:hover { border-color: var(--border-hover); }
|
||||
.status-pill.selected.sangat_miskin { background: #fef2f2; border-color: var(--danger); color: #991b1b; }
|
||||
.status-pill.selected.miskin { background: #fff7ed; border-color: var(--orange); color: #9a3412; }
|
||||
.status-pill.selected.tidak_miskin { background: var(--green-light); border-color: var(--green); color: #166534; }
|
||||
|
||||
/* ===================================================================
|
||||
MEMBER CARD
|
||||
=================================================================== */
|
||||
.member-card {
|
||||
border: 1px solid var(--border); border-radius: 10px;
|
||||
margin-bottom: 10px; overflow: hidden;
|
||||
}
|
||||
.member-card-header {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg); border-bottom: 1px solid var(--border);
|
||||
font-size: 12px; font-weight: 700; color: var(--text-muted);
|
||||
}
|
||||
.member-num {
|
||||
width: 20px; height: 20px; border-radius: 50%;
|
||||
background: var(--primary); color: #fff;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 10px; font-weight: 700; flex-shrink: 0;
|
||||
}
|
||||
.member-card-body { padding: 10px 12px; }
|
||||
|
||||
/* ===================================================================
|
||||
DETAIL MODAL
|
||||
=================================================================== */
|
||||
.detail-section { margin-bottom: 16px; }
|
||||
.detail-section-title {
|
||||
font-size: 11px; font-weight: 700; text-transform: uppercase;
|
||||
letter-spacing: .8px; color: var(--text-muted); margin-bottom: 8px;
|
||||
padding-bottom: 5px; border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||
.detail-kv { background: var(--bg); border-radius: 7px; padding: 8px 10px; }
|
||||
.detail-key { font-size: 10px; color: var(--text-dim); font-weight: 600; text-transform: uppercase; letter-spacing: .4px; }
|
||||
.detail-val { font-size: 13px; font-weight: 600; color: var(--text); margin-top: 2px; }
|
||||
.member-table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
||||
.member-table th { padding: 6px 10px; background: var(--bg); font-weight: 700; text-align: left; border-bottom: 1px solid var(--border); color: var(--text-muted); font-size: 10px; text-transform: uppercase; }
|
||||
.member-table td { padding: 7px 10px; border-bottom: 1px solid var(--border); color: var(--text); }
|
||||
.member-table tr:last-child td { border-bottom: none; }
|
||||
.member-table tr:hover td { background: var(--bg); }
|
||||
|
||||
.aid-btn-row { display: flex; gap: 6px; margin-top: 10px; }
|
||||
.aid-btn {
|
||||
flex: 1; padding: 8px; border-radius: 8px; border: 1px solid var(--border);
|
||||
background: var(--bg); cursor: pointer; font-family: var(--font);
|
||||
font-size: 11px; font-weight: 700; transition: all .2s; color: var(--text-muted);
|
||||
}
|
||||
.aid-btn:hover { border-color: var(--border-hover); }
|
||||
.aid-btn.active-helped { background: #fef9c3; border-color: #fcd34d; color: #854d0e; }
|
||||
.aid-btn.active-not_helped { background: #fef2f2; border-color: #fca5a5; color: var(--danger); }
|
||||
.aid-btn.active-outside { background: var(--green-light); border-color: #86efac; color: var(--green); }
|
||||
|
||||
/* ===================================================================
|
||||
ROLE CARDS
|
||||
=================================================================== */
|
||||
.role-cards { display: flex; flex-direction: column; gap: 8px; }
|
||||
.role-card {
|
||||
padding: 12px 14px; border: 2px solid var(--border); border-radius: 10px;
|
||||
cursor: pointer; transition: all .2s; background: var(--surface);
|
||||
}
|
||||
.role-card:hover { border-color: var(--border-hover); }
|
||||
.role-card.active-role { border-color: var(--primary); background: var(--primary-light); }
|
||||
.role-card-icon { font-size: 20px; margin-bottom: 4px; }
|
||||
.role-card-name { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 3px; }
|
||||
.role-card-desc { font-size: 11px; color: var(--text-muted); margin-bottom: 8px; }
|
||||
.role-card-perms { display: flex; flex-wrap: wrap; gap: 5px; }
|
||||
.perm { padding: 2px 8px; border-radius: 5px; font-size: 10px; font-weight: 600; }
|
||||
.perm-green { background: var(--green-light); color: var(--green); }
|
||||
.perm-red { background: #fef2f2; color: var(--danger); }
|
||||
|
||||
/* ===================================================================
|
||||
REPORT PAGE
|
||||
=================================================================== */
|
||||
#pagePelaporan.active-page { overflow: hidden; }
|
||||
.report-page {
|
||||
display: flex; width: 100%; height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.report-col {
|
||||
display: flex; flex-direction: column;
|
||||
height: 100%; overflow: hidden;
|
||||
}
|
||||
.report-col-form { width: 380px; min-width: 320px; border-right: 1px solid var(--border); background: var(--surface); }
|
||||
.report-col-list { flex: 1; background: var(--bg); }
|
||||
.report-col-viewer { flex: 1; background: var(--bg); }
|
||||
.report-col-header {
|
||||
padding: 16px 20px 14px; border-bottom: 1px solid var(--border);
|
||||
background: var(--surface); flex-shrink: 0;
|
||||
}
|
||||
.report-col-title { font-size: 15px; font-weight: 700; color: var(--text); display: flex; align-items: center; }
|
||||
.report-col-sub { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
|
||||
.report-form-body { padding: 16px 20px; overflow-y: auto; flex: 1; }
|
||||
.report-list-toolbar {
|
||||
display: flex; gap: 8px; align-items: center;
|
||||
padding: 12px 16px; border-bottom: 1px solid var(--border);
|
||||
background: var(--surface); flex-shrink: 0;
|
||||
}
|
||||
.report-list-container { flex: 1; overflow-y: auto; padding: 12px 16px; }
|
||||
|
||||
.upload-area {
|
||||
border: 2px dashed var(--border); border-radius: 10px;
|
||||
padding: 20px; text-align: center; cursor: pointer;
|
||||
transition: all .2s; background: var(--bg);
|
||||
}
|
||||
.upload-area:hover { border-color: var(--primary); background: var(--primary-light); }
|
||||
.upload-icon { font-size: 28px; margin-bottom: 6px; }
|
||||
.upload-text { font-size: 13px; font-weight: 600; color: var(--text); }
|
||||
.upload-sub { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
|
||||
.remove-img-btn {
|
||||
margin-top: 6px; padding: 4px 10px; border: 1px solid #fca5a5; border-radius: 6px;
|
||||
background: var(--danger-light); color: var(--danger); cursor: pointer;
|
||||
font-size: 11px; font-weight: 600;
|
||||
}
|
||||
|
||||
/* Report card */
|
||||
.report-card {
|
||||
background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
|
||||
margin-bottom: 10px; overflow: hidden; transition: all .2s;
|
||||
}
|
||||
.report-card:hover { border-color: var(--border-hover); box-shadow: var(--shadow); }
|
||||
.report-card-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px 8px; }
|
||||
.report-card-meta { font-size: 10px; color: var(--text-muted); }
|
||||
.report-card-name { font-weight: 700; color: var(--text); }
|
||||
.report-status {
|
||||
padding: 2px 8px; border-radius: 10px; font-size: 10px; font-weight: 700;
|
||||
}
|
||||
.report-status.baru { background: #dbeafe; color: #1d4ed8; }
|
||||
.report-status.ditangani { background: #fef9c3; color: #854d0e; }
|
||||
.report-status.selesai { background: var(--green-light); color: var(--green); }
|
||||
.report-card-text { padding: 0 14px 8px; font-size: 12px; color: var(--text); line-height: 1.6; }
|
||||
.report-card-footer {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 8px 14px; border-top: 1px solid var(--border); background: var(--bg);
|
||||
}
|
||||
.report-card-time { font-size: 10px; color: var(--text-dim); }
|
||||
.report-card-actions { display: flex; gap: 5px; }
|
||||
.report-action-btn {
|
||||
padding: 3px 8px; border: 1px solid var(--border); border-radius: 6px;
|
||||
background: var(--surface); cursor: pointer; font-size: 10px; font-weight: 600;
|
||||
color: var(--text-muted); font-family: var(--font); transition: all .2s;
|
||||
}
|
||||
.report-action-btn:hover { border-color: var(--primary); color: var(--primary); }
|
||||
.report-action-btn.danger { border-color: #fca5a5; color: var(--danger); }
|
||||
.report-action-btn.danger:hover { background: var(--danger-light); }
|
||||
|
||||
/* ===================================================================
|
||||
DATE PICKER
|
||||
=================================================================== */
|
||||
.datepicker-overlay {
|
||||
position: fixed; inset: 0; z-index: 3000;
|
||||
background: rgba(15,23,42,.3);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.datepicker-overlay.hidden { display: none !important; }
|
||||
.datepicker-box {
|
||||
background: var(--surface); border-radius: 14px;
|
||||
box-shadow: var(--shadow-md); padding: 16px;
|
||||
width: 280px; user-select: none;
|
||||
}
|
||||
.dp-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.dp-nav { background: none; border: 1px solid var(--border); border-radius: 7px; width: 28px; height: 28px; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; transition: all .2s; }
|
||||
.dp-nav:hover { background: var(--bg); }
|
||||
.dp-title { font-size: 13px; font-weight: 700; color: var(--text); }
|
||||
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 8px; }
|
||||
.dp-day-label { font-size: 9px; font-weight: 700; text-align: center; color: var(--text-muted); padding: 4px 0; text-transform: uppercase; }
|
||||
.dp-day {
|
||||
aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
|
||||
border-radius: 6px; font-size: 11px; cursor: pointer; transition: all .15s; color: var(--text);
|
||||
}
|
||||
.dp-day:hover { background: var(--primary-light); color: var(--primary-dark); }
|
||||
.dp-day.selected { background: var(--primary); color: #fff; font-weight: 700; }
|
||||
.dp-day.today { font-weight: 700; color: var(--primary); }
|
||||
.dp-day.other-month { color: var(--text-dim); }
|
||||
.dp-day.disabled { opacity: .35; pointer-events: none; }
|
||||
.dp-footer { display: flex; justify-content: flex-end; gap: 6px; }
|
||||
.dp-btn {
|
||||
padding: 6px 14px; border-radius: 7px; border: 1px solid var(--border);
|
||||
cursor: pointer; font-size: 12px; font-weight: 600; font-family: var(--font);
|
||||
background: var(--bg); color: var(--text-muted); transition: all .2s;
|
||||
}
|
||||
.dp-btn.primary { background: var(--primary); border-color: var(--primary); color: #fff; }
|
||||
.dp-btn.primary:hover { background: var(--primary-dark); }
|
||||
.date-input-wrap { position: relative; }
|
||||
.date-display-btn {
|
||||
width: 100%; padding: 8px 10px; border: 1.5px solid var(--border); border-radius: 8px;
|
||||
text-align: left; background: var(--bg); cursor: pointer;
|
||||
font-family: var(--font); font-size: 13px; color: var(--text);
|
||||
transition: border-color .2s;
|
||||
}
|
||||
.date-display-btn:hover, .date-display-btn:focus { border-color: var(--primary); }
|
||||
|
||||
/* ===================================================================
|
||||
SALARY TOGGLE
|
||||
=================================================================== */
|
||||
.salary-row { display: none; }
|
||||
.salary-row.visible { display: flex; }
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Hanya menerima POST']); exit;
|
||||
}
|
||||
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$status = $_POST['status'] ?? 'baru';
|
||||
|
||||
$valid = ['baru', 'ditangani', 'selesai'];
|
||||
if (!in_array($status, $valid) || $id < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak valid']); exit;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("UPDATE laporan SET status=? WHERE id=?");
|
||||
$stmt->bind_param('si', $status, $id);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'id' => $id, 'status' => $status]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Hanya menerima POST']); exit;
|
||||
}
|
||||
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$radius = intval($_POST['radius'] ?? 300);
|
||||
|
||||
if ($id < 1) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); exit; }
|
||||
if ($radius < 50) $radius = 50;
|
||||
|
||||
$stmt = $conn->prepare("UPDATE religious_centers SET radius=?, updated_at=NOW() WHERE id=?");
|
||||
$stmt->bind_param('ii', $radius, $id);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'id' => $id, 'radius' => $radius]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
require_once 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Hanya menerima POST']); exit;
|
||||
}
|
||||
|
||||
$houseId = intval($_POST['house_id'] ?? 0);
|
||||
$aidStatus = $_POST['aid_status'] ?? 'outside';
|
||||
$centerId = intval($_POST['center_id'] ?? 0);
|
||||
|
||||
$validAid = ['helped', 'not_helped', 'outside'];
|
||||
if (!in_array($aidStatus, $validAid)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Status tidak valid']); exit;
|
||||
}
|
||||
if ($houseId < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'house_id tidak valid']); exit;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("UPDATE houses SET aid_status=?, updated_at=NOW() WHERE id=?");
|
||||
$stmt->bind_param('si', $aidStatus, $houseId);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$stmt->close();
|
||||
if ($aidStatus === 'helped' && $centerId > 0) {
|
||||
$logStatus = 'helped';
|
||||
$ls = $conn->prepare("INSERT INTO aid_logs (house_id, religious_center_id, status) VALUES (?, ?, ?)");
|
||||
$ls->bind_param('iis', $houseId, $centerId, $logStatus);
|
||||
$ls->execute(); $ls->close();
|
||||
} elseif ($aidStatus !== 'helped') {
|
||||
$logStatus = 'reverted';
|
||||
$ls = $conn->prepare("INSERT INTO aid_logs (house_id, religious_center_id, status) VALUES (?, ?, ?)");
|
||||
$ls->bind_param('iis', $houseId, $centerId, $logStatus);
|
||||
$ls->execute(); $ls->close();
|
||||
}
|
||||
echo json_encode(['success' => true, 'house_id' => $houseId, 'aid_status' => $aidStatus]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $stmt->error]);
|
||||
$stmt->close();
|
||||
}
|
||||
$conn->close();
|
||||
Reference in New Issue
Block a user