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
134 lines
3.5 KiB
Go
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()
|
|
}
|