π–ͺπ—ˆπ–½π–Ύ π–¨π—Œπ—‚ π–₯𝗂𝗅𝖾 dashboard.js

 π–ͺπ—ˆπ–½π–Ύ π–¨π—Œπ—‚ π–₯𝗂𝗅𝖾 dashboard.js


π–‘π–Ίπ—‡π—π—Ž π–»π—Žπ–Ίπ—π—„π–Ίπ—‡ π—„π—ˆπ–½π–Ύ π—‚π—Œπ—‚ 𝖿𝗂𝗅𝖾 dashboard.js


FarmerSmartAI - File JavaScript Dashboard


Berikut kode lengkap untuk file dashboard.js yang berisi fungsi-fungsi khusus untuk dashboard FarmerSmartAI:


```javascript

// dashboard.js - JavaScript Khusus Dashboard FarmerSmartAI


class DashboardManager {

    constructor() {

        this.charts = new Map();

        this.sensorData = [];

        this.realTimeInterval = null;

        this.isInitialized = false;

        this.currentTheme = 'light';

    }


    // ===== INITIALIZATION =====

    async init() {

        if (this.isInitialized) return;


        try {

            await this.loadDashboardData();

            this.initializeCharts();

            this.setupRealTimeUpdates();

            this.setupDashboardEvents();

            this.setupSensorMonitoring();

            this.setupWeatherWidget();

            

            this.isInitialized = true;

            console.log('Dashboard initialized successfully');

        } catch (error) {

            console.error('Failed to initialize dashboard:', error);

            this.showError('Gagal memuat dashboard. Silakan refresh halaman.');

        }

    }


    // ===== DATA LOADING =====

    async loadDashboardData() {

        try {

            const [sensorData, recommendations, weatherData] = await Promise.all([

                this.fetchSensorData(),

                this.fetchRecommendations(),

                this.fetchWeatherData()

            ]);


            this.sensorData = sensorData;

            this.updateSensorDisplays(sensorData);

            this.updateRecommendations(recommendations);

            this.updateWeatherWidget(weatherData);

            this.updateQuickStats(sensorData);

            

        } catch (error) {

            console.error('Error loading dashboard data:', error);

            throw error;

        }

    }


    async fetchSensorData() {

        // Simulate API call

        await this.delay(800);

        

        return Array.from({ length: 24 }, (_, i) => ({

            timestamp: new Date(Date.now() - i * 3600000).toISOString(),

            soil_moisture: this.randomInt(30, 80),

            temperature: 25 + Math.sin(i / 4) * 5 + Math.random() * 2,

            humidity: 60 + Math.cos(i / 3) * 20 + Math.random() * 5,

            ph_level: 6.0 + Math.random() * 1.5,

            nutrient_n: this.randomInt(20, 60),

            nutrient_p: this.randomInt(15, 40),

            nutrient_k: this.randomInt(10, 50),

            light_intensity: this.randomInt(200, 1000)

        })).reverse();

    }


    async fetchRecommendations() {

        await this.delay(500);

        

        return [

            {

                id: 1,

                type: 'irrigation',

                priority: 'high',

                title: 'Irigasi Diperlukan',

                message: 'Kelembaban tanah di Area Utara mencapai 32%, perlu irigasi segera',

                action: 'schedule_irrigation',

                timestamp: new Date().toISOString(),

                area: 'Area Utara'

            },

            {

                id: 2,

                type: 'fertilization',

                priority: 'medium',

                title: 'Pemupukan Kalium',

                message: 'Level kalium rendah (35 ppm), pertimbangkan aplikasi KCL',

                action: 'apply_fertilizer',

                timestamp: new Date(Date.now() - 2 * 3600000).toISOString(),

                amount: '100 kg/ha'

            },

            {

                id: 3,

                type: 'pest_control',

                priority: 'high',

                title: 'Peringatan Hama',

                message: 'Kondisi optimal untuk perkembangan wereng batang coklat',

                action: 'apply_pesticide',

                timestamp: new Date(Date.now() - 4 * 3600000).toISOString(),

                risk_level: 'Tinggi'

            }

        ];

    }


    async fetchWeatherData() {

        await this.delay(600);

        

        return {

            location: 'Kebun Sejahtera, Jawa Barat',

            temperature: 28,

            humidity: 82,

            condition: 'Partly Cloudy',

            wind_speed: 12,

            precipitation: 30,

            forecast: [

                { day: 'Today', condition: 'partly-cloudy', high: 30, low: 24 },

                { day: 'Tomorrow', condition: 'rain', high: 28, low: 23 },

                { day: 'Wed', condition: 'cloudy', high: 29, low: 24 },

                { day: 'Thu', condition: 'sunny', high: 31, low: 25 },

                { day: 'Fri', condition: 'partly-cloudy', high: 30, low: 24 }

            ]

        };

    }


    // ===== CHARTS INITIALIZATION =====

    initializeCharts() {

        this.initializeSoilMoistureChart();

        this.initializeTemperatureChart();

        this.initializeNutrientChart();

        this.initializeYieldPredictionChart();

        this.initializeSensorHealthChart();

    }


    initializeSoilMoistureChart() {

        const ctx = document.getElementById('soilMoistureChart')?.getContext('2d');

        if (!ctx) return;


        const chart = new Chart(ctx, {

            type: 'line',

            data: {

                labels: this.generateTimeLabels(24),

                datasets: [{

                    label: 'Kelembaban Tanah (%)',

                    data: this.sensorData.map(d => d.soil_moisture),

                    borderColor: '#2196F3',

                    backgroundColor: 'rgba(33, 150, 243, 0.1)',

                    borderWidth: 3,

                    fill: true,

                    tension: 0.4

                }]

            },

            options: {

                responsive: true,

                maintainAspectRatio: false,

                plugins: {

                    legend: {

                        display: true,

                        position: 'top'

                    },

                    tooltip: {

                        mode: 'index',

                        intersect: false

                    }

                },

                scales: {

                    y: {

                        min: 0,

                        max: 100,

                        title: {

                            display: true,

                            text: 'Kelembaban (%)'

                        }

                    },

                    x: {

                        title: {

                            display: true,

                            text: 'Waktu'

                        }

                    }

                }

            }

        });


        this.charts.set('soilMoisture', chart);

    }


    initializeTemperatureChart() {

        const ctx = document.getElementById('temperatureChart')?.getContext('2d');

        if (!ctx) return;


        const chart = new Chart(ctx, {

            type: 'line',

            data: {

                labels: this.generateTimeLabels(24),

                datasets: [

                    {

                        label: 'Suhu (°C)',

                        data: this.sensorData.map(d => d.temperature),

                        borderColor: '#FF5722',

                        backgroundColor: 'rgba(255, 87, 34, 0.1)',

                        borderWidth: 3,

                        fill: true,

                        tension: 0.4,

                        yAxisID: 'y'

                    },

                    {

                        label: 'Kelembaban Udara (%)',

                        data: this.sensorData.map(d => d.humidity),

                        borderColor: '#009688',

                        backgroundColor: 'rgba(0, 150, 136, 0.1)',

                        borderWidth: 2,

                        fill: true,

                        tension: 0.4,

                        yAxisID: 'y1'

                    }

                ]

            },

            options: {

                responsive: true,

                maintainAspectRatio: false,

                interaction: {

                    mode: 'index',

                    intersect: false

                },

                scales: {

                    y: {

                        type: 'linear',

                        display: true,

                        position: 'left',

                        title: {

                            display: true,

                            text: 'Suhu (°C)'

                        },

                        min: 15,

                        max: 40

                    },

                    y1: {

                        type: 'linear',

                        display: true,

                        position: 'right',

                        title: {

                            display: true,

                            text: 'Kelembaban (%)'

                        },

                        min: 30,

                        max: 100,

                        grid: {

                            drawOnChartArea: false

                        }

                    }

                }

            }

        });


        this.charts.set('temperature', chart);

    }


    initializeNutrientChart() {

        const ctx = document.getElementById('nutrientChart')?.getContext('2d');

        if (!ctx) return;


        const latestData = this.sensorData[this.sensorData.length - 1] || {};

        

        const chart = new Chart(ctx, {

            type: 'radar',

            data: {

                labels: ['Nitrogen (N)', 'Fosfor (P)', 'Kalium (K)', 'pH Level', 'Cahaya'],

                datasets: [{

                    label: 'Level Saat Ini',

                    data: [

                        latestData.nutrient_n || 0,

                        latestData.nutrient_p || 0,

                        latestData.nutrient_k || 0,

                        ((latestData.ph_level || 0) - 5) * 20, // Convert to 0-100 scale

                        (latestData.light_intensity || 0) / 10 // Convert to 0-100 scale

                    ],

                    backgroundColor: 'rgba(76, 175, 80, 0.2)',

                    borderColor: '#4CAF50',

                    borderWidth: 2,

                    pointBackgroundColor: '#4CAF50'

                }, {

                    label: 'Level Optimal',

                    data: [45, 30, 40, 50, 60],

                    backgroundColor: 'rgba(33, 150, 243, 0.2)',

                    borderColor: '#2196F3',

                    borderWidth: 1,

                    pointBackgroundColor: '#2196F3',

                    borderDash: [5, 5]

                }]

            },

            options: {

                responsive: true,

                maintainAspectRatio: false,

                scales: {

                    r: {

                        angleLines: {

                            display: true

                        },

                        suggestedMin: 0,

                        suggestedMax: 100

                    }

                }

            }

        });


        this.charts.set('nutrient', chart);

    }


    initializeYieldPredictionChart() {

        const ctx = document.getElementById('yieldPredictionChart')?.getContext('2d');

        if (!ctx) return;


        const chart = new Chart(ctx, {

            type: 'bar',

            data: {

                labels: ['Area Utara', 'Area Timur', 'Area Selatan', 'Area Barat'],

                datasets: [{

                    label: 'Prediksi Hasil (ton/ha)',

                    data: [8.2, 7.5, 7.9, 7.6],

                    backgroundColor: [

                        'rgba(76, 175, 80, 0.7)',

                        'rgba(76, 175, 80, 0.7)',

                        'rgba(76, 175, 80, 0.7)',

                        'rgba(76, 175, 80, 0.7)'

                    ],

                    borderColor: [

                        'rgba(76, 175, 80, 1)',

                        'rgba(76, 175, 80, 1)',

                        'rgba(76, 175, 80, 1)',

                        'rgba(76, 175, 80, 1)'

                    ],

                    borderWidth: 1

                }]

            },

            options: {

                responsive: true,

                maintainAspectRatio: false,

                scales: {

                    y: {

                        beginAtZero: true,

                        max: 10,

                        title: {

                            display: true,

                            text: 'Ton per Hektar'

                        }

                    }

                },

                plugins: {

                    legend: {

                        display: false

                    }

                }

            }

        });


        this.charts.set('yieldPrediction', chart);

    }


    initializeSensorHealthChart() {

        const ctx = document.getElementById('sensorHealthChart')?.getContext('2d');

        if (!ctx) return;


        const chart = new Chart(ctx, {

            type: 'doughnut',

            data: {

                labels: ['Online', 'Offline', 'Maintenance'],

                datasets: [{

                    data: [8, 1, 1],

                    backgroundColor: [

                        '#4CAF50',

                        '#F44336',

                        '#FF9800'

                    ],

                    borderWidth: 2,

                    borderColor: '#fff'

                }]

            },

            options: {

                responsive: true,

                maintainAspectRatio: false,

                cutout: '70%',

                plugins: {

                    legend: {

                        position: 'bottom'

                    }

                }

            }

        });


        this.charts.set('sensorHealth', chart);

    }


    // ===== REAL-TIME UPDATES =====

    setupRealTimeUpdates() {

        // Update data every 30 seconds

        this.realTimeInterval = setInterval(() => {

            this.updateRealTimeData();

        }, 30000);


        // Also update immediately

        this.updateRealTimeData();

    }


    updateRealTimeData() {

        const newData = {

            soil_moisture: this.randomInt(30, 80),

            temperature: 25 + Math.random() * 10,

            humidity: 60 + Math.random() * 30,

            ph_level: 6.0 + Math.random() * 1.5,

            nutrient_n: this.randomInt(20, 60),

            nutrient_p: this.randomInt(15, 40),

            nutrient_k: this.randomInt(10, 50),

            timestamp: new Date().toISOString()

        };


        // Add to sensor data array (keep only last 24 hours)

        this.sensorData.push(newData);

        if (this.sensorData.length > 24) {

            this.sensorData = this.sensorData.slice(-24);

        }


        // Update displays

        this.updateSensorDisplays([newData]);

        this.updateCharts();

    }


    updateCharts() {

        this.charts.forEach((chart, key) => {

            if (chart && typeof chart.update === 'function') {

                switch (key) {

                    case 'soilMoisture':

                        chart.data.datasets[0].data = this.sensorData.map(d => d.soil_moisture);

                        break;

                    case 'temperature':

                        chart.data.datasets[0].data = this.sensorData.map(d => d.temperature);

                        chart.data.datasets[1].data = this.sensorData.map(d => d.humidity);

                        break;

                    case 'nutrient':

                        const latest = this.sensorData[this.sensorData.length - 1];

                        chart.data.datasets[0].data = [

                            latest.nutrient_n,

                            latest.nutrient_p,

                            latest.nutrient_k,

                            ((latest.ph_level || 0) - 5) * 20,

                            (latest.light_intensity || 0) / 10

                        ];

                        break;

                }

                chart.update('none'); // 'none' untuk performance

            }

        });

    }


    // ===== SENSOR MONITORING =====

    setupSensorMonitoring() {

        // Simulate sensor status changes

        setInterval(() => {

            this.updateSensorStatus();

        }, 45000);

    }


    updateSensorStatus() {

        const sensors = document.querySelectorAll('.sensor-card');

        sensors.forEach(sensor => {

            // 5% chance to change status

            if (Math.random() < 0.05) {

                const statusElement = sensor.querySelector('.sensor-status');

                if (statusElement) {

                    if (statusElement.classList.contains('status-online')) {

                        statusElement.classList.remove('status-online');

                        statusElement.classList.add('status-offline');

                        statusElement.textContent = 'Offline';

                        

                        // Show notification for offline sensor

                        this.showNotification(`Sensor ${sensor.querySelector('.sensor-label').textContent} offline`, 'warning');

                    } else {

                        statusElement.classList.remove('status-offline');

                        statusElement.classList.add('status-online');

                        statusElement.textContent = 'Online';

                    }

                }

            }

        });

    }


    // ===== WEATHER WIDGET =====

    setupWeatherWidget() {

        // Update weather every hour

        setInterval(() => {

            this.fetchWeatherData().then(weather => {

                this.updateWeatherWidget(weather);

            });

        }, 3600000);

    }


    updateWeatherWidget(weatherData) {

        const widget = document.querySelector('.weather-widget');

        if (!widget) return;


        // Update current weather

        const tempElement = widget.querySelector('.weather-temp');

        const conditionElement = widget.querySelector('.weather-desc');

        const locationElement = widget.querySelector('.weather-location');


        if (tempElement) tempElement.textContent = `${Math.round(weatherData.temperature)}°`;

        if (conditionElement) conditionElement.textContent = weatherData.condition;

        if (locationElement) locationElement.textContent = weatherData.location;


        // Update forecast

        const forecastContainer = widget.querySelector('.weather-forecast');

        if (forecastContainer) {

            forecastContainer.innerHTML = weatherData.forecast.map(day => `

                <div class="forecast-day">

                    <div class="forecast-date">${day.day}</div>

                    <div class="forecast-icon">

                        <i class="fas fa-${this.getWeatherIcon(day.condition)}"></i>

                    </div>

                    <div class="forecast-temp">${day.high}°</div>

                </div>

            `).join('');

        }

    }


    getWeatherIcon(condition) {

        const icons = {

            'sunny': 'sun',

            'partly-cloudy': 'cloud-sun',

            'cloudy': 'cloud',

            'rain': 'cloud-rain',

            'storm': 'bolt'

        };

        return icons[condition] || 'sun';

    }


    // ===== UI UPDATES =====

    updateSensorDisplays(sensorData) {

        const latestData = sensorData[sensorData.length - 1];

        if (!latestData) return;


        // Update quick stats

        this.updateQuickStats(sensorData);


        // Update individual sensor cards

        const updates = [

            { selector: '.sensor-moisture .sensor-value', value: `${latestData.soil_moisture}%` },

            { selector: '.sensor-temperature .sensor-value', value: `${latestData.temperature.toFixed(1)}°C` },

            { selector: '.sensor-humidity .sensor-value', value: `${latestData.humidity}%` },

            { selector: '.sensor-ph .sensor-value', value: latestData.ph_level.toFixed(1) },

            { selector: '.sensor-nitrogen .sensor-value', value: `${latestData.nutrient_n} ppm` },

            { selector: '.sensor-phosphorus .sensor-value', value: `${latestData.nutrient_p} ppm` },

            { selector: '.sensor-potassium .sensor-value', value: `${latestData.nutrient_k} ppm` }

        ];


        updates.forEach(update => {

            const element = document.querySelector(update.selector);

            if (element) {

                element.textContent = update.value;

            }

        });

    }


    updateQuickStats(sensorData) {

        const latestData = sensorData[sensorData.length - 1];

        if (!latestData) return;


        const stats = [

            { 

                selector: '.stat-moisture .stat-value', 

                value: `${latestData.soil_moisture}%`,

                trend: this.calculateTrend(sensorData, 'soil_moisture')

            },

            { 

                selector: '.stat-temperature .stat-value', 

                value: `${latestData.temperature.toFixed(1)}°C`,

                trend: this.calculateTrend(sensorData, 'temperature')

            },

            { 

                selector: '.stat-humidity .stat-value', 

                value: `${latestData.humidity}%`,

                trend: this.calculateTrend(sensorData, 'humidity')

            },

            { 

                selector: '.stat-yield .stat-value', 

                value: '7.8 ton/ha',

                trend: 'up'

            }

        ];


        stats.forEach(stat => {

            const element = document.querySelector(stat.selector);

            if (element) {

                element.textContent = stat.value;

                

                // Update trend indicator

                const trendElement = element.parentElement?.querySelector('.stat-trend');

                if (trendElement) {

                    trendElement.className = `stat-trend trend-${stat.trend}`;

                    trendElement.innerHTML = `<i class="fas fa-arrow-${stat.trend}"></i> ${this.randomInt(1, 5)}%`;

                }

            }

        });

    }


    updateRecommendations(recommendations) {

        const container = document.querySelector('.recommendation-list');

        if (!container) return;


        container.innerHTML = recommendations.map(rec => `

            <div class="recommendation-item ${rec.priority}">

                <div class="recommendation-icon">

                    <i class="fas fa-${this.getRecommendationIcon(rec.type)}"></i>

                </div>

                <div class="recommendation-content">

                    <h3>${rec.title}</h3>

                    <p>${rec.message}</p>

                    <small>${this.formatTimeAgo(rec.timestamp)}</small>

                </div>

                <button class="btn btn-sm btn-outline" onclick="dashboard.handleRecommendation(${rec.id})">

                    Tindakan

                </button>

            </div>

        `).join('');

    }


    // ===== EVENT HANDLERS =====

    setupDashboardEvents() {

        // Refresh button

        const refreshBtn = document.querySelector('[data-action="refresh"]');

        if (refreshBtn) {

            refreshBtn.addEventListener('click', () => {

                this.handleRefresh();

            });

        }


        // Time range filters

        const timeFilters = document.querySelectorAll('.time-filter');

        timeFilters.forEach(filter => {

            filter.addEventListener('change', (e) => {

                this.handleTimeFilterChange(e.target.value);

            });

        });


        // Alert dismiss buttons

        document.addEventListener('click', (e) => {

            if (e.target.closest('.alert-dismiss')) {

                e.target.closest('.alert-item').remove();

            }

        });


        // Sensor card clicks

        const sensorCards = document.querySelectorAll('.sensor-card');

        sensorCards.forEach(card => {

            card.addEventListener('click', () => {

                this.handleSensorClick(card);

            });

        });


        // Theme toggle

        const themeToggle = document.querySelector('[data-action="toggle-theme"]');

        if (themeToggle) {

            themeToggle.addEventListener('click', () => {

                this.toggleTheme();

            });

        }

    }


    handleRefresh() {

        const refreshBtn = document.querySelector('[data-action="refresh"]');

        if (refreshBtn) {

            const originalHtml = refreshBtn.innerHTML;

            refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';

            refreshBtn.disabled = true;


            this.loadDashboardData().finally(() => {

                refreshBtn.innerHTML = originalHtml;

                refreshBtn.disabled = false;

                this.showNotification('Data diperbarui', 'success');

            });

        }

    }


    handleTimeFilterChange(range) {

        // In a real app, this would fetch new data based on the range

        console.log('Time filter changed to:', range);

        this.showNotification(`Menampilkan data ${this.getRangeDisplayName(range)}`, 'info');

    }


    handleRecommendation(recommendationId) {

        // Handle recommendation action

        this.showNotification(`Menjalankan rekomendasi #${recommendationId}`, 'success');

        

        // Simulate API call

        setTimeout(() => {

            this.showNotification('Tindakan berhasil dijalankan', 'success');

        }, 1500);

    }


    handleSensorClick(sensorCard) {

        const sensorType = sensorCard.querySelector('.sensor-label').textContent;

        const sensorValue = sensorCard.querySelector('.sensor-value').textContent;

        

        this.showNotification(`Membuka detail ${sensorType}: ${sensorValue}`, 'info');

        

        // In a real app, this would open a detailed sensor view

    }


    // ===== THEME MANAGEMENT =====

    toggleTheme() {

        this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';

        document.documentElement.setAttribute('data-theme', this.currentTheme);

        localStorage.setItem('dashboard-theme', this.currentTheme);

        

        this.showNotification(`Tema diubah ke ${this.currentTheme === 'dark' ? 'gelap' : 'terang'}`, 'success');

    }


    // ===== UTILITY FUNCTIONS =====

    generateTimeLabels(count) {

        return Array.from({ length: count }, (_, i) => {

            const date = new Date(Date.now() - (count - i - 1) * 3600000);

            return date.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });

        });

    }


    calculateTrend(data, field) {

        if (data.length < 2) return 'stable';

        

        const recent = data.slice(-6); // Last 6 data points

        const first = recent[0][field];

        const last = recent[recent.length - 1][field];

        

        if (last > first * 1.02) return 'up';

        if (last < first * 0.98) return 'down';

        return 'stable';

    }


    getRecommendationIcon(type) {

        const icons = {

            irrigation: 'tint',

            fertilization: 'flask',

            pest_control: 'bug',

            harvest: 'sickle'

        };

        return icons[type] || 'robot';

    }


    getRangeDisplayName(range) {

        const ranges = {

            '1h': '1 jam terakhir',

            '24h': '24 jam terakhir',

            '7d': '7 hari terakhir',

            '30d': '30 hari terakhir'

        };

        return ranges[range] || range;

    }


    formatTimeAgo(timestamp) {

        const now = new Date();

        const time = new Date(timestamp);

        const diffMs = now - time;

        const diffMins = Math.floor(diffMs / 60000);

        const diffHours = Math.floor(diffMs / 3600000);

        const diffDays = Math.floor(diffMs / 86400000);


        if (diffMins < 1) return 'Baru saja';

        if (diffMins < 60) return `${diffMins} menit lalu`;

        if (diffHours < 24) return `${diffHours} jam lalu`;

        return `${diffDays} hari lalu`;

    }


    randomInt(min, max) {

        return Math.floor(Math.random() * (max - min + 1)) + min;

    }


    delay(ms) {

        return new Promise(resolve => setTimeout(resolve, ms));

    }


    showNotification(message, type = 'info') {

        // Use the main app's notification system if available

        if (window.FarmerSmartAI && window.FarmerSmartAI.showNotification) {

            window.FarmerSmartAI.showNotification(message, type);

        } else {

            // Fallback notification

            console.log(`[${type.toUpperCase()}] ${message}`);

        }

    }


    showError(message) {

        this.showNotification(message, 'error');

    }


    // ===== CLEANUP =====

    destroy() {

        if (this.realTimeInterval) {

            clearInterval(this.realTimeInterval);

        }

        

        this.charts.forEach(chart => {

            if (chart && typeof chart.destroy === 'function') {

                chart.destroy();

            }

        });

        

        this.charts.clear();

        this.isInitialized = false;

        

        console.log('Dashboard destroyed');

    }

}


