From ce26f9fac67ed395f8516e54024fef41ae618144 Mon Sep 17 00:00:00 2001 From: envicy Date: Thu, 11 Jun 2026 22:29:16 +0700 Subject: [PATCH] Update --- webgis-poverty-pontianak.zip | Bin 68238 -> 61891 bytes webgis-poverty-pontianak/README.md | 84 - .../api/dashboard/stats.php | 196 -- .../api/dashboard/stats_extended.php | 171 -- .../api/penduduk_miskin/create.php | 52 +- .../api/penduduk_miskin/update.php | 2 +- webgis-poverty-pontianak/assets/css/style.css | 2503 ++++++++++------- .../assets/js/features/auth.js | 4 +- .../assets/js/features/geolocation.js | 3 +- .../assets/js/features/kemiskinan.js | 16 +- webgis-poverty-pontianak/assets/js/map.js | 180 +- webgis-poverty-pontianak/assets/js/panel.js | 251 +- webgis-poverty-pontianak/index.php | 482 ++-- webgis-poverty-pontianak/uploads/.htaccess | 4 + .../uploads/6a2ad0dd9e8b9_Profil.jpeg | Bin 0 -> 30944 bytes 15 files changed, 1933 insertions(+), 2015 deletions(-) delete mode 100644 webgis-poverty-pontianak/README.md delete mode 100644 webgis-poverty-pontianak/api/dashboard/stats.php delete mode 100644 webgis-poverty-pontianak/api/dashboard/stats_extended.php create mode 100644 webgis-poverty-pontianak/uploads/.htaccess create mode 100644 webgis-poverty-pontianak/uploads/6a2ad0dd9e8b9_Profil.jpeg diff --git a/webgis-poverty-pontianak.zip b/webgis-poverty-pontianak.zip index 83ce69f2dae7942d41a95733a803527573ba2581..805b99304a76bbf994ce8bc158e7a13973ed10e2 100644 GIT binary patch delta 26141 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 delta 31860 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 diff --git a/webgis-poverty-pontianak/README.md b/webgis-poverty-pontianak/README.md deleted file mode 100644 index 5ba2575..0000000 --- a/webgis-poverty-pontianak/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# WebGIS Pemetaan Kemiskinan Kota Pontianak - -Aplikasi WebGIS sederhana berbasis Leaflet.js untuk memetakan data kemiskinan, sebaran penduduk miskin, log bantuan, dan rumah ibadah di Kota Pontianak. - -> [!WARNING] -> **DISCLAIMER β€” DATA DUMMY / BUKAN DATA NYATA** -> -> Seluruh data yang ditampilkan dalam aplikasi ini, termasuk namun tidak terbatas pada: -> - Data nama, lokasi, dan kategori **penduduk miskin** -> - **Foto rumah** dan **Foto Kartu Keluarga (KK)** yang diunggah -> - Data **rumah ibadah** beserta radius jangkauannya -> - **Log bantuan** yang tercatat di sistem -> -> adalah **data fiktif / dummy** yang dibuat semata-mata untuk keperluan **demonstrasi, pengembangan, dan pengujian sistem** ini. -> Data tersebut **tidak mewakili kondisi, identitas, atau situasi nyata** dari individu maupun lokasi mana pun di Kota Pontianak. - -## Fitur Utama - -- **Pemetaan Interaktif**: Visualisasi peta dengan Leaflet.js yang berpusat di Kota Pontianak. -- **Manajemen Data Spasial**: - - **Rumah Ibadah**: Lokasi titik rumah ibadah (Masjid, Gereja, Vihara, dll) beserta jangkauan radius jemaah/bantuan. - - **Penduduk Miskin**: Pemetaan warga kurang mampu dan integrasi log/riwayat bantuan sosial yang disalurkan melalui rumah ibadah terdekat. -- **Pencarian Cepat**: Cari data rumah ibadah langsung dari bilah pencarian. -- **Impor GeoJSON Eksternal**: Pengguna dapat menambahkan file GeoJSON dari perangkat lokal ke dalam peta. - -## Persyaratan Sistem - -- **Server Web**: Apache (misal menggunakan XAMPP atau Laragon). -- **Bahasa Pemrograman**: PHP 7.4 ke atas. -- **Database**: MySQL versi 5.7+ atau MariaDB versi 10.2+ (Wajib mendukung fitur fungsi spasial seperti `ST_GeomFromGeoJSON` dan `ST_AsGeoJSON`). - -## Cara Instalasi & Penggunaan - -1. **Unduh/Clone Repositori**: - Tempatkan folder `webgis-poverty-pontianak` di dalam direktori root server web Anda (misalnya `C:/xampp/htdocs/webgis/webgis-poverty-pontianak`). - -2. **Setup Database**: - - Buka phpMyAdmin atau MySQL client pilihan Anda. - - Buat database baru bernama `webgis`: - ```sql - CREATE DATABASE webgis; - ``` - - Import file `database.sql` ke dalam database `webgis` tersebut. - -3. **Konfigurasi Koneksi**: - - Buka file [api/db_connect.php](api/db_connect.php). - - Sesuaikan konfigurasi host, user, password, dan database sesuai dengan setup database MySQL lokal Anda: - ```php - $host = "localhost"; - $user = "root"; - $pass = ""; - $db = "webgis"; - ``` - -4. **Jalankan Aplikasi**: - - Aktifkan modul **Apache** dan **MySQL** di Control Panel XAMPP Anda. - - Buka browser dan navigasikan ke alamat: - [http://localhost/webgis/webgis-poverty-pontianak/index.php](http://localhost/webgis/webgis-poverty-pontianak/index.php) - -## Akun Akses Default (Login) - -Anda dapat masuk ke dalam sistem menggunakan akun simulasi berikut: - -- **Akun Administrator (Admin)**: - - **Username**: `admin` - - **Password**: `password` - - **Hak Akses**: Mengelola data spasial rumah ibadah, penduduk miskin, mengelola akun pengelola rumah ibadah, dan manajemen pengguna secara penuh. - -- **Akun Pengelola Rumah Ibadah**: - - **Username**: `pengelola_mujahidin` - - **Password**: `password` - - **Hak Akses**: Mengelola (tambah, edit, hapus, impor, geser lokasi) data penduduk miskin, serta menginput log bantuan sosial yang disalurkan melalui masjid kelolaannya (Masjid Raya Mujahidin). - -## Struktur Folder - -- `api/` β€” Kumpulan REST API endpoint untuk operasi CRUD masing-masing fitur. - - `rumah_ibadah/`, `penduduk_miskin/`, `log_bantuan/` -- `assets/` β€” File aset frontend. - - `css/` β€” Gaya UI (custom style.css). - - `js/` β€” Logika frontend peta dan pengelolaan interaksi panel data. - - `features/` β€” Modul fitur Leaflet (manajemen layer, geolokasi, dll). -- `index.php` β€” Halaman utama interface WebGIS. -- `database.sql` β€” Skema database beserta **data dummy** Pontianak untuk pengetesan awal. Seluruh data di dalamnya bersifat fiktif dan tidak merepresentasikan data nyata. -- `uploads/` β€” Folder penyimpanan foto yang diunggah. File yang sudah ada merupakan contoh dummy semata. diff --git a/webgis-poverty-pontianak/api/dashboard/stats.php b/webgis-poverty-pontianak/api/dashboard/stats.php deleted file mode 100644 index f058620..0000000 --- a/webgis-poverty-pontianak/api/dashboard/stats.php +++ /dev/null @@ -1,196 +0,0 @@ -query("SELECT * FROM rumah_ibadah WHERE id=$ibadahId"); -} else { - $riRes = $conn->query("SELECT * FROM rumah_ibadah"); -} -$ibadahList = []; -while ($row = $riRes->fetch_assoc()) $ibadahList[] = $row; -$totalIbadah = count($ibadahList); - -// Distribusi per jenis -$jenisDist = []; -foreach ($ibadahList as $ib) { - $j = $ib['jenis'] ?? 'Lainnya'; - $jenisDist[$j] = ($jenisDist[$j] ?? 0) + 1; -} - -// ── 2. Penduduk Miskin ──────────────────────────────────────────────────── -$pmRes = $conn->query("SELECT * FROM penduduk_miskin"); -$allMiskin = []; -while ($row = $pmRes->fetch_assoc()) $allMiskin[] = $row; - -// Filter: pengelola hanya lihat yang dalam radius ibadahnya -if ($isPengelola && $ibadahId && !empty($ibadahList)) { - $ib = $ibadahList[0]; - $filteredMiskin = array_filter($allMiskin, function($m) use ($ib) { - return haversine((float)$ib['lat'], (float)$ib['lng'], (float)$m['lat'], (float)$m['lng']) <= (float)$ib['radius']; - }); - $filteredMiskin = array_values($filteredMiskin); -} else { - $filteredMiskin = $allMiskin; -} - -$totalMiskin = count($filteredMiskin); -$totalJiwa = array_sum(array_column($filteredMiskin, 'jumlah_jiwa')); - -// Kategori bantuan -$kategoriDist = []; -foreach ($filteredMiskin as $m) { - $k = $m['kategori_bantuan'] ?? 'Lainnya'; - $kategoriDist[$k] = ($kategoriDist[$k] ?? 0) + 1; -} - -// Terurus vs Tidak (dalam radius salah satu ibadah) -$terurusCount = 0; -$tidakTerurusCount = 0; -foreach ($filteredMiskin as $m) { - $inAnyRadius = false; - foreach ($ibadahList as $ib) { - if (haversine((float)$ib['lat'], (float)$ib['lng'], (float)$m['lat'], (float)$m['lng']) <= (float)$ib['radius']) { - $inAnyRadius = true; - break; - } - } - if ($inAnyRadius) $terurusCount++; - else $tidakTerurusCount++; -} - -// Jika admin: hitung seluruh miskin dalam radius seluruh ibadah -if (!$isPengelola) { - $allIbadahRes = $conn->query("SELECT * FROM rumah_ibadah"); - $allIbadahList = []; - while ($row = $allIbadahRes->fetch_assoc()) $allIbadahList[] = $row; - - $terurusCount = 0; - $tidakTerurusCount = 0; - foreach ($allMiskin as $m) { - $inAnyRadius = false; - foreach ($allIbadahList as $ib) { - if (haversine((float)$ib['lat'], (float)$ib['lng'], (float)$m['lat'], (float)$m['lng']) <= (float)$ib['radius']) { - $inAnyRadius = true; - break; - } - } - if ($inAnyRadius) $terurusCount++; - else $tidakTerurusCount++; - } -} - -// ── 3. Log Bantuan ──────────────────────────────────────────────────────── -if ($isPengelola && $ibadahId) { - $logRes = $conn->query(" - SELECT lb.*, ri.nama as nama_ibadah, ri.jenis as jenis_ibadah, pm.nama as nama_kk - FROM log_bantuan lb - JOIN rumah_ibadah ri ON ri.id = lb.ibadah_id - JOIN penduduk_miskin pm ON pm.id = lb.miskin_id - WHERE lb.ibadah_id = $ibadahId - ORDER BY lb.tanggal DESC, lb.id DESC - LIMIT 10 - "); - $totalLogRes = $conn->query("SELECT COUNT(*) as cnt FROM log_bantuan WHERE ibadah_id=$ibadahId"); -} else { - $logRes = $conn->query(" - SELECT lb.*, ri.nama as nama_ibadah, ri.jenis as jenis_ibadah, pm.nama as nama_kk - FROM log_bantuan lb - JOIN rumah_ibadah ri ON ri.id = lb.ibadah_id - JOIN penduduk_miskin pm ON pm.id = lb.miskin_id - ORDER BY lb.tanggal DESC, lb.id DESC - LIMIT 10 - "); - $totalLogRes = $conn->query("SELECT COUNT(*) as cnt FROM log_bantuan"); -} - -$recentLogs = []; -while ($row = $logRes->fetch_assoc()) $recentLogs[] = $row; - -$totalLog = 0; -if ($totalLogRes && $trow = $totalLogRes->fetch_assoc()) $totalLog = (int)$trow['cnt']; - -// Distribusi tipe bantuan dari log -$tipeDist = []; -if ($isPengelola && $ibadahId) { - $tipeRes = $conn->query("SELECT tipe_bantuan, COUNT(*) as cnt FROM log_bantuan WHERE ibadah_id=$ibadahId GROUP BY tipe_bantuan"); -} else { - $tipeRes = $conn->query("SELECT tipe_bantuan, COUNT(*) as cnt FROM log_bantuan GROUP BY tipe_bantuan"); -} -while ($row = $tipeRes->fetch_assoc()) $tipeDist[$row['tipe_bantuan']] = (int)$row['cnt']; - -// Bantuan per bulan (6 bulan terakhir) -if ($isPengelola && $ibadahId) { - $bulanRes = $conn->query(" - SELECT DATE_FORMAT(tanggal, '%Y-%m') as bulan, COUNT(*) as cnt - FROM log_bantuan - WHERE ibadah_id=$ibadahId AND tanggal >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) - GROUP BY bulan ORDER BY bulan ASC - "); -} else { - $bulanRes = $conn->query(" - SELECT DATE_FORMAT(tanggal, '%Y-%m') as bulan, COUNT(*) as cnt - FROM log_bantuan - WHERE tanggal >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) - GROUP BY bulan ORDER BY bulan ASC - "); -} -$bantuanPerBulan = []; -while ($row = $bulanRes->fetch_assoc()) $bantuanPerBulan[] = $row; - -// ── 4. Ibadah Tipe (admin only: performa per ibadah) ───────────────────── -$ibadahPerforma = []; -if (!$isPengelola) { - $perfRes = $conn->query(" - SELECT ri.id, ri.nama, ri.jenis, ri.radius, - COUNT(DISTINCT lb.id) as total_log, - COUNT(DISTINCT lb.miskin_id) as total_penerima - FROM rumah_ibadah ri - LEFT JOIN log_bantuan lb ON lb.ibadah_id = ri.id - GROUP BY ri.id - ORDER BY total_log DESC - "); - while ($row = $perfRes->fetch_assoc()) $ibadahPerforma[] = $row; -} - -// ── Output ──────────────────────────────────────────────────────────────── -echo json_encode([ - 'status' => 'success', - 'role' => $user['role'], - 'ibadah_id' => $ibadahId, - 'stats' => [ - 'total_ibadah' => $totalIbadah, - 'total_miskin' => $totalMiskin, - 'total_jiwa' => $totalJiwa, - 'total_log' => $totalLog, - 'terurus' => $terurusCount, - 'tidak_terurus' => $tidakTerurusCount, - 'pct_terurus' => $totalMiskin > 0 ? round($terurusCount / $totalMiskin * 100, 1) : 0, - ], - 'distribusi' => [ - 'jenis_ibadah' => $jenisDist, - 'kategori' => $kategoriDist, - 'tipe_bantuan' => $tipeDist, - 'per_bulan' => $bantuanPerBulan, - ], - 'ibadah_list' => $ibadahList, - 'ibadah_performa' => $ibadahPerforma, - 'recent_logs' => $recentLogs, -]); diff --git a/webgis-poverty-pontianak/api/dashboard/stats_extended.php b/webgis-poverty-pontianak/api/dashboard/stats_extended.php deleted file mode 100644 index 5ce391c..0000000 --- a/webgis-poverty-pontianak/api/dashboard/stats_extended.php +++ /dev/null @@ -1,171 +0,0 @@ -connect_error) return null; - $c->set_charset("utf8mb4"); - return $c; -} - -// ── Koneksi ke masing-masing database ──────────────────────────────────── -$connPoverty = makeConn("webgis_poverty"); // atau "webgis" sesuai db.sql -$connJalan = makeConn("jalan"); -$connSpbu = makeConn("spbu"); - -// ── 1. Data dari webgis_poverty (kemiskinan) ───────────────────────────── -$poverty = [ - 'total_miskin' => 0, - 'total_jiwa' => 0, - 'total_ibadah' => 0, - 'total_log' => 0, - 'terurus' => 0, - 'tidak_terurus' => 0, - 'pct_terurus' => 0, - 'distribusi' => ['kategori' => [], 'tipe_bantuan' => [], 'per_bulan' => []], -]; - -if ($connPoverty) { - // Total KK miskin & jiwa - $r = $connPoverty->query("SELECT COUNT(*) as c, COALESCE(SUM(jumlah_jiwa),0) as j FROM penduduk_miskin"); - if ($r && $row = $r->fetch_assoc()) { - $poverty['total_miskin'] = (int)$row['c']; - $poverty['total_jiwa'] = (int)$row['j']; - } - - // Total rumah ibadah - $r = $connPoverty->query("SELECT COUNT(*) as c FROM rumah_ibadah"); - if ($r && $row = $r->fetch_assoc()) $poverty['total_ibadah'] = (int)$row['c']; - - // Total log bantuan - $r = $connPoverty->query("SELECT COUNT(*) as c FROM log_bantuan"); - if ($r && $row = $r->fetch_assoc()) $poverty['total_log'] = (int)$row['c']; - - // Kategori bantuan - $r = $connPoverty->query("SELECT kategori_bantuan, COUNT(*) as c FROM penduduk_miskin GROUP BY kategori_bantuan"); - if ($r) while ($row = $r->fetch_assoc()) $poverty['distribusi']['kategori'][$row['kategori_bantuan']] = (int)$row['c']; - - // Tipe bantuan dari log - $r = $connPoverty->query("SELECT tipe_bantuan, COUNT(*) as c FROM log_bantuan GROUP BY tipe_bantuan"); - if ($r) while ($row = $r->fetch_assoc()) $poverty['distribusi']['tipe_bantuan'][$row['tipe_bantuan']] = (int)$row['c']; - - // Bantuan per bulan (6 bulan terakhir) - $r = $connPoverty->query(" - SELECT DATE_FORMAT(tanggal,'%Y-%m') as bulan, COUNT(*) as cnt - FROM log_bantuan - WHERE tanggal >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) - GROUP BY bulan ORDER BY bulan ASC - "); - if ($r) while ($row = $r->fetch_assoc()) $poverty['distribusi']['per_bulan'][] = $row; - - // Terurus vs tidak (radius sederhana: ada di radius salah satu ibadah) - $ibadahRes = $connPoverty->query("SELECT lat, lng, radius FROM rumah_ibadah"); - $ibadahArr = []; - if ($ibadahRes) while ($row = $ibadahRes->fetch_assoc()) $ibadahArr[] = $row; - - $miskinRes = $connPoverty->query("SELECT lat, lng FROM penduduk_miskin"); - if ($miskinRes) { - while ($m = $miskinRes->fetch_assoc()) { - $inRadius = false; - foreach ($ibadahArr as $ib) { - $dlat = deg2rad($ib['lat'] - $m['lat']); - $dlng = deg2rad($ib['lng'] - $m['lng']); - $a = sin($dlat/2)**2 + cos(deg2rad($m['lat']))*cos(deg2rad($ib['lat']))*sin($dlng/2)**2; - $dist = 6371000 * 2 * atan2(sqrt($a), sqrt(1-$a)); - if ($dist <= (float)$ib['radius']) { $inRadius = true; break; } - } - if ($inRadius) $poverty['terurus']++; else $poverty['tidak_terurus']++; - } - $tot = $poverty['total_miskin']; - $poverty['pct_terurus'] = $tot > 0 ? round($poverty['terurus'] / $tot * 100, 1) : 0; - } - $connPoverty->close(); -} - -// ── 2. Data Jalan ───────────────────────────────────────────────────────── -$jalan = [ - 'total_jalan' => 0, - 'total_panjang_m' => 0, - 'per_status' => [], - 'total_parsil' => 0, - 'total_luas_m2' => 0, - 'per_kepemilikan' => [], -]; - -if ($connJalan) { - $r = $connJalan->query("SELECT COUNT(*) as c, COALESCE(SUM(panjang_meter),0) as p FROM jalan"); - if ($r && $row = $r->fetch_assoc()) { - $jalan['total_jalan'] = (int)$row['c']; - $jalan['total_panjang_m'] = (float)$row['p']; - } - - $r = $connJalan->query("SELECT status_jalan, COUNT(*) as c FROM jalan GROUP BY status_jalan"); - if ($r) while ($row = $r->fetch_assoc()) $jalan['per_status'][$row['status_jalan']] = (int)$row['c']; - - $r = $connJalan->query("SELECT COUNT(*) as c, COALESCE(SUM(luas_m2),0) as l FROM parsil_tanah"); - if ($r && $row = $r->fetch_assoc()) { - $jalan['total_parsil'] = (int)$row['c']; - $jalan['total_luas_m2'] = (float)$row['l']; - } - - $r = $connJalan->query("SELECT status_kepemilikan, COUNT(*) as c FROM parsil_tanah GROUP BY status_kepemilikan"); - if ($r) while ($row = $r->fetch_assoc()) $jalan['per_kepemilikan'][$row['status_kepemilikan']] = (int)$row['c']; - - $connJalan->close(); -} - -// ── 3. Data SPBU ────────────────────────────────────────────────────────── -$spbu = [ - 'total_spbu' => 0, - 'buka_24_jam' => 0, - 'tidak_24_jam' => 0, - 'list' => [], -]; - -if ($connSpbu) { - $r = $connSpbu->query("SELECT COUNT(*) as c FROM spbu"); - if ($r && $row = $r->fetch_assoc()) $spbu['total_spbu'] = (int)$row['c']; - - $r = $connSpbu->query("SELECT SUM(buka_24_jam=1) as buka, SUM(buka_24_jam=0) as tidak FROM spbu"); - if ($r && $row = $r->fetch_assoc()) { - $spbu['buka_24_jam'] = (int)$row['buka']; - $spbu['tidak_24_jam'] = (int)$row['tidak']; - } - - $r = $connSpbu->query("SELECT nama_spbu, nomor_wa, buka_24_jam, latitude, longitude FROM spbu ORDER BY nama_spbu"); - if ($r) while ($row = $r->fetch_assoc()) $spbu['list'][] = $row; - - $connSpbu->close(); -} - -// ── 4. Data Penduduk (choropleth β€” static dari pontianak.json) ──────────── -// Data kepadatan per kecamatan sudah ada di file JS, ringkasan di sini -$penduduk = [ - 'kecamatan' => [ - ['nama' => 'Pontianak Barat', 'density' => 9.331], - ['nama' => 'Pontianak Kota', 'density' => 8.102], - ['nama' => 'Pontianak Timur', 'density' => 9.196], - ['nama' => 'Pontianak Selatan', 'density' => 5.673], - ['nama' => 'Pontianak Utara', 'density' => 3.642], - ['nama' => 'Pontianak Tenggara', 'density' => 3.103], - ] -]; -$penduduk['total_kecamatan'] = count($penduduk['kecamatan']); -$penduduk['rata_density'] = round(array_sum(array_column($penduduk['kecamatan'],'density')) / $penduduk['total_kecamatan'], 3); -$penduduk['terpadat'] = $penduduk['kecamatan'][array_search(max(array_column($penduduk['kecamatan'],'density')), array_column($penduduk['kecamatan'],'density'))]['nama']; - -// ── Output ──────────────────────────────────────────────────────────────── -echo json_encode([ - 'status' => 'success', - 'poverty' => $poverty, - 'jalan' => $jalan, - 'spbu' => $spbu, - 'penduduk' => $penduduk, -], JSON_UNESCAPED_UNICODE); diff --git a/webgis-poverty-pontianak/api/penduduk_miskin/create.php b/webgis-poverty-pontianak/api/penduduk_miskin/create.php index 85037bd..c2a4978 100644 --- a/webgis-poverty-pontianak/api/penduduk_miskin/create.php +++ b/webgis-poverty-pontianak/api/penduduk_miskin/create.php @@ -59,42 +59,34 @@ if ($currentUser && $currentUser['role'] === 'pengelola') { } } -if ($lat != 0 && $lng != 0) { - $upload_dir = "../../uploads/"; - if (!file_exists($upload_dir)) { - mkdir($upload_dir, 0777, true); - } +// Handle file upload +$upload_dir = dirname(dirname(__DIR__)) . '/uploads/'; +if (!file_exists($upload_dir)) { + mkdir($upload_dir, 0777, true); +} - $foto_rumah = null; - $foto_kk = null; - - function uploadFile($fileKey, $upload_dir) { - if (isset($_FILES[$fileKey]) && $_FILES[$fileKey]['error'] === UPLOAD_ERR_OK) { - $fileTmpPath = $_FILES[$fileKey]['tmp_name']; - $fileName = $_FILES[$fileKey]['name']; - - $fileNameCmps = explode(".", $fileName); - $fileExtension = strtolower(end($fileNameCmps)); - - $allowedfileExtensions = array('jpg', 'jpeg', 'png', 'gif'); - if (in_array($fileExtension, $allowedfileExtensions)) { - $newFileName = uniqid() . '_' . preg_replace('/[^a-zA-Z0-9\._-]/', '_', $fileName); - $dest_path = $upload_dir . $newFileName; - - if (move_uploaded_file($fileTmpPath, $dest_path)) { - return $newFileName; - } +function uploadFileCreate($fileKey, $upload_dir) { + if (isset($_FILES[$fileKey]) && $_FILES[$fileKey]['error'] === UPLOAD_ERR_OK) { + $fileTmpPath = $_FILES[$fileKey]['tmp_name']; + $fileName = $_FILES[$fileKey]['name']; + $fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif']; + if (in_array($fileExtension, $allowedExtensions)) { + $newFileName = uniqid() . '_' . preg_replace('/[^a-zA-Z0-9\\._-]/', '_', $fileName); + if (move_uploaded_file($fileTmpPath, $upload_dir . $newFileName)) { + return $newFileName; } } - return null; } + return null; +} - $foto_rumah = uploadFile('foto_rumah', $upload_dir); - $foto_kk = uploadFile('foto_kk', $upload_dir); - - $val_foto_rumah = $foto_rumah ? "'$foto_rumah'" : "NULL"; - $val_foto_kk = $foto_kk ? "'$foto_kk'" : "NULL"; +$foto_rumah = uploadFileCreate('foto_rumah', $upload_dir); +$foto_kk = uploadFileCreate('foto_kk', $upload_dir); +$val_foto_rumah = $foto_rumah ? "'" . $conn->real_escape_string($foto_rumah) . "'" : "NULL"; +$val_foto_kk = $foto_kk ? "'" . $conn->real_escape_string($foto_kk) . "'" : "NULL"; +if ($lat != 0 && $lng != 0) { $query = "INSERT INTO penduduk_miskin (nama, kategori_bantuan, jumlah_jiwa, lat, lng, foto_rumah, foto_kk) VALUES ('$nama', '$kategori', $jumlah_jiwa, $lat, $lng, $val_foto_rumah, $val_foto_kk)"; diff --git a/webgis-poverty-pontianak/api/penduduk_miskin/update.php b/webgis-poverty-pontianak/api/penduduk_miskin/update.php index 2f8539c..85561be 100644 --- a/webgis-poverty-pontianak/api/penduduk_miskin/update.php +++ b/webgis-poverty-pontianak/api/penduduk_miskin/update.php @@ -81,7 +81,7 @@ if ($id > 0) { $old_foto_kk = $row['foto_kk']; } - $upload_dir = "../../uploads/"; + $upload_dir = dirname(dirname(__DIR__)) . '/uploads/'; if (!file_exists($upload_dir)) { mkdir($upload_dir, 0777, true); } diff --git a/webgis-poverty-pontianak/assets/css/style.css b/webgis-poverty-pontianak/assets/css/style.css index d6762c2..c478109 100644 --- a/webgis-poverty-pontianak/assets/css/style.css +++ b/webgis-poverty-pontianak/assets/css/style.css @@ -1,718 +1,827 @@ +/* ============================================================ + WebGIS Kemiskinan Pontianak β€” Redesign + Theme: Dark Sidebar + Clean Map UI + ============================================================ */ + /* Reset & Base */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; +* { margin: 0; padding: 0; box-sizing: border-box; } + +:root { + --primary: #2563eb; + --primary-dark: #1d4ed8; + --primary-light: #dbeafe; + --accent: #0ea5e9; + --danger: #ef4444; + --success: #10b981; + --warning: #f59e0b; + --sidebar-bg: #0f172a; + --sidebar-item: #1e293b; + --sidebar-active: #2563eb; + --sidebar-text: #94a3b8; + --sidebar-text-active: #ffffff; + --surface: #ffffff; + --surface-2: #f8fafc; + --border: #e2e8f0; + --text-primary: #0f172a; + --text-secondary: #64748b; + --shadow-sm: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06); + --shadow-md: 0 4px 12px rgba(0,0,0,0.12); + --shadow-lg: 0 8px 30px rgba(0,0,0,0.16); + --radius: 10px; + --sidebar-w: 220px; } body, html { height: 100%; - font-family: 'Google Sans Flex', 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: 'Inter', 'Segoe UI', system-ui, sans-serif; overflow: hidden; + background: #0f172a; } -/* Map Container */ +/* ============================================================ + MAP + ============================================================ */ #map { - width: 100vw; - height: 100vh; + position: fixed; + top: 0; + left: var(--sidebar-w); + right: 0; + bottom: 0; z-index: 1; + transition: left 0.3s ease; } -/* Custom UI Container over Map */ -.ui-container { - position: absolute; - top: 20px; - left: 30%; - transform: translateX(-70%); - z-index: 1000; - width: 100%; - max-width: 400px; +body.sidebar-collapsed #map { left: 64px; } + +/* ============================================================ + LEFT SIDEBAR (Vertical Nav) + ============================================================ */ +.left-sidebar { + position: fixed; + top: 0; + left: 0; + width: var(--sidebar-w); + height: 100%; + background: var(--sidebar-bg); + z-index: 1100; display: flex; flex-direction: column; - gap: 10px; - padding: 0 15px; + transition: width 0.3s ease; + overflow: hidden; } -/* Search Bar */ -.search-bar { +body.sidebar-collapsed .left-sidebar { width: 64px; } + +/* Brand */ +.sidebar-brand { display: flex; align-items: center; - background-color: white; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - overflow: hidden; - height: 48px; - transition: box-shadow 0.3s; + gap: 12px; + padding: 18px 16px; + border-bottom: 1px solid rgba(255,255,255,0.07); + flex-shrink: 0; } -.search-bar:focus-within { - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); -} - -.search-icon { - padding: 0 15px; - color: #666; +.sidebar-brand-icon { + width: 36px; + height: 36px; + background: var(--primary); + border-radius: 10px; display: flex; align-items: center; justify-content: center; + font-size: 18px; + flex-shrink: 0; +} + +.sidebar-brand-text { flex: 1; overflow: hidden; } +.sidebar-brand-title { + font-size: 13px; + font-weight: 700; + color: #f1f5f9; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.sidebar-brand-sub { + font-size: 10px; + color: var(--sidebar-text); + white-space: nowrap; +} + +/* Nav Items */ +.sidebar-nav { + flex: 1; + padding: 12px 8px; + display: flex; + flex-direction: column; + gap: 2px; + overflow-y: auto; +} + +.sidebar-nav-label { + font-size: 9px; + font-weight: 700; + color: #334155; + text-transform: uppercase; + letter-spacing: 0.8px; + padding: 8px 10px 4px; + white-space: nowrap; + overflow: hidden; +} + +.sidebar-btn { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 10px; + border-radius: 8px; + border: none; + background: transparent; + color: var(--sidebar-text); + cursor: pointer; + text-align: left; + width: 100%; + transition: all 0.15s; + white-space: nowrap; + font-family: inherit; +} + +.sidebar-btn:hover { + background: var(--sidebar-item); + color: #e2e8f0; +} + +.sidebar-btn.active { + background: var(--sidebar-active); + color: white; +} + +.sidebar-btn .btn-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + font-size: 16px; + flex-shrink: 0; + background: rgba(255,255,255,0.05); +} + +.sidebar-btn.active .btn-icon { + background: rgba(255,255,255,0.2); +} + +.btn-label { + font-size: 13px; + font-weight: 500; + flex: 1; +} + +/* Sidebar Footer */ +.sidebar-footer { + padding: 12px 8px; + border-top: 1px solid rgba(255,255,255,0.07); +} + +/* Collapse Toggle */ +.sidebar-collapse-btn { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 10px; + border-radius: 8px; + border: none; + background: transparent; + color: var(--sidebar-text); + cursor: pointer; + width: 100%; + font-family: inherit; + transition: all 0.15s; +} + +.sidebar-collapse-btn:hover { + background: var(--sidebar-item); + color: #e2e8f0; +} + +.collapse-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-size: 14px; +} + +.collapse-label { + font-size: 13px; + font-weight: 500; +} + +/* ============================================================ + TOP BAR (search + controls) + ============================================================ */ +.top-bar { + position: fixed; + top: 14px; + left: calc(var(--sidebar-w) + 14px); + right: 14px; + z-index: 1000; + display: flex; + align-items: center; + gap: 10px; + transition: left 0.3s ease; + pointer-events: none; +} + +body.sidebar-collapsed .top-bar { left: 78px; } + +.top-bar > * { pointer-events: auto; } + +/* Search */ +.search-bar { + flex: 1; + max-width: 380px; + display: flex; + align-items: center; + background: white; + border-radius: var(--radius); + box-shadow: var(--shadow-md); + height: 44px; + overflow: hidden; + border: 1.5px solid transparent; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.search-bar:focus-within { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(37,99,235,0.12), var(--shadow-md); +} + +.search-icon { + padding: 0 12px; + color: #94a3b8; + font-size: 14px; + display: flex; + align-items: center; } .search-input { flex: 1; border: none; outline: none; - font-size: 16px; - padding: 10px 0; - color: #333; + font-size: 14px; + color: var(--text-primary); + font-family: inherit; + background: transparent; } +.search-input::placeholder { color: #94a3b8; } + .search-clear { - padding: 0 15px; - color: #999; + padding: 0 12px; + color: #cbd5e1; cursor: pointer; background: none; border: none; - display: flex; - align-items: center; - justify-content: center; visibility: hidden; + font-size: 14px; } -.search-clear:hover { - color: #333; +.search-clear:hover { color: #64748b; } + +#searchResults { + position: absolute; + top: calc(100% + 6px); + left: 0; + right: 0; + max-width: 380px; + background: white; + border-radius: var(--radius); + box-shadow: var(--shadow-lg); + max-height: 220px; + overflow-y: auto; + border: 1px solid var(--border); + z-index: 1001; } -/* Custom Layer Control Button */ -.custom-layer-btn { - background-color: white; - border-radius: 8px; - width: 48px; - height: 48px; +.search-result-item { + padding: 10px 14px; + cursor: pointer; + font-size: 13px; + color: var(--text-primary); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + gap: 8px; + transition: background 0.1s; +} + +.search-result-item:last-child { border-bottom: none; } +.search-result-item:hover { background: var(--primary-light); } + +/* Top Bar Buttons */ +.top-btn { + width: 44px; + height: 44px; + background: white; + border: none; + border-radius: var(--radius); + box-shadow: var(--shadow-md); display: flex; align-items: center; justify-content: center; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); cursor: pointer; - border: none; - color: #333; -} - -.custom-layer-btn:hover { - background-color: #f8f9fa; -} - -#layerBtn { - position: absolute; - top: 20px; - right: 20px; - z-index: 1000; - transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -body.right-panel-open #layerBtn { - right: 340px; -} - -.custom-layer-panel { - position: absolute; - top: 80px; - right: 20px; - z-index: 1000; - background-color: white; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - padding: 15px; - width: 250px; - display: none; - transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -body.right-panel-open .custom-layer-panel { - right: 340px; -} - - -.custom-layer-panel h3 { - margin-bottom: 10px; + color: var(--text-secondary); font-size: 16px; - border-bottom: 1px solid #eee; - padding-bottom: 5px; + transition: all 0.15s; + flex-shrink: 0; +} + +.top-btn:hover { + background: var(--primary); + color: white; + box-shadow: 0 4px 14px rgba(37,99,235,0.35); +} + +.top-btn.active { + background: var(--primary); + color: white; +} + +/* Auth button in top bar */ +#authWidget { + margin-left: auto; +} + +.btn-login { + background: var(--primary); + color: white; + border: none; + border-radius: var(--radius); + height: 44px; + padding: 0 18px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + box-shadow: var(--shadow-md); + display: flex; + align-items: center; + gap: 8px; + transition: all 0.15s; + font-family: inherit; +} + +.btn-login:hover { + background: var(--primary-dark); + box-shadow: 0 4px 14px rgba(37,99,235,0.4); +} + +.user-profile-pill { + background: white; + border-radius: var(--radius); + height: 44px; + padding: 0 8px 0 14px; + box-shadow: var(--shadow-md); + display: flex; + align-items: center; + gap: 10px; + border: 1px solid var(--border); +} + +.user-profile-info { display: flex; flex-direction: column; } +.user-profile-name { font-size: 12px; font-weight: 700; color: var(--text-primary); } +.user-profile-role { font-size: 9px; font-weight: 600; color: var(--primary); text-transform: uppercase; } + +.btn-profile-logout { + width: 30px; + height: 30px; + border-radius: 8px; + border: none; + background: var(--surface-2); + color: var(--text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; +} + +.btn-profile-logout:hover { background: #fee2e2; color: var(--danger); } + +/* ============================================================ + ZOOM CONTROLS + ============================================================ */ +.zoom-controls { + position: fixed; + bottom: 80px; + right: 14px; + z-index: 1000; + display: flex; + flex-direction: column; + gap: 4px; +} + +.zoom-btn { + width: 38px; + height: 38px; + background: white; + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: var(--shadow-sm); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + color: var(--text-secondary); + transition: all 0.15s; +} + +.zoom-btn:hover { background: var(--primary); color: white; border-color: var(--primary); } + +/* ============================================================ + LAYER PANEL + ============================================================ */ +.custom-layer-panel { + position: fixed; + top: 72px; + right: 14px; + z-index: 1000; + background: white; + border-radius: var(--radius); + box-shadow: var(--shadow-lg); + padding: 16px; + width: 240px; + display: none; + border: 1px solid var(--border); +} + +.custom-layer-panel.show { display: block; } + +.layer-panel-title { + font-size: 13px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 14px; + display: flex; + align-items: center; + gap: 8px; } .layer-section-title { - font-size: 11px; + font-size: 10px; font-weight: 700; - color: #6366f1; + color: var(--primary); text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 8px; - margin-top: 12px; - padding-bottom: 2px; - border-bottom: 1px dashed #e2e8f0; + letter-spacing: 0.6px; + margin: 12px 0 8px; + padding-bottom: 4px; + border-bottom: 1px solid var(--border); } .layer-option { display: flex; align-items: center; - gap: 10px; - margin-bottom: 8px; - font-size: 14px; -} - -/* ===== Sidebar Toggle Button ===== */ -.sidebar-toggle-btn { - position: absolute; - left: 20px; - top: 190px; - z-index: 1001; - border-radius: 8px; - width: 48px; - height: 48px; - background: white; - border: none; - 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: 250px; /* di bawah toggle button */ - z-index: 1000; - display: flex; - flex-direction: column; gap: 8px; -} - -.sidebar-section-label { - font-size: 9px; - font-weight: 700; - color: #94a3b8; - text-transform: uppercase; - text-align: center; - letter-spacing: 0.8px; - margin-top: 6px; - margin-bottom: 2px; - pointer-events: none; - user-select: none; -} - -.sidebar-divider { - height: 1px; - background: #e2e8f0; - margin: 4px 6px; -} - -.sidebar-btn { - width: 60px; - height: 60px; - background: white; - border: none; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0,0,0,0.12); + margin-bottom: 8px; + font-size: 13px; + color: var(--text-secondary); 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; +.layer-option input[type="checkbox"] { + accent-color: var(--primary); + width: 15px; + height: 15px; } -.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; +.layer-group-header { display: flex; align-items: center; justify-content: space-between; +} + +.layer-toggle-icon { + cursor: pointer; + padding: 2px 6px; + color: #94a3b8; + font-size: 10px; + border-radius: 4px; + transition: background 0.15s; +} + +.layer-toggle-icon:hover { background: var(--surface-2); } +.layer-toggle-icon i { transition: transform 0.2s; } +.layer-toggle-icon.collapsed i { transform: rotate(-90deg); } + +.sub-layer-list { + padding-left: 16px; + margin-top: 4px; + border-left: 2px solid var(--primary-light); + overflow: hidden; +} + +.sub-option { font-size: 12px !important; margin-bottom: 4px !important; } + +/* GeoJSON section */ +.geojson-section { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--border); +} + +.geojson-section-header { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + cursor: pointer; + margin-bottom: 6px; +} + +/* ============================================================ + RIGHT PANEL (Data) + ============================================================ */ +.right-panel { + position: fixed; + top: 0; + right: -360px; + width: 340px; + height: 100%; + background: white; + z-index: 1050; + box-shadow: -4px 0 24px rgba(0,0,0,0.12); + display: flex; + flex-direction: column; + transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border-left: 1px solid var(--border); +} + +.right-panel.open { right: 0; } + +.right-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 18px; + border-bottom: 1px solid var(--border); + background: white; flex-shrink: 0; - padding-top: 26px; } .right-panel-header h3 { font-size: 15px; font-weight: 700; - color: #1e1e2e; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 8px; } .right-panel-close { - width: 30px; - height: 30px; + width: 32px; + height: 32px; border: none; - background: #f3f4f6; + background: var(--surface-2); border-radius: 8px; cursor: pointer; - color: #555; display: flex; align-items: center; justify-content: center; - font-size: 13px; - transition: background 0.2s; + color: var(--text-secondary); + font-size: 14px; + transition: all 0.15s; } -.right-panel-close:hover { - background: #e5e7eb; - color: #333; -} +.right-panel-close:hover { background: #fee2e2; color: var(--danger); } .right-panel-actions { - padding: 12px 14px; - background: white; - border-bottom: 1px solid #eee; + padding: 12px 18px; + border-bottom: 1px solid var(--border); display: flex; gap: 8px; + flex-wrap: wrap; 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; + padding: 14px 18px; } -/* 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 Items */ .panel-empty { text-align: center; - color: #aaa; + color: #94a3b8; font-size: 13px; - padding: 40px 10px; + padding: 40px 20px; } -/* ===== 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 { +.panel-item { display: flex; - align-items: center; - gap: 10px; + align-items: flex-start; + gap: 12px; + padding: 12px; + border-radius: var(--radius); + cursor: pointer; + border: 1.5px solid transparent; + transition: all 0.15s; + margin-bottom: 8px; } -.data-card-icon { - width: 40px; - height: 40px; +.panel-item:hover { + background: var(--surface-2); + border-color: var(--border); +} + +.panel-item.active { + background: var(--primary-light); + border-color: var(--primary); +} + +.panel-item-icon { + width: 38px; + height: 38px; border-radius: 10px; + background: var(--surface-2); display: flex; align-items: center; justify-content: center; - font-size: 20px; + font-size: 18px; flex-shrink: 0; } -.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 { +.panel-item-info { flex: 1; min-width: 0; } +.panel-item-name { font-size: 13px; font-weight: 600; - color: #1e1e2e; + color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.panel-item-meta { font-size: 11px; color: var(--text-secondary); margin-top: 2px; } -.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 { +.panel-item-actions { display: flex; - gap: 6px; + gap: 4px; flex-shrink: 0; - align-items: center; + opacity: 0; + transition: opacity 0.15s; } +.panel-item:hover .panel-item-actions { opacity: 1; } -.btn-card-action { - width: 30px; - height: 30px; +.panel-action-btn { + width: 28px; + height: 28px; border: none; border-radius: 7px; + background: white; + border: 1px solid var(--border); cursor: pointer; display: flex; align-items: center; justify-content: center; - font-size: 12px; + font-size: 11px; + color: var(--text-secondary); transition: all 0.15s; } -.btn-card-edit { - background: #eff6ff; - color: #3b82f6; -} +.panel-action-btn:hover { background: var(--primary); color: white; border-color: var(--primary); } +.panel-action-btn.danger:hover { background: var(--danger); color: white; border-color: var(--danger); } -.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; +/* Panel action buttons in header */ +.btn-panel-action { + height: 34px; + padding: 0 14px; border-radius: 8px; + border: 1px solid var(--border); + background: white; + color: var(--text-secondary); font-size: 12px; font-weight: 600; cursor: pointer; display: flex; align-items: center; - justify-content: center; gap: 6px; - transition: all 0.2s; + transition: all 0.15s; + font-family: inherit; } -.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; - flex-direction: column; - gap: 10px; - width: 200px; -} - -.popup-form label { - font-size: 12px; - font-weight: bold; - color: #555; -} - -.popup-form input[type="text"], -.popup-form input[type="number"] { - width: 100%; - padding: 6px; - border: 1px solid #ccc; - border-radius: 4px; -} - -.popup-form .radio-group { - display: flex; - gap: 10px; -} - -.popup-form button { - padding: 8px; - background-color: #007bff; +.btn-panel-action:hover { + background: var(--primary); color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-weight: bold; + border-color: var(--primary); } -.popup-form button.btn-danger { - background-color: #dc3545; +.btn-panel-action.primary { + background: var(--primary); + color: white; + border-color: var(--primary); } -.popup-form button:hover { - opacity: 0.9; +.btn-panel-action.primary:hover { + background: var(--primary-dark); } -/* Unified Modal */ +/* ============================================================ + MODALS + ============================================================ */ .unified-modal { display: none; position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0,0,0,0.5); + inset: 0; + background: rgba(15,23,42,0.55); z-index: 2000; align-items: center; justify-content: center; + backdrop-filter: blur(2px); } -.unified-modal.show { - display: flex; -} +.unified-modal.show { display: flex; } .modal-content { - background-color: white; - border-radius: 12px; - width: 400px; - max-width: 90%; - box-shadow: 0 8px 30px rgba(0,0,0,0.2); + background: white; + border-radius: 14px; + width: 420px; + max-width: 95vw; + box-shadow: var(--shadow-lg); display: flex; flex-direction: column; + max-height: 90vh; + overflow: hidden; } .modal-header { - padding: 15px; - border-bottom: 1px solid #eee; + padding: 16px 20px; + border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; + flex-shrink: 0; } .modal-header h3 { - font-size: 16px; - margin: 0; + font-size: 15px; + font-weight: 700; + color: var(--text-primary); } .modal-close { - font-size: 20px; - font-weight: bold; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; cursor: pointer; - color: #888; + color: var(--text-secondary); + font-size: 18px; + font-weight: 400; + border-radius: 7px; + background: var(--surface-2); + transition: all 0.15s; } -.modal-close:hover { - color: #333; -} +.modal-close:hover { background: #fee2e2; color: var(--danger); } .modal-body { - padding: 15px; + padding: 16px 20px; display: flex; flex-direction: column; - gap: 12px; + gap: 14px; + overflow-y: auto; + flex: 1; } -.modal-body label { - font-size: 13px; +.modal-footer { + padding: 14px 20px; + border-top: 1px solid var(--border); + display: flex; + justify-content: flex-end; + gap: 8px; + flex-shrink: 0; +} + +/* Form */ +.form-group { + display: flex; + flex-direction: column; + gap: 5px; +} + +.form-group label, .modal-body label { + font-size: 12px; font-weight: 600; - color: #444; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.3px; } .modal-body input[type="text"], @@ -722,140 +831,120 @@ body.right-panel-open .custom-layer-panel { .modal-body select, .modal-body textarea { width: 100%; - padding: 8px; - border: 1px solid #ccc; - border-radius: 6px; - font-size: 14px; + padding: 9px 12px; + border: 1.5px solid var(--border); + border-radius: 8px; + font-size: 13px; font-family: inherit; + color: var(--text-primary); + background: white; + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; } -.modal-footer { - padding: 15px; - border-top: 1px solid #eee; - display: flex; - justify-content: flex-end; - gap: 10px; +.modal-body input:focus, +.modal-body select:focus, +.modal-body textarea:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(37,99,235,0.1); } .btn-save { - background: linear-gradient(135deg, #6366f1, #4f46e5); + background: var(--primary); color: white; border: none; - padding: 8px 18px; - border-radius: 7px; + padding: 9px 20px; + border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 13px; + font-family: inherit; + transition: all 0.15s; } +.btn-save:hover { background: var(--primary-dark); } + .btn-cancel { - background-color: #f8f9fa; - border: 1px solid #ddd; - padding: 8px 18px; - border-radius: 7px; + background: var(--surface-2); + border: 1px solid var(--border); + padding: 9px 20px; + border-radius: 8px; cursor: pointer; font-size: 13px; + font-family: inherit; + color: var(--text-secondary); + transition: all 0.15s; } -.form-group { +.btn-cancel:hover { background: var(--border); } + +/* ============================================================ + TOAST + ============================================================ */ +#toastContainer { + position: fixed; + bottom: 24px; + left: 50%; + transform: translateX(-50%); + z-index: 9999; display: flex; flex-direction: column; - gap: 4px; -} - -/* Custom Cursor Tooltip */ -.custom-cursor-tooltip { - position: absolute; - display: none; - background-color: rgba(0, 0, 0, 0.75); - color: white; - padding: 6px 12px; - border-radius: 4px; - font-size: 13px; - font-weight: 500; + align-items: center; + gap: 8px; pointer-events: none; - z-index: 9999; - white-space: nowrap; - box-shadow: 0 2px 6px rgba(0,0,0,0.2); } -/* Sembunyikan default tooltip Leaflet Draw */ -.leaflet-draw-tooltip { - display: none !important; -} - -/* Sub-layer collapsible */ -.layer-group-item { - margin-bottom: 6px; -} - -.layer-group-item > .layer-group-header { +.toast { display: flex; align-items: center; - justify-content: space-between; + gap: 10px; + padding: 11px 18px; + border-radius: 10px; + font-size: 13px; + font-weight: 500; + color: white; + box-shadow: 0 6px 20px rgba(0,0,0,0.15); + pointer-events: auto; + animation: toastIn 0.28s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; + max-width: 320px; } -.layer-toggle-icon { - cursor: pointer; - padding: 2px 6px; - color: #666; - font-size: 11px; - border-radius: 4px; - transition: background 0.2s; - flex-shrink: 0; -} +.toast.hiding { animation: toastOut 0.25s ease forwards; } +.toast-success { background: #065f46; } +.toast-error { background: #7f1d1d; } +.toast-info { background: #1e3a5f; } +.toast-warning { background: #78350f; } +.toast-icon { font-size: 15px; flex-shrink: 0; } -.layer-toggle-icon:hover { - background: #f0f0f0; - color: #333; -} +@keyframes toastIn { from { opacity:0; transform:translateY(12px) scale(0.92); } to { opacity:1; transform:translateY(0) scale(1); } } +@keyframes toastOut { from { opacity:1; } to { opacity:0; transform:translateY(6px) scale(0.95); } } -.layer-toggle-icon i { - transition: transform 0.2s; -} - -.layer-toggle-icon.collapsed i { - transform: rotate(-90deg); -} - -.sub-layer-list { - padding-left: 18px; - margin-top: 4px; - border-left: 2px solid #eee; - overflow: hidden; - transition: max-height 0.2s ease; -} - -.sub-option { - font-size: 12px !important; - color: #555; - margin-bottom: 3px !important; -} - -/* ===== Emoji Marker (Pin Bubble) ===== */ +/* ============================================================ + MAP MARKERS + ============================================================ */ .emoji-marker { display: flex; flex-direction: column; align-items: center; - pointer-events: auto; /* changed from none to allow click/drag */ + pointer-events: auto; } .emoji-marker .bubble { - width: 38px; - height: 38px; + width: 36px; + height: 36px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg); display: flex; align-items: center; justify-content: center; - box-shadow: 0 3px 10px rgba(0,0,0,0.25); - border: 2px solid rgba(255,255,255,0.8); - animation: markerPop 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; + box-shadow: 0 3px 10px rgba(0,0,0,0.2); + border: 2px solid rgba(255,255,255,0.9); + animation: markerPop 0.22s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; } .emoji-marker .bubble span { transform: rotate(45deg); - font-size: 18px; + font-size: 16px; line-height: 1; } @@ -864,195 +953,26 @@ body.right-panel-open .custom-layer-panel { 100% { transform: rotate(-45deg) scale(1); } } -/* Warna bubble per tipe */ -.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); } - -/* ===== Toast Notification ===== */ -#toastContainer { - position: fixed; - bottom: 28px; - left: 50%; - transform: translateX(-50%); - z-index: 9999; - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - pointer-events: none; -} - -.toast { - display: flex; - align-items: center; - gap: 10px; - padding: 12px 20px; - border-radius: 12px; - font-size: 13px; - font-weight: 500; - color: white; - box-shadow: 0 6px 24px rgba(0,0,0,0.18); - backdrop-filter: blur(6px); - pointer-events: auto; - animation: toastSlideIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; - max-width: 340px; - text-align: center; -} - -.toast.hiding { - animation: toastSlideOut 0.3s ease forwards; -} - -.toast-success { background: linear-gradient(135deg, #22c55e, #16a34a); } -.toast-error { background: linear-gradient(135deg, #ef4444, #dc2626); } -.toast-info { background: linear-gradient(135deg, #6366f1, #4f46e5); } -.toast-warning { background: linear-gradient(135deg, #f59e0b, #d97706); } - -.toast-icon { font-size: 16px; flex-shrink: 0; } - -@keyframes toastSlideIn { - 0% { opacity: 0; transform: translateY(20px) scale(0.9); } - 100% { opacity: 1; transform: translateY(0) scale(1); } -} - -@keyframes toastSlideOut { - 0% { opacity: 1; transform: translateY(0) scale(1); } - 100% { opacity: 0; transform: translateY(10px) scale(0.95); } -} - -/* Auth Widget Styling */ -.auth-widget { - position: absolute; - top: 20px; - right: 80px; - z-index: 1000; - transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -body.right-panel-open .auth-widget { - right: 400px; -} - -.btn-login { - background: linear-gradient(135deg, #6366f1, #4f46e5); - color: white; - border: none; - border-radius: 24px; - height: 48px; - padding: 0 24px; - font-size: 14px; - font-weight: 600; - cursor: pointer; - box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); - display: flex; - align-items: center; - gap: 8px; - transition: all 0.2s ease; -} - -.btn-login:hover { - box-shadow: 0 6px 16px rgba(99, 102, 241, 0.45); - transform: translateY(-1px); -} - -.user-profile-pill { - background-color: white; - border-radius: 24px; - height: 48px; - padding: 0 8px 0 16px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - display: flex; - align-items: center; - gap: 12px; - border: 1px solid rgba(226, 232, 240, 0.8); - backdrop-filter: blur(10px); - background: rgba(255, 255, 255, 0.9); -} - -.user-profile-info { - display: flex; - flex-direction: column; -} - -.user-profile-name { - font-size: 12px; - font-weight: 700; - color: #1e1e2e; -} - -.user-profile-role { - font-size: 9px; - font-weight: 600; - color: #6366f1; - text-transform: uppercase; -} - -.btn-profile-logout { - width: 32px; - height: 32px; - border-radius: 50%; - border: none; - background: #f1f5f9; - color: #64748b; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; -} - -.btn-profile-logout:hover { - background: #fee2e2; - color: #ef4444; -} - -/* User management table styling */ -#userManagementModal table th { - padding: 10px 12px; - font-weight: 600; - background-color: #f1f5f9; - border-bottom: 1px solid #cbd5e1; -} - -#userManagementModal table td { - padding: 10px 12px; - border-bottom: 1px solid #e2e8f0; -} - +.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, #10b981, #059669); } /* ============================================================ - DASHBOARD STYLES + DASHBOARD MODAL ============================================================ */ - #dashboardModal .modal-content { - width: min(920px, 96vw); + width: min(940px, 96vw); max-height: 90vh; - overflow: hidden; - display: flex; - flex-direction: column; } #dashboardModal .modal-header { - background: linear-gradient(135deg, #1e1b4b 0%, #312e81 50%, #4338ca 100%); - border-radius: 12px 12px 0 0; - padding: 18px 20px; - flex-shrink: 0; + background: linear-gradient(135deg, #0f172a 0%, #1e3a5f 100%); + border-radius: 14px 14px 0 0; } -#dashboardModal .modal-header h3 { - color: white; - font-size: 17px; - font-weight: 700; - letter-spacing: 0.3px; -} - -#dashboardModal .modal-close { - color: rgba(255,255,255,0.7); - font-size: 22px; - transition: color 0.2s; -} -#dashboardModal .modal-close:hover { color: white; } +#dashboardModal .modal-header h3 { color: white; } +#dashboardModal .modal-close { color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.1); } +#dashboardModal .modal-close:hover { background: rgba(239,68,68,0.3); color: white; } #dashboardBody { flex: 1; @@ -1060,242 +980,16 @@ body.right-panel-open .auth-widget { padding: 20px; display: flex; flex-direction: column; - gap: 20px; - background: #f1f5f9; -} - -.db-loading { - display: flex; - align-items: center; - justify-content: center; - gap: 14px; - padding: 60px 0; - color: #6b7280; - font-size: 14px; -} - -.db-spinner { - width: 28px; - height: 28px; - border: 3px solid #e2e8f0; - border-top-color: #6366f1; - border-radius: 50%; - animation: dbSpin 0.7s linear infinite; -} -@keyframes dbSpin { to { transform: rotate(360deg); } } - -.db-error { - text-align: center; - color: #ef4444; - padding: 40px; - font-size: 14px; -} - -.db-ibadah-header { - background: linear-gradient(135deg, #312e81, #4338ca); - border-radius: 14px; - padding: 16px 20px; - display: flex; - align-items: center; gap: 16px; - color: white; - box-shadow: 0 4px 20px rgba(67,56,202,0.3); + background: var(--surface-2); } -.db-ibadah-emoji { font-size: 36px; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); } -.db-ibadah-name { font-size: 18px; font-weight: 700; } -.db-ibadah-meta { font-size: 12px; color: rgba(255,255,255,0.75); margin-top: 3px; } - -.db-kpi-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); - gap: 14px; -} - -.db-kpi-card { - background: white; - border-radius: 16px; - padding: 18px 16px; - display: flex; - flex-direction: column; - align-items: center; - gap: 6px; - box-shadow: 0 2px 8px rgba(0,0,0,0.06); - transition: transform 0.2s, box-shadow 0.2s; - position: relative; - overflow: hidden; -} - -.db-kpi-card::before { - content: ''; - position: absolute; - top: 0; left: 0; right: 0; - height: 4px; - border-radius: 16px 16px 0 0; -} - -.kpi-ibadah::before { background: linear-gradient(90deg, #f97316, #ea580c); } -.kpi-miskin::before { background: linear-gradient(90deg, #ef4444, #dc2626); } -.kpi-jiwa::before { background: linear-gradient(90deg, #6366f1, #4f46e5); } -.kpi-log::before { background: linear-gradient(90deg, #22c55e, #16a34a); } - -.db-kpi-card:hover { transform: translateY(-3px); box-shadow: 0 8px 24px rgba(0,0,0,0.1); } - -.db-kpi-icon { font-size: 28px; line-height: 1; margin-bottom: 2px; } -.db-kpi-val { font-size: 32px; font-weight: 800; color: #1e1e2e; line-height: 1; } -.db-kpi-label { font-size: 11px; font-weight: 600; color: #6b7280; text-align: center; text-transform: uppercase; letter-spacing: 0.4px; } - -.db-section { - background: white; - border-radius: 14px; - padding: 16px 18px; - box-shadow: 0 2px 8px rgba(0,0,0,0.06); -} - -.db-section-title { - font-size: 13px; - font-weight: 700; - color: #374151; - margin-bottom: 12px; - letter-spacing: 0.2px; -} - -.db-coverage-labels { - display: flex; - justify-content: space-between; - font-size: 13px; - color: #374151; - flex-wrap: wrap; - gap: 6px; - margin-bottom: 8px; -} - -.db-progress-bar { - height: 12px; - background: #fee2e2; - border-radius: 99px; - overflow: hidden; -} - -.db-progress-fill { - height: 100%; - border-radius: 99px; - transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1); -} - -.db-charts-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 14px; -} - -.db-chart-card { - background: white; - border-radius: 14px; - padding: 14px 16px; - box-shadow: 0 2px 8px rgba(0,0,0,0.06); -} - -.db-chart-wide { grid-column: 1 / -1; } - -.db-chart-title { - font-size: 11px; - font-weight: 700; - color: #374151; - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 0.4px; -} - -.db-chart-card canvas { width: 100% !important; display: block; } - -.db-log-list { - display: flex; - flex-direction: column; - gap: 8px; - max-height: 260px; - overflow-y: auto; -} - -.db-log-item { - display: flex; - align-items: flex-start; - gap: 10px; - padding: 10px 12px; - background: #f8fafc; - border-radius: 10px; - transition: background 0.15s; -} - -.db-log-item:hover { background: #f1f5f9; } - -.db-log-dot { - width: 8px; height: 8px; - border-radius: 50%; - background: #6366f1; - flex-shrink: 0; - margin-top: 5px; -} - -.db-log-info { flex: 1; min-width: 0; } - -.db-log-main { - font-size: 12px; - font-weight: 600; - color: #1e1e2e; - display: flex; - gap: 6px; - flex-wrap: wrap; - align-items: center; -} - -.db-log-tipe { - background: #ede9fe; - color: #4f46e5; - padding: 1px 7px; - border-radius: 99px; - font-size: 11px; - font-weight: 700; -} - -.db-log-kk { color: #374151; } - -.db-log-meta { font-size: 10px; color: #9ca3af; margin-top: 3px; } - -.db-table-wrap { - overflow-x: auto; - border-radius: 8px; - border: 1px solid #e5e7eb; -} - -.db-table { width: 100%; border-collapse: collapse; font-size: 13px; } -.db-table thead tr { background: #f8fafc; } -.db-table th { padding: 10px 14px; text-align: left; font-size: 11px; font-weight: 700; color: #6b7280; text-transform: uppercase; letter-spacing: 0.4px; border-bottom: 1px solid #e5e7eb; } -.db-table td { padding: 10px 14px; color: #374151; border-bottom: 1px solid #f3f4f6; } -.db-table tbody tr:last-child td { border-bottom: none; } -.db-table tbody tr:hover td { background: #f8fafc; } - -.db-badge { - background: linear-gradient(135deg, #6366f1, #4f46e5); - color: white; - padding: 2px 9px; - border-radius: 99px; - font-size: 11px; - font-weight: 700; -} - -.db-empty { text-align: center; color: #aaa; font-size: 13px; padding: 20px; } - -/* ══════════════════════════════════════════════════════ - Dashboard Tab Bar (Integrasi Multi-Database) - ══════════════════════════════════════════════════════ */ - .db-tab-bar { display: flex; gap: 4px; padding: 12px 20px 0; - background: #f8fafc; - border-bottom: 2px solid #e5e7eb; + background: white; + border-bottom: 2px solid var(--border); flex-wrap: wrap; } @@ -1306,25 +1000,16 @@ body.right-panel-open .auth-widget { border-radius: 8px 8px 0 0; font-size: 13px; font-weight: 500; - color: #6b7280; + color: var(--text-secondary); cursor: pointer; - transition: all 0.2s; + transition: all 0.15s; margin-bottom: -2px; border-bottom: 2px solid transparent; font-family: inherit; } -.db-tab-btn:hover { - background: #ede9fe; - color: #4f46e5; -} - -.db-tab-btn.active { - background: white; - color: #4f46e5; - border-bottom: 2px solid #4f46e5; - font-weight: 700; -} +.db-tab-btn:hover { background: var(--primary-light); color: var(--primary); } +.db-tab-btn.active { background: white; color: var(--primary); border-bottom-color: var(--primary); font-weight: 700; } .db-tab-content { padding: 20px; @@ -1332,10 +1017,728 @@ body.right-panel-open .auth-widget { max-height: calc(80vh - 100px); } -/* KPI card color override via CSS variable */ +.db-loading { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 60px 0; color: var(--text-secondary); font-size: 14px; } +.db-spinner { width: 24px; height: 24px; border: 3px solid var(--border); border-top-color: var(--primary); border-radius: 50%; animation: dbSpin 0.7s linear infinite; } +@keyframes dbSpin { to { transform: rotate(360deg); } } + +.db-error { text-align: center; color: var(--danger); padding: 40px; font-size: 14px; } + +.db-ibadah-header { + background: linear-gradient(135deg, #1e3a5f, #2563eb); + border-radius: 12px; + padding: 16px 20px; + display: flex; + align-items: center; + gap: 16px; + color: white; + box-shadow: 0 4px 16px rgba(37,99,235,0.25); +} + +.db-ibadah-emoji { font-size: 34px; } +.db-ibadah-name { font-size: 17px; font-weight: 700; } +.db-ibadah-meta { font-size: 12px; color: rgba(255,255,255,0.7); margin-top: 2px; } + +.db-kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; } + .db-kpi-card { - --kpi-color: #6366f1; + background: white; + border-radius: 12px; + padding: 16px; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + box-shadow: var(--shadow-sm); + position: relative; + overflow: hidden; + transition: transform 0.15s, box-shadow 0.15s; } -.db-kpi-card .db-kpi-val { - color: var(--kpi-color, #6366f1); + +.db-kpi-card::before { + content: ''; + position: absolute; + top: 0; left: 0; right: 0; + height: 3px; + border-radius: 12px 12px 0 0; +} + +.kpi-ibadah::before { background: linear-gradient(90deg, #f97316, #ea580c); } +.kpi-miskin::before { background: linear-gradient(90deg, #ef4444, #dc2626); } +.kpi-jiwa::before { background: linear-gradient(90deg, #2563eb, #1d4ed8); } +.kpi-log::before { background: linear-gradient(90deg, #10b981, #059669); } + +.db-kpi-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); } +.db-kpi-icon { font-size: 26px; } +.db-kpi-val { font-size: 28px; font-weight: 800; color: var(--text-primary); } +.db-kpi-label { font-size: 10px; font-weight: 700; color: var(--text-secondary); text-align: center; text-transform: uppercase; letter-spacing: 0.4px; } + +.db-section { background: white; border-radius: 12px; padding: 16px; box-shadow: var(--shadow-sm); } +.db-section-title { font-size: 12px; font-weight: 700; color: var(--text-primary); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.4px; } + +.db-coverage-labels { display: flex; justify-content: space-between; font-size: 13px; color: var(--text-primary); flex-wrap: wrap; gap: 6px; margin-bottom: 8px; } +.db-progress-bar { height: 10px; background: #fee2e2; border-radius: 99px; overflow: hidden; } +.db-progress-fill { height: 100%; border-radius: 99px; transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1); } + +.db-charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; } +.db-chart-card { background: white; border-radius: 12px; padding: 14px 16px; box-shadow: var(--shadow-sm); } +.db-chart-wide { grid-column: 1 / -1; } +.db-chart-title { font-size: 11px; font-weight: 700; color: var(--text-secondary); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.4px; } +.db-chart-card canvas { width: 100% !important; display: block; } + +.db-log-list { display: flex; flex-direction: column; gap: 8px; max-height: 260px; overflow-y: auto; } +.db-log-item { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; background: var(--surface-2); border-radius: 10px; transition: background 0.1s; } +.db-log-item:hover { background: var(--primary-light); } +.db-log-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--primary); flex-shrink: 0; margin-top: 5px; } +.db-log-info { flex: 1; min-width: 0; } +.db-log-main { font-size: 12px; font-weight: 600; color: var(--text-primary); display: flex; gap: 6px; flex-wrap: wrap; align-items: center; } +.db-log-tipe { background: var(--primary-light); color: var(--primary); padding: 1px 7px; border-radius: 99px; font-size: 11px; font-weight: 700; } +.db-log-kk { color: var(--text-primary); } +.db-log-meta { font-size: 10px; color: #9ca3af; margin-top: 3px; } + +.db-table-wrap { overflow-x: auto; border-radius: 8px; border: 1px solid var(--border); } +.db-table { width: 100%; border-collapse: collapse; font-size: 13px; } +.db-table thead tr { background: var(--surface-2); } +.db-table th { padding: 9px 14px; text-align: left; font-size: 11px; font-weight: 700; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.4px; border-bottom: 1px solid var(--border); } +.db-table td { padding: 9px 14px; color: var(--text-primary); border-bottom: 1px solid #f3f4f6; } +.db-table tbody tr:last-child td { border-bottom: none; } +.db-table tbody tr:hover td { background: var(--surface-2); } + +.db-badge { background: var(--primary); color: white; padding: 2px 8px; border-radius: 99px; font-size: 11px; font-weight: 700; } +.db-empty { text-align: center; color: #9ca3af; font-size: 13px; padding: 20px; } + +/* ============================================================ + USER MANAGEMENT MODAL + ============================================================ */ +#userManagementModal .modal-content { + width: min(720px, 96vw); +} + +/* ============================================================ + CUSTOM CURSOR TOOLTIP + ============================================================ */ +.custom-cursor-tooltip { + position: absolute; + display: none; + background: rgba(15,23,42,0.85); + color: white; + padding: 5px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + pointer-events: none; + z-index: 9999; + white-space: nowrap; +} + +/* ============================================================ + POPUP FORM (Leaflet) + ============================================================ */ +.popup-form { display: flex; flex-direction: column; gap: 8px; width: 200px; } +.popup-form label { font-size: 11px; font-weight: 600; color: var(--text-secondary); } +.popup-form input[type="text"], .popup-form input[type="number"] { width: 100%; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; font-size: 12px; } +.popup-form button { padding: 7px; background: var(--primary); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 12px; } +.popup-form button.btn-danger { background: var(--danger); } + +/* ============================================================ + MISC + ============================================================ */ +.leaflet-draw-tooltip { display: none !important; } + +.sidebar-divider { + height: 1px; + background: rgba(255,255,255,0.07); + margin: 6px 10px; +} + +/* Leaflet popup customization */ +.leaflet-popup-content-wrapper { + border-radius: 10px; + box-shadow: var(--shadow-lg); + border: 1px solid var(--border); +} + +.leaflet-popup-tip { display: none; } + +/* Attribution */ +.leaflet-control-attribution { + font-size: 10px; +} + +/* ============================================================ + LOG BANTUAN MODAL + ============================================================ */ +.modal-log-bantuan { + width: min(560px, 96vw); + max-height: 90vh; + display: flex; + flex-direction: column; +} + +.modal-log-bantuan .modal-body { + flex: 1; + overflow: hidden; + padding: 0; + display: flex; + flex-direction: column; +} + +.log-form-section { + padding: 16px 20px; + background: var(--surface-2); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.log-form-title { + font-size: 12px; + font-weight: 700; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.4px; + margin-bottom: 12px; +} + +.log-form-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-bottom: 12px; +} + +.log-list-section { + flex: 1; + overflow-y: auto; + padding: 16px 20px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.log-list-title { + font-size: 12px; + font-weight: 700; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.4px; + margin-bottom: 4px; + flex-shrink: 0; +} + +.log-item { + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: 10px; + padding: 12px 14px; + display: flex; + flex-direction: column; + gap: 5px; + transition: background 0.15s; +} + +.log-item:hover { background: var(--primary-light); } + +.log-item-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.log-tipe-badge { + background: var(--primary); + color: white; + font-size: 11px; + font-weight: 700; + padding: 2px 10px; + border-radius: 99px; +} + +.log-tgl { + font-size: 11px; + color: var(--text-secondary); + white-space: nowrap; +} + +.log-ibadah { + font-size: 12px; + color: var(--text-primary); + font-weight: 500; + display: flex; + align-items: center; + gap: 5px; +} + +.log-jenis { + font-size: 10px; + background: #f1f5f9; + color: var(--text-secondary); + padding: 1px 6px; + border-radius: 4px; +} + +.log-ket { + font-size: 11px; + color: var(--text-secondary); + font-style: italic; +} + +/* ============================================================ + RESPONSIVE β€” MOBILE (max-width: 768px) + ============================================================ */ +@media (max-width: 768px) { + :root { --sidebar-w: 64px; } + + .left-sidebar { + width: 64px !important; + } + + .sidebar-brand-text, + .btn-label, + .sidebar-nav-label, + .collapse-label { + display: none !important; + } + + .sidebar-collapse-btn { display: none !important; } + + #map { + left: 64px !important; + } + + .top-bar { + left: 64px !important; + padding: 0 8px; + gap: 6px; + } + + .top-bar .search-bar { max-width: 100%; } + + .right-panel { + width: 100vw !important; + max-width: 100vw; + right: 0; + border-radius: 16px 16px 0 0; + top: auto; + bottom: 0; + height: 70vh; + transform: translateY(100%); + transition: transform 0.3s ease; + } + + .right-panel.open { + transform: translateY(0); + } + + body.right-panel-open #map { + bottom: 70vh; + } + + .modal-content { + width: 96vw !important; + margin: auto 2vw; + max-height: 90vh; + } + + .log-form-grid { + grid-template-columns: 1fr; + } + + #userManagementModal .modal-content { + width: 96vw !important; + } + + #userManagementModal [style*="grid-template-columns:1fr 1fr"] { + grid-template-columns: 1fr !important; + } + + .zoom-controls { + bottom: calc(70vh + 12px) !important; + } + + .custom-layer-panel { + left: 72px; + max-width: calc(100vw - 80px); + } + + .db-charts-grid { + grid-template-columns: 1fr !important; + } + + .db-kpi-grid { grid-template-columns: 1fr 1fr !important; } +} + +/* ============================================================ + RESPONSIVE β€” TABLET (769px – 1024px) + ============================================================ */ +@media (min-width: 769px) and (max-width: 1024px) { + :root { --sidebar-w: 180px; } + + .right-panel { width: 300px; } + + body.right-panel-open #map { right: 300px; } +} + +/* ============================================================ + DATA CARD β€” Panel Kiri (Ibadah & Miskin) + ============================================================ */ +.data-card { + background: white; + border: 1.5px solid var(--border); + border-radius: 12px; + margin-bottom: 8px; + overflow: hidden; + transition: box-shadow 0.15s, border-color 0.15s; +} +.data-card:hover { border-color: #bfdbfe; box-shadow: 0 2px 10px rgba(37,99,235,0.08); } +.data-card.expanded { border-color: var(--primary); } + +.data-card-main { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 14px; + cursor: pointer; + user-select: none; +} + +.data-card-icon { + width: 38px; + height: 38px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + flex-shrink: 0; +} +.card-icon-ibadah { background: #fef3c7; } +.card-icon-miskin-in { background: #dcfce7; } +.card-icon-miskin-out { background: #fee2e2; } + +.data-card-info { flex: 1; min-width: 0; } +.data-card-name { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.data-card-sub { + font-size: 11px; + color: var(--text-secondary); + margin-top: 2px; + display: flex; + align-items: center; + gap: 5px; + flex-wrap: wrap; +} + +.data-card-badge { + font-size: 10px; + font-weight: 700; + padding: 1px 7px; + border-radius: 99px; + white-space: nowrap; +} +.badge-orange { background: #ffedd5; color: #c2410c; } +.badge-blue { background: #dbeafe; color: #1d4ed8; } +.badge-green { background: #dcfce7; color: #15803d; } + +.data-card-actions { + display: flex; + gap: 4px; + flex-shrink: 0; + opacity: 0; + transition: opacity 0.15s; +} +.data-card:hover .data-card-actions { opacity: 1; } + +.btn-card-action { + width: 28px; + height: 28px; + border: 1px solid var(--border); + border-radius: 7px; + background: white; + color: var(--text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + transition: all 0.15s; +} +.btn-card-edit:hover { background: var(--primary); color: white; border-color: var(--primary); } +.btn-card-delete:hover { background: var(--danger); color: white; border-color: var(--danger); } + +/* Expanded section */ +.data-card-expand { + display: none; + padding: 0 14px 14px; + flex-direction: column; + gap: 6px; + border-top: 1px dashed var(--border); +} +.data-card.expanded .data-card-expand { display: flex; } + +.expand-row { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + gap: 10px; + padding: 4px 0; +} +.expand-row > span:first-child { color: var(--text-secondary); flex-shrink: 0; } +.expand-row > span:last-child { color: var(--text-primary); font-weight: 500; text-align: right; } + +.btn-log-bantuan { + margin-top: 6px; + width: 100%; + padding: 8px 12px; + border: 1.5px solid #c7d2fe; + background: #eef2ff; + color: #4338ca; + border-radius: 8px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s; + font-family: inherit; +} +.btn-log-bantuan:hover { + background: #4338ca; + color: white; + border-color: #4338ca; +} + +/* Panel action buttons */ +.btn-panel-add, .btn-panel-import { + display: flex; + align-items: center; + gap: 6px; + height: 34px; + padding: 0 14px; + border-radius: 8px; + border: none; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s; + font-family: inherit; + white-space: nowrap; +} +.btn-panel-add { + background: var(--primary); + color: white; +} +.btn-panel-add:hover { background: var(--primary-dark); } +.btn-panel-import { + background: white; + color: var(--text-secondary); + border: 1px solid var(--border); +} +.btn-panel-import:hover { background: var(--surface-2); color: var(--text-primary); } + +/* Section title inside panel */ +.panel-section-title { + font-size: 10px; + font-weight: 700; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 10px 2px 4px; +} + +/* ============================================================ + LOG BANTUAN PANEL β€” Kartu Penduduk (lebih bagus) + ============================================================ */ +.log-person-card { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 14px; + background: white; + border: 1.5px solid var(--border); + border-radius: 12px; + margin-bottom: 8px; + cursor: pointer; + transition: all 0.18s; + text-decoration: none; +} +.log-person-card:hover { + border-color: var(--primary); + background: #f0f5ff; + transform: translateX(2px); + box-shadow: 0 2px 10px rgba(37,99,235,0.1); +} +.log-person-avatar { + width: 42px; + height: 42px; + border-radius: 12px; + background: linear-gradient(135deg, #6366f1, #8b5cf6); + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + flex-shrink: 0; +} +.log-person-avatar.avatar-makan { background: linear-gradient(135deg, #f97316, #ef4444); } +.log-person-avatar.avatar-pmbr { background: linear-gradient(135deg, #3b82f6, #6366f1); } +.log-person-info { flex: 1; min-width: 0; } +.log-person-name { + font-size: 13px; + font-weight: 700; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.log-person-meta { + font-size: 11px; + color: var(--text-secondary); + margin-top: 3px; + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} +.log-person-arrow { + color: #c7d2fe; + font-size: 14px; + flex-shrink: 0; + transition: transform 0.15s, color 0.15s; +} +.log-person-card:hover .log-person-arrow { + color: var(--primary); + transform: translateX(3px); +} + +/* Panel hint strip */ +.panel-hint { + background: #eff6ff; + border: 1px solid #bfdbfe; + border-radius: 10px; + padding: 8px 12px; + font-size: 11px; + color: #1e40af; + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 6px; +} + +/* Improved log-item in modal */ +.log-item { + background: white; + border: 1.5px solid var(--border); + border-radius: 12px; + padding: 12px 14px; + display: flex; + flex-direction: column; + gap: 6px; + transition: all 0.15s; + margin-bottom: 8px; +} +.log-item:hover { border-color: #bfdbfe; background: #f8faff; box-shadow: 0 2px 8px rgba(37,99,235,0.07); } + +.log-item-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.log-tipe-badge { + background: var(--primary); + color: white; + font-size: 11px; + font-weight: 700; + padding: 3px 10px; + border-radius: 99px; + letter-spacing: 0.2px; +} +.log-tgl { + font-size: 11px; + color: var(--text-secondary); + white-space: nowrap; + display: flex; + align-items: center; + gap: 4px; +} +.log-ibadah { + font-size: 12px; + color: var(--text-primary); + font-weight: 500; + display: flex; + align-items: center; + gap: 5px; +} +.log-jenis { + font-size: 10px; + background: #f1f5f9; + color: var(--text-secondary); + padding: 1px 6px; + border-radius: 5px; +} +.log-ket { + font-size: 11px; + color: var(--text-secondary); + font-style: italic; + padding-top: 2px; + border-top: 1px dashed var(--border); + margin-top: 2px; +} + +/* Log bantuan modal improvements */ +.modal-log-bantuan { + width: min(560px, 96vw); + max-height: 90vh; + display: flex; + flex-direction: column; +} +.modal-log-bantuan .modal-body { + flex: 1; + overflow: hidden; + padding: 0; + display: flex; + flex-direction: column; +} +.log-form-section { + padding: 16px 20px; + background: var(--surface-2); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.log-form-title { + font-size: 11px; + font-weight: 700; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 12px; + display: flex; + align-items: center; + gap: 6px; +} +.log-form-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-bottom: 12px; +} +.log-list-section { + flex: 1; + overflow-y: auto; + padding: 16px 20px; +} +.log-list-title { + font-size: 11px; + font-weight: 700; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 12px; + display: flex; + align-items: center; + gap: 6px; } diff --git a/webgis-poverty-pontianak/assets/js/features/auth.js b/webgis-poverty-pontianak/assets/js/features/auth.js index 6d76deb..a381708 100644 --- a/webgis-poverty-pontianak/assets/js/features/auth.js +++ b/webgis-poverty-pontianak/assets/js/features/auth.js @@ -77,7 +77,7 @@ if (dashboardBtn) dashboardBtn.style.display = 'flex'; // Show user management button in sidebar for Admin - const adminDivider = document.getElementById('sidebarAdminDivider'); + const adminDivider = document.getElementById('adminNavLabel'); if (window.currentUser.role === 'admin') { if (menuUsersBtn) menuUsersBtn.style.display = 'flex'; if (adminDivider) adminDivider.style.display = 'block'; @@ -93,7 +93,7 @@ document.getElementById('authLoginBtn').addEventListener('click', showLoginModal); if (menuUsersBtn) menuUsersBtn.style.display = 'none'; if (dashboardBtn) dashboardBtn.style.display = 'none'; - const adminDivider = document.getElementById('sidebarAdminDivider'); + const adminDivider = document.getElementById('adminNavLabel'); if (adminDivider) adminDivider.style.display = 'none'; } } diff --git a/webgis-poverty-pontianak/assets/js/features/geolocation.js b/webgis-poverty-pontianak/assets/js/features/geolocation.js index aa6179b..579575a 100644 --- a/webgis-poverty-pontianak/assets/js/features/geolocation.js +++ b/webgis-poverty-pontianak/assets/js/features/geolocation.js @@ -9,7 +9,8 @@ geoBtn.style.left = '0'; geoBtn.style.right = 'auto'; geoBtn.innerHTML = ''; geoBtn.title = 'Lokasi Saya'; -document.querySelector('.custom-zoom-control').appendChild(geoBtn); +const zoomContainer = document.querySelector('.zoom-controls'); +if (zoomContainer) zoomContainer.appendChild(geoBtn); let userMarker = null; diff --git a/webgis-poverty-pontianak/assets/js/features/kemiskinan.js b/webgis-poverty-pontianak/assets/js/features/kemiskinan.js index f977216..60b7587 100644 --- a/webgis-poverty-pontianak/assets/js/features/kemiskinan.js +++ b/webgis-poverty-pontianak/assets/js/features/kemiskinan.js @@ -444,8 +444,8 @@ function addMiskinMarker(item) { photoLinksHtml += `
Foto Rumah:
- - Foto Rumah + Foto Rumah ❌ Foto tidak dapat dimuat @@ -456,8 +456,8 @@ function addMiskinMarker(item) { photoLinksHtml += `
Foto KK:
-
- Foto KK + Foto KK ❌ Foto tidak dapat dimuat @@ -590,8 +590,8 @@ window.openEditMiskinModal = function(id) { ${d.foto_rumah ? `
- - Foto Rumah + Foto Rumah ❌ File gambar rusak atau tidak ditemukan @@ -603,8 +603,8 @@ window.openEditMiskinModal = function(id) { ${d.foto_kk ? `
- - Foto KK + Foto KK ❌ File gambar rusak atau tidak ditemukan diff --git a/webgis-poverty-pontianak/assets/js/map.js b/webgis-poverty-pontianak/assets/js/map.js index c660d8f..307a772 100644 --- a/webgis-poverty-pontianak/assets/js/map.js +++ b/webgis-poverty-pontianak/assets/js/map.js @@ -1,5 +1,8 @@ +// ============================================================ +// WebGIS Kemiskinan Pontianak β€” map.js (Redesign) +// ============================================================ + // Inisialisasi Peta -// Koordinat awal: [-0.0263, 109.3425] (Pontianak) const map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13); // ===== Toast Notification ===== @@ -7,86 +10,94 @@ window.showToast = function(message, type = 'success', duration = 3500) { const icons = { success: 'βœ…', error: '❌', info: 'ℹ️', warning: '⚠️' }; const container = document.getElementById('toastContainer'); if (!container) return; - const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = `${icons[type] || 'ℹ️'}${message}`; container.appendChild(toast); - setTimeout(() => { toast.classList.add('hiding'); toast.addEventListener('animationend', () => toast.remove()); }, duration); }; -// Custom Zoom Control Logic -document.getElementById('zoomInBtn').addEventListener('click', function() { map.zoomIn(); }); -document.getElementById('zoomOutBtn').addEventListener('click', function() { map.zoomOut(); }); +// ===== Zoom Controls ===== +document.getElementById('zoomInBtn').addEventListener('click', () => map.zoomIn()); +document.getElementById('zoomOutBtn').addEventListener('click', () => map.zoomOut()); -// Base Map dari OpenStreetMap +// ===== Base Map ===== L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© OpenStreetMap contributors' + attribution: 'Β© OpenStreetMap' }).addTo(map); -// Inisialisasi FeatureGroups untuk masing-masing layer -const rumahIbadahLayer = L.featureGroup().addTo(map); +// ===== Feature Groups ===== +const rumahIbadahLayer = L.featureGroup().addTo(map); const pendudukMiskinLayer = L.featureGroup().addTo(map); -const geoJsonLayer = L.featureGroup().addTo(map); +const geoJsonLayer = L.featureGroup().addTo(map); +// ===== Sidebar Collapse ===== +const collapseBtn = document.getElementById('sidebarCollapseBtn'); +const collapseIcon = document.getElementById('collapseIcon'); + +if (collapseBtn) { + collapseBtn.addEventListener('click', function() { + const collapsed = document.body.classList.toggle('sidebar-collapsed'); + if (collapseIcon) collapseIcon.className = collapsed ? 'fas fa-chevron-right' : 'fas fa-chevron-left'; + setTimeout(() => map.invalidateSize(), 320); + }); +} + +// ===== Search ===== const searchInput = document.getElementById('searchInput'); const searchClear = document.getElementById('searchClear'); +const searchResultsEl = document.getElementById('searchResults'); -searchInput.addEventListener('focus', function() { +searchInput.addEventListener('focus', () => { if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode(); }); searchInput.addEventListener('input', function() { - const val = this.value.toLowerCase(); + const val = this.value.toLowerCase().trim(); if (val.length > 0) { searchClear.style.visibility = 'visible'; window.showSearchResults(val); } else { searchClear.style.visibility = 'hidden'; - document.getElementById('searchResults').style.display = 'none'; + searchResultsEl.style.display = 'none'; } }); searchClear.addEventListener('click', function() { searchInput.value = ''; searchClear.style.visibility = 'hidden'; - document.getElementById('searchResults').style.display = 'none'; + searchResultsEl.style.display = 'none'; searchInput.focus(); }); -window.showSearchResults = function(query) { - const resultsContainer = document.getElementById('searchResults'); - resultsContainer.innerHTML = ''; - let results = []; - +document.addEventListener('click', function(e) { + if (!e.target.closest('.search-bar') && !e.target.closest('#searchResults')) { + searchResultsEl.style.display = 'none'; + } +}); + +window.showSearchResults = function(query) { + searchResultsEl.innerHTML = ''; + let results = []; - if (typeof rumahIbadahLayer !== 'undefined') { rumahIbadahLayer.eachLayer(layer => { - if (layer.ibadahData && layer.ibadahData.nama && layer.ibadahData.nama.toLowerCase().includes(query)) { - if(layer instanceof L.Marker) { - results.push({ type: 'Rumah Ibadah', nama: layer.ibadahData.nama, layer: layer }); - } + if (layer.ibadahData?.nama?.toLowerCase().includes(query) && layer instanceof L.Marker) { + results.push({ type: 'Rumah Ibadah', icon: 'πŸ•Œ', nama: layer.ibadahData.nama, layer }); } }); } - + if (results.length === 0) { - resultsContainer.innerHTML = '
Tidak ada hasil
'; + searchResultsEl.innerHTML = '
Tidak ada hasil ditemukan
'; } else { results.forEach(res => { const item = document.createElement('div'); - item.style.padding = '10px 15px'; - item.style.cursor = 'pointer'; - item.style.borderBottom = '1px solid #eee'; - item.style.fontSize = '14px'; - item.innerHTML = `${res.type}: ${res.nama}`; - item.addEventListener('mouseenter', () => item.style.backgroundColor = '#f8f9fa'); - item.addEventListener('mouseleave', () => item.style.backgroundColor = 'white'); + item.className = 'search-result-item'; + item.innerHTML = `${res.icon}${res.type}
${res.nama}
`; item.addEventListener('click', () => { if (res.layer instanceof L.Marker) { map.setView(res.layer.getLatLng(), 17); @@ -94,38 +105,40 @@ window.showSearchResults = function(query) { map.fitBounds(res.layer.getBounds()); } res.layer.openPopup(); - resultsContainer.style.display = 'none'; + searchResultsEl.style.display = 'none'; + searchInput.value = res.nama; + searchClear.style.visibility = 'visible'; }); - resultsContainer.appendChild(item); + searchResultsEl.appendChild(item); }); } - resultsContainer.style.display = 'block'; + searchResultsEl.style.display = 'block'; }; -// UI Logic: Custom Layer Control Toggle +// ===== Layer Panel Toggle ===== const layerBtn = document.getElementById('layerBtn'); const layerPanel = document.getElementById('layerPanel'); layerBtn.addEventListener('click', function() { if (typeof window.deactivateAddMode === 'function') window.deactivateAddMode(); - layerPanel.style.display = layerPanel.style.display === 'block' ? 'none' : 'block'; + const isOpen = layerPanel.classList.toggle('show'); + layerBtn.classList.toggle('active', isOpen); }); -// Sembunyikan layer panel saat klik di peta map.on('click', function() { - layerPanel.style.display = 'none'; + layerPanel.classList.remove('show'); + layerBtn.classList.remove('active'); }); - -// Logic untuk Toggle Layer Visibility +// ===== Layer Visibility ===== document.getElementById('layerRumahIbadah').addEventListener('change', function(e) { e.target.checked ? map.addLayer(rumahIbadahLayer) : map.removeLayer(rumahIbadahLayer); }); + document.getElementById('layerMiskin').addEventListener('change', function(e) { e.target.checked ? map.addLayer(pendudukMiskinLayer) : map.removeLayer(pendudukMiskinLayer); }); -// --- Sub-layer Toggle (expand/collapse) --- window.toggleSubLayer = function(subId, iconEl) { const sub = document.getElementById(subId); if (!sub) return; @@ -134,87 +147,58 @@ window.toggleSubLayer = function(subId, iconEl) { iconEl.classList.toggle('collapsed', !isHidden); }; -// --- Sub-layer Filter --- window.applySubFilter = function(type) { - if (type === 'miskin') { const checked = [...document.querySelectorAll('.sub-miskin:checked')].map(el => el.value); pendudukMiskinLayer.eachLayer(layer => { if (!layer.miskinData) return; - if (checked.includes(layer.miskinData.kategori_bantuan)) { - layer.getElement && layer.getElement() && (layer.getElement().style.display = ''); - } else { - layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none'); - } + const el = layer.getElement?.(); + if (el) el.style.display = checked.includes(layer.miskinData.kategori_bantuan) ? '' : 'none'; }); } }; -// --- Modal Logic --- +// ===== Modal Logic ===== const unifiedModal = document.getElementById('unifiedModal'); -const modalTitle = document.getElementById('modalTitle'); -const modalBody = document.getElementById('modalBody'); window.openModal = function(title, bodyHTML, saveCallback) { - modalTitle.textContent = title; - modalBody.innerHTML = bodyHTML; + document.getElementById('modalTitle').textContent = title; + document.getElementById('modalBody').innerHTML = bodyHTML; unifiedModal.classList.add('show'); - - // Ambil elemen tombol save terbaru dari DOM - const currentSaveBtn = document.getElementById('modalSaveBtn'); - - // Clone untuk menghapus semua event listener lama - const newSaveBtn = currentSaveBtn.cloneNode(true); - currentSaveBtn.parentNode.replaceChild(newSaveBtn, currentSaveBtn); - - newSaveBtn.addEventListener('click', saveCallback); + + const oldBtn = document.getElementById('modalSaveBtn'); + const newBtn = oldBtn.cloneNode(true); + oldBtn.parentNode.replaceChild(newBtn, oldBtn); + newBtn.addEventListener('click', saveCallback); }; -window.closeModal = function() { - unifiedModal.classList.remove('show'); -}; +window.closeModal = function() { unifiedModal.classList.remove('show'); }; const confirmModal = document.getElementById('confirmModal'); -const confirmMessage = document.getElementById('confirmMessage'); window.openConfirmModal = function(msg, confirmCallback) { - confirmMessage.textContent = msg; + document.getElementById('confirmMessage').textContent = msg; confirmModal.classList.add('show'); - - const currentYesBtn = document.getElementById('confirmYesBtn'); - const newYesBtn = currentYesBtn.cloneNode(true); - currentYesBtn.parentNode.replaceChild(newYesBtn, currentYesBtn); - - newYesBtn.addEventListener('click', function() { + + const oldBtn = document.getElementById('confirmYesBtn'); + const newBtn = oldBtn.cloneNode(true); + oldBtn.parentNode.replaceChild(newBtn, oldBtn); + newBtn.addEventListener('click', function() { confirmModal.classList.remove('show'); confirmCallback(); }); }; -window.closeConfirmModal = function() { - confirmModal.classList.remove('show'); -}; +window.closeConfirmModal = function() { confirmModal.classList.remove('show'); }; -// --- Action Menu Logic (replaced by left sidebar) --- - -window.currentAddMode = null; // 'rumah_ibadah' atau 'miskin_click' +// ===== Add Mode ===== +window.currentAddMode = null; window.activateAddMode = function(mode) { - // Toggle: jika mode yang sama diklik lagi, batalkan - if (window.currentAddMode === mode) { - window.deactivateAddMode(); - return; - } - + if (window.currentAddMode === mode) { window.deactivateAddMode(); return; } window.currentAddMode = mode; - - // Tooltip - const tooltips = { - 'rumah_ibadah': 'Klik untuk Tambah Rumah Ibadah' - }; + const tooltips = { 'rumah_ibadah': 'Klik peta untuk tambah Rumah Ibadah' }; if (window.cursorTooltip && tooltips[mode]) window.cursorTooltip.textContent = tooltips[mode]; - - // Ubah kursor map menjadi crosshair document.getElementById('map').style.cursor = 'crosshair'; }; @@ -224,18 +208,16 @@ window.deactivateAddMode = function() { if (window.cursorTooltip) window.cursorTooltip.style.display = 'none'; }; -// --- Custom Tooltip Cursor --- +// ===== Cursor Tooltip ===== window.cursorTooltip = document.createElement('div'); window.cursorTooltip.className = 'custom-cursor-tooltip'; document.body.appendChild(window.cursorTooltip); -// Gunakan document-level mousemove agar selalu terpicu -// bahkan saat Leaflet Draw overlay aktif menangkap event map document.addEventListener('mousemove', function(e) { if (window.currentAddMode) { window.cursorTooltip.style.display = 'block'; window.cursorTooltip.style.left = e.pageX + 15 + 'px'; - window.cursorTooltip.style.top = e.pageY + 15 + 'px'; + window.cursorTooltip.style.top = e.pageY + 15 + 'px'; } else { window.cursorTooltip.style.display = 'none'; } diff --git a/webgis-poverty-pontianak/assets/js/panel.js b/webgis-poverty-pontianak/assets/js/panel.js index 7eeab4b..39e0857 100644 --- a/webgis-poverty-pontianak/assets/js/panel.js +++ b/webgis-poverty-pontianak/assets/js/panel.js @@ -16,8 +16,8 @@ let activePanel = null; // ---- Elements ---- - const sidebarToggleBtn = document.getElementById('sidebarToggleBtn'); - const sidebarToggleIcon = document.getElementById('sidebarToggleIcon'); + // removed + // removed const leftSidebar = document.getElementById('leftSidebar'); const rightPanel = document.getElementById('rightPanel'); const rightPanelTitle = document.getElementById('rightPanelTitle'); @@ -25,19 +25,7 @@ const rightPanelBody = document.getElementById('rightPanelBody'); const rightPanelClose = document.getElementById('rightPanelClose'); - // ---- Sidebar Toggle ---- - sidebarToggleBtn.addEventListener('click', function () { - const isOpen = leftSidebar.style.display !== 'none'; - if (isOpen) { - leftSidebar.style.display = 'none'; - sidebarToggleBtn.classList.remove('open'); - sidebarToggleIcon.className = 'fas fa-chevron-right'; - } else { - leftSidebar.style.display = 'flex'; - sidebarToggleBtn.classList.add('open'); - sidebarToggleIcon.className = 'fas fa-chevron-left'; - } - }); + // Sidebar collapse handled in map.js // ---- Sidebar Buttons ---- document.querySelectorAll('.sidebar-btn').forEach(btn => { @@ -83,8 +71,9 @@ rightPanelActions.innerHTML = ''; switch (type) { - case 'ibadah': renderIbadahPanel(); break; - case 'miskin': renderMiskinPanel(); break; + case 'ibadah': renderIbadahPanel(); break; + case 'miskin': renderMiskinPanel(); break; + case 'log_bantuan': renderLogBantuanPanel(); break; } } @@ -238,6 +227,59 @@ } } + + // ========================== + // ===== Log Bantuan Panel == + // ========================== + function renderLogBantuanPanel() { + rightPanelTitle.textContent = 'πŸ“‹ Log Bantuan'; + rightPanelActions.innerHTML = ''; + const all = []; + if (typeof pendudukMiskinLayer !== 'undefined') { + pendudukMiskinLayer.eachLayer(l => { if (l.miskinData) all.push(l.miskinData); }); + } + if (!all.length) { + rightPanelBody.innerHTML = '
Belum ada data penduduk miskin
'; + return; + } + rightPanelBody.innerHTML = ''; + const canLog = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola')); + + // Hint strip + const hint = document.createElement('div'); + hint.className = 'panel-hint'; + hint.innerHTML = ` ${canLog ? 'Klik penduduk untuk lihat atau tambah log bantuan.' : 'Klik penduduk untuk melihat riwayat log bantuan.'}`; + rightPanelBody.appendChild(hint); + + // Summary badge + const summary = document.createElement('div'); + summary.className = 'panel-section-title'; + summary.textContent = `${all.length} Kepala Keluarga`; + rightPanelBody.appendChild(summary); + + all.forEach(d => { + const isMakan = d.kategori_bantuan === 'Makan'; + const card = document.createElement('div'); + card.className = 'log-person-card'; + const badgeHtml = isMakan + ? 'Bantuan Makan' + : 'Pemberdayaan'; + const avatarCls = isMakan ? 'avatar-makan' : 'avatar-pmbr'; + card.innerHTML = ` +
🏠
+
+
${escHtml(d.nama)}
+
+ πŸ‘¨β€πŸ‘©β€πŸ‘§ ${d.jumlah_jiwa || '-'} jiwa + ${badgeHtml} +
+
+ `; + card.addEventListener('click', () => window.openLogBantuan(d.id, d.nama)); + rightPanelBody.appendChild(card); + }); + } + function buildMiskinCard(d, terurus) { // Pengelola hanya boleh edit/hapus penduduk yang dalam radius ibadahnya const isPengelolaOrAdmin = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola')); @@ -344,115 +386,140 @@ } }); - // ========================== - // ===== Log Bantuan Modal == - // ========================== - const logModal = document.getElementById('logBantuanModal'); - const logTitle = document.getElementById('logBantuanTitle'); - const logList = document.getElementById('logBantuanList'); - const logSaveBtn = document.getElementById('logSaveBtn'); - const logIbadahSel = document.getElementById('logIbadahId'); - let _logMiskinId = null; + // ========================================== + // ===== Log Bantuan Modal ================== + // ========================================== window.openLogBantuan = function (miskinId, nama) { - _logMiskinId = miskinId; - logTitle.textContent = `Log Bantuan β€” ${nama}`; - logModal.classList.add('show'); + const modal = document.getElementById('logBantuanModal'); + document.getElementById('logBantuanTitle').textContent = 'πŸ“‹ Log Bantuan β€” ' + nama; + document.getElementById('logBantuanMiskinId').value = miskinId; + modal.classList.add('show'); + loadLogBantuan(miskinId); - const isAuth = !!window.currentUser; - const formContainer = document.querySelector('#logBantuanModal .modal-body > div:last-child'); - const modalFooter = document.querySelector('#logBantuanModal .modal-footer'); - - if (isAuth) { - if (formContainer) formContainer.style.display = 'block'; - if (modalFooter) modalFooter.style.display = 'flex'; - } else { - if (formContainer) formContainer.style.display = 'none'; - if (modalFooter) modalFooter.style.display = 'none'; - } + // Tampilkan form tambah hanya untuk admin/pengelola + const canLog = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola')); + document.getElementById('logBantuanFormSection').style.display = canLog ? 'block' : 'none'; - // Isi dropdown ibadah - logIbadahSel.innerHTML = ''; - if (typeof ibadahDataList !== 'undefined') { - ibadahDataList.forEach(ib => { - const opt = document.createElement('option'); - opt.value = ib.id; - opt.textContent = `${IBADAH_EMOJI[ib.jenis] || 'πŸ•Œ'} ${ib.nama}`; - logIbadahSel.appendChild(opt); - }); - } - - if (window.currentUser && window.currentUser.role === 'pengelola') { - logIbadahSel.value = window.currentUser.ibadah_id; - logIbadahSel.disabled = true; - } else { - logIbadahSel.disabled = false; - } - - // Set tanggal default hari ini - document.getElementById('logTanggal').valueAsDate = new Date(); - fetchLogBantuan(miskinId); + // Isi dropdown ibadah jika pengelola (auto-select ibadahnya) + if (canLog) populateLogIbadahSelect(); }; window.closeLogBantuanModal = function () { - logModal.classList.remove('show'); - _logMiskinId = null; + document.getElementById('logBantuanModal').classList.remove('show'); }; - function fetchLogBantuan(miskinId) { - logList.innerHTML = '
Memuat...
'; - fetch(`api/log_bantuan/read.php?miskin_id=${miskinId}`) + function loadLogBantuan(miskinId) { + const listEl = document.getElementById('logBantuanList'); + listEl.innerHTML = '
Memuat log...
'; + fetch('api/log_bantuan/read.php?miskin_id=' + miskinId) .then(r => r.json()) - .then(data => { - if (!data.data || !data.data.length) { - logList.innerHTML = '
Belum ada riwayat bantuan
'; + .then(res => { + if (res.status !== 'success' || !res.data.length) { + listEl.innerHTML = '
Belum ada log bantuan tercatat.
'; return; } - logList.innerHTML = ''; - data.data.forEach(log => { - const tgl = new Date(log.tanggal).toLocaleDateString('id-ID', { day:'2-digit', month:'short', year:'numeric' }); + listEl.innerHTML = ''; + res.data.forEach(log => { const item = document.createElement('div'); item.className = 'log-item'; + const tgl = new Date(log.tanggal).toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }); item.innerHTML = ` -
-
-
${escHtml(log.tipe_bantuan)}
-
${IBADAH_EMOJI[log.jenis_ibadah] || 'πŸ•Œ'} ${escHtml(log.nama_ibadah)} β€’ ${tgl}
- ${log.keterangan ? `
${escHtml(log.keterangan)}
` : ''} -
`; - logList.appendChild(item); +
+ ${escHtml(log.tipe_bantuan)} + ${tgl} +
+
πŸ•Œ ${escHtml(log.nama_ibadah)} ${escHtml(log.jenis_ibadah)}
+ ${log.keterangan ? `
${escHtml(log.keterangan)}
` : ''} + `; + listEl.appendChild(item); }); }) .catch(() => { - logList.innerHTML = '
Gagal memuat data
'; + listEl.innerHTML = '
Gagal memuat data log.
'; }); } - logSaveBtn.addEventListener('click', function () { - const ibadah_id = logIbadahSel.value; - const tipe_bantuan = document.getElementById('logTipeBantuan').value; - const tanggal = document.getElementById('logTanggal').value; - const keterangan = document.getElementById('logKeterangan').value; + function populateLogIbadahSelect() { + const sel = document.getElementById('logIbadahId'); + sel.innerHTML = ''; + if (window.currentUser && window.currentUser.role === 'pengelola' && window.currentUser.ibadah_id) { + // Pengelola hanya bisa pilih ibadah miliknya β€” fetch dari layer + const id = window.currentUser.ibadah_id; + let found = false; + if (typeof rumahIbadahLayer !== 'undefined') { + rumahIbadahLayer.eachLayer(l => { + if (l.ibadahData) { + const opt = document.createElement('option'); + opt.value = l.ibadahData.id; + opt.textContent = l.ibadahData.nama; + if (l.ibadahData.id == id) { opt.selected = true; found = true; } + sel.appendChild(opt); + } + }); + } + } else { + // Admin bisa pilih semua + if (typeof rumahIbadahLayer !== 'undefined') { + rumahIbadahLayer.eachLayer(l => { + if (l.ibadahData) { + const opt = document.createElement('option'); + opt.value = l.ibadahData.id; + opt.textContent = l.ibadahData.nama; + sel.appendChild(opt); + } + }); + } + } + } - if (!ibadah_id) { alert('Pilih rumah ibadah terlebih dahulu!'); return; } - if (!tanggal) { alert('Tanggal harus diisi!'); return; } + document.getElementById('btnSaveLog').addEventListener('click', function () { + const miskinId = parseInt(document.getElementById('logBantuanMiskinId').value); + const ibadahId = parseInt(document.getElementById('logIbadahId').value); + const tipe = document.getElementById('logTipeBantuan').value.trim(); + const tanggal = document.getElementById('logTanggal').value; + const keterangan = document.getElementById('logKeterangan').value.trim(); + + if (!ibadahId || !tipe || !tanggal) { + window.showToast('Lengkapi semua field wajib', 'error'); + return; + } + + const btn = this; + btn.disabled = true; + btn.textContent = 'Menyimpan...'; fetch('api/log_bantuan/create.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ miskin_id: _logMiskinId, ibadah_id, tipe_bantuan, tanggal, keterangan }) + body: JSON.stringify({ miskin_id: miskinId, ibadah_id: ibadahId, tipe_bantuan: tipe, tanggal, keterangan }) }) .then(r => r.json()) - .then(data => { - if (data.status === 'success') { + .then(res => { + btn.disabled = false; + btn.textContent = 'Simpan Log'; + if (res.status === 'success') { + window.showToast('Log bantuan berhasil disimpan', 'success'); + document.getElementById('logTipeBantuan').value = ''; + document.getElementById('logTanggal').value = ''; document.getElementById('logKeterangan').value = ''; - fetchLogBantuan(_logMiskinId); + loadLogBantuan(miskinId); } else { - alert(data.message || 'Gagal menyimpan'); + window.showToast(res.message || 'Gagal menyimpan log', 'error'); } + }) + .catch(() => { + btn.disabled = false; + btn.textContent = 'Simpan Log'; + window.showToast('Terjadi kesalahan koneksi', 'error'); }); }); + // Close on overlay click + document.getElementById('logBantuanModal').addEventListener('click', function(e) { + if (e.target === this) window.closeLogBantuanModal(); + }); + // ---- Utility ---- function escHtml(str) { if (!str) return ''; diff --git a/webgis-poverty-pontianak/index.php b/webgis-poverty-pontianak/index.php index 447fe90..abcbac8 100644 --- a/webgis-poverty-pontianak/index.php +++ b/webgis-poverty-pontianak/index.php @@ -3,159 +3,157 @@ - WebGIS Pemetaan Kemiskinan + WebGIS Kemiskinan Pontianak - - + - + - - - -
- -