Add server-side rendered setup UI accessible via `heatwave web`. The dashboard is now re-rendered per request and includes a nav bar linking to the new /setup page. Setup provides full CRUD for profiles, rooms, devices, occupants, AC units (with room assignment), scenario toggles, and forecast fetching — all via POST/redirect/GET forms. - Add ShowNav field to DashboardData for conditional nav bar - Extract fetchForecastForProfile() for reuse by web handler - Create setup.html.tmpl with Tailwind-styled entity sections - Create web_handlers.go with 15 route handlers and flash cookies - Switch web.go from pre-rendered to per-request dashboard rendering - Graceful dashboard fallback when no forecast data exists
79 lines
1.7 KiB
Go
79 lines
1.7 KiB
Go
package store
|
|
|
|
import "fmt"
|
|
|
|
type Toggle struct {
|
|
ID int64
|
|
ProfileID int64
|
|
Name string
|
|
Active bool
|
|
ActivatedAt string
|
|
}
|
|
|
|
func (s *Store) SetToggle(profileID int64, name string, active bool) error {
|
|
activeInt := 0
|
|
activatedAt := ""
|
|
if active {
|
|
activeInt = 1
|
|
activatedAt = "datetime('now')"
|
|
}
|
|
|
|
if active {
|
|
_, err := s.db.Exec(
|
|
`INSERT INTO toggles (profile_id, name, active, activated_at) VALUES (?, ?, ?, datetime('now'))
|
|
ON CONFLICT DO NOTHING`,
|
|
profileID, name, activeInt,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("set toggle: %w", err)
|
|
}
|
|
_, err = s.db.Exec(
|
|
`UPDATE toggles SET active = ?, activated_at = datetime('now') WHERE profile_id = ? AND name = ?`,
|
|
activeInt, profileID, name,
|
|
)
|
|
return err
|
|
}
|
|
|
|
_ = activatedAt
|
|
_, err := s.db.Exec(
|
|
`UPDATE toggles SET active = 0, activated_at = NULL WHERE profile_id = ? AND name = ?`,
|
|
profileID, name,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *Store) GetToggles(profileID int64) ([]Toggle, error) {
|
|
rows, err := s.db.Query(
|
|
`SELECT id, profile_id, name, active, COALESCE(activated_at, '') FROM toggles WHERE profile_id = ? ORDER BY name`, profileID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var toggles []Toggle
|
|
for rows.Next() {
|
|
var t Toggle
|
|
var active int
|
|
if err := rows.Scan(&t.ID, &t.ProfileID, &t.Name, &active, &t.ActivatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
t.Active = active != 0
|
|
toggles = append(toggles, t)
|
|
}
|
|
return toggles, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetActiveToggleNames(profileID int64) (map[string]bool, error) {
|
|
toggles, err := s.GetToggles(profileID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m := make(map[string]bool)
|
|
for _, t := range toggles {
|
|
if t.Active {
|
|
m[t.Name] = true
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|