Files
HeatGuard/internal/store/ac.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

134 lines
3.5 KiB
Go

package store
import (
"database/sql"
"fmt"
"time"
)
type ACUnit struct {
ID int64
ProfileID int64
Name string
ACType string
CapacityBTU float64
HasDehumidify bool
EfficiencyEER float64
CreatedAt time.Time
}
func (s *Store) CreateACUnit(profileID int64, name, acType string, capacityBTU float64, hasDehumidify bool, efficiencyEER float64) (*ACUnit, error) {
if acType == "" {
acType = "portable"
}
if efficiencyEER == 0 {
efficiencyEER = 10.0
}
dehumid := 0
if hasDehumidify {
dehumid = 1
}
res, err := s.db.Exec(
`INSERT INTO ac_units (profile_id, name, ac_type, capacity_btu, has_dehumidify, efficiency_eer) VALUES (?, ?, ?, ?, ?, ?)`,
profileID, name, acType, capacityBTU, dehumid, efficiencyEER,
)
if err != nil {
return nil, fmt.Errorf("create ac unit: %w", err)
}
id, _ := res.LastInsertId()
return s.GetACUnit(id)
}
func (s *Store) GetACUnit(id int64) (*ACUnit, error) {
a := &ACUnit{}
var created string
var dehumid int
err := s.db.QueryRow(
`SELECT id, profile_id, name, ac_type, capacity_btu, has_dehumidify, efficiency_eer, created_at FROM ac_units WHERE id = ?`, id,
).Scan(&a.ID, &a.ProfileID, &a.Name, &a.ACType, &a.CapacityBTU, &dehumid, &a.EfficiencyEER, &created)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("ac unit not found: %d", id)
}
if err != nil {
return nil, fmt.Errorf("get ac unit: %w", err)
}
a.HasDehumidify = dehumid != 0
a.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", created)
return a, nil
}
func (s *Store) ListACUnits(profileID int64) ([]ACUnit, error) {
rows, err := s.db.Query(
`SELECT id, profile_id, name, ac_type, capacity_btu, has_dehumidify, efficiency_eer, created_at FROM ac_units WHERE profile_id = ? ORDER BY name`, profileID,
)
if err != nil {
return nil, err
}
defer rows.Close()
var units []ACUnit
for rows.Next() {
var a ACUnit
var created string
var dehumid int
if err := rows.Scan(&a.ID, &a.ProfileID, &a.Name, &a.ACType, &a.CapacityBTU, &dehumid, &a.EfficiencyEER, &created); err != nil {
return nil, err
}
a.HasDehumidify = dehumid != 0
a.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", created)
units = append(units, a)
}
return units, rows.Err()
}
func (s *Store) UpdateACUnit(id int64, field, value string) error {
allowed := map[string]bool{
"name": true, "ac_type": true, "capacity_btu": true,
"has_dehumidify": true, "efficiency_eer": true,
}
if !allowed[field] {
return fmt.Errorf("invalid field: %s", field)
}
_, err := s.db.Exec(
fmt.Sprintf(`UPDATE ac_units SET %s = ? WHERE id = ?`, field), value, id,
)
return err
}
func (s *Store) DeleteACUnit(id int64) error {
_, err := s.db.Exec(`DELETE FROM ac_units WHERE id = ?`, id)
return err
}
func (s *Store) AssignACToRoom(acID, roomID int64) error {
_, err := s.db.Exec(
`INSERT OR IGNORE INTO ac_room_assignments (ac_id, room_id) VALUES (?, ?)`,
acID, roomID,
)
return err
}
func (s *Store) UnassignACFromRoom(acID, roomID int64) error {
_, err := s.db.Exec(
`DELETE FROM ac_room_assignments WHERE ac_id = ? AND room_id = ?`,
acID, roomID,
)
return err
}
func (s *Store) GetACRoomAssignments(acID int64) ([]int64, error) {
rows, err := s.db.Query(`SELECT room_id FROM ac_room_assignments WHERE ac_id = ?`, acID)
if err != nil {
return nil, err
}
defer rows.Close()
var roomIDs []int64
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
return nil, err
}
roomIDs = append(roomIDs, id)
}
return roomIDs, rows.Err()
}