first commit
This commit is contained in:
@@ -0,0 +1,876 @@
|
||||
<?php
|
||||
// Cek session — kalau sudah login, langsung ke app
|
||||
session_start();
|
||||
if (!empty($_SESSION['user'])) {
|
||||
header('Location: sig1.html');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GeoSosial — Sistem Informasi Kemiskinan Kota Pontianak</title>
|
||||
<meta name="description" content="Sistem Informasi Geografis kemiskinan dan sosial Kota Pontianak">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--blue: #1a5fff;
|
||||
--blue2: #0f3cc7;
|
||||
--teal: #00c9aa;
|
||||
--green: #10b981;
|
||||
--red: #f03e5a;
|
||||
--ink: #0b1120;
|
||||
--ink2: #3d4b66;
|
||||
--ink3: #8a97ab;
|
||||
--paper: #f0f4fb;
|
||||
--w: #ffffff;
|
||||
--bdr: rgba(11,17,32,.09);
|
||||
}
|
||||
|
||||
html, body { height: 100%; overflow: hidden; }
|
||||
body {
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
display: flex; height: 100vh;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* ══════════════ LEFT: MAP ══════════════ */
|
||||
.map-side {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
#landingMap {
|
||||
width: 100%; height: 100%;
|
||||
}
|
||||
|
||||
/* Map overlay gradient on right edge (blends into login panel) */
|
||||
.map-side::after {
|
||||
content: '';
|
||||
position: absolute; top: 0; right: 0; bottom: 0;
|
||||
width: 80px;
|
||||
background: linear-gradient(to right, transparent, var(--paper));
|
||||
pointer-events: none; z-index: 400;
|
||||
}
|
||||
|
||||
/* Branding overlay on map */
|
||||
.map-brand {
|
||||
position: absolute; top: 28px; left: 28px; z-index: 500;
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
background: rgba(255,255,255,.92);
|
||||
backdrop-filter: blur(16px);
|
||||
border-radius: 14px;
|
||||
padding: 10px 16px;
|
||||
box-shadow: 0 4px 24px rgba(11,17,32,.12);
|
||||
border: 1px solid var(--bdr);
|
||||
}
|
||||
.map-brand-ic {
|
||||
width: 36px; height: 36px; border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--blue), var(--teal));
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
.map-brand-name {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 17px; font-weight: 700;
|
||||
color: var(--ink); letter-spacing: -.3px;
|
||||
}
|
||||
.map-brand-sub { font-size: 11px; color: var(--ink3); margin-top: 1px; }
|
||||
|
||||
/* Stats overlay on map bottom */
|
||||
.map-stats {
|
||||
position: absolute; bottom: 28px; left: 28px; z-index: 500;
|
||||
display: flex; gap: 10px;
|
||||
}
|
||||
.mstat {
|
||||
background: rgba(255,255,255,.92);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--bdr);
|
||||
border-radius: 12px;
|
||||
padding: 10px 16px;
|
||||
box-shadow: 0 4px 20px rgba(11,17,32,.1);
|
||||
min-width: 100px;
|
||||
}
|
||||
.mstat-n { font-size: 10px; color: var(--ink3); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
|
||||
.mstat-v {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 20px; font-weight: 700; color: var(--ink);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.mstat-v.red { color: var(--red); }
|
||||
.mstat-v.blue { color: var(--blue); }
|
||||
.mstat-v.teal { color: var(--teal); }
|
||||
|
||||
/* ══════════════ RIGHT: LOGIN ══════════════ */
|
||||
.login-side {
|
||||
width: 420px;
|
||||
flex-shrink: 0;
|
||||
background: var(--paper);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 48px 44px;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Subtle dot pattern */
|
||||
.login-side::before {
|
||||
content: '';
|
||||
position: absolute; inset: 0; z-index: 0;
|
||||
background-image: radial-gradient(rgba(26,95,255,.07) 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.login-inner { position: relative; z-index: 1; }
|
||||
|
||||
.login-eyebrow {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 4px 12px; border-radius: 99px;
|
||||
background: rgba(26,95,255,.08);
|
||||
border: 1px solid rgba(26,95,255,.15);
|
||||
font-size: 11px; font-weight: 600; color: var(--blue);
|
||||
letter-spacing: .04em;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 32px; font-weight: 700;
|
||||
color: var(--ink); letter-spacing: -.8px;
|
||||
line-height: 1.1; margin-bottom: 10px;
|
||||
}
|
||||
.login-sub { font-size: 13.5px; color: var(--ink3); line-height: 1.65; margin-bottom: 32px; font-weight: 400; }
|
||||
|
||||
/* Form */
|
||||
.form-group { margin-bottom: 16px; }
|
||||
.form-label {
|
||||
display: block; font-size: 11px; font-weight: 600;
|
||||
text-transform: uppercase; letter-spacing: .1em;
|
||||
color: var(--ink3); margin-bottom: 7px;
|
||||
}
|
||||
.form-input-wrap { position: relative; }
|
||||
.form-input-icon {
|
||||
position: absolute; left: 14px; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 15px; pointer-events: none;
|
||||
}
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 12px 42px 12px 42px;
|
||||
background: var(--w);
|
||||
border: 1.5px solid var(--bdr);
|
||||
border-radius: 11px;
|
||||
font-size: 14px; font-family: 'Inter', sans-serif; font-weight: 400;
|
||||
color: var(--ink); outline: none;
|
||||
transition: border-color .18s, box-shadow .18s;
|
||||
}
|
||||
.form-input:focus {
|
||||
border-color: var(--blue);
|
||||
box-shadow: 0 0 0 3px rgba(26,95,255,.1);
|
||||
}
|
||||
.form-input.error { border-color: var(--red); box-shadow: 0 0 0 3px rgba(240,62,90,.08); }
|
||||
.toggle-pw {
|
||||
position: absolute; right: 13px; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none; border: none; cursor: pointer;
|
||||
color: var(--ink3); font-size: 14px; padding: 2px;
|
||||
transition: color .15s; line-height: 1;
|
||||
}
|
||||
.toggle-pw:hover { color: var(--blue); }
|
||||
|
||||
.err-box {
|
||||
display: none;
|
||||
padding: 11px 14px; border-radius: 10px;
|
||||
background: rgba(240,62,90,.07);
|
||||
border: 1px solid rgba(240,62,90,.18);
|
||||
font-size: 12px; color: var(--red); font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.err-box.show { display: block; }
|
||||
|
||||
.btn-login {
|
||||
width: 100%; padding: 13px;
|
||||
border: none; border-radius: 11px;
|
||||
background: linear-gradient(135deg, var(--blue) 0%, #4b6fff 100%);
|
||||
color: #fff; font-size: 14px; font-weight: 600;
|
||||
font-family: 'Inter', sans-serif; letter-spacing: .01em;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 20px rgba(26,95,255,.28);
|
||||
transition: opacity .18s, transform .15s;
|
||||
display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.btn-login:hover { opacity: .9; transform: translateY(-1px); }
|
||||
.btn-login:disabled { opacity: .55; cursor: not-allowed; transform: none; }
|
||||
.btn-login .spin {
|
||||
width: 16px; height: 16px; border-radius: 50%;
|
||||
border: 2px solid rgba(255,255,255,.4);
|
||||
border-top-color: #fff;
|
||||
animation: spin .7s linear infinite; display: none;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.login-footer {
|
||||
margin-top: 20px; padding-top: 16px;
|
||||
border-top: 1px solid var(--bdr);
|
||||
font-size: 11px; color: var(--ink3);
|
||||
text-align: center; line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 840px) {
|
||||
body { flex-direction: column; overflow: auto; }
|
||||
html, body { height: auto; }
|
||||
.map-side { height: 45vh; flex: none; }
|
||||
.map-side::after { display: none; }
|
||||
.login-side { width: 100%; padding: 36px 28px; }
|
||||
}
|
||||
|
||||
/* Leaflet custom */
|
||||
.leaflet-popup-content-wrapper {
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 8px 32px rgba(11,17,32,.15) !important;
|
||||
border: 1px solid var(--bdr) !important;
|
||||
}
|
||||
.leaflet-popup-tip-container { display: none; }
|
||||
|
||||
/* Button Report Trigger */
|
||||
.btn-report-trigger {
|
||||
width: 100%; padding: 13px;
|
||||
border: 1.5px solid var(--blue); border-radius: 11px;
|
||||
background: transparent;
|
||||
color: var(--blue); font-size: 14px; font-weight: 600;
|
||||
font-family: 'Inter', sans-serif; letter-spacing: .01em;
|
||||
cursor: pointer;
|
||||
transition: background .18s, color .18s, transform .15s;
|
||||
display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.btn-report-trigger:hover {
|
||||
background: var(--blue);
|
||||
color: #fff;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(26,95,255,.15);
|
||||
}
|
||||
|
||||
/* Modal Overlay */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; z-index: 1000;
|
||||
background: rgba(11, 17, 32, 0.4);
|
||||
backdrop-filter: blur(12px);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
opacity: 0; pointer-events: none;
|
||||
transition: opacity .3s ease;
|
||||
padding: 20px;
|
||||
}
|
||||
.modal-overlay.show {
|
||||
opacity: 1; pointer-events: auto;
|
||||
}
|
||||
.modal-box {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
border-radius: 20px;
|
||||
width: 1000px; max-width: 95vw;
|
||||
max-height: 90vh;
|
||||
box-shadow: 0 20px 50px rgba(11, 17, 32, 0.15);
|
||||
display: flex; flex-direction: column;
|
||||
overflow: hidden;
|
||||
transform: scale(0.95);
|
||||
transition: transform .3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
.modal-overlay.show .modal-box {
|
||||
transform: scale(1);
|
||||
}
|
||||
.modal-header {
|
||||
padding: 20px 28px;
|
||||
border-bottom: 1px solid var(--bdr);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
.modal-title {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 20px; font-weight: 700; color: var(--ink);
|
||||
}
|
||||
.modal-close {
|
||||
background: none; border: none; font-size: 24px; cursor: pointer; color: var(--ink3);
|
||||
transition: color .15s; line-height: 1;
|
||||
}
|
||||
.modal-close:hover { color: var(--red); }
|
||||
|
||||
.modal-body {
|
||||
padding: 28px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Two-column layout in desktop */
|
||||
.report-grid {
|
||||
display: grid; grid-template-columns: 1.2fr 1fr; gap: 28px;
|
||||
}
|
||||
|
||||
.report-form-side {
|
||||
display: flex; flex-direction: column; gap: 16px;
|
||||
}
|
||||
|
||||
.report-map-side {
|
||||
display: flex; flex-direction: column; gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
.report-map-label {
|
||||
font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .1em; color: var(--ink3);
|
||||
}
|
||||
.report-map {
|
||||
width: 100%; height: 350px; border-radius: 12px; border: 1.5px solid var(--bdr);
|
||||
}
|
||||
|
||||
.form-row-2 {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
|
||||
}
|
||||
|
||||
/* Success Receipt Card */
|
||||
.success-card {
|
||||
text-align: center; padding: 40px 20px;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.success-icon {
|
||||
width: 64px; height: 64px; border-radius: 50%; background: #e6fdf5; border: 2px solid var(--green);
|
||||
display: flex; align-items: center; justify-content: center; font-size: 32px; color: var(--green);
|
||||
animation: scale-up .4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
@keyframes scale-up { 0% { transform: scale(0.5); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
|
||||
.success-title { font-family: 'Space Grotesk', sans-serif; font-size: 24px; font-weight: 700; color: var(--ink); }
|
||||
.success-no-card {
|
||||
background: var(--paper); border: 1.5px dashed var(--blue); padding: 12px 24px; border-radius: 12px;
|
||||
font-family: 'Space Grotesk', sans-serif; font-size: 20px; font-weight: 700; color: var(--blue);
|
||||
letter-spacing: 0.05em; margin: 10px 0; display: inline-block;
|
||||
}
|
||||
.success-desc { font-size: 14px; color: var(--ink2); max-width: 450px; line-height: 1.6; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.report-grid { grid-template-columns: 1fr; }
|
||||
.report-map { height: 260px; }
|
||||
.modal-box { max-height: 95vh; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ══ KIRI: PETA ══ -->
|
||||
<div class="map-side">
|
||||
<!-- Branding di atas peta -->
|
||||
<div class="map-brand">
|
||||
<div class="map-brand-ic">📍</div>
|
||||
<div>
|
||||
<div class="map-brand-name">GeoSosial</div>
|
||||
<div class="map-brand-sub">Kota Pontianak</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Peta Leaflet -->
|
||||
<div id="landingMap"></div>
|
||||
|
||||
<!-- Stat cards di bawah peta -->
|
||||
<div class="map-stats">
|
||||
<div class="mstat">
|
||||
<div class="mstat-n">Kelurahan</div>
|
||||
<div class="mstat-v blue" id="msKel">24</div>
|
||||
</div>
|
||||
<div class="mstat">
|
||||
<div class="mstat-n">Total Warga</div>
|
||||
<div class="mstat-v" id="msWarga">—</div>
|
||||
</div>
|
||||
<div class="mstat">
|
||||
<div class="mstat-n">Sangat Miskin</div>
|
||||
<div class="mstat-v red" id="msSM">—</div>
|
||||
</div>
|
||||
<div class="mstat">
|
||||
<div class="mstat-n">Total Bantuan</div>
|
||||
<div class="mstat-v teal" id="msBantuan">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ KANAN: LOGIN ══ -->
|
||||
<aside class="login-side">
|
||||
<div class="login-inner">
|
||||
<div class="login-eyebrow">🔐 Portal Petugas</div>
|
||||
<h1 class="login-title">Masuk ke<br>Sistem GeoSosial</h1>
|
||||
<p class="login-sub">Masukkan kredensial Anda untuk mengakses data kemiskinan dan sosial Kota Pontianak.</p>
|
||||
|
||||
<div class="err-box" id="errBox">❌ <span id="errMsg">Username atau password salah.</span></div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="liUser">Username</label>
|
||||
<div class="form-input-wrap">
|
||||
<span class="form-input-icon">👤</span>
|
||||
<input class="form-input" id="liUser" type="text"
|
||||
placeholder="Masukkan username" autocomplete="username"
|
||||
onkeydown="if(event.key==='Enter') document.getElementById('liPass').focus()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="liPass">Password</label>
|
||||
<div class="form-input-wrap">
|
||||
<span class="form-input-icon">🔒</span>
|
||||
<input class="form-input" id="liPass" type="password"
|
||||
placeholder="Masukkan password" autocomplete="current-password"
|
||||
onkeydown="if(event.key==='Enter') doLogin()">
|
||||
<button class="toggle-pw" onclick="togglePw()" tabindex="-1" type="button">👁</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn-login" id="btnLogin" onclick="doLogin()">
|
||||
<div class="spin" id="loginSpin"></div>
|
||||
<span id="btnTxt">Masuk Sekarang →</span>
|
||||
</button>
|
||||
|
||||
<button class="btn-report-trigger" onclick="openReportModal()">
|
||||
📢 Laporkan Warga / Ajukan Bantuan
|
||||
</button>
|
||||
|
||||
<div class="login-footer">
|
||||
Sistem Informasi Geografis Sosial<br>
|
||||
Dinas Sosial Kota Pontianak © 2025
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ══ MODAL LAPORAN WARGA ══ -->
|
||||
<div class="modal-overlay" id="reportModal">
|
||||
<div class="modal-box">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">📢 Formulir Pengaduan & Laporan Warga</h2>
|
||||
<button class="modal-close" onclick="closeReportModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Form Input State -->
|
||||
<div id="reportFormState">
|
||||
<div class="report-grid">
|
||||
<!-- Form fields side -->
|
||||
<div class="report-form-side">
|
||||
<div class="err-box" id="repErrBox">❌ <span id="repErrMsg">Lengkapi kolom wajib diisi.</span></div>
|
||||
|
||||
<div class="form-row-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repPelapor">Nama Pelapor (Anda)</label>
|
||||
<input class="form-input" id="repPelapor" type="text" placeholder="Nama lengkap Anda">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repHP">No. HP Pelapor</label>
|
||||
<input class="form-input" id="repHP" type="text" placeholder="Contoh: 0812xxxx">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repNama">Warga yang Dilaporkan *</label>
|
||||
<input class="form-input" id="repNama" type="text" placeholder="Nama kepala keluarga" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repNIK">NIK Terlapor (16 Digit)</label>
|
||||
<input class="form-input" id="repNIK" type="text" maxlength="16" placeholder="Nomor Induk Kependudukan">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repKelurahan">Kelurahan Rumah Terlapor *</label>
|
||||
<select class="form-input" id="repKelurahan" required style="padding-left: 14px;">
|
||||
<option value="">-- Pilih Kelurahan --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repKategori">Kategori Kemiskinan</label>
|
||||
<select class="form-input" id="repKategori" style="padding-left: 14px;">
|
||||
<option value="sangat_miskin">Sangat Miskin</option>
|
||||
<option value="miskin" selected>Miskin</option>
|
||||
<option value="hampir_miskin">Hampir Miskin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repAnggota">Jumlah Anggota Keluarga</label>
|
||||
<input class="form-input" id="repAnggota" type="number" min="1" value="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repPenghasilan">Estimasi Penghasilan Bulanan</label>
|
||||
<input class="form-input" id="repPenghasilan" type="number" min="0" value="0" placeholder="Rp">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repKeterangan">Keterangan Kondisi Rumah / Keluarga *</label>
|
||||
<textarea class="form-input" id="repKeterangan" rows="3" placeholder="Jelaskan alasan mengapa warga ini layak menerima bantuan sosial..." style="resize: none; padding-left: 14px;" required></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repLat">Latitude *</label>
|
||||
<input class="form-input" id="repLat" type="text" readonly placeholder="Klik peta di kanan" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="repLng">Longitude *</label>
|
||||
<input class="form-input" id="repLng" type="text" readonly placeholder="Klik peta di kanan" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn-login" id="btnSubmitReport" onclick="submitReport()" style="margin-top: 10px;">
|
||||
<div class="spin" id="reportSpin" style="display: none;"></div>
|
||||
<span id="btnRepTxt">Kirim Laporan Pengaduan</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mini-map side -->
|
||||
<div class="report-map-side">
|
||||
<div class="report-map-label">📍 Tentukan Lokasi Rumah Terlapor (Klik pada peta) *</div>
|
||||
<div class="report-map" id="reportMiniMap"></div>
|
||||
<div style="font-size: 11px; color: var(--ink3); line-height: 1.4;">
|
||||
* Cari dan perbesar peta mini di atas, kemudian **klik pada lokasi rumah** warga yang dilaporkan untuk mendapatkan koordinat Lat/Lng secara tepat.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Success Receipt State -->
|
||||
<div id="reportSuccessState" style="display: none;">
|
||||
<div class="success-card">
|
||||
<div class="success-icon">✓</div>
|
||||
<h3 class="success-title">Laporan Berhasil Terkirim!</h3>
|
||||
<div class="success-no-card" id="successReportNo">RPT-2026-000</div>
|
||||
<p class="success-desc">
|
||||
Terima kasih atas kepedulian Anda. Laporan pengaduan telah berhasil disimpan ke database. Nomor laporan di atas adalah bukti pengajuan Anda. Admin/Petugas Dinas Sosial akan segera memverifikasi laporan ini ke lapangan.
|
||||
</p>
|
||||
<button class="btn-login" onclick="closeReportModal()" style="width: auto; padding: 12px 32px; margin-top: 15px;">
|
||||
Selesai & Tutup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script>
|
||||
// ── Init Peta ────────────────────────────────────────────
|
||||
const map = L.map('landingMap', {
|
||||
center: [-0.0263, 109.3425],
|
||||
zoom: 13,
|
||||
zoomControl: false,
|
||||
attributionControl: false
|
||||
});
|
||||
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
|
||||
// Warna per kategori
|
||||
const KAT_COLOR = { sangat_miskin: '#f03e5a', miskin: '#f97316', hampir_miskin: '#f59e0b' };
|
||||
const KAT_LABEL = { sangat_miskin: 'Sangat Miskin', miskin: 'Miskin', hampir_miskin: 'Hampir Miskin' };
|
||||
|
||||
function makeIcon(kat) {
|
||||
const c = KAT_COLOR[kat] || '#8a97ab';
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="36" viewBox="0 0 28 36">
|
||||
<ellipse cx="14" cy="34" rx="6" ry="2" fill="rgba(0,0,0,.18)"/>
|
||||
<path d="M14 2C8.48 2 4 6.48 4 12c0 7.5 10 22 10 22s10-14.5 10-22c0-5.52-4.48-10-10-10z"
|
||||
fill="${c}" stroke="#fff" stroke-width="1.5"/>
|
||||
<circle cx="14" cy="12" r="4" fill="rgba(255,255,255,.9)"/>
|
||||
</svg>`;
|
||||
return L.divIcon({
|
||||
html: svg, className: '', iconSize: [28, 36], iconAnchor: [14, 36], popupAnchor: [0, -38]
|
||||
});
|
||||
}
|
||||
|
||||
// Load data publik (dashboard stats + markers)
|
||||
fetch('api/dashboard.php')
|
||||
.then(r => r.json())
|
||||
.then(j => {
|
||||
if (!j.success) return;
|
||||
const d = j.data;
|
||||
|
||||
// Update stat cards — gunakan d.kpis (field yang benar dari dashboard.php)
|
||||
const kpis = d.kpis || {};
|
||||
const tot = (kpis.total_kk || 0);
|
||||
document.getElementById('msWarga').textContent = tot.toLocaleString('id-ID');
|
||||
document.getElementById('msSM').textContent = (kpis.sangat_miskin || 0).toLocaleString('id-ID');
|
||||
const totBantuan = (d.ranking || []).reduce((s, r) => s + (parseInt(r.total_bantuan) || 0), 0);
|
||||
document.getElementById('msBantuan').textContent =
|
||||
totBantuan >= 1e6
|
||||
? 'Rp ' + (totBantuan / 1e6).toFixed(0) + ' Jt'
|
||||
: 'Rp ' + totBantuan.toLocaleString('id-ID');
|
||||
|
||||
|
||||
// Plot marker per kelurahan (bukan warga individual — hanya titik tengah kelurahan)
|
||||
if (d.ranking) {
|
||||
d.ranking.forEach(r => {
|
||||
if (!r.lat || !r.lng) return;
|
||||
const kat = r.sangat_miskin >= r.miskin ? 'sangat_miskin' : 'miskin';
|
||||
L.marker([parseFloat(r.lat), parseFloat(r.lng)], { icon: makeIcon(kat) })
|
||||
.addTo(map)
|
||||
.bindPopup(`
|
||||
<div style="font-family:'Plus Jakarta Sans',sans-serif;padding:4px 2px;min-width:160px">
|
||||
<div style="font-weight:800;font-size:14px;color:#0b1120">${r.kelurahan}</div>
|
||||
<div style="font-size:11px;color:#8a97ab;margin-bottom:8px">${r.kecamatan}</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">
|
||||
<div style="background:#fde8ec;border-radius:8px;padding:6px 8px;text-align:center">
|
||||
<div style="font-size:9px;color:#f03e5a;font-weight:700">SANGAT MISKIN</div>
|
||||
<div style="font-size:16px;font-weight:800;color:#f03e5a">${r.sangat_miskin}</div>
|
||||
</div>
|
||||
<div style="background:#fff3e8;border-radius:8px;padding:6px 8px;text-align:center">
|
||||
<div style="font-size:9px;color:#f97316;font-weight:700">MISKIN</div>
|
||||
<div style="font-size:16px;font-weight:800;color:#f97316">${r.miskin}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:8px;font-size:11px;color:#3d4b66">
|
||||
Total: <strong>${r.total}</strong> KK • Bantuan: <strong style="color:#00c9aa">Rp ${parseInt(r.total_bantuan||0).toLocaleString('id-ID')}</strong>
|
||||
</div>
|
||||
</div>`);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
// ── Login ────────────────────────────────────────────────
|
||||
function togglePw() {
|
||||
|
||||
const inp = document.getElementById('liPass');
|
||||
const btn = document.querySelector('.toggle-pw');
|
||||
inp.type = inp.type === 'password' ? 'text' : 'password';
|
||||
btn.textContent = inp.type === 'password' ? '👁' : '🙈';
|
||||
}
|
||||
|
||||
async function doLogin() {
|
||||
const u = document.getElementById('liUser').value.trim();
|
||||
const p = document.getElementById('liPass').value;
|
||||
const eb = document.getElementById('errBox');
|
||||
const btn = document.getElementById('btnLogin');
|
||||
const spin = document.getElementById('loginSpin');
|
||||
const txt = document.getElementById('btnTxt');
|
||||
|
||||
eb.classList.remove('show');
|
||||
document.getElementById('liUser').classList.remove('error');
|
||||
document.getElementById('liPass').classList.remove('error');
|
||||
|
||||
if (!u || !p) {
|
||||
document.getElementById('errMsg').textContent = 'Username dan password wajib diisi.';
|
||||
eb.classList.add('show');
|
||||
if (!u) document.getElementById('liUser').classList.add('error');
|
||||
if (!p) document.getElementById('liPass').classList.add('error');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
spin.style.display = 'block';
|
||||
txt.textContent = 'Memverifikasi...';
|
||||
|
||||
try {
|
||||
const res = await fetch('api/auth.php?action=login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: u, password: p })
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
txt.textContent = '✓ Berhasil! Mengalihkan...';
|
||||
setTimeout(() => { location.href = 'sig1.html'; }, 400);
|
||||
} else {
|
||||
document.getElementById('errMsg').textContent = json.message || 'Username atau password salah.';
|
||||
eb.classList.add('show');
|
||||
document.getElementById('liUser').classList.add('error');
|
||||
document.getElementById('liPass').classList.add('error');
|
||||
btn.disabled = false;
|
||||
spin.style.display = 'none';
|
||||
txt.textContent = 'Masuk Sekarang →';
|
||||
}
|
||||
} catch {
|
||||
document.getElementById('errMsg').textContent = 'Koneksi server gagal. Coba lagi.';
|
||||
eb.classList.add('show');
|
||||
btn.disabled = false;
|
||||
spin.style.display = 'none';
|
||||
txt.textContent = 'Masuk Sekarang →';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Portal Pengaduan Publik (Laporan Warga) ───────────────
|
||||
let reportMap = null;
|
||||
let reportMarker = null;
|
||||
let kelurahanLoaded = false;
|
||||
|
||||
async function openReportModal() {
|
||||
const modal = document.getElementById('reportModal');
|
||||
modal.classList.add('show');
|
||||
|
||||
// Load kelurahan options dynamically
|
||||
if (!kelurahanLoaded) {
|
||||
try {
|
||||
const res = await fetch('api/kelurahan.php');
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
const select = document.getElementById('repKelurahan');
|
||||
json.data.forEach(kel => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = kel.id;
|
||||
opt.textContent = `${kel.nama} (${kel.kecamatan})`;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
kelurahanLoaded = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Gagal memuat data kelurahan: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize/refresh Leaflet mini map inside modal
|
||||
setTimeout(() => {
|
||||
if (!reportMap) {
|
||||
reportMap = L.map('reportMiniMap', {
|
||||
center: [-0.0263, 109.3425],
|
||||
zoom: 13,
|
||||
attributionControl: false
|
||||
});
|
||||
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
||||
maxZoom: 19
|
||||
}).addTo(reportMap);
|
||||
|
||||
reportMap.on('click', function(e) {
|
||||
const lat = e.latlng.lat.toFixed(7);
|
||||
const lng = e.latlng.lng.toFixed(7);
|
||||
document.getElementById('repLat').value = lat;
|
||||
document.getElementById('repLng').value = lng;
|
||||
|
||||
if (reportMarker) {
|
||||
reportMarker.setLatLng(e.latlng);
|
||||
} else {
|
||||
reportMarker = L.marker(e.latlng, { draggable: true }).addTo(reportMap);
|
||||
reportMarker.on('dragend', function(ev) {
|
||||
const pos = reportMarker.getLatLng();
|
||||
document.getElementById('repLat').value = pos.lat.toFixed(7);
|
||||
document.getElementById('repLng').value = pos.lng.toFixed(7);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reportMap.invalidateSize();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function closeReportModal() {
|
||||
document.getElementById('reportModal').classList.remove('show');
|
||||
|
||||
// Reset form fields
|
||||
document.getElementById('repPelapor').value = '';
|
||||
document.getElementById('repHP').value = '';
|
||||
document.getElementById('repNama').value = '';
|
||||
document.getElementById('repNIK').value = '';
|
||||
document.getElementById('repKelurahan').value = '';
|
||||
document.getElementById('repKategori').value = 'miskin';
|
||||
document.getElementById('repAnggota').value = '1';
|
||||
document.getElementById('repPenghasilan').value = '0';
|
||||
document.getElementById('repKeterangan').value = '';
|
||||
document.getElementById('repLat').value = '';
|
||||
document.getElementById('repLng').value = '';
|
||||
|
||||
// Remove marker if exists
|
||||
if (reportMarker) {
|
||||
reportMap.removeLayer(reportMarker);
|
||||
reportMarker = null;
|
||||
}
|
||||
|
||||
// Reset view states
|
||||
document.getElementById('reportFormState').style.display = 'block';
|
||||
document.getElementById('reportSuccessState').style.display = 'none';
|
||||
document.getElementById('repErrBox').classList.remove('show');
|
||||
}
|
||||
|
||||
async function submitReport() {
|
||||
const pelapor = document.getElementById('repPelapor').value.trim();
|
||||
const hp = document.getElementById('repHP').value.trim();
|
||||
const nama = document.getElementById('repNama').value.trim();
|
||||
const nik = document.getElementById('repNIK').value.trim();
|
||||
const kelurahan_id = document.getElementById('repKelurahan').value;
|
||||
const kategori = document.getElementById('repKategori').value;
|
||||
const jumlah_anggota = parseInt(document.getElementById('repAnggota').value) || 1;
|
||||
const penghasilan = parseInt(document.getElementById('repPenghasilan').value) || 0;
|
||||
const keterangan = document.getElementById('repKeterangan').value.trim();
|
||||
const lat = document.getElementById('repLat').value;
|
||||
const lng = document.getElementById('repLng').value;
|
||||
|
||||
const eb = document.getElementById('repErrBox');
|
||||
const errMsg = document.getElementById('repErrMsg');
|
||||
const btn = document.getElementById('btnSubmitReport');
|
||||
const spin = document.getElementById('reportSpin');
|
||||
const txt = document.getElementById('btnRepTxt');
|
||||
|
||||
eb.classList.remove('show');
|
||||
|
||||
// Validation
|
||||
if (!nama || !kelurahan_id || !keterangan || !lat || !lng) {
|
||||
errMsg.textContent = 'Nama warga, kelurahan, keterangan kondisi, dan koordinat peta wajib diisi.';
|
||||
eb.classList.add('show');
|
||||
return;
|
||||
}
|
||||
|
||||
if (nik && nik.length !== 16) {
|
||||
errMsg.textContent = 'NIK harus tepat 16 digit.';
|
||||
eb.classList.add('show');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
spin.style.display = 'inline-block';
|
||||
txt.textContent = 'Mengirim Laporan...';
|
||||
|
||||
try {
|
||||
const res = await fetch('api/laporan.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
pelapor,
|
||||
hp,
|
||||
nama,
|
||||
nik: nik || null,
|
||||
kelurahan_id: parseInt(kelurahan_id),
|
||||
kategori,
|
||||
jumlah_anggota,
|
||||
penghasilan,
|
||||
keterangan,
|
||||
lat: parseFloat(lat),
|
||||
lng: parseFloat(lng)
|
||||
})
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
document.getElementById('successReportNo').textContent = json.data.no;
|
||||
document.getElementById('reportFormState').style.display = 'none';
|
||||
document.getElementById('reportSuccessState').style.display = 'block';
|
||||
} else {
|
||||
errMsg.textContent = json.message || 'Gagal mengirim laporan. Coba lagi.';
|
||||
eb.classList.add('show');
|
||||
}
|
||||
} catch (e) {
|
||||
errMsg.textContent = 'Koneksi server gagal. Silakan coba lagi.';
|
||||
eb.classList.add('show');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
spin.style.display = 'none';
|
||||
txt.textContent = 'Kirim Laporan Pengaduan';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user