Show weather compensation summary banner on the Dashboard
New lib/ac-template.js factors the "which occupied AC template applies right now" resolution out of scheduler.js (dedupes the tier-pick loop that used to live separately in scheduler.js and status.js) into a shared resolveOccupiedAcTier()/resolveGlobalOccupiedAcTemplate() pair. GET /api/status now returns active_ac_template alongside weather_peak_c. Dashboard shows a small banner above the zone grid: "Forecast high today: XC" + "Aircon default: ON, 20C, High fan — Warm tier" — so staff don't have to visit Settings to see what today's weather tier resolved to. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
e7a8153990
commit
5fc576aabd
6 changed files with 88 additions and 32 deletions
|
|
@ -1,24 +1,32 @@
|
|||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { WifiOff } from 'lucide-react'
|
||||
import { fetchStatus } from '../api'
|
||||
import type { ZoneStatus } from '../types'
|
||||
import type { ZoneStatus, ActiveAcTemplate } from '../types'
|
||||
import ZoneCard from '../components/ZoneCard'
|
||||
import ZoneDetailModal from '../components/ZoneDetailModal'
|
||||
|
||||
const POLL_MS = 30000
|
||||
|
||||
function cap(s: string) {
|
||||
return s[0].toUpperCase() + s.slice(1)
|
||||
}
|
||||
|
||||
export default function Dashboard() {
|
||||
const [zones, setZones] = useState<ZoneStatus[]>([])
|
||||
const [mqttConnected, setMqttConnected] = useState(true)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [selected, setSelected] = useState<ZoneStatus | null>(null)
|
||||
const [weatherPeakC, setWeatherPeakC] = useState<number | null>(null)
|
||||
const [activeAcTemplate, setActiveAcTemplate] = useState<ActiveAcTemplate | null>(null)
|
||||
|
||||
const load = useCallback(() => {
|
||||
fetchStatus()
|
||||
.then(res => {
|
||||
setZones(res.zones)
|
||||
setMqttConnected(res.mqtt_connected)
|
||||
setWeatherPeakC(res.weather_peak_c)
|
||||
setActiveAcTemplate(res.active_ac_template)
|
||||
setError('')
|
||||
})
|
||||
.catch(e => setError(e instanceof Error ? e.message : 'Failed to load status'))
|
||||
|
|
@ -42,6 +50,23 @@ export default function Dashboard() {
|
|||
<h1>Dashboard</h1>
|
||||
</div>
|
||||
|
||||
{weatherPeakC != null && activeAcTemplate && (
|
||||
<div className="card" style={{
|
||||
display: 'flex', alignItems: 'center', gap: 20, flexWrap: 'wrap',
|
||||
marginBottom: 16, fontSize: 13,
|
||||
}}>
|
||||
<div>
|
||||
Forecast high today: <strong style={{ color: 'var(--gold)' }}>{weatherPeakC}°C</strong>
|
||||
</div>
|
||||
<div className="muted">
|
||||
Aircon default: <strong style={{ color: 'var(--text-dark)' }}>{activeAcTemplate.power === 'on' ? 'ON' : 'OFF'}</strong>
|
||||
{activeAcTemplate.setpoint != null && <>, {activeAcTemplate.setpoint}°C</>}
|
||||
{activeAcTemplate.fan_speed && <>, {cap(activeAcTemplate.fan_speed)} fan</>}
|
||||
{activeAcTemplate.tierApplied && <> — <strong>{cap(activeAcTemplate.tierApplied)}</strong> tier</>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
{!mqttConnected && (
|
||||
<div className="error-banner" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue