Files
HeatGuard/internal/store/toggle.go
vikingowl 1c9db02334 feat: add web UI with full CRUD setup page
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
2026-02-09 10:39:00 +01:00

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
}