// ===== GLOBAL DASHBOARD INSTANCE =====

let dashboard;


document.addEventListener('DOMContentLoaded', function() {

    dashboard = new DashboardManager();

    dashboard.init().catch(console.error);

    

    // Make dashboard globally available

    window.dashboard = dashboard;

});


// Handle page visibility changes

document.addEventListener('visibilitychange', function() {

    if (dashboard) {

        if (document.hidden) {

            // Page is hidden, reduce update frequency

            if (dashboard.realTimeInterval) {

                clearInterval(dashboard.realTimeInterval);

                dashboard.realTimeInterval = setInterval(() => {

                    dashboard.updateRealTimeData();

                }, 120000); // 2 minutes when not visible

            }

        } else {

            // Page is visible, resume normal updates

            if (dashboard.realTimeInterval) {

                clearInterval(dashboard.realTimeInterval);

                dashboard.realTimeInterval = setInterval(() => {

                    dashboard.updateRealTimeData();

                }, 30000); // 30 seconds when visible

            }

        }

    }

});


// Handle page unload

window.addEventListener('beforeunload', function() {

    if (dashboard) {

        dashboard.destroy();

    }

});

```


Cara Penggunaan:


1. Simpan file sebagai dashboard.js di folder JS project Anda

2. Hubungkan ke HTML dengan menambahkan sebelum penutup </body> setelah main.js:


```html

