ESP32 Temperature Dashboard
<style>
    body {
        background-color: #1a1a2e; /* Deep dark blue */
        color: #ecf0f1;           /* Off-white text */
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    }

    .container {
        width: 80%;
        margin: auto;
        background-color: #16213e; /* Slightly lighter card background */
        border-radius: 15px;
        padding: 20px;
        box-shadow: 0 4px 15px rgba(0,0,0,0.5);
    }

    #status {
        padding: 8px 15px;
        font-weight: bold;
        border-radius: 20px;
        display: inline-block;
        margin-bottom: 20px;
        font-size: 0.9em;
        text-transform: uppercase;
        letter-spacing: 1px;
    }

    h1 {
        text-align: center;
        margin-top: 20px;
        font-weight: 300;
        color: #57E0FF;
    }

    #forecastChart {
        margin-top: 80px; /* Big gap between charts */
    }
</style>

End-to-End Temperature Monitoring & ML Prediction

Checking Sensor...
<script>
    // Helper function to align data to the master timeline
    function alignData(masterLabels, rawData, timeKey, valueKey) {
        return masterLabels.map(label => {
            const match = rawData.find(d => {
                // Formatting date to ISO to ensure string comparison works
                return new Date(d[timeKey]).toISOString() === new Date(label).toISOString();
            });
            return match ? match[valueKey] : null;
        });
    }

    async function loadDashboard() {
        // 1. Fetch all data
        const [sensorRes, forecastRes, statusRes] = await Promise.all([
            fetch('/api/sensor-data'),
            fetch('/api/forecast-data'),
            fetch('/api/status')
        ]);

        const sensorData = await sensorRes.json();
        const forecastData = await forecastRes.json();
        const statusData = await statusRes.json();

        // 2. Update Status
        const statusEl = document.getElementById('status');
        statusEl.innerText = statusData.status;
        statusEl.style.backgroundColor = statusData.color;

        // 3. Create Master Timeline (Synchronization)
        // Combine all timestamps, remove duplicates, and sort
        const masterLabels = [...new Set([
            ...sensorData.map(d => d.time),
            ...forecastData.map(d => d.time)
        ])].sort((a, b) => new Date(a) - new Date(b));

        // 4. Align both datasets to the same timeline
        const alignedSensor = alignData(masterLabels, sensorData, 'time', 'temperature');
        const alignedForecast = alignData(masterLabels, forecastData, 'time', 'predicted_temp');
        // Bulletproof NOW line: Find the last index where we have real sensor data
        // This forces the red line to be the "bridge" between reality and prediction
        const nowIdx = alignedSensor.reduce((lastIdx, val, idx) => val !== null ? idx : lastIdx, 0);

        // 5. Clean the labels for the Chart (MM-DD HH:MM)
        const uiLabels = masterLabels.map(label => {
            const d = new Date(label);
            return d.toISOString().replace('T', ' ').substring(5, 16);
        });

        // 6. Prepare and Render Chart
        const ctx = document.getElementById('tempChart').getContext('2d');
        
        // Safety: Destroy old chart instance if it exists to avoid overlaps
        if (window.myChart) { window.myChart.destroy(); }

/////////////////////////////// CHART ///////////////////////////////////////////////////////

        // 6. Define custom plugin for "NOW" line
        const nowLinePlugin = {
            id: 'nowLinePlugin',
            beforeDraw: (chart) => {
                const {ctx, chartArea: {top, bottom}, scales: {x}} = chart;
                const xPos = x.getPixelForValue(nowIdx);
                
                // ctx.save();
                // ctx.strokeStyle = '#ff4757'; // Red line for "NOW"
                // ctx.lineWidth = 2;
                // ctx.setLineDash([5, 5]);
                // ctx.beginPath();
                // ctx.moveTo(xPos, top);
                // ctx.lineTo(xPos, bottom);
                // ctx.stroke();
                
                // // Add "NOW" text
                // ctx.fillStyle = '#ff4757';
                // ctx.font = 'bold 12px Arial';
                // ctx.fillText('NOW', xPos + 5, bottom - 10);
                // ctx.restore();
            }
        };

        window.myChart = new Chart(ctx, {
            type: 'line',
            plugins: [nowLinePlugin],
            data: {
                labels: uiLabels,
                datasets: [
                    {
                        label: 'Real Temp (°C)',
                        data: alignedSensor,
                        borderColor: '#78FF7F',
                        tension: 0.3, // Smoother lines
                        fill: false,
                        spanGaps: false // Line stops when no sensor data exists
                    },
                    {
                        label: 'Predicted Temp (°C)',
                        data: alignedForecast,
                        borderColor: '#57E0FF',
                        tension: 0.3,
                        borderDash: [5, 5],
                        fill: false,
                        spanGaps: true // Connects past predictions to future ones
                    }
                ]
            },
            options: {
                responsive: true,
                plugins: {
                    legend: { labels: { color: '#ecf0f1', font: { size: 14 } } }
                },
                scales: {
                    x: {
                        grid: { color: '#334155', borderColor: '#475569' },
                        ticks: { color: '#94a3b8' }
                    },
                    y: {
                        grid: { color: '#334155', borderColor: '#475569' },
                        ticks: { color: '#94a3b8' },
                        beginAtZero: false
                    }
                }
            }
        });
    }

    loadDashboard();
</script>