// Import Firebase import { initializeApp } from 'https://www.gstatic.com/firebasejs/12.7.0/firebase-app.js'; import { getAuth, onAuthStateChanged, signOut } from 'https://www.gstatic.com/firebasejs/12.7.0/firebase-auth.js'; import { getDatabase, ref, onValue, update } from 'https://www.gstatic.com/firebasejs/12.7.0/firebase-database.js'; // Firebase configuration const firebaseConfig = { apiKey: 'AIzaSyCQWvoDxDyVCuLEDiwammjUIVYxVARzJig', authDomain: 'project-ta-951b4.firebaseapp.com', databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com', projectId: 'project-ta-951b4', storageBucket: 'project-ta-951b4.firebasestorage.app', messagingSenderId: '217854138058', appId: '1:217854138058:web:50a5bcd5a61ac1820c4633', measurementId: 'G-6ML8QQEGNZ' }; const app = initializeApp(firebaseConfig); const auth = getAuth(app); const database = getDatabase(app); const KONTROL_BASE_PATH = 'kontrol_1'; const kontrolRef = ref(database, KONTROL_BASE_PATH); let activeThresholdKey = 'threshold_1'; let thresholdProfiles = ['threshold_1']; let lastKontrolData = {}; let mainModeActive = false; let isPageLoaded = false; function clampNumber(value, min, max) { const parsed = Number(value); if (!Number.isFinite(parsed)) { return min; } return Math.min(Math.max(parsed, min), max); } function normalizePotValue(value) { if (typeof value === 'boolean') { return value; } if (typeof value === 'number') { return value !== 0; } if (typeof value === 'string') { const normalized = value.trim().toLowerCase(); if (['1', 'true', 'on', 'aktif', 'yes', 'y'].includes(normalized)) { return true; } if (['0', 'false', 'off', 'nonaktif', 'no', 'n', ''].includes(normalized)) { return false; } const numeric = Number(normalized); if (!Number.isNaN(numeric)) { return numeric !== 0; } } return false; } function escapeHtml(value) { return String(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function getThresholdNumber(thresholdKey) { const match = String(thresholdKey || '').match(/^threshold_(\d+)$/i); return match ? parseInt(match[1], 10) : 1; } function getThresholdLabel(thresholdKey) { return `Threshold ${getThresholdNumber(thresholdKey)}`; } function extractThresholdKeys(data) { const keys = Object.keys(data || {}) .filter((key) => /^threshold_\d+$/i.test(key)) .sort((a, b) => getThresholdNumber(a) - getThresholdNumber(b)); return keys.length > 0 ? keys : ['threshold_1']; } function resolveThresholdKey(value) { if (!value) { return thresholdProfiles[0] || 'threshold_1'; } return thresholdProfiles.includes(value) ? value : (thresholdProfiles[0] || 'threshold_1'); } function normalizePotAktif(potAktifSource = {}) { if (Array.isArray(potAktifSource)) { const normalized = { pot_1: false, pot_2: false, pot_3: false, pot_4: false, pot_5: false }; potAktifSource.forEach((value) => { const potNumber = Number(value); if (Number.isFinite(potNumber) && potNumber >= 1 && potNumber <= 5) { normalized[`pot_${potNumber}`] = true; } }); return normalized; } if (potAktifSource && typeof potAktifSource === 'object') { const numericKeys = Object.keys(potAktifSource).filter((key) => /^\d+$/.test(key)); if (numericKeys.length > 0) { const normalized = { pot_1: false, pot_2: false, pot_3: false, pot_4: false, pot_5: false }; Object.values(potAktifSource).forEach((value) => { const potNumber = Number(value); if (Number.isFinite(potNumber) && potNumber >= 1 && potNumber <= 5) { normalized[`pot_${potNumber}`] = true; } }); return normalized; } } return { pot_1: normalizePotValue(potAktifSource.pot_1 ?? potAktifSource.pot1), pot_2: normalizePotValue(potAktifSource.pot_2 ?? potAktifSource.pot2), pot_3: normalizePotValue(potAktifSource.pot_3 ?? potAktifSource.pot3), pot_4: normalizePotValue(potAktifSource.pot_4 ?? potAktifSource.pot4), pot_5: normalizePotValue(potAktifSource.pot_5 ?? potAktifSource.pot5) }; } function getThresholdDataByKey(data, thresholdKey) { const source = data?.[thresholdKey] || {}; const batasBawah = Number(source.batas_bawah); const batasAtas = Number(source.batas_atas); const namaTanaman = String(source.nama_tanaman || '').trim(); return { nama_tanaman: namaTanaman || getThresholdLabel(thresholdKey), batas_bawah: Number.isFinite(batasBawah) ? batasBawah : 30, batas_atas: Number.isFinite(batasAtas) ? batasAtas : 70, pot_aktif: normalizePotAktif(source.pot_aktif), pompa_air: source.pompa_air === true, pompa_pupuk: source.pompa_pupuk === true, pompa_pengaduk: source.pompa_pengaduk === true, aktif: source.aktif === true, smart_mode: source.smart_mode === true }; } function getActiveThresholdData(data) { return getThresholdDataByKey(data, activeThresholdKey); } function getNextThresholdKey() { const indices = thresholdProfiles .map(getThresholdNumber) .filter((value) => Number.isFinite(value)); const nextIndex = indices.length > 0 ? Math.max(...indices) + 1 : 1; return `threshold_${nextIndex}`; } function getPumpType(thresholdData) { if (thresholdData.pompa_pengaduk) { return 'pengaduk'; } if (thresholdData.pompa_pupuk) { return 'nutrisi'; } return 'air'; } function getPumpLabelByType(type) { switch (type) { case 'nutrisi': return 'Pompa Nutrisi'; case 'pengaduk': return 'Pengaduk'; default: return 'Pompa Air'; } } function getPumpIconByType(type) { switch (type) { case 'nutrisi': return 'fas fa-flask'; case 'pengaduk': return 'fas fa-blender'; default: return 'fas fa-water'; } } function getPotSummary(thresholdData) { const selectedPots = []; for (let i = 1; i <= 5; i++) { if (thresholdData.pot_aktif[`pot_${i}`] === true) { selectedPots.push(`Pot ${i}`); } } return selectedPots.length > 0 ? selectedPots.join(', ') : 'Belum ada pot dipilih'; } function setMainModeVisual(isActive) { const mainToggle = document.getElementById('mainToggle'); const statusIndicator = document.getElementById('statusIndicator'); const statusText = document.getElementById('statusText'); const addThresholdBtn = document.getElementById('btnAddThreshold'); const saveThresholdBtn = document.getElementById('btnSaveThreshold'); if (mainToggle) { mainToggle.classList.toggle('active', isActive); } if (statusIndicator) { statusIndicator.classList.toggle('active', isActive); } if (statusText) { statusText.textContent = isActive ? 'Aktif' : 'Tidak Aktif'; } if (addThresholdBtn) { addThresholdBtn.disabled = !isActive; } if (saveThresholdBtn) { saveThresholdBtn.disabled = !isActive; } } function updateThresholdProfileUI() { const activeLabel = document.getElementById('activeThresholdLabel'); const container = document.getElementById('thresholdProfileContainer'); if (activeLabel) { const activeData = getThresholdDataByKey(lastKontrolData, activeThresholdKey); activeLabel.textContent = `Profil aktif: ${getThresholdLabel(activeThresholdKey)} - ${activeData.nama_tanaman}`; } if (!container) { return; } container.innerHTML = thresholdProfiles.map((thresholdKey) => { const thresholdData = getThresholdDataByKey(lastKontrolData, thresholdKey); const isActive = thresholdKey === activeThresholdKey; const pumpType = getPumpType(thresholdData); const deleteDisabled = thresholdProfiles.length <= 1 || !mainModeActive; const safePlantName = escapeHtml(thresholdData.nama_tanaman); return `
${getThresholdLabel(thresholdKey)}
${isActive ? 'Aktif' : ''}
Nama: ${safePlantName}
Batas: ${thresholdData.batas_bawah}% - ${thresholdData.batas_atas}%
Pot: ${getPotSummary(thresholdData)}
Penyiraman: ${getPumpLabelByType(pumpType)}
`; }).join(''); } async function switchThresholdProfile(selectedKey) { if (!mainModeActive) { showNotification('Aktifkan Mode Otomatis terlebih dahulu', 'warning'); return; } const thresholdKey = resolveThresholdKey(selectedKey); if (thresholdKey === activeThresholdKey) { return; } const previousKey = activeThresholdKey; activeThresholdKey = thresholdKey; updateThresholdProfileUI(); try { await update(kontrolRef, { threshold_aktif: thresholdKey }); showNotification(`Berpindah ke ${getThresholdLabel(thresholdKey)}`, 'info'); } catch (error) { console.error('Error switching threshold profile:', error); activeThresholdKey = previousKey; updateThresholdProfileUI(); showNotification('Gagal mengganti profil threshold', 'error'); } } function openThresholdModal(thresholdKey = null) { if (!mainModeActive) { showNotification('Aktifkan Mode Otomatis terlebih dahulu', 'warning'); return; } const modal = document.getElementById('thresholdModal'); const title = document.getElementById('thresholdModalTitle'); const editInput = document.getElementById('editThresholdKey'); const plantNameInput = document.getElementById('thresholdPlantName'); const minInput = document.getElementById('thresholdMinInput'); const maxInput = document.getElementById('thresholdMaxInput'); if (!modal || !title || !editInput || !plantNameInput || !minInput || !maxInput) { return; } const isEditMode = Boolean(thresholdKey); const sourceData = isEditMode ? getThresholdDataByKey(lastKontrolData, thresholdKey) : getActiveThresholdData(lastKontrolData); title.innerHTML = isEditMode ? ' Edit Threshold' : ' Tambah Threshold'; editInput.value = isEditMode ? thresholdKey : ''; plantNameInput.value = isEditMode ? (sourceData.nama_tanaman || '') : ''; minInput.value = sourceData.batas_bawah; maxInput.value = sourceData.batas_atas; const potCheckboxes = document.querySelectorAll('.threshold-pot-checkbox'); potCheckboxes.forEach((checkbox) => { const potNumber = checkbox.value.replace('pot', ''); checkbox.checked = sourceData.pot_aktif[`pot_${potNumber}`] === true; }); const pumpType = getPumpType(sourceData); const pumpRadio = document.querySelector(`input[name="thresholdPumpType"][value="${pumpType}"]`); if (pumpRadio) { pumpRadio.checked = true; } modal.style.display = 'flex'; } window.closeThresholdModal = function() { const modal = document.getElementById('thresholdModal'); if (modal) { modal.style.display = 'none'; } }; window.selectAllThresholdPotsModal = function() { document.querySelectorAll('.threshold-pot-checkbox').forEach((checkbox) => { checkbox.checked = true; }); }; window.deselectAllThresholdPotsModal = function() { document.querySelectorAll('.threshold-pot-checkbox').forEach((checkbox) => { checkbox.checked = false; }); }; function getThresholdPayloadFromForm() { const editThresholdKey = document.getElementById('editThresholdKey')?.value || ''; const fallbackKey = editThresholdKey || activeThresholdKey; const plantNameInput = document.getElementById('thresholdPlantName'); const minInput = document.getElementById('thresholdMinInput'); const maxInput = document.getElementById('thresholdMaxInput'); const namaTanaman = String(plantNameInput?.value || '').trim(); if (!namaTanaman) { showNotification('Nama tanaman untuk threshold wajib diisi', 'warning'); return null; } const batasBawah = clampNumber(minInput?.value, 0, 99); const batasAtas = clampNumber(maxInput?.value, 1, 100); if (batasBawah >= batasAtas) { showNotification('Batas bawah harus lebih kecil dari batas atas', 'warning'); return null; } const selectedPotValues = Array.from(document.querySelectorAll('.threshold-pot-checkbox:checked')) .map((checkbox) => checkbox.value); if (selectedPotValues.length === 0) { showNotification('Pilih minimal 1 pot untuk threshold', 'warning'); return null; } const selectedPump = document.querySelector('input[name="thresholdPumpType"]:checked')?.value || 'air'; const potAktifList = Array.from(new Set( selectedPotValues .map((pot) => Number(String(pot).replace('pot', ''))) .filter((potNumber) => Number.isFinite(potNumber) && potNumber >= 1 && potNumber <= 5) )); return { nama_tanaman: namaTanaman || getThresholdLabel(fallbackKey), aktif: mainModeActive, smart_mode: mainModeActive, batas_bawah: batasBawah, batas_atas: batasAtas, pot_aktif: potAktifList, pompa_air: selectedPump === 'air', pompa_pupuk: selectedPump === 'nutrisi', pompa_pengaduk: selectedPump === 'pengaduk' }; } window.saveThresholdProfile = async function() { if (!mainModeActive) { showNotification('Aktifkan Mode Otomatis terlebih dahulu', 'warning'); return; } const payload = getThresholdPayloadFromForm(); if (!payload) { return; } const editThresholdKey = document.getElementById('editThresholdKey')?.value || ''; const isEditMode = editThresholdKey !== ''; const targetKey = isEditMode ? resolveThresholdKey(editThresholdKey) : getNextThresholdKey(); const updates = { [targetKey]: payload }; if (!isEditMode) { updates.threshold_aktif = targetKey; } try { await update(kontrolRef, updates); closeThresholdModal(); showNotification( isEditMode ? `${getThresholdLabel(targetKey)} berhasil diperbarui` : `${getThresholdLabel(targetKey)} berhasil ditambahkan`, 'success' ); } catch (error) { console.error('Error saving threshold profile:', error); showNotification('Gagal menyimpan threshold', 'error'); } }; async function deleteThresholdProfile(thresholdKey) { if (!mainModeActive) { showNotification('Aktifkan Mode Otomatis terlebih dahulu', 'warning'); return; } if (!thresholdProfiles.includes(thresholdKey)) { return; } if (thresholdProfiles.length <= 1) { showNotification('Minimal harus ada 1 profil threshold', 'warning'); return; } if (!confirm(`Hapus ${getThresholdLabel(thresholdKey)}?`)) { return; } const fallbackKey = thresholdProfiles.find((key) => key !== thresholdKey) || 'threshold_1'; const updates = { [thresholdKey]: null }; if (activeThresholdKey === thresholdKey) { updates.threshold_aktif = fallbackKey; } try { await update(kontrolRef, updates); showNotification(`${getThresholdLabel(thresholdKey)} berhasil dihapus`, 'success'); } catch (error) { console.error('Error deleting threshold profile:', error); showNotification('Gagal menghapus profil threshold', 'error'); } } function showNotification(message, type = 'success') { const notification = document.getElementById('notification'); if (!notification) { return; } notification.className = `notification ${type}`; const icon = notification.querySelector('.notification-icon'); if (icon) { if (type === 'success') { icon.className = 'notification-icon fas fa-check-circle'; } else if (type === 'error') { icon.className = 'notification-icon fas fa-exclamation-circle'; } else if (type === 'info') { icon.className = 'notification-icon fas fa-info-circle'; } else if (type === 'warning') { icon.className = 'notification-icon fas fa-exclamation-triangle'; } } const messageEl = notification.querySelector('.notification-message'); if (messageEl) { messageEl.textContent = message; } notification.classList.add('show'); notification.classList.remove('hidden'); setTimeout(() => { notification.classList.remove('show'); notification.classList.add('hidden'); }, 3000); } function loadControllerData() { onValue(kontrolRef, (snapshot) => { const data = snapshot.exists() ? snapshot.val() : {}; lastKontrolData = data; thresholdProfiles = extractThresholdKeys(data); if (data.threshold_aktif) { activeThresholdKey = resolveThresholdKey(data.threshold_aktif); } else { activeThresholdKey = resolveThresholdKey(activeThresholdKey); } mainModeActive = data.otomatis === true || data.otomatis === 1; setMainModeVisual(mainModeActive); updateThresholdProfileUI(); }); } window.toggleMainMode = function() { if (!isPageLoaded) { return; } const nextState = !mainModeActive; mainModeActive = nextState; setMainModeVisual(mainModeActive); updateThresholdProfileUI(); // Ketika Mode Otomatis diaktifkan, matikan Mode Waktu // Ketika Mode Otomatis dimatikan, Mode Waktu tetap bisa digunakan update(kontrolRef, { otomatis: nextState, waktu: nextState ? false : undefined, [`${activeThresholdKey}/aktif`]: nextState, [`${activeThresholdKey}/smart_mode`]: nextState, threshold_aktif: activeThresholdKey }).then(() => { showNotification(nextState ? 'Mode Otomatis diaktifkan' : 'Mode Otomatis dinonaktifkan', nextState ? 'success' : 'info'); }).catch((error) => { console.error('Error updating mode status:', error); mainModeActive = !nextState; setMainModeVisual(mainModeActive); updateThresholdProfileUI(); showNotification('Gagal menyimpan status mode', 'error'); }); }; onAuthStateChanged(auth, (user) => { const loginMessage = document.getElementById('loginMessage'); if (user) { document.getElementById('userEmail').textContent = user.email; document.getElementById('dashboardPage').style.display = 'block'; if (loginMessage) { loginMessage.hidden = true; } loadControllerData(); isPageLoaded = true; } else { document.getElementById('dashboardPage').style.display = 'none'; if (loginMessage) { loginMessage.hidden = false; } setTimeout(() => { window.location.href = '../index.html'; }, 2000); } }); document.getElementById('signOutBtn')?.addEventListener('click', async () => { try { await signOut(auth); window.location.href = '../index.html'; } catch (error) { showNotification(`Error logging out: ${error.message}`, 'error'); } }); window.addEventListener('DOMContentLoaded', () => { setMainModeVisual(false); updateThresholdProfileUI(); document.getElementById('btnAddThreshold')?.addEventListener('click', () => { openThresholdModal(); }); document.getElementById('thresholdProfileContainer')?.addEventListener('click', (event) => { const actionBtn = event.target.closest('[data-threshold-action]'); if (!actionBtn) { return; } const action = actionBtn.dataset.thresholdAction; const thresholdKey = actionBtn.dataset.thresholdKey; if (!thresholdKey) { return; } if (action === 'select') { switchThresholdProfile(thresholdKey); return; } if (action === 'edit') { openThresholdModal(thresholdKey); return; } if (action === 'delete') { deleteThresholdProfile(thresholdKey); } }); document.getElementById('thresholdModal')?.addEventListener('click', (event) => { if (event.target.id === 'thresholdModal') { closeThresholdModal(); } }); document.getElementById('thresholdMinInput')?.addEventListener('change', (event) => { const minInput = event.target; minInput.value = clampNumber(minInput.value, 0, 99); }); document.getElementById('thresholdMaxInput')?.addEventListener('change', (event) => { const maxInput = event.target; maxInput.value = clampNumber(maxInput.value, 1, 100); }); document.getElementById('thresholdPlantName')?.addEventListener('change', (event) => { event.target.value = String(event.target.value || '').trim(); }); });