TKK_E32232188/smartcollar-fe/assets/js/suhu.js

365 lines
9.8 KiB
JavaScript

/*
|--------------------------------------------------------------------------
| Monitoring Suhu Real-time
|--------------------------------------------------------------------------
*/
const cowSelect = document.getElementById("cowSelect");
let currentCow = cowSelect && cowSelect.value ? cowSelect.value : "";
const MAX_POINTS = 30; // jumlah titik chart yang disimpan (± 1 menit @ interval 2 detik)
const CHART_JS_URL = "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.4/chart.umd.min.js";
let tempChart;
let historyData = []; // { timestamp, temperature }[]
let lastPlottedTimestamp = null; // hanya push ke chart kalau timestamp data berubah (data baru masuk)
let chartReady = null; // promise, biar renderChart() tidak dipanggil sebelum library siap
let resizeTimer = null;
/*
|--------------------------------------------------------------------------
| Load Chart.js (dynamic, hindari race condition)
|--------------------------------------------------------------------------
*/
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
function ensureChartJs() {
if (chartReady) {
return chartReady;
}
if (typeof Chart !== "undefined") {
chartReady = Promise.resolve();
return chartReady;
}
chartReady = loadScript(CHART_JS_URL).catch((error) => {
console.error("Gagal memuat Chart.js dari CDN. Periksa koneksi internet / ad-blocker.", error);
throw error;
});
return chartReady;
}
/*
|--------------------------------------------------------------------------
| Helper: deteksi layar mobile
|--------------------------------------------------------------------------
*/
function isMobileScreen() {
return window.innerWidth < 576;
}
function isTabletScreen() {
return window.innerWidth < 768;
}
/*
|--------------------------------------------------------------------------
| Render Chart
|--------------------------------------------------------------------------
*/
function renderChart(data) {
const ctx = document.getElementById("tempChart");
if (!ctx || typeof Chart === "undefined") {
return;
}
// Urutkan lama -> baru khusus untuk chart biar tren terbaca alami
const sorted = [...data].sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
const mobile = isMobileScreen();
const tablet = isTabletScreen();
const labels = sorted.map(d => {
const t = new Date(d.timestamp);
if (isNaN(t.getTime())) return "";
// Di layar sempit tampilkan jam:menit saja biar tidak penuh
return mobile
? t.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })
: t.toLocaleString(undefined, { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" });
});
const temps = sorted.map(d => parseFloat(d.temperature) || null);
if (tempChart) tempChart.destroy();
const tickFont = mobile ? 10 : 12;
const legendFont = mobile ? 11 : 13;
const maxTicks = mobile ? 4 : (tablet ? 6 : 8);
const pointRadius = mobile ? 1.5 : 2;
tempChart = new Chart(ctx, {
type: "line",
data: {
labels: labels,
datasets: [{
label: "Suhu (°C)",
data: temps,
borderColor: "#60a5fa",
backgroundColor: "rgba(96, 165, 250, 0.15)",
fill: true,
tension: 0.35,
borderWidth: mobile ? 1.5 : 2,
pointRadius: pointRadius,
pointBackgroundColor: "#38bdf8"
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
layout: {
padding: mobile ? 4 : 8
},
plugins: {
legend: {
display: !mobile, // sembunyikan legend di HP biar hemat ruang
labels: { color: "#e2e8f0", font: { size: legendFont } }
}
},
scales: {
x: {
ticks: { color: "#94a3b8", maxTicksLimit: maxTicks, font: { size: tickFont } },
grid: { color: "rgba(255,255,255,0.05)" }
},
y: {
ticks: { color: "#94a3b8", font: { size: tickFont } },
grid: { color: "rgba(255,255,255,0.05)" }
}
}
}
});
}
function resetChart() {
historyData = [];
lastPlottedTimestamp = null;
if (tempChart) {
tempChart.destroy();
tempChart = null;
}
}
/*
|--------------------------------------------------------------------------
| Resize handling
|--------------------------------------------------------------------------
| Chart.js sendiri sudah responsive (auto resize saat container berubah),
| tapi label/font/ticks perlu di-render ulang penuh saat breakpoint mobile
| <-> desktop berubah (mis. rotasi layar HP). Di-debounce biar tidak
| render ulang berkali-kali saat drag resize.
*/
window.addEventListener("resize", () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (historyData.length > 0) {
renderChart(historyData);
}
}, 300);
});
/*
|--------------------------------------------------------------------------
| Change Cow
|--------------------------------------------------------------------------
*/
if (cowSelect) {
cowSelect.addEventListener(
"change",
function () {
currentCow = this.value;
resetChart();
loadSuhu();
}
);
}
/*
|--------------------------------------------------------------------------
| Load Data Suhu
|--------------------------------------------------------------------------
*/
async function loadSuhu() {
if (!currentCow) {
return;
}
try {
const response =
await fetch(
"../ajax/dashboard.php?cowId=" +
currentCow
);
const result =
await response.json();
if (!result.success) {
console.log(result.message);
return;
}
const data = result.data;
if (!data || Object.keys(data).length === 0) {
console.log("Data suhu tidak tersedia untuk sapi ini.");
return;
}
updateSuhu(data);
}
catch (error) {
console.log(error);
}
}
/*
|--------------------------------------------------------------------------
| Update Card & Chart
|--------------------------------------------------------------------------
*/
function updateSuhu(data) {
if (!data) {
return;
}
const t = (typeof data.temperature === "object" && data.temperature !== null)
? data.temperature.value
: data.temperature;
// Card nilai suhu
const valSuhu = document.getElementById("val-suhu");
if (valSuhu) {
valSuhu.innerText = (t !== null && typeof t !== "undefined") ? t : "-";
}
// Badge kondisi
const valKondisi = document.getElementById("val-kondisi");
if (valKondisi && t !== null && typeof t !== "undefined") {
if (t >= 40) {
valKondisi.className = "badge bg-danger px-3 py-1 rounded-pill";
valKondisi.innerText = "BAHAYA";
} else if (t >= 39.5) {
valKondisi.className = "badge bg-warning text-dark px-3 py-1 rounded-pill";
valKondisi.innerText = "WASPADA";
} else {
valKondisi.className = "badge bg-light text-dark px-3 py-1 rounded-pill";
valKondisi.innerText = "NORMAL";
}
}
// Status online/offline collar
const lastUpdate = data.timestamp ? new Date(data.timestamp) : null;
const age = lastUpdate ? (Date.now() - lastUpdate.getTime()) : Infinity;
const isOnline = age < 30000;
const status = document.getElementById("status");
if (status) {
status.innerText = isOnline ? "ONLINE" : "OFFLINE";
status.className = "value-text " + (isOnline ? "text-success" : "text-danger");
}
const elLast = document.getElementById("lastUpdate");
if (elLast && lastUpdate) {
elLast.innerText = lastUpdate.toLocaleString();
}
// Chart: hanya tambah titik & render ulang kalau ada data BARU dari
// device (timestamp berbeda dari titik terakhir yang sudah diplot).
// Bukan setiap kali polling 2 detik meskipun datanya masih sama.
if (t !== null && typeof t !== "undefined" && data.timestamp) {
const isNewData = data.timestamp !== lastPlottedTimestamp;
if (isNewData) {
lastPlottedTimestamp = data.timestamp;
historyData.push({ timestamp: data.timestamp, temperature: t });
if (historyData.length > MAX_POINTS) {
historyData.shift();
}
renderChart(historyData);
}
}
}
/*
|--------------------------------------------------------------------------
| Init & Refresh
|--------------------------------------------------------------------------
*/
async function start() {
try {
await ensureChartJs();
} catch (error) {
// Chart.js gagal dimuat (mis. offline / CDN diblokir).
// Card suhu tetap jalan normal, hanya grafik yang tidak tampil.
const cardChart = document.getElementById("tempChart");
if (cardChart && cardChart.parentElement) {
cardChart.parentElement.innerHTML =
'<p class="text-muted mb-0">Grafik tidak dapat dimuat (Chart.js gagal diakses). Data suhu tetap diperbarui pada card di atas.</p>';
}
}
loadSuhu();
setInterval(loadSuhu, 2000);
}
start();