diff --git a/01/api/delete_data.php b/01/api/delete_data.php deleted file mode 100644 index a387307..0000000 --- a/01/api/delete_data.php +++ /dev/null @@ -1,21 +0,0 @@ - "error", "message" => "ID tidak valid"]); - exit; -} - -$sql = "DELETE FROM spbu WHERE id=$id"; - -if ($conn->query($sql)) { - echo json_encode(["status" => "success"]); -} else { - echo json_encode(["status" => "error", "message" => $conn->error]); -} -?> \ No newline at end of file diff --git a/01/api/delete_jalan.php b/01/api/delete_jalan.php deleted file mode 100644 index 08c4943..0000000 --- a/01/api/delete_jalan.php +++ /dev/null @@ -1,21 +0,0 @@ - "error", "message" => "ID tidak valid"]); - exit; -} - -$sql = "DELETE FROM jalan WHERE id=$id"; - -if ($conn->query($sql)) { - echo json_encode(["status" => "success"]); -} else { - echo json_encode(["status" => "error", "message" => $conn->error]); -} -?> \ No newline at end of file diff --git a/01/api/delete_parsil.php b/01/api/delete_parsil.php deleted file mode 100644 index 3f4496b..0000000 --- a/01/api/delete_parsil.php +++ /dev/null @@ -1,21 +0,0 @@ - "error", "message" => "ID tidak valid"]); - exit; -} - -$sql = "DELETE FROM parsil WHERE id=$id"; - -if ($conn->query($sql)) { - echo json_encode(["status" => "success"]); -} else { - echo json_encode(["status" => "error", "message" => $conn->error]); -} -?> \ No newline at end of file diff --git a/01/api/read_data.php b/01/api/read_data.php deleted file mode 100644 index 64845fd..0000000 --- a/01/api/read_data.php +++ /dev/null @@ -1,25 +0,0 @@ -query($sql); - -if (!$result) { -echo json_encode([ -"status" => "error", -"message" => "Gagal membaca tabel spbu", -"detail" => $conn->error -]); -exit; -} - -$data = []; - -while($row = $result->fetch_assoc()){ -$data[] = $row; -} - -echo json_encode($data); -?> \ No newline at end of file diff --git a/01/api/read_jalan.php b/01/api/read_jalan.php deleted file mode 100644 index 65ab6d4..0000000 --- a/01/api/read_jalan.php +++ /dev/null @@ -1,25 +0,0 @@ -query($sql); - -if (!$result) { -echo json_encode([ -"status" => "error", -"message" => "Gagal membaca tabel jalan", -"detail" => $conn->error -]); -exit; -} - -$data = []; - -while($row = $result->fetch_assoc()){ -$data[] = $row; -} - -echo json_encode($data); -?> \ No newline at end of file diff --git a/01/api/read_parsil.php b/01/api/read_parsil.php deleted file mode 100644 index 078a4cc..0000000 --- a/01/api/read_parsil.php +++ /dev/null @@ -1,25 +0,0 @@ -query($sql); - -if (!$result) { -echo json_encode([ -"status" => "error", -"message" => "Gagal membaca tabel parsil", -"detail" => $conn->error -]); -exit; -} - -$data = []; - -while($row = $result->fetch_assoc()){ -$data[] = $row; -} - -echo json_encode($data); -?> \ No newline at end of file diff --git a/01/api/save_data.php b/01/api/save_data.php deleted file mode 100644 index 62b6c1f..0000000 --- a/01/api/save_data.php +++ /dev/null @@ -1,53 +0,0 @@ - "error", "message" => "Data tidak lengkap"]); - exit; -} - -try { - $stmt = $conn->prepare("INSERT INTO spbu (nama,no,status,lat,lng) VALUES (?,?,?,?,?)"); - if(!$stmt) { - $err = $conn->error; - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " SPBU PREPARE ERR: $err\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"prepare","message"=>$err]); - exit; - } - - $stmt->bind_param("sssdd", $nama, $no, $status, $lat, $lng); - - if($stmt->execute()){ - $id = $stmt->insert_id; - ob_clean(); - echo json_encode(["status"=>"success","id"=>$id]); - } else { - $err = $stmt->error; - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " SPBU EXEC ERR: $err | Payload: $raw\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"execute","message"=>$err]); - } - $stmt->close(); -} catch (Exception $e) { - $err = $e->getMessage(); - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " SPBU EXCEPTION: $err | Payload: $raw\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"exception","message"=>$err]); -} -?> \ No newline at end of file diff --git a/01/api/save_jalan.php b/01/api/save_jalan.php deleted file mode 100644 index 828e75c..0000000 --- a/01/api/save_jalan.php +++ /dev/null @@ -1,59 +0,0 @@ - "error", "message" => "Nama tidak boleh kosong"]); - exit; -} - -try { - $stmt = $conn->prepare("INSERT INTO jalan (nama,status,panjang,koordinat) VALUES (?,?,?,?)"); - if(!$stmt) { - $err = $conn->error; - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " JALAN PREPARE ERR: $err\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"prepare","message"=>$err]); - exit; - } - - $stmt->bind_param("ssds", $nama, $status, $panjang, $koordinat); - - if($stmt->execute()){ - $id = $stmt->insert_id; - ob_clean(); - echo json_encode(["status"=>"success","id"=>$id]); - } else { - $err = $stmt->error; - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " JALAN EXEC ERR: $err | Payload: $raw\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"execute","message"=>$err]); - } - $stmt->close(); -} catch (Exception $e) { - $err = $e->getMessage(); - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " JALAN EXCEPTION: $err | Payload: $raw\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"exception","message"=>$err]); -} -?> \ No newline at end of file diff --git a/01/api/save_parsil.php b/01/api/save_parsil.php deleted file mode 100644 index 2542008..0000000 --- a/01/api/save_parsil.php +++ /dev/null @@ -1,60 +0,0 @@ - "error", "message" => "Nama tidak boleh kosong"]); - exit; -} - -try { - $stmt = $conn->prepare("INSERT INTO parsil (nama, pemilik, status, luas, koordinat) VALUES (?,?,?,?,?)"); - if(!$stmt) { - $err = $conn->error; - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " PARSIL PREPARE ERR: $err\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"prepare","message"=>$err]); - exit; - } - - $stmt->bind_param("sssds", $nama, $pemilik, $status, $luas, $koordinat); - - if($stmt->execute()){ - $id = $stmt->insert_id; - ob_clean(); - echo json_encode(["status"=>"success","id"=>$id]); - } else { - $err = $stmt->error; - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " PARSIL EXEC ERR: $err | Payload: $raw\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"execute","message"=>$err]); - } - $stmt->close(); -} catch (Exception $e) { - $err = $e->getMessage(); - file_put_contents('error_log.txt', date('[Y-m-d H:i:s]') . " PARSIL EXCEPTION: $err | Payload: $raw\n", FILE_APPEND); - ob_clean(); - echo json_encode(["status"=>"error","step"=>"exception","message"=>$err]); -} -?> \ No newline at end of file diff --git a/01/api/update_data.php b/01/api/update_data.php deleted file mode 100644 index f5f82b3..0000000 --- a/01/api/update_data.php +++ /dev/null @@ -1,27 +0,0 @@ -real_escape_string($data['nama'] ?? ''); -$no = $conn->real_escape_string($data['no'] ?? ''); -$status = $conn->real_escape_string($data['status'] ?? 'Tidak'); -$lat = isset($data['lat']) ? floatval($data['lat']) : 0; -$lng = isset($data['lng']) ? floatval($data['lng']) : 0; - -if ($id <= 0 || $nama === '' || $no === '') { - echo json_encode(["status" => "error", "message" => "Data tidak valid"]); - exit; -} - -$sql = "UPDATE spbu SET nama='$nama', no='$no', status='$status', lat='$lat', lng='$lng' WHERE id=$id"; - -if ($conn->query($sql)) { - echo json_encode(["status" => "success"]); -} else { - echo json_encode(["status" => "error", "message" => $conn->error]); -} -?> \ No newline at end of file diff --git a/01/api/update_jalan.php b/01/api/update_jalan.php deleted file mode 100644 index 4ce6b3e..0000000 --- a/01/api/update_jalan.php +++ /dev/null @@ -1,35 +0,0 @@ -real_escape_string($data['nama'] ?? ''); -$status = $conn->real_escape_string($data['status'] ?? 'Kabupaten'); -$panjang = isset($data['panjang']) ? floatval($data['panjang']) : 0; -$koordinatInput = $data['koordinat'] ?? []; - -if ($id <= 0 || $nama === '') { - echo json_encode(["status" => "error", "message" => "Data tidak valid"]); - exit; -} - -if (is_string($koordinatInput)) { - $decoded = json_decode($koordinatInput, true); - $koordinat = (json_last_error() === JSON_ERROR_NONE) ? json_encode($decoded) : '[]'; -} else { - $koordinat = json_encode($koordinatInput); -} - -$koordinat = $conn->real_escape_string($koordinat); - -$sql = "UPDATE jalan SET nama='$nama', status='$status', panjang='$panjang', koordinat='$koordinat' WHERE id=$id"; - -if ($conn->query($sql)) { - echo json_encode(["status" => "success"]); -} else { - echo json_encode(["status" => "error", "message" => $conn->error]); -} -?> \ No newline at end of file diff --git a/01/api/update_parsil.php b/01/api/update_parsil.php deleted file mode 100644 index b8b225c..0000000 --- a/01/api/update_parsil.php +++ /dev/null @@ -1,36 +0,0 @@ -real_escape_string($data['nama'] ?? ''); -$pemilik = $conn->real_escape_string($data['pemilik'] ?? ''); -$status = $conn->real_escape_string($data['status'] ?? 'SHM'); -$luas = isset($data['luas']) ? floatval($data['luas']) : 0; -$koordinatInput = $data['koordinat'] ?? []; - -if ($id <= 0 || $nama === '') { - echo json_encode(["status" => "error", "message" => "Data tidak valid"]); - exit; -} - -if (is_string($koordinatInput)) { - $decoded = json_decode($koordinatInput, true); - $koordinat = (json_last_error() === JSON_ERROR_NONE) ? json_encode($decoded) : '[]'; -} else { - $koordinat = json_encode($koordinatInput); -} - -$koordinat = $conn->real_escape_string($koordinat); - -$sql = "UPDATE parsil SET nama='$nama', pemilik='$pemilik', status='$status', luas='$luas', koordinat='$koordinat' WHERE id=$id"; - -if ($conn->query($sql)) { - echo json_encode(["status" => "success"]); -} else { - echo json_encode(["status" => "error", "message" => $conn->error]); -} -?> \ No newline at end of file diff --git a/01/index.html b/01/index.html index 1782b23..b1f1e38 100644 --- a/01/index.html +++ b/01/index.html @@ -1319,8 +1319,13 @@ top: -30px; } - .path-glow { filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.35)); } - .parsil-glow { filter: drop-shadow(0px 2px 6px rgba(0,0,0,0.25)); } + .path-glow { + filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.35)); + } + + .parsil-glow { + filter: drop-shadow(0px 2px 6px rgba(0, 0, 0, 0.25)); + } @keyframes markerPulse { 0% { @@ -1487,15 +1492,19 @@ padding: 0 !important; border-radius: 14px !important; overflow: hidden !important; - box-shadow: 0 20px 60px rgba(15,23,42,.18) !important; + box-shadow: 0 20px 60px rgba(15, 23, 42, .18) !important; border: 1px solid var(--border) !important; } + .leaflet-popup-content { margin: 0 !important; width: auto !important; min-width: 260px !important; } - .leaflet-popup-tip-container { margin-top: -1px; } + + .leaflet-popup-tip-container { + margin-top: -1px; + } /* Sembunyikan X default Leaflet — semua popup pakai custom close */ .leaflet-popup-close-button { @@ -1522,14 +1531,17 @@ width: 36px; height: 36px; border-radius: 10px; - background: rgba(255,255,255,.18); + background: rgba(255, 255, 255, .18); display: grid; place-items: center; font-size: 15px; flex-shrink: 0; } - .pi-header-info { flex: 1; min-width: 0; } + .pi-header-info { + flex: 1; + min-width: 0; + } .pi-header-name { font-weight: 800; @@ -1551,7 +1563,7 @@ height: 26px; border: none; border-radius: 7px; - background: rgba(255,255,255,.18); + background: rgba(255, 255, 255, .18); color: #fff; cursor: pointer; display: grid; @@ -1562,7 +1574,9 @@ margin-left: auto; } - .pi-close:hover { background: rgba(255,255,255,.32); } + .pi-close:hover { + background: rgba(255, 255, 255, .32); + } .pi-close-alt { width: 26px; @@ -1579,7 +1593,11 @@ flex-shrink: 0; margin-left: auto; } - .pi-close-alt:hover { background: #e2e8f0; color: var(--red); } + + .pi-close-alt:hover { + background: #e2e8f0; + color: var(--red); + } .pi-body { padding: 10px 14px 6px; @@ -1598,7 +1616,9 @@ font-size: 12px; } - .pi-row:last-child { border-bottom: none; } + .pi-row:last-child { + border-bottom: none; + } .pi-k { color: var(--muted); @@ -1622,9 +1642,20 @@ letter-spacing: .2px; } - .pi-badge.extreme { background:#fee2e2; color:#991b1b; } - .pi-badge.poor { background:#ffedd5; color:#9a3412; } - .pi-badge.vulnerable { background:#fef3c7; color:#92400e; } + .pi-badge.extreme { + background: #fee2e2; + color: #991b1b; + } + + .pi-badge.poor { + background: #ffedd5; + color: #9a3412; + } + + .pi-badge.vulnerable { + background: #fef3c7; + color: #92400e; + } /* ── ACTION ROW ── */ .act-row { @@ -1655,7 +1686,7 @@ width: 32px; height: 32px; border-radius: 9px; - background: rgba(255,255,255,.2); + background: rgba(255, 255, 255, .2); display: grid; place-items: center; font-size: 14px; @@ -1674,7 +1705,7 @@ height: 26px; border: none; border-radius: 7px; - background: rgba(255,255,255,.18); + background: rgba(255, 255, 255, .18); color: #fff; cursor: pointer; display: grid; @@ -1684,7 +1715,9 @@ flex-shrink: 0; } - .km-close:hover { background: rgba(255,255,255,.3); } + .km-close:hover { + background: rgba(255, 255, 255, .3); + } .km-body { padding: 14px; @@ -1692,8 +1725,14 @@ overflow-y: auto; } - .km-body::-webkit-scrollbar { width: 4px; } - .km-body::-webkit-scrollbar-thumb { background: #e2e8f0; border-radius: 4px; } + .km-body::-webkit-scrollbar { + width: 4px; + } + + .km-body::-webkit-scrollbar-thumb { + background: #e2e8f0; + border-radius: 4px; + } .km-section { font-size: 10px; @@ -1706,7 +1745,9 @@ border-bottom: 1.5px solid #dbeafe; } - .km-section:first-child { margin-top: 0; } + .km-section:first-child { + margin-top: 0; + } .km-field { margin-bottom: 10px; @@ -1746,11 +1787,14 @@ .km-field textarea:focus, .km-field select:focus { border-color: var(--blue); - box-shadow: 0 0 0 3px rgba(59,130,246,.12); + box-shadow: 0 0 0 3px rgba(59, 130, 246, .12); } .km-field input::placeholder, - .km-field textarea::placeholder { color: #94a3b8; font-weight: 500; } + .km-field textarea::placeholder { + color: #94a3b8; + font-weight: 500; + } .km-gps-box { border-radius: 10px; @@ -1767,9 +1811,23 @@ flex-wrap: wrap; } - .km-gps-row span { color: rgba(255,255,255,.7); font-size: 11px; font-weight: 600; } - .km-gps-row strong { color: #fff; font-size: 13px; font-weight: 700; } - .km-gps-row i { color: rgba(255,255,255,.8); width: 16px; text-align: center; } + .km-gps-row span { + color: rgba(255, 255, 255, .7); + font-size: 11px; + font-weight: 600; + } + + .km-gps-row strong { + color: #fff; + font-size: 13px; + font-weight: 700; + } + + .km-gps-row i { + color: rgba(255, 255, 255, .8); + width: 16px; + text-align: center; + } .km-btn { padding: 9px 16px; @@ -1786,14 +1844,19 @@ flex: 1; } - .km-btn-save:hover { background: #1d4ed8; transform: translateY(-1px); } + .km-btn-save:hover { + background: #1d4ed8; + transform: translateY(-1px); + } .km-btn-cancel { background: var(--panel2); color: var(--navy); } - .km-btn-cancel:hover { background: #e2e8f0; } + .km-btn-cancel:hover { + background: #e2e8f0; + } .km-foot { display: flex; @@ -1802,7 +1865,10 @@ border-top: 1px solid var(--border); } - .req { color: var(--red); margin-left: 2px; } + .req { + color: var(--red); + margin-left: 2px; + } .km-hint { color: var(--muted); @@ -1893,7 +1959,10 @@ background: #fff; } - .km-check:hover { border-color: var(--blue); background: #eff6ff; } + .km-check:hover { + border-color: var(--blue); + background: #eff6ff; + } .km-check input[type=checkbox] { width: 14px; @@ -1909,9 +1978,22 @@ gap: 6px; } - .km-gps-item span { color: rgba(255,255,255,.7); font-size: 10px; font-weight: 600; } - .km-gps-item strong { color: #fff; font-size: 12px; font-weight: 700; font-family: monospace; } - .km-gps-item i { color: rgba(255,255,255,.8); } + .km-gps-item span { + color: rgba(255, 255, 255, .7); + font-size: 10px; + font-weight: 600; + } + + .km-gps-item strong { + color: #fff; + font-size: 12px; + font-weight: 700; + font-family: monospace; + } + + .km-gps-item i { + color: rgba(255, 255, 255, .8); + } /* ── VULNERABILITY BOX ── */ .km-vuln-box { @@ -1926,7 +2008,11 @@ margin: 6px 0 10px; } - .kvb-left { display: flex; flex-direction: column; gap: 3px; } + .kvb-left { + display: flex; + flex-direction: column; + gap: 3px; + } .kvb-label { font-size: 10px; @@ -1963,7 +2049,10 @@ } /* ── MASJID FORM GPS MAP ── */ - #m-map, #e-m-map, #k-map, #e-k-map { + #m-map, + #e-m-map, + #k-map, + #e-k-map { height: 110px; border-radius: 8px; overflow: hidden; @@ -1976,7 +2065,7 @@ align-items: flex-start; gap: 7px; padding: 8px 12px 10px; - border-top: 1px solid rgba(255,255,255,.12); + border-top: 1px solid rgba(255, 255, 255, .12); } .km-addr-row span, @@ -1990,7 +2079,7 @@ .km-gps-lbl { font-size: 10px !important; font-weight: 700 !important; - color: rgba(255,255,255,.65) !important; + color: rgba(255, 255, 255, .65) !important; letter-spacing: .8px; text-transform: uppercase; } @@ -2790,7 +2879,7 @@ #cp-modal { position: fixed; inset: 0; - background: rgba(15,23,42,.55); + background: rgba(15, 23, 42, .55); backdrop-filter: blur(4px); z-index: 9999; display: flex; @@ -2798,101 +2887,238 @@ justify-content: center; animation: riseIn .2s ease; } - #cp-modal.hidden { display: none; } + + #cp-modal.hidden { + display: none; + } + .cp-box { background: #fff; border-radius: 20px; width: 420px; max-width: 95vw; - box-shadow: 0 28px 80px rgba(15,23,42,.25); + box-shadow: 0 28px 80px rgba(15, 23, 42, .25); overflow: hidden; - animation: riseIn .25s cubic-bezier(.16,1,.3,1); + animation: riseIn .25s cubic-bezier(.16, 1, .3, 1); } + .cp-head { display: flex; align-items: center; justify-content: space-between; padding: 18px 22px; - background: linear-gradient(135deg,#1e3a8a,#2563eb); + background: linear-gradient(135deg, #1e3a8a, #2563eb); color: #fff; } - .cp-head-left { display:flex; align-items:center; gap:10px; } + + .cp-head-left { + display: flex; + align-items: center; + gap: 10px; + } + .cp-head-icon { - width: 34px; height: 34px; border-radius: 10px; - background: rgba(255,255,255,.15); - display: grid; place-items: center; + width: 34px; + height: 34px; + border-radius: 10px; + background: rgba(255, 255, 255, .15); + display: grid; + place-items: center; font-size: 15px; } - .cp-head h3 { font-size:15px; font-weight:800; } - .cp-head p { font-size:11px; opacity:.7; margin-top:1px; } + + .cp-head h3 { + font-size: 15px; + font-weight: 800; + } + + .cp-head p { + font-size: 11px; + opacity: .7; + margin-top: 1px; + } + .cp-close { - width:30px; height:30px; border-radius:8px; - background: rgba(255,255,255,.15); - border: none; color: #fff; cursor: pointer; - display: grid; place-items: center; font-size:14px; + width: 30px; + height: 30px; + border-radius: 8px; + background: rgba(255, 255, 255, .15); + border: none; + color: #fff; + cursor: pointer; + display: grid; + place-items: center; + font-size: 14px; transition: background .15s; } - .cp-close:hover { background: rgba(255,255,255,.3); } - .cp-body { padding: 24px; } - .cp-field { margin-bottom: 16px; } + + .cp-close:hover { + background: rgba(255, 255, 255, .3); + } + + .cp-body { + padding: 24px; + } + + .cp-field { + margin-bottom: 16px; + } + .cp-field label { - display:block; font-size:11px; font-weight:800; - text-transform:uppercase; letter-spacing:.5px; - color: #0f172a; margin-bottom:6px; + display: block; + font-size: 11px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: .5px; + color: #0f172a; + margin-bottom: 6px; } - .cp-input-wrap { position: relative; } + + .cp-input-wrap { + position: relative; + } + .cp-input-wrap .cp-ico { - position:absolute; left:13px; top:50%; transform:translateY(-50%); - color: #94a3b8; font-size:13px; pointer-events:none; + position: absolute; + left: 13px; + top: 50%; + transform: translateY(-50%); + color: #94a3b8; + font-size: 13px; + pointer-events: none; } + .cp-input-wrap input { - width:100%; padding:12px 42px; - background:#f1f5f9; border:1.5px solid #e2e8f0; - border-radius:11px; font:500 14px 'Inter',sans-serif; - color:#1e293b; outline:none; + width: 100%; + padding: 12px 42px; + background: #f1f5f9; + border: 1.5px solid #e2e8f0; + border-radius: 11px; + font: 500 14px 'Inter', sans-serif; + color: #1e293b; + outline: none; transition: border-color .2s, box-shadow .2s, background .2s; } - .cp-input-wrap input::placeholder { color:#94a3b8; font-weight:400; } - .cp-input-wrap input:focus { - background:#fff; border-color:#3b82f6; - box-shadow:0 0 0 4px rgba(59,130,246,.1); + + .cp-input-wrap input::placeholder { + color: #94a3b8; + font-weight: 400; } + + .cp-input-wrap input:focus { + background: #fff; + border-color: #3b82f6; + box-shadow: 0 0 0 4px rgba(59, 130, 246, .1); + } + .cp-eye { - position:absolute; right:12px; top:50%; transform:translateY(-50%); - background:none; border:none; cursor:pointer; - color:#94a3b8; font-size:13px; padding:4px; + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + cursor: pointer; + color: #94a3b8; + font-size: 13px; + padding: 4px; transition: color .2s; } - .cp-eye:hover { color:#1e293b; } - .cp-strength { height:4px; border-radius:4px; background:#e2e8f0; margin-top:8px; overflow:hidden; } - .cp-strength-fill { height:100%; border-radius:4px; width:0; transition:width .3s, background .3s; } - .cp-strength-lbl { font-size:11px; color:#94a3b8; margin-top:4px; font-weight:600; } - .cp-alert { - display:none; align-items:center; gap:9px; - padding:12px 14px; border-radius:11px; - font-size:13px; font-weight:600; margin-bottom:16px; + + .cp-eye:hover { + color: #1e293b; } - .cp-alert.show { display:flex; } - .cp-alert.err { background:#fff1f2; border:1.5px solid #fecdd3; color:#be123c; } - .cp-alert.ok { background:#f0fdf4; border:1.5px solid #bbf7d0; color:#15803d; } + + .cp-strength { + height: 4px; + border-radius: 4px; + background: #e2e8f0; + margin-top: 8px; + overflow: hidden; + } + + .cp-strength-fill { + height: 100%; + border-radius: 4px; + width: 0; + transition: width .3s, background .3s; + } + + .cp-strength-lbl { + font-size: 11px; + color: #94a3b8; + margin-top: 4px; + font-weight: 600; + } + + .cp-alert { + display: none; + align-items: center; + gap: 9px; + padding: 12px 14px; + border-radius: 11px; + font-size: 13px; + font-weight: 600; + margin-bottom: 16px; + } + + .cp-alert.show { + display: flex; + } + + .cp-alert.err { + background: #fff1f2; + border: 1.5px solid #fecdd3; + color: #be123c; + } + + .cp-alert.ok { + background: #f0fdf4; + border: 1.5px solid #bbf7d0; + color: #15803d; + } + .cp-submit { - width:100%; padding:13px; - background: linear-gradient(135deg,#1d4ed8,#3b82f6); - border:none; border-radius:11px; - color:#fff; font:700 14px 'Inter',sans-serif; - cursor:pointer; display:flex; align-items:center; - justify-content:center; gap:8px; - box-shadow: 0 6px 18px rgba(37,99,235,.28); + width: 100%; + padding: 13px; + background: linear-gradient(135deg, #1d4ed8, #3b82f6); + border: none; + border-radius: 11px; + color: #fff; + font: 700 14px 'Inter', sans-serif; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + box-shadow: 0 6px 18px rgba(37, 99, 235, .28); transition: all .25s; } - .cp-submit:hover { transform:translateY(-2px); box-shadow:0 12px 28px rgba(37,99,235,.38); } - .cp-submit:disabled { opacity:.6; transform:none; cursor:not-allowed; } - .cp-footer { - display:flex; align-items:center; justify-content:center; - gap:6px; margin-top:14px; - font-size:11px; color:#94a3b8; + + .cp-submit:hover { + transform: translateY(-2px); + box-shadow: 0 12px 28px rgba(37, 99, 235, .38); + } + + .cp-submit:disabled { + opacity: .6; + transform: none; + cursor: not-allowed; + } + + .cp-footer { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + margin-top: 14px; + font-size: 11px; + color: #94a3b8; + } + + .cp-footer i { + color: #10b981; } - .cp-footer i { color:#10b981; } /* ══════════════════════════════════════════ UI ENHANCEMENT — SiPetaSos Premium Look @@ -2900,34 +3126,50 @@ /* ── STAT PILLS — Glassmorphism upgrade ── */ .stat-pill { - background: rgba(255,255,255,.96) !important; + background: rgba(255, 255, 255, .96) !important; border: none !important; border-radius: 14px !important; padding: 9px 14px !important; - box-shadow: 0 4px 24px rgba(15,23,42,.1), 0 1px 4px rgba(15,23,42,.06) !important; + box-shadow: 0 4px 24px rgba(15, 23, 42, .1), 0 1px 4px rgba(15, 23, 42, .06) !important; gap: 10px !important; transition: transform .2s, box-shadow .2s; } - .stat-pill:hover { transform: translateY(-2px); box-shadow: 0 8px 32px rgba(15,23,42,.14) !important; } + + .stat-pill:hover { + transform: translateY(-2px); + box-shadow: 0 8px 32px rgba(15, 23, 42, .14) !important; + } + .stat-pill i { - width: 34px !important; height: 34px !important; + width: 34px !important; + height: 34px !important; border-radius: 10px !important; font-size: 14px !important; } - .stat-pill strong { font-size: 17px !important; font-weight: 800 !important; } - .stat-pill small { font-size: 10px !important; font-weight: 600 !important; color: #94a3b8 !important; letter-spacing:.3px; } + + .stat-pill strong { + font-size: 17px !important; + font-weight: 800 !important; + } + + .stat-pill small { + font-size: 10px !important; + font-weight: 600 !important; + color: #94a3b8 !important; + letter-spacing: .3px; + } /* ── HINT BAR — Modern pill ── */ #hint { - background: linear-gradient(135deg, rgba(15,23,42,.92), rgba(30,58,138,.88)) !important; + background: linear-gradient(135deg, rgba(15, 23, 42, .92), rgba(30, 58, 138, .88)) !important; backdrop-filter: blur(8px) !important; - border: 1px solid rgba(255,255,255,.12) !important; + border: 1px solid rgba(255, 255, 255, .12) !important; padding: 12px 22px !important; border-radius: 999px !important; font-size: 13px !important; font-weight: 700 !important; letter-spacing: .2px; - box-shadow: 0 8px 32px rgba(15,23,42,.25) !important; + box-shadow: 0 8px 32px rgba(15, 23, 42, .25) !important; } /* ── POPUP FORM .pf — Complete redesign ── */ @@ -2936,6 +3178,7 @@ padding: 0; min-width: 260px; } + .pf h4 { display: flex !important; align-items: center !important; @@ -2948,11 +3191,13 @@ border-bottom: none !important; border-radius: 0; } - .pf h4 i { font-size: 15px !important; opacity: .9; } + + .pf h4 i { + font-size: 15px !important; + opacity: .9; + } + /* Color the h4 based on icon color */ - .pf h4:has(.fa-gas-pump) { background: linear-gradient(135deg,#064e3b,#059669); } - .pf h4:has(.fa-road) { background: linear-gradient(135deg,#1e3a8a,#2563eb); } - .pf h4:has(.fa-vector-square){ background: linear-gradient(135deg,#78350f,#d97706); } .pf label { display: block; @@ -2963,7 +3208,10 @@ color: #64748b !important; margin: 12px 16px 5px !important; } - .pf input, .pf select, .pf textarea { + + .pf input, + .pf select, + .pf textarea { display: block; box-sizing: border-box !important; width: auto !important; @@ -2971,12 +3219,13 @@ padding: 9px 12px !important; border: 1.5px solid #e2e8f0 !important; border-radius: 9px !important; - font: 600 13px 'Inter',sans-serif !important; + font: 600 13px 'Inter', sans-serif !important; color: #1e293b !important; background: #f8fafc !important; outline: none; transition: border-color .2s, box-shadow .2s, background .2s; } + .pf textarea { resize: vertical; min-height: 60px; @@ -2984,20 +3233,29 @@ font-size: 11px !important; font-weight: 500 !important; } - .pf input:focus, .pf select:focus, .pf textarea:focus { + + .pf input:focus, + .pf select:focus, + .pf textarea:focus { border-color: #3b82f6 !important; background: #fff !important; - box-shadow: 0 0 0 3px rgba(59,130,246,.12) !important; + box-shadow: 0 0 0 3px rgba(59, 130, 246, .12) !important; + } + + .pf select { + cursor: pointer; } - .pf select { cursor: pointer; } .pf .coord-row { - display: flex; gap: 8px; + display: flex; + gap: 8px; margin: 0 16px !important; padding: 0 !important; } + .pf .coord-row input { - flex: 1; margin: 0 !important; + flex: 1; + margin: 0 !important; width: auto !important; background: #f1f5f9 !important; color: #64748b !important; @@ -3011,9 +3269,15 @@ font-size: 11px !important; color: #94a3b8 !important; margin: 6px 16px 0 !important; - display: flex; align-items: center; gap: 5px; + display: flex; + align-items: center; + gap: 5px; + } + + .pf .hint-txt::before { + content: '⚡'; + font-size: 10px; } - .pf .hint-txt::before { content: '⚡'; font-size: 10px; } .pf .pbtn { display: block !important; @@ -3022,28 +3286,35 @@ margin: 12px 16px 14px !important; padding: 10px !important; border-radius: 10px !important; - font: 700 13px 'Inter',sans-serif !important; + font: 700 13px 'Inter', sans-serif !important; border: none !important; cursor: pointer !important; text-align: center; transition: all .2s !important; } + .pf .pbtn-g { - background: linear-gradient(135deg,#059669,#10b981) !important; + background: linear-gradient(135deg, #059669, #10b981) !important; color: #fff !important; - box-shadow: 0 4px 14px rgba(16,185,129,.3); + box-shadow: 0 4px 14px rgba(16, 185, 129, .3); } + .pf .pbtn-b { - background: linear-gradient(135deg,#1d4ed8,#3b82f6) !important; + background: linear-gradient(135deg, #1d4ed8, #3b82f6) !important; color: #fff !important; - box-shadow: 0 4px 14px rgba(37,99,235,.3); + box-shadow: 0 4px 14px rgba(37, 99, 235, .3); } + .pf .pbtn-y { - background: linear-gradient(135deg,#d97706,#f59e0b) !important; + background: linear-gradient(135deg, #d97706, #f59e0b) !important; color: #fff !important; - box-shadow: 0 4px 14px rgba(217,119,6,.3); + box-shadow: 0 4px 14px rgba(217, 119, 6, .3); + } + + .pf .pbtn:hover { + transform: translateY(-2px) !important; + filter: brightness(1.06); } - .pf .pbtn:hover { transform: translateY(-2px) !important; filter: brightness(1.06); } /* ── LEAFLET LAYER CONTROL — OPSIONAL section ── */ .lc-group-title { @@ -3056,7 +3327,11 @@ border-top: 1px solid #f1f5f9; margin-top: 4px !important; } - .lc-group-title:first-child { border-top: none; margin-top: 0; } + + .lc-group-title:first-child { + border-top: none; + margin-top: 0; + } /* ── TOPBAR search upgrade ── */ .map-search input { @@ -3064,24 +3339,47 @@ font-family: 'Inter', sans-serif !important; font-size: 13px !important; } + .map-search button { border-radius: 0 10px 10px 0 !important; } /* ── ADD DROPDOWN upgrade ── */ - .add-dropdown { border-radius: 16px !important; } + .add-dropdown { + border-radius: 16px !important; + } + .add-item { border-radius: 10px !important; margin: 2px 4px !important; transition: background .15s, transform .15s !important; } - .add-item:hover { transform: translateX(3px) !important; } - .ai-icon { border-radius: 10px !important; font-size: 15px !important; } - .add-item span:last-child { font-size: 13px !important; font-weight: 700 !important; } - .add-item small { color: #94a3b8 !important; font-weight: 500 !important; font-size: 11px !important; } + + .add-item:hover { + transform: translateX(3px) !important; + } + + .ai-icon { + border-radius: 10px !important; + font-size: 15px !important; + } + + .add-item span:last-child { + font-size: 13px !important; + font-weight: 700 !important; + } + + .add-item small { + color: #94a3b8 !important; + font-weight: 500 !important; + font-size: 11px !important; + } /* ── SIDEBAR brand upgrade ── */ - .brand-title { font-size: 17px !important; } + .brand-title { + font-size: 17px !important; + } + .brand-sub { font-size: 10px !important; color: #94a3b8 !important; @@ -3091,157 +3389,595 @@ } /* ── USER DROPDOWN upgrade ── */ - .user-dropdown { border-radius: 16px !important; min-width: 200px !important; } - .ud-item { border-radius: 10px !important; margin: 1px 4px !important; } - .ud-header { border-bottom: 1px solid #f1f5f9; padding-bottom: 10px !important; } - .ud-name { font-size: 13px !important; } + .user-dropdown { + border-radius: 16px !important; + min-width: 200px !important; + } + + .ud-item { + border-radius: 10px !important; + margin: 1px 4px !important; + } + + .ud-header { + border-bottom: 1px solid #f1f5f9; + padding-bottom: 10px !important; + } + + .ud-name { + font-size: 13px !important; + } /* ── SCROLLBAR refinement ── */ - ::-webkit-scrollbar { width: 5px; height: 5px; } - ::-webkit-scrollbar-track { background: transparent; } - ::-webkit-scrollbar-thumb { background: #e2e8f0; border-radius: 999px; } - ::-webkit-scrollbar-thumb:hover { background: #cbd5e1; } + ::-webkit-scrollbar { + width: 5px; + height: 5px; + } + + ::-webkit-scrollbar-track { + background: transparent; + } + + ::-webkit-scrollbar-thumb { + background: #e2e8f0; + border-radius: 999px; + } + + ::-webkit-scrollbar-thumb:hover { + background: #cbd5e1; + } /* ── FULL TABLE MODAL CSS ── */ .ftm-overlay { - position: fixed; inset: 0; z-index: 10000; + position: fixed; + inset: 0; + z-index: 10000; background: rgba(15, 23, 42, 0.75); backdrop-filter: blur(8px); - display: flex; align-items: center; justify-content: center; + display: flex; + align-items: center; + justify-content: center; padding: 20px; animation: fadeIn .25s ease; } + .ftm-box { - background: var(--bg); border-radius: 16px; - width: 100%; max-width: 1200px; max-height: 90vh; - display: flex; flex-direction: column; - box-shadow: 0 25px 60px rgba(0,0,0,0.25); + background: var(--bg); + border-radius: 16px; + width: 100%; + max-width: 1200px; + max-height: 90vh; + display: flex; + flex-direction: column; + box-shadow: 0 25px 60px rgba(0, 0, 0, 0.25); overflow: hidden; - animation: riseIn .3s cubic-bezier(.16,1,.3,1); + animation: riseIn .3s cubic-bezier(.16, 1, .3, 1); } + .ftm-head { - display: flex; align-items: center; justify-content: space-between; - padding: 20px 24px; background: #fff; border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + background: #fff; + border-bottom: 1px solid var(--border); } - .ftm-title { font-size: 20px; font-weight: 700; color: var(--navy); } - .ftm-sub { font-size: 13px; color: var(--muted); margin-top: 4px; } + + .ftm-title { + font-size: 20px; + font-weight: 700; + color: var(--navy); + } + + .ftm-sub { + font-size: 13px; + color: var(--muted); + margin-top: 4px; + } + .ftm-close { - width: 32px; height: 32px; border-radius: 8px; border: none; - background: var(--panel2); color: var(--muted); cursor: pointer; - display: flex; align-items: center; justify-content: center; font-size: 16px; + width: 32px; + height: 32px; + border-radius: 8px; + border: none; + background: var(--panel2); + color: var(--muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; transition: .2s; } - .ftm-close:hover { background: #fee2e2; color: #dc2626; } + + .ftm-close:hover { + background: #fee2e2; + color: #dc2626; + } + .ftm-body { - flex: 1; overflow: auto; padding: 24px; background: var(--bg); + flex: 1; + overflow: auto; + padding: 24px; + background: var(--bg); } + .ftm-filters { - display: flex; gap: 12px; margin-bottom: 16px; align-items: center; + display: flex; + gap: 12px; + margin-bottom: 16px; + align-items: center; } + .ftm-filter-select { - padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; - font-family: inherit; font-size: 13px; background: #fff; outline: none; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 6px; + font-family: inherit; + font-size: 13px; + background: #fff; + outline: none; } + .ftm-table-card { - background: #fff; border-radius: 12px; border: 1px solid var(--border); - box-shadow: 0 4px 16px rgba(0,0,0,0.03); overflow: auto; + background: #fff; + border-radius: 12px; + border: 1px solid var(--border); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.03); + overflow: auto; } - .ftm-table-card table { width: 100%; border-collapse: collapse; } + + .ftm-table-card table { + width: 100%; + border-collapse: collapse; + } + .ftm-table-card th { - text-align: left; padding: 14px 16px; font-size: 12px; font-weight: 700; - color: var(--muted); text-transform: uppercase; border-bottom: 1px solid var(--border); - background: var(--panel-soft); position: sticky; top: 0; z-index: 10; + text-align: left; + padding: 14px 16px; + font-size: 12px; + font-weight: 700; + color: var(--muted); + text-transform: uppercase; + border-bottom: 1px solid var(--border); + background: var(--panel-soft); + position: sticky; + top: 0; + z-index: 10; + } + + .ftm-table-card td { + padding: 14px 16px; + font-size: 13px; + border-bottom: 1px solid var(--border); + } + + .ftm-table-card tr:last-child td { + border-bottom: none; + } + + .ftm-table-card tr:hover td { + background: var(--panel-soft); + } + + .ftm-badge { + display: inline-block; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + } + + .ftm-btn-icon { + background: none; + border: none; + cursor: pointer; + color: var(--muted); + font-size: 15px; + transition: .2s; + } + + .ftm-btn-icon:hover { + color: var(--blue); } - .ftm-table-card td { padding: 14px 16px; font-size: 13px; border-bottom: 1px solid var(--border); } - .ftm-table-card tr:last-child td { border-bottom: none; } - .ftm-table-card tr:hover td { background: var(--panel-soft); } - .ftm-badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; } - .ftm-btn-icon { background: none; border: none; cursor: pointer; color: var(--muted); font-size: 15px; transition: .2s; } - .ftm-btn-icon:hover { color: var(--blue); } /* ── LOGIN MODAL ── */ .lm-overlay { - position: fixed; inset: 0; z-index: 12000; - background: rgba(15,23,42,.82); backdrop-filter: blur(10px); - display: flex; align-items: center; justify-content: center; + position: fixed; + inset: 0; + z-index: 12000; + background: rgba(15, 23, 42, .82); + backdrop-filter: blur(10px); + display: flex; + align-items: center; + justify-content: center; animation: fadeIn .2s ease; } + .lm-box { - background: #fff; border-radius: 20px; width: 420px; max-width: 95vw; - box-shadow: 0 32px 80px rgba(0,0,0,.3); - overflow: hidden; animation: riseIn .3s cubic-bezier(.16,1,.3,1); + background: #fff; + border-radius: 20px; + width: 420px; + max-width: 95vw; + box-shadow: 0 32px 80px rgba(0, 0, 0, .3); + overflow: hidden; + animation: riseIn .3s cubic-bezier(.16, 1, .3, 1); } + .lm-head { - background: linear-gradient(135deg,#1e3a8a,#2563eb); - padding: 28px 32px 24px; text-align: center; color: #fff; + background: linear-gradient(135deg, #1e3a8a, #2563eb); + padding: 28px 32px 24px; + text-align: center; + color: #fff; + } + + .lm-head-icon { + font-size: 36px; + margin-bottom: 10px; + } + + .lm-head-title { + font-size: 22px; + font-weight: 800; + } + + .lm-head-sub { + font-size: 13px; + opacity: .8; + margin-top: 4px; + } + + .lm-body { + padding: 28px 32px; + } + + .lm-field { + margin-bottom: 16px; + } + + .lm-field label { + display: block; + font-size: 12px; + font-weight: 700; + color: var(--muted); + margin-bottom: 6px; + letter-spacing: .5px; + text-transform: uppercase; + } + + .lm-input-wrap { + position: relative; + } + + .lm-input-wrap .lm-ico { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: var(--muted); + font-size: 14px; + } + + .lm-input-wrap input { + width: 100%; + padding: 12px 14px 12px 40px; + border: 1.5px solid var(--border); + border-radius: 10px; + font: 500 14px 'Plus Jakarta Sans', sans-serif; + outline: none; + transition: .2s; + box-sizing: border-box; + } + + .lm-input-wrap input:focus { + border-color: var(--blue); + box-shadow: 0 0 0 3px rgba(37, 99, 235, .12); + } + + .lm-eye { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + cursor: pointer; + color: var(--muted); + font-size: 14px; + } + + .lm-submit { + width: 100%; + padding: 13px; + border: none; + border-radius: 12px; + background: linear-gradient(135deg, #1e3a8a, #2563eb); + color: #fff; + font: 700 14px 'Plus Jakarta Sans', sans-serif; + cursor: pointer; + box-shadow: 0 6px 20px rgba(37, 99, 235, .3); + transition: .2s; + margin-top: 4px; + } + + .lm-submit:hover { + transform: translateY(-1px); + box-shadow: 0 8px 28px rgba(37, 99, 235, .4); + } + + .lm-submit:disabled { + opacity: .6; + cursor: not-allowed; + transform: none; + } + + .lm-alert { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 600; + margin-bottom: 16px; + } + + .lm-alert.error { + background: #fee2e2; + color: #dc2626; + } + + .lm-close-btn { + position: absolute; + top: 14px; + right: 14px; + background: rgba(255, 255, 255, .15); + border: none; + border-radius: 8px; + width: 30px; + height: 30px; + cursor: pointer; + color: #fff; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; } - .lm-head-icon { font-size: 36px; margin-bottom: 10px; } - .lm-head-title { font-size: 22px; font-weight: 800; } - .lm-head-sub { font-size: 13px; opacity: .8; margin-top: 4px; } - .lm-body { padding: 28px 32px; } - .lm-field { margin-bottom: 16px; } - .lm-field label { display: block; font-size: 12px; font-weight: 700; color: var(--muted); margin-bottom: 6px; letter-spacing: .5px; text-transform: uppercase; } - .lm-input-wrap { position: relative; } - .lm-input-wrap .lm-ico { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: 14px; } - .lm-input-wrap input { width: 100%; padding: 12px 14px 12px 40px; border: 1.5px solid var(--border); border-radius: 10px; font: 500 14px 'Plus Jakarta Sans',sans-serif; outline: none; transition: .2s; box-sizing: border-box; } - .lm-input-wrap input:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(37,99,235,.12); } - .lm-eye { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; color: var(--muted); font-size: 14px; } - .lm-submit { width: 100%; padding: 13px; border: none; border-radius: 12px; background: linear-gradient(135deg,#1e3a8a,#2563eb); color: #fff; font: 700 14px 'Plus Jakarta Sans',sans-serif; cursor: pointer; box-shadow: 0 6px 20px rgba(37,99,235,.3); transition: .2s; margin-top: 4px; } - .lm-submit:hover { transform: translateY(-1px); box-shadow: 0 8px 28px rgba(37,99,235,.4); } - .lm-submit:disabled { opacity: .6; cursor: not-allowed; transform: none; } - .lm-alert { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-radius: 8px; font-size: 13px; font-weight: 600; margin-bottom: 16px; } - .lm-alert.error { background: #fee2e2; color: #dc2626; } - .lm-close-btn { position: absolute; top: 14px; right: 14px; background: rgba(255,255,255,.15); border: none; border-radius: 8px; width: 30px; height: 30px; cursor: pointer; color: #fff; font-size: 14px; display: flex; align-items: center; justify-content: center; } /* ── PROFIL MODAL ── */ .prf-overlay { - position: fixed; inset: 0; z-index: 12000; - background: rgba(15,23,42,.75); backdrop-filter: blur(8px); - display: flex; align-items: center; justify-content: center; padding: 20px; + position: fixed; + inset: 0; + z-index: 12000; + background: rgba(15, 23, 42, .75); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; animation: fadeIn .2s ease; } + .prf-box { - background: #fff; border-radius: 20px; width: 480px; max-width: 95vw; - box-shadow: 0 28px 70px rgba(0,0,0,.25); - overflow: hidden; animation: riseIn .3s cubic-bezier(.16,1,.3,1); + background: #fff; + border-radius: 20px; + width: 480px; + max-width: 95vw; + box-shadow: 0 28px 70px rgba(0, 0, 0, .25); + overflow: hidden; + animation: riseIn .3s cubic-bezier(.16, 1, .3, 1); } + .prf-head { - background: linear-gradient(135deg,#0f172a,#1e3a8a); - padding: 28px 28px 60px; text-align: center; position: relative; + background: linear-gradient(135deg, #0f172a, #1e3a8a); + padding: 28px 28px 60px; + text-align: center; + position: relative; } + .prf-avatar { - width: 80px; height: 80px; border-radius: 50%; margin: 0 auto 12px; - background: linear-gradient(135deg,#3b82f6,#6366f1); - display: flex; align-items: center; justify-content: center; - font-size: 32px; font-weight: 800; color: #fff; - border: 3px solid rgba(255,255,255,.3); + width: 80px; + height: 80px; + border-radius: 50%; + margin: 0 auto 12px; + background: linear-gradient(135deg, #3b82f6, #6366f1); + display: flex; + align-items: center; + justify-content: center; + font-size: 32px; + font-weight: 800; + color: #fff; + border: 3px solid rgba(255, 255, 255, .3); + } + + .prf-head-name { + font-size: 18px; + font-weight: 700; + color: #fff; + } + + .prf-head-role { + font-size: 12px; + color: rgba(255, 255, 255, .7); + margin-top: 4px; + } + + .prf-body { + padding: 0 28px 28px; + margin-top: -30px; + } + + .prf-card { + background: #f8fafc; + border: 1px solid var(--border); + border-radius: 12px; + padding: 20px; + margin-bottom: 16px; + } + + .prf-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 0; + border-bottom: 1px solid var(--border); + } + + .prf-row:last-child { + border-bottom: none; + padding-bottom: 0; + } + + .prf-label { + font-size: 12px; + color: var(--muted); + font-weight: 600; + text-transform: uppercase; + } + + .prf-value { + font-size: 14px; + font-weight: 600; + color: var(--navy); + } + + .prf-edit-row { + display: flex; + gap: 8px; + align-items: center; + } + + .prf-input { + padding: 8px 12px; + border: 1.5px solid var(--border); + border-radius: 8px; + font: 600 14px 'Plus Jakarta Sans', sans-serif; + outline: none; + transition: .2s; + } + + .prf-input:focus { + border-color: var(--blue); + } + + .prf-save-btn { + padding: 8px 14px; + background: var(--blue); + color: #fff; + border: none; + border-radius: 8px; + font: 700 12px 'Plus Jakarta Sans', sans-serif; + cursor: pointer; + } + + .prf-close { + position: absolute; + top: 14px; + right: 14px; + background: rgba(255, 255, 255, .15); + border: none; + border-radius: 8px; + width: 30px; + height: 30px; + cursor: pointer; + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; } - .prf-head-name { font-size: 18px; font-weight: 700; color: #fff; } - .prf-head-role { font-size: 12px; color: rgba(255,255,255,.7); margin-top: 4px; } - .prf-body { padding: 0 28px 28px; margin-top: -30px; } - .prf-card { background: #f8fafc; border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 16px; } - .prf-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--border); } - .prf-row:last-child { border-bottom: none; padding-bottom: 0; } - .prf-label { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; } - .prf-value { font-size: 14px; font-weight: 600; color: var(--navy); } - .prf-edit-row { display: flex; gap: 8px; align-items: center; } - .prf-input { padding: 8px 12px; border: 1.5px solid var(--border); border-radius: 8px; font: 600 14px 'Plus Jakarta Sans',sans-serif; outline: none; transition: .2s; } - .prf-input:focus { border-color: var(--blue); } - .prf-save-btn { padding: 8px 14px; background: var(--blue); color: #fff; border: none; border-radius: 8px; font: 700 12px 'Plus Jakarta Sans',sans-serif; cursor: pointer; } - .prf-close { position: absolute; top: 14px; right: 14px; background: rgba(255,255,255,.15); border: none; border-radius: 8px; width: 30px; height: 30px; cursor: pointer; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 14px; } /* ── KELOLA AKUN MODAL (Users) ── */ - .ua-role-badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 700; } - .ua-role-admin { background: #dbeafe; color: #1d4ed8; } - .ua-role-operator { background: #f0fdf4; color: #15803d; } - .ua-form { background: #f8fafc; border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 20px; } - .ua-form-title { font-size: 14px; font-weight: 700; color: var(--navy); margin-bottom: 14px; display: flex; align-items: center; gap: 8px; } - .ua-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } - .ua-field label { display: block; font-size: 11px; font-weight: 700; color: var(--muted); margin-bottom: 5px; text-transform: uppercase; } - .ua-field input, .ua-field select { width: 100%; padding: 9px 12px; border: 1.5px solid var(--border); border-radius: 8px; font: 500 13px 'Plus Jakarta Sans',sans-serif; outline: none; transition: .2s; box-sizing: border-box; } - .ua-field input:focus, .ua-field select:focus { border-color: var(--blue); } - .ua-submit { padding: 10px 20px; background: var(--blue); color: #fff; border: none; border-radius: 8px; font: 700 13px 'Plus Jakarta Sans',sans-serif; cursor: pointer; margin-top: 14px; } - .ua-avatar { width: 32px; height: 32px; border-radius: 8px; display: grid; place-items: center; font: 700 13px 'Space Grotesk',sans-serif; color: #fff; } + .ua-role-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + } + + .ua-role-admin { + background: #dbeafe; + color: #1d4ed8; + } + + .ua-role-operator { + background: #f0fdf4; + color: #15803d; + } + + .ua-form { + background: #f8fafc; + border: 1px solid var(--border); + border-radius: 12px; + padding: 20px; + margin-bottom: 20px; + } + + .ua-form-title { + font-size: 14px; + font-weight: 700; + color: var(--navy); + margin-bottom: 14px; + display: flex; + align-items: center; + gap: 8px; + } + + .ua-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + } + + .ua-field label { + display: block; + font-size: 11px; + font-weight: 700; + color: var(--muted); + margin-bottom: 5px; + text-transform: uppercase; + } + + .ua-field input, + .ua-field select { + width: 100%; + padding: 9px 12px; + border: 1.5px solid var(--border); + border-radius: 8px; + font: 500 13px 'Plus Jakarta Sans', sans-serif; + outline: none; + transition: .2s; + box-sizing: border-box; + } + + .ua-field input:focus, + .ua-field select:focus { + border-color: var(--blue); + } + + .ua-submit { + padding: 10px 20px; + background: var(--blue); + color: #fff; + border: none; + border-radius: 8px; + font: 700 13px 'Plus Jakarta Sans', sans-serif; + cursor: pointer; + margin-top: 14px; + } + + .ua-avatar { + width: 32px; + height: 32px; + border-radius: 8px; + display: grid; + place-items: center; + font: 700 13px 'Space Grotesk', sans-serif; + color: #fff; + } @@ -3284,7 +4020,6 @@ -
@@ -3304,7 +4039,8 @@
Mode Publik — Data KK miskin tidak ditampilkan. - Login untuk akses penuh termasuk peta, daftar, dan pencarian data. + Login untuk akses penuh termasuk peta, daftar, dan + pencarian data.
@@ -3317,7 +4053,8 @@
@@ -3335,12 +4072,11 @@ Data KK Miskin Tambah keluarga miskin
- + Rumah Ibadah Tambah lokasi ibadah
-
Opsional
- @@ -3377,9 +4113,11 @@
Administrator
- Profil Saya + + Profil Saya
Ganti Sandi
- Kelola Akun + Kelola Akun
Keluar
@@ -3510,11 +4248,11 @@ /* STATE */ const state = { - mode: null, spbu: [], jalan: [], parsil: [], kemiskinan: [], masjid: [], - featureRefs: { spbu: {}, jalan: {}, parsil: {}, kemiskinan: {}, masjid: {} }, + mode: null, kemiskinan: [], masjid: [], + featureRefs: { kemiskinan: {}, masjid: {} }, kkLayerRefs: {}, blankRefs: {}, useSatellite: false, geometryEdit: null, activeCard: null, - spbuFilterVis: { open24: true, non24: true }, drawingLine: false, drawingPoly: false, - tempPoints: [], tempMarkers: [], tempPolyline: null, tempPolygon: null + + }; /* MAP */ @@ -3528,19 +4266,14 @@ }); const layers = { kemiskinan: L.layerGroup().addTo(map), - masjid: L.layerGroup().addTo(map), - blankspot: L.layerGroup().addTo(map), - // Layer opsional — OFF by default (tidak .addTo(map)) - spbu24: L.layerGroup(), - spbuNon24: L.layerGroup(), - jalan: L.layerGroup(), - parsil: L.layerGroup() + masjid: L.layerGroup().addTo(map), + blankspot: L.layerGroup().addTo(map) }; const layersControl = L.control.layers(null, { 'KemiskinanData KK miskin': layers.kemiskinan, 'Rumah IbadahFasilitas ibadah': layers.masjid, 'Blank SpotDi luar radius rumah ibadah': layers.blankspot, - }, { collapsed: false, position: 'topright' }).addTo(map); + }, { collapsed: false, position: 'topright' }).addTo(map); decorateLayersControl(); function decorateLayersControl() { const container = layersControl.getContainer(); @@ -3548,7 +4281,7 @@ const overlays = container.querySelector('.leaflet-control-layers-overlays'); if (!overlays) return; const labels = Array.from(overlays.querySelectorAll('label')); - if (labels.length < 6) return; + if (labels.length < 3) return; overlays.innerHTML = ''; const addGroup = (title, items, color) => { const head = document.createElement('div'); @@ -3596,7 +4329,7 @@ }); }; addGroup('Tematik Utama', [labels[0], labels[1], labels[2]]); - addGroup('Opsional', [labels[3], labels[4], labels[5], labels[6]], '#64748b'); + } const statusStoreKey = 'gis-kemiskinan-status'; @@ -3642,7 +4375,7 @@ } const colors = { success: { bg: '#059669', color: '#fff' }, - info: { bg: '#2563eb', color: '#fff' }, + info: { bg: '#2563eb', color: '#fff' }, warning: { bg: '#f59e0b', color: '#fff' } }; const c = colors[type] || colors.success; @@ -3880,8 +4613,6 @@ const fWarga = state.kemiskinan.find(d => (`${d.nama_kk} ${d.alamat}`).toLowerCase().includes(q)); if (fWarga) return focusFeature('kemiskinan', fWarga.id); - - // Jika tidak ketemu alert('Data "' + q + '" tidak ditemukan.'); } @@ -4090,14 +4821,14 @@ // log removed }) - .catch(err => {}); + .catch(err => { }); } /* ── MODE ── */ function setMode(m) { cancelDrawing(); state.mode = state.mode === m ? null : m; - ['kemiskinan', 'masjid', 'spbu', 'jalan', 'parsil'].forEach(x => { + ['kemiskinan', 'masjid'].forEach(x => { const el = document.getElementById('btn-' + x); if (el) el.className = 'mbtn'; }); @@ -4106,12 +4837,7 @@ if (state.mode) { const msgs = { kemiskinan: 'Klik peta untuk menambah data Kemiskinan', - masjid: 'Klik peta untuk menambah titik Rumah Ibadah', - spbu: 'Klik peta untuk menambah titik SPBU', - jalan: 'Klik untuk gambar jalan — klik ganda untuk selesai', - parsil: 'Klik untuk gambar parsil — klik titik awal atau dbl-klik selesai', - line: 'Klik untuk gambar jalan — klik ganda untuk selesai', - poly: 'Klik untuk gambar parsil — klik titik awal atau dbl-klik selesai', + masjid: 'Klik peta untuk menambah titik Rumah Ibadah', }; hint.textContent = msgs[m] || 'Klik peta'; hint.classList.add('show'); @@ -4128,19 +4854,14 @@ return; } if (!state.mode) return; - if (state.mode === 'kemiskinan') handleKemiskinanClick(e); - else if (state.mode === 'masjid') handleMasjidClick(e); - else if (state.mode === 'spbu') handlePointClick(e); - else if (state.mode === 'jalan') handleLineClick(e); - else if (state.mode === 'parsil') handlePolyClick(e); + if (state.mode === 'kemiskinan') handleKemiskinanClick(e); + else if (state.mode === 'masjid') handleMasjidClick(e); }); map.on('dblclick', function (e) { - if ((state.mode === 'jalan' || state.mode === 'line') && state.drawingLine) finishLine(e); - if ((state.mode === 'parsil' || state.mode === 'poly') && state.drawingPoly) finishPoly(e); - if (['jalan','parsil','line','poly'].includes(state.mode)) L.DomEvent.stopPropagation(e); + }); - /* ── MODERN MARKER ICON ── */ + /* ── MODERN MARKER ICON ── */ function createModernMarker(color, iconClass, label) { const html = `
@@ -4162,556 +4883,13 @@ }); } - /* ── POINT SPBU ── */ - window.updateTempMarkerIcon = function() { - if (!window._tmpM) return; - const s = document.getElementById('p-status') ? document.getElementById('p-status').value : 'Ya'; - const color = s === 'Ya' ? '#10b981' : '#ef4444'; - window._tmpM.setIcon(createModernMarker(color, 'fas fa-gas-pump', 'SPBU Baru')); - }; - function handlePointClick(e) { - const icon = createModernMarker('#10b981', 'fas fa-gas-pump', 'SPBU Baru'); - const tmpM = L.marker(e.latlng, { icon, draggable: true }).addTo(map); - const upd = ll => { - const a = document.getElementById('p-lat'), b = document.getElementById('p-lng'); - if (a) a.value = ll.lat.toFixed(6); - if (b) b.value = ll.lng.toFixed(6); - }; - tmpM.on('dragend', ev => upd(ev.target.getLatLng())); - const form = `
-

Tambah SPBU

- - - - - - -
- - -
-
* Geser marker untuk menyesuaikan posisi
- -
`; - tmpM.bindPopup(form, { maxWidth: 280, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }).openPopup(); - tmpM.on('popupclose', () => { if (map.hasLayer(tmpM)) map.removeLayer(tmpM); }); - window._tmpM = tmpM; - } - function savePoint() { - const nama = document.getElementById('p-nama').value.trim(); - const no = document.getElementById('p-no').value.trim(); - const status = document.getElementById('p-status').value; - const lat = parseFloat(document.getElementById('p-lat').value); - const lng = parseFloat(document.getElementById('p-lng').value); - if (!nama || !no) { - alert('Lengkapi nama dan nomor SPBU!'); - return; - } - fetch("api/save_data.php", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - nama: nama, - no: no, - status: status, - lat: lat, - lng: lng - }) - }) - .then(res => res.json()) - .then(response => { - if (response.status == "success") { - const d = { - id: response.id || Date.now(), - nama, - no, - status, - lat, - lng - }; - - state.spbu.push(d); - - renderSPBU(d); - - updateCount('spbu', state.spbu.length); - - addCard( - 'spbu', - d.id, - nama, - 'No: ' + no, - status === 'Ya' ? 'Buka 24 Jam' : 'Tidak Buka 24 Jam', - status === 'Ya' ? '#1a6b3c' : '#b93328' - ); - - if (window._tmpM) { - map.closePopup(); - map.removeLayer(window._tmpM); - window._tmpM = null; - } - - setMode(null); - ensureLayerVisible('spbu', d.id); - focusFeature('spbu', d.id); - - } else { - alert("Gagal menyimpan: " + (response.message || "Periksa database")); - } - - }) - .catch(err => { - console.error(err); - alert("Terjadi error jaringan"); - }); - - } - - function renderSPBU(d) { - const color = d.status === 'Ya' ? '#10b981' : '#ef4444'; - const icon = createModernMarker(color, 'fas fa-gas-pump', d.nama); - const m = L.marker([d.lat, d.lng], { icon, draggable: true }); - m.bindPopup(`
-

${d.nama}

-
-
No. SPBU${d.no}
-
Status${d.status === 'Ya' ? '✔ Buka 24 Jam' : '✖ Tidak Buka 24 Jam'}
-
Koordinat${d.lat.toFixed(5)}, ${d.lng.toFixed(5)}
-
-
- - -
-
`, { autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }); - m.addTo(d.status === 'Ya' ? layers.spbu24 : layers.spbuNon24); - state.featureRefs.spbu[String(d.id)] = m; - } - - /* ── JALAN ── */ - function handleLineClick(e) { - if (!state.drawingLine) state.drawingLine = true; - state.tempPoints.push(e.latlng); - const vm = L.circleMarker(e.latlng, { radius: 4, color: '#e74c3c', fillColor: '#e74c3c', fillOpacity: 1, weight: 2 }).addTo(map); - state.tempMarkers.push(vm); - if (state.tempPolyline) map.removeLayer(state.tempPolyline); - state.tempPolyline = L.polyline(state.tempPoints, { color: '#e74c3c', weight: 3, dashArray: '7,5', opacity: 0.8 }).addTo(map); - document.getElementById('hint').textContent = `📍 ${state.tempPoints.length} titik — klik ganda untuk selesai`; - } - - function finishLine(e) { - if (state.tempPoints.length < 2) { alert('Minimal 2 titik!'); return; } - const pts = [...state.tempPoints]; - const len = calcLen(pts); - window._tempLinePts = pts; - window._tempLineLen = len; - clearTemp(); - L.popup({ closeOnClick: false, keepInView: true, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }) - .setLatLng(pts[Math.floor(pts.length / 2)]) - .setContent(`
-

Tambah Jalan

- - - - -
- - Panjang terukur: - ${fmtLen(len)} -
- -
`).addTo(map); - } - - function saveLine() { - const pts = window._tempLinePts || []; - const len = window._tempLineLen || 0; - - const nama = document.getElementById('j-nama').value.trim(); - const status = document.getElementById('j-status').value; - - if (!nama) { - alert('Isi nama jalan!'); - return; - } - - fetch("api/save_jalan.php", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - nama: nama, - status: status, - panjang: len, - koordinat: pts - }) - }) - .then(res => res.json()) - .then(response => { - - if (response.status == "success") { - - const d = { - id: response.id || Date.now(), - nama, - status, - pts, - len - }; - - state.jalan.push(d); - - renderJalan(d); - - updateCount('jalan', state.jalan.length); - - addCard( - 'jalan', - d.id, - nama, - status + ' · ' + fmtLen(len), - status, - jColor[status] - ); - - map.closePopup(); - setMode(null); - ensureLayerVisible('jalan', d.id); - focusFeature('jalan', d.id); - - } else { - - // log removed - alert("Gagal simpan jalan"); - - } - - }) - .catch(err => { - // log removed - alert("Error simpan jalan"); - }); - - } - function renderJalan(d) { - - const pts = normalizeLinePoints(d.pts); - if (pts.length < 2) { - // log removed - return; - } - - const c = jColor[d.status] || '#ef4444'; - - const w = - d.status === 'Nasional' ? 6 : - d.status === 'Provinsi' ? 5 : 4; - - const line = L.polyline(pts, { - color: c, - weight: w, - opacity: 0.95, - lineCap: 'round', - lineJoin: 'round', - className: 'path-glow' - }) - .bindPopup(` -
-

${d.nama}

- -
-
- Status - ${d.status} -
-
- Panjang - ${fmtLen(d.len)} -
-
- -
- - -
- -
-`, { autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }) - .addTo(layers.jalan); - - state.featureRefs.jalan[String(d.id)] = line; - - } - - function normalizeLinePoints(raw) { - - let pts = raw; - - if (typeof pts === 'string') { - try { - pts = JSON.parse(pts); - } catch (_) { - return []; - } - } - - // Kompatibilitas data lama: koordinat sempat tersimpan sebagai JSON string di dalam JSON string. - if (typeof pts === 'string') { - try { - pts = JSON.parse(pts); - } catch (_) { - return []; - } - } - - if (!Array.isArray(pts) || pts.length === 0) return []; - - if (Array.isArray(pts[0])) { - return pts - .map(p => [parseFloat(p[0]), parseFloat(p[1])]) - .filter(p => Number.isFinite(p[0]) && Number.isFinite(p[1])); - } - - if (pts[0] && typeof pts[0] === 'object' && pts[0].lat !== undefined) { - return pts - .map(p => [parseFloat(p.lat), parseFloat(p.lng)]) - .filter(p => Number.isFinite(p[0]) && Number.isFinite(p[1])); - } - - return []; - - } - - function normalizePolyPoints(raw) { - - let pts = raw; - - if (typeof pts === 'string') { - try { - pts = JSON.parse(pts); - } catch (_) { - return []; - } - } - - if (typeof pts === 'string') { - try { - pts = JSON.parse(pts); - } catch (_) { - return []; - } - } - - if (!Array.isArray(pts) || pts.length === 0) return []; - - if (Array.isArray(pts[0])) { - return pts - .map(p => [parseFloat(p[0]), parseFloat(p[1])]) - .filter(p => Number.isFinite(p[0]) && Number.isFinite(p[1])); - } - - if (pts[0] && typeof pts[0] === 'object' && pts[0].lat !== undefined) { - return pts - .map(p => [parseFloat(p.lat), parseFloat(p.lng)]) - .filter(p => Number.isFinite(p[0]) && Number.isFinite(p[1])); - } - - return []; - - } - - /* ── PARSIL ── */ - function handlePolyClick(e) { - if (!state.drawingPoly) state.drawingPoly = true; - if (state.tempPoints.length >= 3) { - const dist = map.distance(e.latlng, state.tempPoints[0]); - if (dist < 15) { finishPoly(null); return; } - } - state.tempPoints.push(e.latlng); - const vm = L.circleMarker(e.latlng, { radius: 4, color: '#1a5276', fillColor: '#1a5276', fillOpacity: 1, weight: 2 }).addTo(map); - state.tempMarkers.push(vm); - if (state.tempPolygon) map.removeLayer(state.tempPolygon); - if (state.tempPolyline) map.removeLayer(state.tempPolyline); - if (state.tempPoints.length >= 2) - state.tempPolyline = L.polyline([...state.tempPoints, state.tempPoints[0]], - { color: '#1a5276', weight: 2, dashArray: '5,4', opacity: 0.7 }).addTo(map); - if (state.tempPoints.length >= 3) - state.tempPolygon = L.polygon(state.tempPoints, - { color: '#1a5276', fillColor: '#1a5276', fillOpacity: 0.1, weight: 2, dashArray: '5,4' }).addTo(map); - document.getElementById('hint').textContent = - `🗺️ ${state.tempPoints.length} titik — klik titik awal atau dbl-klik selesai`; - } - - function finishPoly(e) { - if (state.tempPoints.length < 3) { alert('Minimal 3 titik!'); return; } - const pts = [...state.tempPoints]; - const area = calcArea(pts); - window._tempPolyPts = pts; - window._tempPolyArea = area; - clearTemp(); - const center = L.polygon(pts).getBounds().getCenter(); - L.popup({ closeOnClick: false, keepInView: true, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }) - .setLatLng(center) - .setContent(`
-

Tambah Parsil / Lahan

- - - - - - -
- - Luas terukur: - ${fmtArea(area)} -
- -
`).addTo(map); - } - - function saveParsil() { - const pts = window._tempPolyPts || []; - const area = window._tempPolyArea || 0; - - const nama = document.getElementById('ps-nama').value.trim(); - const pemilik = document.getElementById('ps-pemilik').value.trim(); - const status = document.getElementById('ps-status').value; - - if (!nama) { - alert('Isi nama parsil!'); - return; - } - - fetch("api/save_parsil.php", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - nama: nama, - pemilik: pemilik, - status: status, - luas: area, - koordinat: pts - }) - }) - .then(res => res.json()) - .then(response => { - - if (response.status == "success") { - - const parsil = { - id: response.id || Date.now(), - nama, - pemilik, - status, - pts, - area - }; - - state.parsil.push(parsil); - - renderParsil(parsil); - - updateCount('parsil', state.parsil.length); - - addCard( - 'parsil', - parsil.id, - parsil.nama, - parsil.status, - parsil.status, - '#1a5276' - ); - - map.closePopup(); - setMode(null); - ensureLayerVisible('parsil', parsil.id); - focusFeature('parsil', parsil.id); - - } - - else { - // log removed - alert("Gagal simpan parsil"); - } - - }) - .catch(err => {}); - } - - function renderParsil(d) { - - const pts = normalizePolyPoints(d.pts); - if (pts.length < 3) { - // log removed - return; - } - - const c = pColor[d.status]; - - const poly = L.polygon(pts, { - color: c, - fillColor: c, - fillOpacity: 0.25, - weight: 3, - dashArray: '8, 6', - lineCap: 'round', - lineJoin: 'round', - className: 'parsil-glow' - }) - .bindPopup(` -
-

${d.nama}

- -
-
- Pemilik - ${d.pemilik || '-'} -
-
- Status - ${d.status} -
-
- Luas - ${fmtArea(d.area)} -
-
- -
- - -
- -
-`) - .addTo(layers.parsil); - - state.featureRefs.parsil[String(d.id)] = poly; - - } function getById(type, id) { return state[type].find(x => String(x.id) === String(id)); @@ -4884,11 +5062,7 @@ } function refreshType(type) { - if (state.geometryEdit && state.geometryEdit.type === type) stopGeometryEdit(); - if (type === 'spbu') { - layers.spbu24.clearLayers(); - layers.spbuNon24.clearLayers(); - } else { + if (state.geometryEdit && state.geometryEdit.type === type) stopGeometryEdit(); else { layers[type].clearLayers(); } state.featureRefs[type] = {}; @@ -4925,196 +5099,9 @@ renderDashboard(); } - function openEditSpbu(id) { - const d = getById('spbu', id); - if (!d) return; - const ref = state.featureRefs.spbu[String(id)]; - const ll = ref && ref.getLatLng ? ref.getLatLng() : { lat: d.lat, lng: d.lng }; - const html = `
-

Edit SPBU

- - - - - - - - - - - -
`; - map.closePopup(); - L.popup({ maxWidth: 290, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }).setLatLng([ll.lat, ll.lng]).setContent(html).openOn(map); - } - function saveEditSpbu(id) { - const nama = document.getElementById('e-spbu-nama').value.trim(); - const no = document.getElementById('e-spbu-no').value.trim(); - const status = document.getElementById('e-spbu-status').value; - const lat = parseFloat(document.getElementById('e-spbu-lat').value); - const lng = parseFloat(document.getElementById('e-spbu-lng').value); - if (!nama || !no || !Number.isFinite(lat) || !Number.isFinite(lng)) { - alert('Data SPBU belum valid'); - return; - } - fetch('api/update_data.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id, nama, no, status, lat, lng }) - }) - .then(res => res.json()) - .then(resp => { - if (resp.status !== 'success') { - alert('Gagal update SPBU'); - return; - } - const d = getById('spbu', id); - if (!d) return; - d.nama = nama; d.no = no; d.status = status; d.lat = lat; d.lng = lng; - refreshType('spbu'); - map.closePopup(); - focusFeature('spbu', id); - }) - .catch(() => alert('Error update SPBU')); - } - - function openEditJalan(id) { - if (state.geometryEdit && (state.geometryEdit.type !== 'jalan' || String(state.geometryEdit.id) !== String(id))) { - stopGeometryEdit(); - } - const d = getById('jalan', id); - if (!d) return; - const pts = normalizeLinePoints(d.pts); - const ref = state.featureRefs.jalan[String(id)]; - const center = ref && ref.getBounds ? ref.getBounds().getCenter() : map.getCenter(); - const html = `
-

Edit Jalan

- - - - - - - - -
`; - map.closePopup(); - L.popup({ maxWidth: 330, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }).setLatLng(center).setContent(html).openOn(map); - } - - function saveEditJalan(id) { - if (state.geometryEdit && state.geometryEdit.type === 'jalan' && String(state.geometryEdit.id) === String(id)) { - syncEditTextarea('jalan', state.geometryEdit.points); - } - const nama = document.getElementById('e-jalan-nama').value.trim(); - const status = document.getElementById('e-jalan-status').value; - const pts = textToPoints(document.getElementById('e-jalan-pts').value, 2); - if (!nama || !pts) { - alert('Data jalan atau koordinat belum valid'); - return; - } - - const len = calcLen(pts); - - fetch('api/update_jalan.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id, nama, status, panjang: len, koordinat: toLatLngObjects(pts) }) - }) - .then(res => res.json()) - .then(resp => { - if (resp.status !== 'success') { - alert('Gagal update jalan'); - return; - } - const d = getById('jalan', id); - if (!d) return; - d.nama = nama; d.status = status; d.len = len; d.pts = pts; - stopGeometryEdit(); - refreshType('jalan'); - map.closePopup(); - focusFeature('jalan', id); - }) - .catch(() => alert('Error update jalan')); - } - - function openEditParsil(id) { - if (state.geometryEdit && (state.geometryEdit.type !== 'parsil' || String(state.geometryEdit.id) !== String(id))) { - stopGeometryEdit(); - } - const d = getById('parsil', id); - if (!d) return; - const pts = normalizePolyPoints(d.pts); - const ref = state.featureRefs.parsil[String(id)]; - const center = ref && ref.getBounds ? ref.getBounds().getCenter() : map.getCenter(); - const html = `
-

Edit Parsil

- - - - - - - - - - -
`; - map.closePopup(); - L.popup({ maxWidth: 330, autoPanPaddingTopLeft: [0, 80], autoPanPaddingBottomRight: [0, 20] }).setLatLng(center).setContent(html).openOn(map); - } - - function saveEditParsil(id) { - if (state.geometryEdit && state.geometryEdit.type === 'parsil' && String(state.geometryEdit.id) === String(id)) { - syncEditTextarea('parsil', state.geometryEdit.points); - } - const nama = document.getElementById('e-parsil-nama').value.trim(); - const pemilik = document.getElementById('e-parsil-pemilik').value.trim(); - const status = document.getElementById('e-parsil-status').value; - const pts = textToPoints(document.getElementById('e-parsil-pts').value, 3); - if (!nama || !pts) { - alert('Data parsil atau koordinat belum valid'); - return; - } - - const area = calcArea(toLatLngObjects(pts)); - - fetch('api/update_parsil.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id, nama, pemilik, status, luas: area, koordinat: toLatLngObjects(pts) }) - }) - .then(res => res.json()) - .then(resp => { - if (resp.status !== 'success') { - alert('Gagal update parsil'); - return; - } - const d = getById('parsil', id); - if (!d) return; - d.nama = nama; d.pemilik = pemilik; d.status = status; d.area = area; d.pts = pts; - stopGeometryEdit(); - refreshType('parsil'); - map.closePopup(); - focusFeature('parsil', id); - }) - .catch(() => alert('Error update parsil')); - } function deleteFeature(type, id) { if (state.geometryEdit && state.geometryEdit.type === type && String(state.geometryEdit.id) === String(id)) { @@ -5170,14 +5157,8 @@ } function fmtArea(m) { return m >= 10000 ? (m / 10000).toFixed(2) + ' ha' : Math.round(m) + ' m²' } - function clearTemp() { - state.tempPoints = []; state.drawingLine = false; state.drawingPoly = false; - state.tempMarkers.forEach(m => map.removeLayer(m)); state.tempMarkers = []; - if (state.tempPolyline) { map.removeLayer(state.tempPolyline); state.tempPolyline = null; } - if (state.tempPolygon) { map.removeLayer(state.tempPolygon); state.tempPolygon = null; } - } + function cancelDrawing() { - clearTemp(); if (window._tmpM) { map.removeLayer(window._tmpM); window._tmpM = null; } if (window._tmpC) { map.removeLayer(window._tmpC); window._tmpC = null; } } @@ -5263,8 +5244,6 @@ document.addEventListener("DOMContentLoaded", function () { - - // Kemiskinan butuh data Masjid untuk hitung Blank Spot (merah/hijau) // Jadi pastikan loadMasjid selesai dulu baru loadKemiskinan loadMasjid().then(() => { @@ -5273,120 +5252,11 @@ }); - function loadSPBU() { - fetch("api/read_data.php") - .then(res => res.json()) - .then(data => { - data.forEach(d => { - const spbu = { - id: d.id, - nama: d.nama, - no: d.no, - status: d.status, - lat: parseFloat(d.lat), - lng: parseFloat(d.lng) - }; - state.spbu.push(spbu); - renderSPBU(spbu); - - updateCount('spbu', state.spbu.length); - - addCard( - 'spbu', - spbu.id, - spbu.nama, - 'No: ' + spbu.no, - spbu.status === 'Ya' ? 'Buka 24 Jam' : 'Tidak Buka 24 Jam', - spbu.status === 'Ya' ? '#1a6b3c' : '#b93328' - ); - - }); - - }) - .catch(err => {}); - - } - - function loadJalan() { - - fetch("api/read_jalan.php") - .then(res => res.json()) - .then(data => { - - data.forEach(d => { - - const jalan = { - id: d.id, - nama: d.nama, - status: d.status, - len: parseFloat(d.panjang), - pts: d.koordinat - }; - - state.jalan.push(jalan); - - renderJalan(jalan); - - updateCount('jalan', state.jalan.length); - - addCard( - 'jalan', - jalan.id, - jalan.nama, - jalan.status, - jalan.status, - '#e74c3c' - ); - - }); - - }) - .catch(err => {}); - - } - - function loadParsil() { - - fetch("api/read_parsil.php") - .then(res => res.json()) - .then(data => { - - data.forEach(d => { - - const parsil = { - id: d.id, - nama: d.nama, - pemilik: d.pemilik, - status: d.status, - area: parseFloat(d.luas), - pts: d.koordinat - }; - - state.parsil.push(parsil); - - renderParsil(parsil); - - updateCount('parsil', state.parsil.length); - - addCard( - 'parsil', - parsil.id, - parsil.nama, - parsil.status, - parsil.status, - '#1a5276' - ); - - }); - - }) - .catch(err => {}); - } /* ── KEMISKINAN ── */ function getKemiskinanDraft() { @@ -5766,7 +5636,7 @@ }); renderDashboard(); if (state.masjid.length) refreshType('masjid'); - }).catch(err => {}); + }).catch(err => { }); } function openEditKemiskinan(id) { @@ -5884,7 +5754,7 @@ fetch('api/update_status_kemiskinan.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: parseInt(id), status }) - }).catch(() => {}); + }).catch(() => { }); renderDashboard(); refreshType('kemiskinan'); map.closePopup(); @@ -6083,10 +5953,10 @@
Sedang Diproses${summary.process}
Sudah Ditangani${summary.handled}
` + (summary.sample.length > 0 - ? '
KK Terdekat
' - + summary.sample.map(k => '
' + k.nama_kk + '' + Math.round(k.distance) + ' m
').join('') - : '
Tidak ada KK dalam radius ini
' - ) + ` + ? '
KK Terdekat
' + + summary.sample.map(k => '
' + k.nama_kk + '' + Math.round(k.distance) + ' m
').join('') + : '
Tidak ada KK dalam radius ini
' + ) + `
` : '') + ` ${adminActions} `); @@ -6104,7 +5974,7 @@ addCard('masjid', m.id, m.nama_masjid, m.pic_masjid, 'Rumah Ibadah', '#0f6250'); }); renderDashboard(); - }).catch(err => {}); + }).catch(err => { }); } function openEditMasjid(id) { @@ -6679,7 +6549,7 @@ /* ── FULL DATA WARGA MODAL ── */ let _allDataWarga = []; - + function openDataWargaModal() { const existing = document.getElementById('ftm-warga-overlay'); if (existing) existing.remove(); @@ -6732,11 +6602,11 @@ `; document.body.appendChild(overlay); - + fetch('api/read_semua_warga.php') .then(r => r.json()) .then(res => { - if(res.status === 'success') { + if (res.status === 'success') { _allDataWarga = res.data; renderDataWargaTable(); } else { @@ -6750,7 +6620,7 @@ function renderDataWargaTable() { const tbody = document.getElementById('ftm-warga-tbody'); - if(!tbody) return; + if (!tbody) return; const catFilter = document.getElementById('ftm-filter-kategori').value; const sickFilter = document.getElementById('ftm-filter-penyakit').value; @@ -6758,12 +6628,12 @@ let matchCat = catFilter ? d.kategori_label === catFilter : true; let isSakit = d.riwayat_penyakit && d.riwayat_penyakit.trim() !== '' && d.riwayat_penyakit.toLowerCase() !== 'tidak ada'; let matchSick = true; - if(sickFilter === 'Sehat') matchSick = !isSakit; - if(sickFilter === 'Sakit') matchSick = isSakit; + if (sickFilter === 'Sehat') matchSick = !isSakit; + if (sickFilter === 'Sakit') matchSick = isSakit; return matchCat && matchSick; }); - if(filtered.length === 0) { + if (filtered.length === 0) { tbody.innerHTML = 'Tidak ada data ditemukan'; return; } @@ -6771,8 +6641,8 @@ let html = ''; filtered.forEach(d => { const isSakit = d.riwayat_penyakit && d.riwayat_penyakit.trim() !== '' && d.riwayat_penyakit.toLowerCase() !== 'tidak ada'; - const healthHtml = isSakit - ? `
${d.riwayat_penyakit}
` + const healthHtml = isSakit + ? `
${d.riwayat_penyakit}
` : `
Sehat
`; const pend = (d.pendidikan && d.pendidikan.trim() !== '') ? d.pendidikan : '-'; @@ -6810,7 +6680,7 @@ /* ── FULL DATA MASJID MODAL ── */ let _allDataMasjid = []; - + function openDataMasjidModal() { const existing = document.getElementById('ftm-masjid-overlay'); if (existing) existing.remove(); @@ -6851,11 +6721,11 @@ `; document.body.appendChild(overlay); - + fetch('api/read_masjid.php') .then(r => r.json()) .then(res => { - if(Array.isArray(res)) _allDataMasjid = res; + if (Array.isArray(res)) _allDataMasjid = res; else if (res.status === 'success' && res.data) _allDataMasjid = res.data; else _allDataMasjid = []; renderDataMasjidTable(); @@ -6867,16 +6737,16 @@ function renderDataMasjidTable() { const tbody = document.getElementById('ftm-masjid-tbody'); - if(!tbody) return; + if (!tbody) return; const searchVal = document.getElementById('ftm-search-masjid').value.toLowerCase(); let filtered = _allDataMasjid.filter(d => { - if(!searchVal) return true; - return (d.nama_masjid && d.nama_masjid.toLowerCase().includes(searchVal)) || - (d.pic_masjid && d.pic_masjid.toLowerCase().includes(searchVal)); + if (!searchVal) return true; + return (d.nama_masjid && d.nama_masjid.toLowerCase().includes(searchVal)) || + (d.pic_masjid && d.pic_masjid.toLowerCase().includes(searchVal)); }); - if(filtered.length === 0) { + if (filtered.length === 0) { tbody.innerHTML = 'Tidak ada data ditemukan'; return; } @@ -6885,9 +6755,9 @@ filtered.forEach(d => { const radiusHtml = `${d.radius || 0} meter`; let tgl_format = '-'; - if(d.updated_at) { - const date = new Date(d.updated_at); - tgl_format = date.toLocaleDateString('id-ID', { year: 'numeric', month: 'short', day: 'numeric' }); + if (d.updated_at) { + const date = new Date(d.updated_at); + tgl_format = date.toLocaleDateString('id-ID', { year: 'numeric', month: 'short', day: 'numeric' }); } html += ` @@ -7152,7 +7022,7 @@ const tb = document.getElementById('ua-tbody'); if (!tb) return; const gisUser = JSON.parse(sessionStorage.getItem('gis_user') || '{}'); - const colors = ['#3b82f6','#8b5cf6','#ec4899','#f59e0b','#10b981']; + const colors = ['#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981']; if (!_uaUsers.length) { tb.innerHTML = 'Tidak ada pengguna'; return; @@ -7163,7 +7033,7 @@ const roleHtml = u.role === 'admin' ? ` Admin` : ` Operator`; - const tgl = u.created_at ? new Date(u.created_at).toLocaleDateString('id-ID', { year:'numeric', month:'short', day:'numeric' }) : '-'; + const tgl = u.created_at ? new Date(u.created_at).toLocaleDateString('id-ID', { year: 'numeric', month: 'short', day: 'numeric' }) : '-'; const isSelf = u.username === gisUser.username; return `
@@ -7272,9 +7142,9 @@ const form = document.getElementById('cp-form'); if (form) form.reset(); const fill = document.getElementById('cp-strength-fill'); - const lbl = document.getElementById('cp-strength-lbl'); - if (fill) { fill.style.width='0'; fill.style.background=''; } - if (lbl) lbl.textContent = ''; + const lbl = document.getElementById('cp-strength-lbl'); + if (fill) { fill.style.width = '0'; fill.style.background = ''; } + if (lbl) lbl.textContent = ''; document.getElementById('cp-err').classList.remove('show'); document.getElementById('cp-ok').classList.remove('show'); const btn = document.getElementById('cp-submit-btn'); @@ -7293,47 +7163,47 @@ const ico = document.getElementById(iconId); if (inp.type === 'password') { inp.type = 'text'; - ico.classList.replace('fa-eye','fa-eye-slash'); + ico.classList.replace('fa-eye', 'fa-eye-slash'); } else { inp.type = 'password'; - ico.classList.replace('fa-eye-slash','fa-eye'); + ico.classList.replace('fa-eye-slash', 'fa-eye'); } } function cpCheckStrength(val) { const fill = document.getElementById('cp-strength-fill'); - const lbl = document.getElementById('cp-strength-lbl'); - if (!val) { fill.style.width='0'; lbl.textContent=''; return; } + const lbl = document.getElementById('cp-strength-lbl'); + if (!val) { fill.style.width = '0'; lbl.textContent = ''; return; } let s = 0; - if (val.length >= 6) s++; + if (val.length >= 6) s++; if (val.length >= 10) s++; if (/[A-Z]/.test(val)) s++; if (/[0-9]/.test(val)) s++; if (/[^A-Za-z0-9]/.test(val)) s++; const lv = [ - {pct:'20%',bg:'#ef4444',txt:'Sangat Lemah'}, - {pct:'40%',bg:'#f97316',txt:'Lemah'}, - {pct:'60%',bg:'#f59e0b',txt:'Sedang'}, - {pct:'80%',bg:'#3b82f6',txt:'Kuat'}, - {pct:'100%',bg:'#10b981',txt:'Sangat Kuat'}, - ][Math.max(0,s-1)]; + { pct: '20%', bg: '#ef4444', txt: 'Sangat Lemah' }, + { pct: '40%', bg: '#f97316', txt: 'Lemah' }, + { pct: '60%', bg: '#f59e0b', txt: 'Sedang' }, + { pct: '80%', bg: '#3b82f6', txt: 'Kuat' }, + { pct: '100%', bg: '#10b981', txt: 'Sangat Kuat' }, + ][Math.max(0, s - 1)]; fill.style.width = lv.pct; fill.style.background = lv.bg; lbl.textContent = lv.txt; lbl.style.color = lv.bg; } - document.getElementById('cp-form').addEventListener('submit', async function(e) { + document.getElementById('cp-form').addEventListener('submit', async function (e) { e.preventDefault(); const gisUser = JSON.parse(sessionStorage.getItem('gis_user') || '{}'); - const uname = gisUser.username || ''; + const uname = gisUser.username || ''; const oldP = document.getElementById('cp-old').value; const newP = document.getElementById('cp-new').value; const conP = document.getElementById('cp-con').value; - const err = document.getElementById('cp-err'); + const err = document.getElementById('cp-err'); const errTxt = document.getElementById('cp-err-text'); - const ok = document.getElementById('cp-ok'); - const btn = document.getElementById('cp-submit-btn'); + const ok = document.getElementById('cp-ok'); + const btn = document.getElementById('cp-submit-btn'); err.classList.remove('show'); ok.classList.remove('show'); @@ -7354,9 +7224,9 @@ btn.innerHTML = ' Menyimpan...'; try { - const res = await fetch('api/change_password.php', { + const res = await fetch('api/change_password.php', { method: 'POST', - headers: {'Content-Type': 'application/json'}, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: uname, old_password: oldP, new_password: newP }) }); const data = await res.json(); @@ -7388,7 +7258,7 @@ }); // Close modal on backdrop click - document.getElementById('cp-modal').addEventListener('click', function(e) { + document.getElementById('cp-modal').addEventListener('click', function (e) { if (e.target === this) closeChangePassword(); }); @@ -7408,25 +7278,34 @@
-
Error
-
Password berhasil diubah!
+
Error
+
Password berhasil diubah! +
- +
- +
- - + + +
+
+
-
@@ -7434,7 +7313,8 @@
- +