remove kecamatan and kelurahan table, using alamat where the lampu merah located, added the photos on /assets/image/<id> on each alamat, update server.js, add new list.html
109
api/server.js
@ -124,23 +124,10 @@ app.get("/sensordata/:id", (req, res) => {
|
|||||||
air_data.bz,
|
air_data.bz,
|
||||||
air_data.aq,
|
air_data.aq,
|
||||||
air_data.updated_at,
|
air_data.updated_at,
|
||||||
|
alamat_data.alamat AS alamat
|
||||||
sensor_data.alamat,
|
|
||||||
|
|
||||||
kelurahan.nama AS kelurahan,
|
|
||||||
kecamatan.nama AS kecamatan
|
|
||||||
|
|
||||||
FROM air_data
|
FROM air_data
|
||||||
|
JOIN alamat_data
|
||||||
JOIN sensor_data
|
ON alamat_data.id = air_data.id
|
||||||
ON air_data.id = sensor_data.id
|
|
||||||
|
|
||||||
JOIN kelurahan
|
|
||||||
ON sensor_data.kelurahan = kelurahan.id
|
|
||||||
|
|
||||||
JOIN kecamatan
|
|
||||||
ON kelurahan.kecamatan_id = kecamatan.id
|
|
||||||
|
|
||||||
WHERE air_data.id = ?
|
WHERE air_data.id = ?
|
||||||
ORDER BY air_data.updated_at DESC
|
ORDER BY air_data.updated_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@ -163,7 +150,7 @@ app.get("/sensordata/:id", (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.get("/sensors", (req, res) => {
|
app.get("/sensors", (req, res) => {
|
||||||
const sql = `SELECT id FROM sensor_data ORDER BY id ASC`;
|
const sql = `SELECT * FROM alamat_data where status = 1 ORDER BY id ASC`;
|
||||||
|
|
||||||
db.query(sql, (err, results) => {
|
db.query(sql, (err, results) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -178,56 +165,52 @@ app.get("/sensors", (req, res) => {
|
|||||||
// ADD SENSOR
|
// ADD SENSOR
|
||||||
// ============================
|
// ============================
|
||||||
app.post("/addSensor", (req, res) => {
|
app.post("/addSensor", (req, res) => {
|
||||||
const { kelurahan, alamat } = req.body;
|
const { id } = req.body;
|
||||||
|
|
||||||
// ============================
|
// check if id is provided
|
||||||
// VALIDATION
|
if (!id) {
|
||||||
// ============================
|
|
||||||
if (!kelurahan || !alamat) {
|
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "kelurahan dan alamat wajib diisi"
|
error: "Sensor ID is required"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================
|
// check if id is numeric
|
||||||
// VALIDATE KELURAHAN EXISTS
|
if (isNaN(id)) {
|
||||||
// ============================
|
return res.status(400).json({
|
||||||
const checkSql = `SELECT id FROM kelurahan WHERE id = ?`;
|
error: "Sensor ID must be numeric"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
db.query(checkSql, [kelurahan], (err, rows) => {
|
// check if id exists in alamat_data table, if available then continue
|
||||||
|
const checkSql = `SELECT id FROM alamat_data WHERE id = ?`;
|
||||||
|
|
||||||
|
db.query(checkSql, [id], (err, results) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return res.status(500).json({ error: "DB error" });
|
return res.status(500).json({ error: "DB error" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rows.length === 0) {
|
if (results.length === 0) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "Kelurahan tidak valid"
|
error: "Sensor ID not found in alamat_data table"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ============================
|
// update the status field to 1 to the id in alamat_data table
|
||||||
// INSERT SENSOR
|
const updateSql = `UPDATE alamat_data SET status = 1 WHERE id = ?`;
|
||||||
// ============================
|
|
||||||
const insertSql = `
|
|
||||||
INSERT INTO sensor_data (kelurahan, alamat)
|
|
||||||
VALUES (?, ?)
|
|
||||||
`;
|
|
||||||
|
|
||||||
db.query(insertSql, [kelurahan, alamat], (err, result) => {
|
db.query(updateSql, [id], (err, result) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return res.status(500).json({
|
return res.status(500).json({ error: "DB error" });
|
||||||
error: "DB error saat insert"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: "Sensor berhasil ditambahkan",
|
message: "Sensor added successfully",
|
||||||
sensor_id: result.insertId
|
id: id
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
@ -252,8 +235,8 @@ app.get("/history", (req, res) => {
|
|||||||
|
|
||||||
|
|
||||||
// get kecamatan
|
// get kecamatan
|
||||||
app.get("/kecamatan", (req, res) => {
|
app.get("/alamat", (req, res) => {
|
||||||
const sql = `SELECT id, nama FROM kecamatan ORDER BY nama ASC`;
|
const sql = `SELECT * from alamat_data where status = 0 ORDER BY id ASC`;
|
||||||
|
|
||||||
db.query(sql, (err, results) => {
|
db.query(sql, (err, results) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -266,38 +249,6 @@ app.get("/kecamatan", (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// get kelurahan by kecamatan_id
|
|
||||||
app.get("/kelurahan", (req, res) => {
|
|
||||||
const { kecamatan_id } = req.query;
|
|
||||||
|
|
||||||
let sql = `
|
|
||||||
SELECT
|
|
||||||
kelurahan.id,
|
|
||||||
kelurahan.nama,
|
|
||||||
kecamatan.nama AS kecamatan
|
|
||||||
FROM kelurahan
|
|
||||||
JOIN kecamatan ON kelurahan.kecamatan_id = kecamatan.id
|
|
||||||
`;
|
|
||||||
|
|
||||||
let params = [];
|
|
||||||
|
|
||||||
if (kecamatan_id) {
|
|
||||||
sql += ` WHERE kelurahan.kecamatan_id = ?`;
|
|
||||||
params.push(kecamatan_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
sql += ` ORDER BY kelurahan.nama ASC`;
|
|
||||||
|
|
||||||
db.query(sql, params, (err, results) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return res.status(500).json({ error: "DB error" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(results);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// START SERVER
|
// START SERVER
|
||||||
// ============================
|
// ============================
|
||||||
|
|||||||
BIN
site/assets/images/1/1.png
Normal file
|
After Width: | Height: | Size: 600 KiB |
BIN
site/assets/images/1/2.png
Normal file
|
After Width: | Height: | Size: 672 KiB |
BIN
site/assets/images/10/1.png
Normal file
|
After Width: | Height: | Size: 541 KiB |
BIN
site/assets/images/10/2.png
Normal file
|
After Width: | Height: | Size: 592 KiB |
BIN
site/assets/images/2/1.png
Normal file
|
After Width: | Height: | Size: 707 KiB |
BIN
site/assets/images/2/2.png
Normal file
|
After Width: | Height: | Size: 635 KiB |
BIN
site/assets/images/3/1.png
Normal file
|
After Width: | Height: | Size: 614 KiB |
BIN
site/assets/images/4/1.png
Normal file
|
After Width: | Height: | Size: 514 KiB |
BIN
site/assets/images/4/2.png
Normal file
|
After Width: | Height: | Size: 552 KiB |
BIN
site/assets/images/5/1.png
Normal file
|
After Width: | Height: | Size: 659 KiB |
BIN
site/assets/images/6/1.png
Normal file
|
After Width: | Height: | Size: 598 KiB |
BIN
site/assets/images/6/2.png
Normal file
|
After Width: | Height: | Size: 552 KiB |
BIN
site/assets/images/7/1.png
Normal file
|
After Width: | Height: | Size: 555 KiB |
BIN
site/assets/images/7/2.png
Normal file
|
After Width: | Height: | Size: 549 KiB |
BIN
site/assets/images/8/1.png
Normal file
|
After Width: | Height: | Size: 599 KiB |
BIN
site/assets/images/9/1.png
Normal file
|
After Width: | Height: | Size: 616 KiB |
149
site/index.html
@ -32,6 +32,12 @@
|
|||||||
<span>Halaman Utama</span>
|
<span>Halaman Utama</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="list.html">
|
||||||
|
<i class="ico fa fa-list"></i>
|
||||||
|
<span>List Sensor</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@ -59,24 +65,15 @@
|
|||||||
<div style="background:#fff; padding:20px; width:350px; margin:100px auto; border-radius:10px;">
|
<div style="background:#fff; padding:20px; width:350px; margin:100px auto; border-radius:10px;">
|
||||||
<h4>Tambah Sensor</h4>
|
<h4>Tambah Sensor</h4>
|
||||||
|
|
||||||
<label>Kecamatan</label>
|
<label>Alamat:</label>
|
||||||
<select id="kecamatan" class="form-control" onchange="loadKelurahan()">
|
<select id="alamat-select" class="form-control" onchange="gambar_preview()">
|
||||||
<option value="">-- Pilih Kecamatan --</option>
|
<option value="">-- Pilih Alamat --</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<br>
|
<div id="preview-container" style="margin-top:15px;"></div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
<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-success" onclick="addSensor()">Simpan</button>
|
||||||
<button class="btn btn-default" onclick="closeDialog()">Batal</button>
|
<button class="btn btn-default" onclick="closeDialog()">Batal</button>
|
||||||
@ -85,7 +82,8 @@
|
|||||||
|
|
||||||
<script src="assets/scripts/jquery.min.js"></script>
|
<script src="assets/scripts/jquery.min.js"></script>
|
||||||
<script src="assets/plugin/bootstrap/js/bootstrap.min.js"></script>
|
<script src="assets/plugin/bootstrap/js/bootstrap.min.js"></script>
|
||||||
<script src="assets/scripts/main.min.js"></script>
|
<script src="assets/plugin/nprogress/nprogress.js"></script>
|
||||||
|
<script src="assets/scripts/main.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function getStatus(aq) {
|
function getStatus(aq) {
|
||||||
@ -134,12 +132,6 @@
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<form class="form-horizontal">
|
<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">
|
<div class="form-group">
|
||||||
<label class="col-sm-4 control-label">Alamat</label>
|
<label class="col-sm-4 control-label">Alamat</label>
|
||||||
@ -224,56 +216,109 @@
|
|||||||
|
|
||||||
function openDialog() {
|
function openDialog() {
|
||||||
document.getElementById("dialog").style.display = "block";
|
document.getElementById("dialog").style.display = "block";
|
||||||
loadKecamatan();
|
// reset dropdown
|
||||||
|
document.getElementById("alamat-select").value = "";
|
||||||
|
$("#preview-gambar").html("");
|
||||||
|
loadAlamat();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDialog() {
|
function closeDialog() {
|
||||||
document.getElementById("dialog").style.display = "none";
|
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() {
|
async function addSensor() {
|
||||||
const kelurahan = document.getElementById("kelurahan").value;
|
const id = document.getElementById("alamat-select").value;
|
||||||
const alamat = document.getElementById("alamat").value;
|
|
||||||
|
|
||||||
await fetch("/api/addSensor", {
|
if (!id) {
|
||||||
|
alert("Pilih alamat terlebih dahulu!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/addSensor", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: {
|
||||||
body: JSON.stringify({ kelurahan, alamat })
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ id })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
alert(result.error || "Gagal menambah sensor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert("Sensor berhasil ditambahkan");
|
||||||
|
|
||||||
closeDialog();
|
closeDialog();
|
||||||
|
|
||||||
|
// reset dropdown
|
||||||
|
document.getElementById("alamat-select").value = "";
|
||||||
|
$("#preview-gambar").html("");
|
||||||
|
|
||||||
|
// rebuild cards
|
||||||
await initSensors();
|
await initSensors();
|
||||||
updateSensorValues();
|
updateSensorValues();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("Terjadi kesalahan");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
initSensors();
|
initSensors();
|
||||||
updateSensorValues();
|
updateSensorValues();
|
||||||
setInterval(updateSensorValues, 5000);
|
setInterval(updateSensorValues, 5000);
|
||||||
|
|
||||||
|
async function loadAlamat() {
|
||||||
|
const res = await fetch("/api/alamat");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const select = document.getElementById("alamat-select");
|
||||||
|
select.innerHTML = `<option value="">-- Pilih Alamat --</option>`;
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
select.innerHTML += `<option value="${item.id}">${item.alamat}</option>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function gambar_preview() {
|
||||||
|
const alamatId = document.getElementById("alamat-select").value;
|
||||||
|
const container = document.getElementById("preview-container");
|
||||||
|
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
if (!alamatId) return;
|
||||||
|
|
||||||
|
const maxImages = 5; // try up to 5 images
|
||||||
|
|
||||||
|
for (let i = 1; i <= maxImages; i++) {
|
||||||
|
const path = `assets/images/${alamatId}/${i}.png`;
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
|
||||||
|
img.onload = function () {
|
||||||
|
img.style.width = "100%";
|
||||||
|
img.style.marginBottom = "10px";
|
||||||
|
img.style.borderRadius = "8px";
|
||||||
|
img.style.border = "1px solid #ddd";
|
||||||
|
|
||||||
|
container.appendChild(img);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = function () {
|
||||||
|
// image doesn't exist → ignore
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
128
site/list.html
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<!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 | List Alamat</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>
|
||||||
|
<a href="index.html">
|
||||||
|
<i class="ico fa fa-home"></i>
|
||||||
|
<span>Halaman Utama</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="current">
|
||||||
|
<a href="list.html">
|
||||||
|
<i class="ico fa fa-list"></i>
|
||||||
|
<span>List Sensor</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="wrapper">
|
||||||
|
<div class="main-content container">
|
||||||
|
<div class="row small-spacing" id="ini_dia"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<ul class="list-inline">
|
||||||
|
<li>2026 © SULAIMAN, Universitas Muhammadiyah Parepare.</li>
|
||||||
|
</ul>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="assets/scripts/jquery.min.js"></script>
|
||||||
|
<script src="assets/plugin/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/plugin/nprogress/nprogress.js"></script>
|
||||||
|
<script src="assets/scripts/main.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function loadSensors() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/sensors");
|
||||||
|
const sensors = await res.json();
|
||||||
|
console.log(sensors);
|
||||||
|
|
||||||
|
const container = document.getElementById("ini_dia");
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="box-content card white">
|
||||||
|
<h4 class="box-title">Daftar Alamat Sensor Aktif</h4>
|
||||||
|
<div class="card-content">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>No</th>
|
||||||
|
<th>Sensor ID</th>
|
||||||
|
<th>Alamat</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!sensors.length) {
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center">Tidak ada data</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
sensors.forEach((sensor, index) => {
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td>${index + 1}</td>
|
||||||
|
<td>${sensor.id}</td>
|
||||||
|
<td>${sensor.alamat}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed load sensors", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSensors();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||