improve index.html interface with ninja template
This commit is contained in:
550
site/index.html
550
site/index.html
@ -1,272 +1,280 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Sensor Kualitas Udara</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #0f172a;
|
||||
color: #e5e7eb;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #1e293b;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>🌱 Sensor Kualitas Udara</h1>
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<hr>
|
||||
<button onclick="openDialog()">Add Sensor</button>
|
||||
|
||||
<!-- MODAL -->
|
||||
<div id="dialog"
|
||||
style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7);">
|
||||
<div style="background:#1e293b; padding:20px; width:300px; margin:100px auto; border-radius:10px;">
|
||||
|
||||
<h3>Tambah Sensor</h3>
|
||||
|
||||
<label>Kecamatan:</label><br>
|
||||
<select id="kecamatan" onchange="loadKelurahan()">
|
||||
<option value="">-- Pilih Kecamatan --</option>
|
||||
</select>
|
||||
<br><br>
|
||||
|
||||
<label>Kelurahan:</label><br>
|
||||
<select id="kelurahan">
|
||||
<option value="">-- Pilih Kelurahan --</option>
|
||||
</select>
|
||||
<br><br>
|
||||
|
||||
<label>Alamat:</label><br>
|
||||
<textarea id="alamat" rows="3" style="width:100%"></textarea>
|
||||
<br><br>
|
||||
|
||||
<button onclick="addSensor()">Simpan</button>
|
||||
<button onclick="closeDialog()">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function getStatus(aq) {
|
||||
if (aq >= 90) return "Sangat Baik";
|
||||
if (aq >= 70) return "Baik";
|
||||
if (aq >= 50) return "Sedang";
|
||||
if (aq > 30) return "Buruk";
|
||||
return "Sangat Buruk";
|
||||
}
|
||||
|
||||
async function fetchAllSensors() {
|
||||
try {
|
||||
const res = await fetch("/api/sensors");
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch sensors", err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSensorData(id) {
|
||||
try {
|
||||
const res = await fetch(`/api/sensordata/${id}`);
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error(`Failed sensor ${id}`, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// BUILD UI ONCE
|
||||
// ============================
|
||||
async function initSensors() {
|
||||
const container = document.getElementById("container");
|
||||
container.innerHTML = "";
|
||||
|
||||
const sensors = await fetchAllSensors();
|
||||
|
||||
for (const sensor of sensors) {
|
||||
const data = await fetchSensorData(sensor.id);
|
||||
|
||||
const card = document.createElement("div");
|
||||
card.className = "card";
|
||||
|
||||
if (!data || data.error) {
|
||||
card.innerHTML = `
|
||||
<h3>Sensor ID: ${sensor.id}</h3>
|
||||
<p style="color: orange;">Belum Ada Data</p>
|
||||
`;
|
||||
} else {
|
||||
card.innerHTML = `
|
||||
<h3>Sensor ID: ${sensor.id}</h3>
|
||||
<small>${data.kecamatan} - ${data.kelurahan}</small><br>
|
||||
<small>${data.alamat}</small><br><br>
|
||||
|
||||
Karbon Oksida: <span id="co2-${sensor.id}">-</span> ppm<br>
|
||||
Karbon Monoksida: <span id="co-${sensor.id}">-</span> ppm<br>
|
||||
Benzena: <span id="bz-${sensor.id}">-</span> ppm<br>
|
||||
Jumlah: <span id="aq-${sensor.id}">-</span> %<br>
|
||||
Kualitas: <span id="qa-${sensor.id}">-</span>
|
||||
`;
|
||||
}
|
||||
|
||||
container.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// UPDATE VALUES ONLY
|
||||
// ============================
|
||||
async function updateSensorValues() {
|
||||
const sensors = await fetchAllSensors();
|
||||
|
||||
for (const sensor of sensors) {
|
||||
const data = await fetchSensorData(sensor.id);
|
||||
|
||||
if (!data || data.error) continue;
|
||||
|
||||
document.getElementById(`co2-${sensor.id}`).textContent = data.co2;
|
||||
document.getElementById(`co-${sensor.id}`).textContent = data.co;
|
||||
document.getElementById(`bz-${sensor.id}`).textContent = data.bz;
|
||||
document.getElementById(`aq-${sensor.id}`).textContent = data.aq;
|
||||
document.getElementById(`qa-${sensor.id}`).textContent = getStatus(data.aq);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// MODAL CONTROL
|
||||
// ============================
|
||||
function openDialog() {
|
||||
document.getElementById("dialog").style.display = "block";
|
||||
loadKecamatan();
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
document.getElementById("dialog").style.display = "none";
|
||||
}
|
||||
|
||||
// ============================
|
||||
// LOAD KECAMATAN
|
||||
// ============================
|
||||
async function loadKecamatan() {
|
||||
try {
|
||||
const res = await fetch("/api/kecamatan");
|
||||
const data = await res.json();
|
||||
|
||||
const select = document.getElementById("kecamatan");
|
||||
select.innerHTML = `<option value="">-- Pilih Kecamatan --</option>`;
|
||||
|
||||
data.forEach(k => {
|
||||
const option = document.createElement("option");
|
||||
option.value = k.id;
|
||||
option.textContent = k.nama;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error("Failed load kecamatan", err);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// LOAD KELURAHAN
|
||||
// ============================
|
||||
async function loadKelurahan() {
|
||||
const kecamatanId = document.getElementById("kecamatan").value;
|
||||
|
||||
const select = document.getElementById("kelurahan");
|
||||
select.innerHTML = `<option value="">-- Pilih Kelurahan --</option>`;
|
||||
|
||||
if (!kecamatanId) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/kelurahan?kecamatan_id=${kecamatanId}`);
|
||||
const data = await res.json();
|
||||
|
||||
data.forEach(k => {
|
||||
const option = document.createElement("option");
|
||||
option.value = k.id;
|
||||
option.textContent = k.nama;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error("Failed load kelurahan", err);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// ADD SENSOR
|
||||
// ============================
|
||||
async function addSensor() {
|
||||
const kelurahan = document.getElementById("kelurahan").value;
|
||||
const alamat = document.getElementById("alamat").value;
|
||||
|
||||
if (!kelurahan || !alamat) {
|
||||
alert("Semua field wajib diisi!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/addSensor", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
kelurahan,
|
||||
alamat
|
||||
})
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
alert(result.error || "Gagal");
|
||||
return;
|
||||
}
|
||||
|
||||
alert("Sensor berhasil ditambahkan!");
|
||||
|
||||
closeDialog();
|
||||
|
||||
document.getElementById("kecamatan").value = "";
|
||||
document.getElementById("kelurahan").innerHTML = `<option value="">-- Pilih Kelurahan --</option>`;
|
||||
document.getElementById("alamat").value = "";
|
||||
|
||||
await initSensors(); // rebuild only when new sensor added
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error");
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// INIT
|
||||
// ============================
|
||||
initSensors();
|
||||
updateSensorValues()
|
||||
setInterval(updateSensorValues, 5000);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Sistem Monitoring Kualitas Udara</title>
|
||||
|
||||
<link rel="stylesheet" href="assets/styles/style-horizontal.min.css">
|
||||
<link rel="stylesheet" href="assets/plugin/mCustomScrollbar/jquery.mCustomScrollbar.min.css">
|
||||
<link rel="stylesheet" href="assets/plugin/waves/waves.min.css">
|
||||
<link rel="stylesheet" href="assets/plugin/sweet-alert/sweetalert.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="fixed-header">
|
||||
<div class="header-top">
|
||||
<div class="container">
|
||||
<div class="pull-left">
|
||||
<a href="index.html" class="logo">Monitor Kualitas Udara</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-horizontal">
|
||||
<div class="container">
|
||||
<ul class="menu">
|
||||
<li class="current">
|
||||
<a href="index.html">
|
||||
<i class="ico fa fa-home"></i>
|
||||
<span>Halaman Utama</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div id="wrapper">
|
||||
<div class="main-content container">
|
||||
<div class="row small-spacing" id="ini_dia"></div>
|
||||
|
||||
<div style="margin-top:20px;">
|
||||
<button class="btn btn-primary" onclick="openDialog()">Add Sensor</button>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<ul class="list-inline">
|
||||
<li>2026 © SULAIMAN, Universitas Muhammadiyah Parepare.</li>
|
||||
</ul>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div id="dialog"
|
||||
style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,.6); z-index:9999;">
|
||||
<div style="background:#fff; padding:20px; width:350px; margin:100px auto; border-radius:10px;">
|
||||
<h4>Tambah Sensor</h4>
|
||||
|
||||
<label>Kecamatan</label>
|
||||
<select id="kecamatan" class="form-control" onchange="loadKelurahan()">
|
||||
<option value="">-- Pilih Kecamatan --</option>
|
||||
</select>
|
||||
|
||||
<br>
|
||||
|
||||
<label>Kelurahan</label>
|
||||
<select id="kelurahan" class="form-control">
|
||||
<option value="">-- Pilih Kelurahan --</option>
|
||||
</select>
|
||||
|
||||
<br>
|
||||
|
||||
<label>Alamat</label>
|
||||
<textarea id="alamat" class="form-control"></textarea>
|
||||
|
||||
<br>
|
||||
|
||||
<button class="btn btn-success" onclick="addSensor()">Simpan</button>
|
||||
<button class="btn btn-default" onclick="closeDialog()">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/scripts/jquery.min.js"></script>
|
||||
<script src="assets/plugin/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/scripts/main.min.js"></script>
|
||||
|
||||
<script>
|
||||
function getStatus(aq) {
|
||||
if (aq >= 90) return "Sangat Baik";
|
||||
if (aq >= 70) return "Baik";
|
||||
if (aq >= 50) return "Sedang";
|
||||
if (aq > 30) return "Buruk";
|
||||
return "Sangat Buruk";
|
||||
}
|
||||
|
||||
async function fetchAllSensors() {
|
||||
const res = await fetch("/api/sensors");
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
async function fetchSensorData(id) {
|
||||
const res = await fetch(`/api/sensordata/${id}`);
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
async function initSensors() {
|
||||
const container = document.getElementById("ini_dia");
|
||||
container.innerHTML = "";
|
||||
|
||||
const sensors = await fetchAllSensors();
|
||||
|
||||
for (const sensor of sensors) {
|
||||
const data = await fetchSensorData(sensor.id);
|
||||
|
||||
const card = document.createElement("div");
|
||||
card.className = "col-lg-6 col-xs-12";
|
||||
|
||||
if (!data || data.error) {
|
||||
card.innerHTML = `
|
||||
<div class="box-content card white">
|
||||
<h4 class="box-title">Sensor ID: ${sensor.id}</h4>
|
||||
<div class="card-content">
|
||||
<p style="color: orange;">Belum Ada Data</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
card.innerHTML = `
|
||||
<div class="box-content card white">
|
||||
<h4 class="box-title">Sensor ID: ${sensor.id}</h4>
|
||||
<div class="card-content">
|
||||
<form class="form-horizontal">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Lokasi</label>
|
||||
<div class="col-sm-8">
|
||||
<p class="form-control-static">${data.kecamatan} - ${data.kelurahan}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Alamat</label>
|
||||
<div class="col-sm-8">
|
||||
<p class="form-control-static">${data.alamat}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Karbon Monoksida</label>
|
||||
<div class="col-sm-8">
|
||||
<p class="form-control-static">
|
||||
<span id="co2-${sensor.id}">-</span> ppm
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Karbon Oksida</label>
|
||||
<div class="col-sm-8">
|
||||
<p class="form-control-static">
|
||||
<span id="co-${sensor.id}">-</span> ppm
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Benzena</label>
|
||||
<div class="col-sm-8">
|
||||
<p class="form-control-static">
|
||||
<span id="bz-${sensor.id}">-</span> ppm
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Kualitas</label>
|
||||
<div class="col-sm-8">
|
||||
<p class="form-control-static">
|
||||
<span id="aq-${sensor.id}">-</span> %
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Status</label>
|
||||
<div class="col-sm-8">
|
||||
<p class="form-control-static">
|
||||
<span id="qa-${sensor.id}">-</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
container.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSensorValues() {
|
||||
const sensors = await fetchAllSensors();
|
||||
|
||||
for (const sensor of sensors) {
|
||||
const data = await fetchSensorData(sensor.id);
|
||||
|
||||
if (!data || data.error) continue;
|
||||
|
||||
const co2 = document.getElementById(`co2-${sensor.id}`);
|
||||
if (!co2) continue;
|
||||
|
||||
document.getElementById(`co2-${sensor.id}`).textContent = data.co2;
|
||||
document.getElementById(`co-${sensor.id}`).textContent = data.co;
|
||||
document.getElementById(`bz-${sensor.id}`).textContent = data.bz;
|
||||
document.getElementById(`aq-${sensor.id}`).textContent = data.aq;
|
||||
document.getElementById(`qa-${sensor.id}`).textContent = getStatus(data.aq);
|
||||
}
|
||||
}
|
||||
|
||||
function openDialog() {
|
||||
document.getElementById("dialog").style.display = "block";
|
||||
loadKecamatan();
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
document.getElementById("dialog").style.display = "none";
|
||||
}
|
||||
|
||||
async function loadKecamatan() {
|
||||
const res = await fetch("/api/kecamatan");
|
||||
const data = await res.json();
|
||||
|
||||
const select = document.getElementById("kecamatan");
|
||||
select.innerHTML = `<option value="">-- Pilih Kecamatan --</option>`;
|
||||
|
||||
data.forEach(item => {
|
||||
select.innerHTML += `<option value="${item.id}">${item.nama}</option>`;
|
||||
});
|
||||
}
|
||||
|
||||
async function loadKelurahan() {
|
||||
const id = document.getElementById("kecamatan").value;
|
||||
const res = await fetch(`/api/kelurahan?kecamatan_id=${id}`);
|
||||
const data = await res.json();
|
||||
|
||||
const select = document.getElementById("kelurahan");
|
||||
select.innerHTML = `<option value="">-- Pilih Kelurahan --</option>`;
|
||||
|
||||
data.forEach(item => {
|
||||
select.innerHTML += `<option value="${item.id}">${item.nama}</option>`;
|
||||
});
|
||||
}
|
||||
|
||||
async function addSensor() {
|
||||
const kelurahan = document.getElementById("kelurahan").value;
|
||||
const alamat = document.getElementById("alamat").value;
|
||||
|
||||
await fetch("/api/addSensor", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ kelurahan, alamat })
|
||||
});
|
||||
|
||||
closeDialog();
|
||||
await initSensors();
|
||||
updateSensorValues();
|
||||
}
|
||||
|
||||
initSensors();
|
||||
updateSensorValues();
|
||||
setInterval(updateSensorValues, 5000);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user