landing_page
This commit is contained in:
+15
-7
@@ -13,6 +13,11 @@ const PORT = process.env.PORT || 3000;
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use("/api/poverty", povertyRoutes);
|
||||
// ─── Root: landing page (harus sebelum express.static) ───────────────────────
|
||||
app.get("/", (req, res) => {
|
||||
res.sendFile(path.join(__dirname, "../frontend/landing.html"));
|
||||
});
|
||||
|
||||
// Sajikan folder frontend secara statis
|
||||
app.use(express.static(path.join(__dirname, "../frontend")));
|
||||
|
||||
@@ -644,19 +649,22 @@ app.delete("/api/bangunan/:id", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Fallback: semua route non-API ke index.html ─────────────────────────────
|
||||
// ─── Fallback: semua route non-API ke landing.html ───────────────────────────
|
||||
app.get("*", (req, res) => {
|
||||
res.sendFile(path.join(__dirname, "../frontend/index.html"));
|
||||
res.sendFile(path.join(__dirname, "../frontend/landing.html"));
|
||||
});
|
||||
|
||||
// ─── Start ────────────────────────────────────────────────────────────────────
|
||||
initDatabase().then(() => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`\n🚀 Server berjalan di http://localhost:${PORT}`);
|
||||
console.log(` Peta → http://localhost:${PORT}/`);
|
||||
console.log(` Admin → http://localhost:${PORT}/admin.html`);
|
||||
console.log(` API → http://localhost:${PORT}/api/spbu`);
|
||||
console.log(` API → http://localhost:${PORT}/api/jalan`);
|
||||
console.log(` API → http://localhost:${PORT}/api/bangunan\n`);
|
||||
console.log(` Portal → http://localhost:${PORT}/`);
|
||||
console.log(` SPBU → http://localhost:${PORT}/index.html`);
|
||||
console.log(` Poverty → http://localhost:${PORT}/poverty-map.html`);
|
||||
console.log(` Pengajuan → http://localhost:${PORT}/pengajuan.html`);
|
||||
console.log(` Admin → http://localhost:${PORT}/admin.html`);
|
||||
console.log(` Admin POV → http://localhost:${PORT}/admin-poverty.html`);
|
||||
console.log(` API SPBU → http://localhost:${PORT}/api/spbu`);
|
||||
console.log(` API POV → http://localhost:${PORT}/api/poverty\n`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,491 @@
|
||||
<!doctype html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WebGIS Pontianak — Portal Sistem Informasi Geografis</title>
|
||||
<meta name="description" content="Portal WebGIS Kota Pontianak — Sistem Informasi SPBU dan WebGIS Distribusi Bantuan Sosial Gereja berbasis pemetaan interaktif." />
|
||||
<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:ital,wght@0,400;0,600;0,700;0,800;0,900;1,400&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--c1: #7c3aed;
|
||||
--c2: #06b6d4;
|
||||
--c3: #f97316;
|
||||
--dark: #060d1f;
|
||||
--panel: rgba(255,255,255,.055);
|
||||
--border: rgba(255,255,255,.10);
|
||||
--shadow: 0 32px 80px rgba(6,13,31,.55);
|
||||
}
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
font-family: "Inter", system-ui, sans-serif;
|
||||
background: var(--dark);
|
||||
color: #f1f5f9;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
a { text-decoration: none; color: inherit; }
|
||||
|
||||
/* ── Animated mesh background ─────────────────────────────── */
|
||||
.bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background:
|
||||
radial-gradient(ellipse 80% 60% at 20% -10%, rgba(124,58,237,.35) 0%, transparent 55%),
|
||||
radial-gradient(ellipse 60% 50% at 85% 90%, rgba(6,182,212,.28) 0%, transparent 50%),
|
||||
radial-gradient(ellipse 50% 40% at 70% 10%, rgba(249,115,22,.18) 0%, transparent 45%),
|
||||
linear-gradient(135deg, #060d1f 0%, #0b1730 50%, #0d1635 100%);
|
||||
animation: bgShift 18s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes bgShift {
|
||||
0% { filter: hue-rotate(0deg); }
|
||||
100% { filter: hue-rotate(20deg); }
|
||||
}
|
||||
|
||||
/* Floating orbs */
|
||||
.orb {
|
||||
position: fixed;
|
||||
border-radius: 50%;
|
||||
filter: blur(90px);
|
||||
opacity: .22;
|
||||
animation: orbFloat 12s ease-in-out infinite alternate;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.orb-1 { width: 480px; height: 480px; background: var(--c1); top: -120px; left: -80px; animation-duration: 14s; }
|
||||
.orb-2 { width: 360px; height: 360px; background: var(--c2); bottom: -80px; right: -60px; animation-duration: 10s; animation-delay: -4s; }
|
||||
.orb-3 { width: 260px; height: 260px; background: var(--c3); top: 40%; left: 60%; animation-duration: 16s; animation-delay: -8s; }
|
||||
@keyframes orbFloat {
|
||||
from { transform: translate(0, 0) scale(1); }
|
||||
to { transform: translate(30px, -40px) scale(1.08); }
|
||||
}
|
||||
|
||||
/* ── Layout ───────────────────────────────────────────────── */
|
||||
.wrapper {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 24px 80px;
|
||||
}
|
||||
|
||||
/* ── Badge ────────────────────────────────────────────────── */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 18px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(124,58,237,.5);
|
||||
background: rgba(124,58,237,.18);
|
||||
backdrop-filter: blur(12px);
|
||||
font-size: .78rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
color: #c4b5fd;
|
||||
margin-bottom: 28px;
|
||||
animation: fadeDown .6s ease both;
|
||||
}
|
||||
.badge .dot {
|
||||
width: 7px; height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #7c3aed;
|
||||
box-shadow: 0 0 8px #7c3aed;
|
||||
animation: pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%,100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: .5; transform: scale(.7); }
|
||||
}
|
||||
|
||||
/* ── Headline ─────────────────────────────────────────────── */
|
||||
.headline {
|
||||
text-align: center;
|
||||
max-width: 780px;
|
||||
animation: fadeDown .7s ease .1s both;
|
||||
}
|
||||
.headline h1 {
|
||||
font-size: clamp(2.4rem, 6vw, 4rem);
|
||||
font-weight: 900;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -.02em;
|
||||
background: linear-gradient(135deg, #fff 20%, #c4b5fd 50%, #67e8f9 80%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.headline p {
|
||||
color: #94a3b8;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.65;
|
||||
max-width: 580px;
|
||||
margin: 0 auto 48px;
|
||||
}
|
||||
|
||||
/* ── Cards grid ───────────────────────────────────────────── */
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 22px;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
margin-bottom: 40px;
|
||||
animation: fadeUp .8s ease .2s both;
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 28px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
backdrop-filter: blur(24px);
|
||||
padding: 36px 32px 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
cursor: pointer;
|
||||
transition: transform .24s cubic-bezier(.23,1,.32,1), box-shadow .24s, border-color .24s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
transition: opacity .24s;
|
||||
border-radius: inherit;
|
||||
}
|
||||
.card-spbu::before { background: radial-gradient(circle at 80% 0%, rgba(249,115,22,.22), transparent 60%); }
|
||||
.card-poverty::before { background: radial-gradient(circle at 80% 0%, rgba(124,58,237,.25), transparent 60%); }
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-8px) scale(1.012);
|
||||
box-shadow: var(--shadow);
|
||||
border-color: rgba(255,255,255,.22);
|
||||
}
|
||||
.card:hover::before { opacity: 1; }
|
||||
.card:active { transform: translateY(-4px) scale(1.006); }
|
||||
|
||||
/* Card inner glow line */
|
||||
.card-glow {
|
||||
position: absolute;
|
||||
top: 0; left: 24px; right: 24px;
|
||||
height: 1px;
|
||||
border-radius: 999px;
|
||||
opacity: .7;
|
||||
transition: opacity .24s;
|
||||
}
|
||||
.card:hover .card-glow { opacity: 1; }
|
||||
.card-spbu .card-glow { background: linear-gradient(90deg, transparent, #f97316, transparent); }
|
||||
.card-poverty .card-glow { background: linear-gradient(90deg, transparent, #a78bfa, transparent); }
|
||||
|
||||
.card-icon {
|
||||
width: 58px; height: 58px;
|
||||
border-radius: 18px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: 22px;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid rgba(255,255,255,.12);
|
||||
}
|
||||
.card-spbu .card-icon { background: linear-gradient(135deg, rgba(249,115,22,.3), rgba(251,146,60,.15)); }
|
||||
.card-poverty .card-icon { background: linear-gradient(135deg, rgba(124,58,237,.3), rgba(6,182,212,.15)); }
|
||||
|
||||
.card-tag {
|
||||
display: inline-flex;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: .68rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: .06em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
width: fit-content;
|
||||
}
|
||||
.card-spbu .card-tag { background: rgba(249,115,22,.18); color: #fb923c; border: 1px solid rgba(249,115,22,.3); }
|
||||
.card-poverty .card-tag { background: rgba(124,58,237,.18); color: #a78bfa; border: 1px solid rgba(124,58,237,.3); }
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.42rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -.01em;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.card p {
|
||||
color: #94a3b8;
|
||||
font-size: .88rem;
|
||||
line-height: 1.6;
|
||||
flex: 1;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.card-features {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.feat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
font-size: .81rem;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
.feat-dot {
|
||||
width: 6px; height: 6px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.card-spbu .feat-dot { background: #f97316; box-shadow: 0 0 6px #f97316; }
|
||||
.card-poverty .feat-dot { background: #a78bfa; box-shadow: 0 0 6px #a78bfa; }
|
||||
|
||||
.card-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
width: 100%;
|
||||
padding: 13px;
|
||||
border-radius: 16px;
|
||||
font-weight: 800;
|
||||
font-size: .9rem;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
transition: filter .18s, transform .18s;
|
||||
letter-spacing: -.005em;
|
||||
color: white;
|
||||
}
|
||||
.card:hover .card-btn { filter: brightness(1.12); transform: scale(1.02); }
|
||||
.card-spbu .card-btn { background: linear-gradient(135deg, #ea580c, #f97316); box-shadow: 0 10px 28px rgba(249,115,22,.35); }
|
||||
.card-poverty .card-btn { background: linear-gradient(135deg, #6d28d9, #7c3aed, #06b6d4); box-shadow: 0 10px 28px rgba(124,58,237,.35); }
|
||||
|
||||
.arrow {
|
||||
transition: transform .2s;
|
||||
font-style: normal;
|
||||
font-size: .9rem;
|
||||
}
|
||||
.card:hover .arrow { transform: translateX(4px); }
|
||||
|
||||
/* ── Sub links ────────────────────────────────────────────── */
|
||||
.sub-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
animation: fadeUp .9s ease .35s both;
|
||||
}
|
||||
.sub-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 9px 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
backdrop-filter: blur(12px);
|
||||
font-size: .8rem;
|
||||
font-weight: 700;
|
||||
color: #94a3b8;
|
||||
transition: color .16s, border-color .16s, background .16s;
|
||||
}
|
||||
.sub-link:hover { color: #f1f5f9; border-color: rgba(255,255,255,.25); background: rgba(255,255,255,.1); }
|
||||
|
||||
/* ── Footer ───────────────────────────────────────────────── */
|
||||
.foot {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
color: #475569;
|
||||
font-size: .78rem;
|
||||
}
|
||||
.foot strong { color: #64748b; }
|
||||
|
||||
/* ── Stats strip ──────────────────────────────────────────── */
|
||||
.stats-strip {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
backdrop-filter: blur(16px);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
margin-bottom: 30px;
|
||||
animation: fadeUp .85s ease .28s both;
|
||||
}
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
padding: 18px 16px;
|
||||
text-align: center;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
.stat-item:last-child { border-right: 0; }
|
||||
.stat-item strong {
|
||||
display: block;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 900;
|
||||
background: linear-gradient(135deg, #c4b5fd, #67e8f9);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.stat-item span {
|
||||
display: block;
|
||||
font-size: .72rem;
|
||||
color: #64748b;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
/* ── Animations ───────────────────────────────────────────── */
|
||||
@keyframes fadeDown {
|
||||
from { opacity: 0; transform: translateY(-20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(24px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ── Responsive ───────────────────────────────────────────── */
|
||||
@media (max-width: 680px) {
|
||||
.cards { grid-template-columns: 1fr; }
|
||||
.stats-strip { flex-direction: column; }
|
||||
.stat-item { border-right: 0; border-bottom: 1px solid var(--border); }
|
||||
.stat-item:last-child { border-bottom: 0; }
|
||||
.wrapper { padding: 40px 16px 60px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg"></div>
|
||||
<div class="orb orb-1"></div>
|
||||
<div class="orb orb-2"></div>
|
||||
<div class="orb orb-3"></div>
|
||||
|
||||
<div class="wrapper">
|
||||
<!-- Badge -->
|
||||
<div class="badge">
|
||||
<span class="dot"></span>
|
||||
Sistem Informasi Geografis — Kota Pontianak
|
||||
</div>
|
||||
|
||||
<!-- Headline -->
|
||||
<div class="headline">
|
||||
<h1>Portal WebGIS<br>Kota Pontianak</h1>
|
||||
<p>Platform pemetaan interaktif berbasis web untuk pengelolaan data spasial SPBU dan distribusi bantuan sosial gereja secara transparan dan tepat sasaran.</p>
|
||||
</div>
|
||||
|
||||
<!-- Stats strip -->
|
||||
<div class="stats-strip" id="statsStrip">
|
||||
<div class="stat-item"><strong id="statSpbu">9</strong><span>SPBU Terpetakan</span></div>
|
||||
<div class="stat-item"><strong id="statGereja">—</strong><span>Gereja Terdaftar</span></div>
|
||||
<div class="stat-item"><strong id="statKeluarga">—</strong><span>Keluarga Dipetakan</span></div>
|
||||
<div class="stat-item"><strong id="statPengajuan">—</strong><span>Pengajuan Bantuan</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Main cards -->
|
||||
<div class="cards">
|
||||
|
||||
<!-- Card 1: SPBU -->
|
||||
<a class="card card-spbu" href="/index.html" id="cardSpbu">
|
||||
<div class="card-glow"></div>
|
||||
<div class="card-icon">⛽</div>
|
||||
<span class="card-tag">WebGIS SPBU</span>
|
||||
<h2>Peta Stasiun Pengisian Bahan Bakar</h2>
|
||||
<p>Sistem informasi geografis untuk pemetaan dan manajemen SPBU di Kota Pontianak. Pantau status operasional, jenis BBM, dan jam operasional secara real-time.</p>
|
||||
<div class="card-features">
|
||||
<div class="feat"><span class="feat-dot"></span>Peta interaktif lokasi SPBU</div>
|
||||
<div class="feat"><span class="feat-dot"></span>Manajemen data jalan & bangunan</div>
|
||||
<div class="feat"><span class="feat-dot"></span>Status buka/tutup real-time</div>
|
||||
<div class="feat"><span class="feat-dot"></span>Filter jenis BBM tersedia</div>
|
||||
</div>
|
||||
<div class="card-btn">Buka WebGIS SPBU <i class="arrow">→</i></div>
|
||||
</a>
|
||||
|
||||
<!-- Card 2: Poverty Map -->
|
||||
<a class="card card-poverty" href="/poverty-map.html" id="cardPoverty">
|
||||
<div class="card-glow"></div>
|
||||
<div class="card-icon">🗺️</div>
|
||||
<span class="card-tag">Bantuan Sosial</span>
|
||||
<h2>Peta Distribusi Bantuan Sosial Gereja</h2>
|
||||
<p>WebGIS untuk memetakan dan mendistribusikan bantuan sosial dari gereja kepada masyarakat yang membutuhkan, berbasis analisis SAW, buffer, dan routing OSM.</p>
|
||||
<div class="card-features">
|
||||
<div class="feat"><span class="feat-dot"></span>Analisis prioritas SAW otomatis</div>
|
||||
<div class="feat"><span class="feat-dot"></span>Buffer analysis 1 / 3 / 5 km</div>
|
||||
<div class="feat"><span class="feat-dot"></span>Nearest church & routing OSM</div>
|
||||
<div class="feat"><span class="feat-dot"></span>Heatmap persebaran kemiskinan</div>
|
||||
</div>
|
||||
<div class="card-btn">Buka Peta Bantuan Sosial <i class="arrow">→</i></div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sub links -->
|
||||
<div class="sub-links">
|
||||
<a class="sub-link" href="/pengajuan.html">📋 Ajukan Bantuan</a>
|
||||
<a class="sub-link" href="/admin-poverty.html">⚙️ Admin Poverty</a>
|
||||
<a class="sub-link" href="/admin.html">🛠️ Admin SPBU</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="foot">
|
||||
<strong>WebGIS Kota Pontianak</strong> · Final Project Sistem Informasi Geografis · 2026
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Load live stats from API
|
||||
async function loadStats() {
|
||||
try {
|
||||
const [spbu, poverty] = await Promise.all([
|
||||
fetch("/api/spbu").then(r => r.json()).catch(() => null),
|
||||
fetch("/api/poverty/dashboard").then(r => r.json()).catch(() => null),
|
||||
]);
|
||||
if (spbu?.success) {
|
||||
document.getElementById("statSpbu").textContent = spbu.data?.length ?? "—";
|
||||
}
|
||||
if (poverty?.success) {
|
||||
const d = poverty.data || {};
|
||||
document.getElementById("statKeluarga").textContent = d.total_keluarga ?? "—";
|
||||
}
|
||||
// Load pengajuan count
|
||||
const pengajuan = await fetch("/api/poverty/pengajuan").then(r => r.json()).catch(() => null);
|
||||
if (pengajuan?.success) {
|
||||
document.getElementById("statPengajuan").textContent = pengajuan.data?.length ?? "—";
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
// Fetch ibadah count separately
|
||||
async function loadIbadahCount() {
|
||||
try {
|
||||
const res = await fetch("/api/poverty/ibadah").then(r => r.json()).catch(() => null);
|
||||
if (res?.success) {
|
||||
document.getElementById("statGereja").textContent = res.data?.length ?? "—";
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
loadStats();
|
||||
loadIbadahCount();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user