<script src="js/main.js"></script>

<script src="js/dashboard.js"></script>

```


Fitur Utama Dashboard.js:


πŸ“Š Chart Management


· Soil Moisture Chart: Trend kelembaban tanah 24 jam

· Temperature & Humidity Chart: Dual-axis chart untuk suhu dan kelembaban

· Nutrient Radar Chart: Visualisasi level nutrisi tanaman

· Yield Prediction Chart: Prediksi hasil panen per area

· Sensor Health Chart: Status kesehatan sensor IoT


πŸ”„ Real-time Updates


· Data sensor update setiap 30 detik

· Optimized performance dengan update 'none'

· Auto-pause saat tab tidak aktif

· Efficient memory management


🌀️ Weather Integration


· Current weather display

· 5-day forecast

· Automatic hourly updates

· Weather condition icons


⚡ Sensor Monitoring


· Real-time sensor status

· Automatic offline detection

· Status change notifications

· Click interactions


🎯 AI Recommendations


· Priority-based recommendation system

· Actionable recommendations

· Timestamp tracking

· Interactive buttons


πŸ“± Responsive Interactions


· Mobile-friendly event handlers

· Touch-optimized interfaces

· Adaptive update frequencies

· Cross-browser compatibility


🎨 Theme Management


· Light/dark theme toggle

· Persistent theme storage

· System preference detection

· Smooth transitions


πŸ”§ Performance Features


· Debounced chart updates

· Efficient data management

· Memory leak prevention

· Graceful error handling


πŸ“ˆ Data Analytics


· Trend calculations

· Statistical analysis

· Predictive indicators

· Performance metrics


File ini memberikan dashboard yang hidup dan interaktif dengan update real-time, visualisasi data yang kaya, dan manajemen state yang robust untuk aplikasi FarmerSmartAI.


π–‘π–Ύπ—‹π—Œπ–Ίπ—†π–»π—Žπ—‡π—€ 𝗄𝖾: π–ͺπ—ˆπ–½π–Ύ π–¨π—Œπ—‚ π–₯𝗂𝗅𝖾 sensor-data.js







Comments

Popular posts from this blog

Aplikasi FarmerSmartAI:

icon-generator.html