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
543 lines
24 KiB
Cheetah
543 lines
24 KiB
Cheetah
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Heatwave Setup</title>
|
||
<style>{{.CSS}}</style>
|
||
</head>
|
||
<body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
||
|
||
<nav class="bg-white dark:bg-gray-800 shadow mb-4">
|
||
<div class="container mx-auto flex items-center gap-6 px-4 py-3">
|
||
<span class="font-bold text-lg">Heatwave</span>
|
||
<a href="/" class="text-gray-600 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400">Dashboard</a>
|
||
<a href="/setup" class="font-medium text-blue-600 dark:text-blue-400 underline">Setup</a>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="container mx-auto py-4 px-4 max-w-4xl">
|
||
|
||
{{if .Flash}}
|
||
<div class="mb-4 p-3 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded">
|
||
{{.Flash}}
|
||
</div>
|
||
{{end}}
|
||
|
||
<h1 class="text-3xl font-bold mb-2">Setup</h1>
|
||
<p class="text-gray-600 dark:text-gray-400 mb-6">
|
||
Active profile: <strong>{{if .Profile}}{{.Profile.Name}}{{else}}(none){{end}}</strong>
|
||
</p>
|
||
|
||
<div class="flex flex-wrap gap-2 mb-8 text-sm">
|
||
<a href="#profiles" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600">Profiles</a>
|
||
<a href="#rooms" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600">Rooms</a>
|
||
<a href="#devices" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600">Devices</a>
|
||
<a href="#occupants" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600">Occupants</a>
|
||
<a href="#ac" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600">AC Units</a>
|
||
<a href="#toggles" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600">Toggles</a>
|
||
<a href="#forecast" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600">Forecast</a>
|
||
</div>
|
||
|
||
{{template "profiles" .}}
|
||
{{template "rooms" .}}
|
||
{{template "devices" .}}
|
||
{{template "occupants" .}}
|
||
{{template "ac_units" .}}
|
||
{{template "toggles" .}}
|
||
{{template "forecast" .}}
|
||
|
||
<footer class="mt-8 text-center text-xs text-gray-500 dark:text-gray-500 py-4">
|
||
<p>Heatwave Autopilot</p>
|
||
</footer>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|
||
{{define "profiles"}}
|
||
<section id="profiles" class="mb-8">
|
||
<h2 class="text-xl font-semibold mb-3">Profiles</h2>
|
||
{{if .Profiles}}
|
||
<div class="overflow-hidden rounded-lg shadow dark:shadow-gray-700 mb-4">
|
||
<table>
|
||
<thead>
|
||
<tr class="dark:bg-gray-800">
|
||
<th>Name</th>
|
||
<th>Latitude</th>
|
||
<th>Longitude</th>
|
||
<th>Timezone</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range .Profiles}}
|
||
<tr class="bg-white dark:bg-gray-800">
|
||
<td class="font-medium">{{.Name}}</td>
|
||
<td>{{printf "%.4f" .Latitude}}</td>
|
||
<td>{{printf "%.4f" .Longitude}}</td>
|
||
<td>{{.Timezone}}</td>
|
||
<td>
|
||
<form method="POST" action="/setup/profiles/{{.ID}}/delete" class="inline">
|
||
<button type="submit" class="text-red-600 dark:text-red-400 hover:underline text-sm" onclick="return confirm('Delete profile?')">Delete</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}
|
||
<p class="text-gray-500 dark:text-gray-400 mb-4">No profiles yet.</p>
|
||
{{end}}
|
||
|
||
<form method="POST" action="/setup/profiles/add" class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow dark:shadow-gray-700">
|
||
<h3 class="font-medium mb-3">Add Profile</h3>
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Name</label>
|
||
<input type="text" name="name" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Latitude</label>
|
||
<input type="number" step="any" name="latitude" value="52.52" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Longitude</label>
|
||
<input type="number" step="any" name="longitude" value="13.405" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Timezone</label>
|
||
<input type="text" name="timezone" value="Europe/Berlin" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
</div>
|
||
<button type="submit" class="mt-3 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Add Profile</button>
|
||
</form>
|
||
</section>
|
||
{{end}}
|
||
|
||
{{define "rooms"}}
|
||
<section id="rooms" class="mb-8">
|
||
<h2 class="text-xl font-semibold mb-3">Rooms</h2>
|
||
{{if not .Profile}}
|
||
<p class="text-gray-500 dark:text-gray-400">Create a profile first.</p>
|
||
{{else}}
|
||
{{if .Rooms}}
|
||
<div class="overflow-hidden rounded-lg shadow dark:shadow-gray-700 mb-4">
|
||
<table>
|
||
<thead>
|
||
<tr class="dark:bg-gray-800">
|
||
<th>Name</th>
|
||
<th>Area (m²)</th>
|
||
<th>Floor</th>
|
||
<th>Orientation</th>
|
||
<th>Ceiling (m)</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range .Rooms}}
|
||
<tr class="bg-white dark:bg-gray-800">
|
||
<td class="font-medium">{{.Name}}</td>
|
||
<td>{{printf "%.1f" .AreaSqm}}</td>
|
||
<td>{{.Floor}}</td>
|
||
<td>{{.Orientation}}</td>
|
||
<td>{{printf "%.2f" .CeilingHeightM}}</td>
|
||
<td>
|
||
<form method="POST" action="/setup/rooms/{{.ID}}/delete" class="inline">
|
||
<button type="submit" class="text-red-600 dark:text-red-400 hover:underline text-sm" onclick="return confirm('Delete room?')">Delete</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}
|
||
<p class="text-gray-500 dark:text-gray-400 mb-4">No rooms yet.</p>
|
||
{{end}}
|
||
|
||
<form method="POST" action="/setup/rooms/add" class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow dark:shadow-gray-700">
|
||
<h3 class="font-medium mb-3">Add Room</h3>
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Name</label>
|
||
<input type="text" name="name" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Area (m²)</label>
|
||
<input type="number" step="any" name="area_sqm" value="20" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Floor</label>
|
||
<input type="number" name="floor" value="0" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Orientation</label>
|
||
<select name="orientation" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
<option value="south">South</option>
|
||
<option value="west">West</option>
|
||
<option value="east">East</option>
|
||
<option value="north">North</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Ceiling Height (m)</label>
|
||
<input type="number" step="any" name="ceiling_height_m" value="2.50" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Window Fraction</label>
|
||
<input type="number" step="any" name="window_fraction" value="0.30" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">SHGC</label>
|
||
<input type="number" step="any" name="shgc" value="0.60" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Shading Type</label>
|
||
<select name="shading_type" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
<option value="none">None</option>
|
||
<option value="blinds">Blinds</option>
|
||
<option value="shutters">Shutters</option>
|
||
<option value="awning">Awning</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Shading Factor</label>
|
||
<input type="number" step="any" name="shading_factor" value="1.0" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Ventilation</label>
|
||
<select name="ventilation" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
<option value="natural">Natural</option>
|
||
<option value="mechanical">Mechanical</option>
|
||
<option value="sealed">Sealed</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Ventilation ACH</label>
|
||
<input type="number" step="any" name="ventilation_ach" value="1.5" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Insulation</label>
|
||
<select name="insulation" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
<option value="standard">Standard</option>
|
||
<option value="poor">Poor</option>
|
||
<option value="good">Good</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<button type="submit" class="mt-3 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Add Room</button>
|
||
</form>
|
||
{{end}}
|
||
</section>
|
||
{{end}}
|
||
|
||
{{define "devices"}}
|
||
<section id="devices" class="mb-8">
|
||
<h2 class="text-xl font-semibold mb-3">Devices</h2>
|
||
{{if not .Profile}}
|
||
<p class="text-gray-500 dark:text-gray-400">Create a profile first.</p>
|
||
{{else}}
|
||
{{if .Devices}}
|
||
<div class="overflow-hidden rounded-lg shadow dark:shadow-gray-700 mb-4">
|
||
<table>
|
||
<thead>
|
||
<tr class="dark:bg-gray-800">
|
||
<th>Name</th>
|
||
<th>Type</th>
|
||
<th>Room</th>
|
||
<th>Idle (W)</th>
|
||
<th>Typical (W)</th>
|
||
<th>Peak (W)</th>
|
||
<th>Duty</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range .Devices}}
|
||
<tr class="bg-white dark:bg-gray-800">
|
||
<td class="font-medium">{{.Name}}</td>
|
||
<td>{{.DeviceType}}</td>
|
||
<td>{{.RoomID}}</td>
|
||
<td>{{printf "%.0f" .WattsIdle}}</td>
|
||
<td>{{printf "%.0f" .WattsTypical}}</td>
|
||
<td>{{printf "%.0f" .WattsPeak}}</td>
|
||
<td>{{printf "%.0f%%" (mul .DutyCycle 100)}}</td>
|
||
<td>
|
||
<form method="POST" action="/setup/devices/{{.ID}}/delete" class="inline">
|
||
<button type="submit" class="text-red-600 dark:text-red-400 hover:underline text-sm" onclick="return confirm('Delete device?')">Delete</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}
|
||
<p class="text-gray-500 dark:text-gray-400 mb-4">No devices yet.</p>
|
||
{{end}}
|
||
|
||
{{if .Rooms}}
|
||
<form method="POST" action="/setup/devices/add" class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow dark:shadow-gray-700">
|
||
<h3 class="font-medium mb-3">Add Device</h3>
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Room</label>
|
||
<select name="room_id" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
{{range .Rooms}}<option value="{{.ID}}">{{.Name}}</option>{{end}}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Name</label>
|
||
<input type="text" name="name" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Type</label>
|
||
<select name="device_type" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
<option value="computer">Computer</option>
|
||
<option value="monitor">Monitor</option>
|
||
<option value="tv">TV</option>
|
||
<option value="console">Console</option>
|
||
<option value="lighting">Lighting</option>
|
||
<option value="appliance">Appliance</option>
|
||
<option value="other">Other</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Watts Idle</label>
|
||
<input type="number" step="any" name="watts_idle" value="10" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Watts Typical</label>
|
||
<input type="number" step="any" name="watts_typical" value="80" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Watts Peak</label>
|
||
<input type="number" step="any" name="watts_peak" value="200" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Duty Cycle (0–1)</label>
|
||
<input type="number" step="any" name="duty_cycle" value="0.5" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
</div>
|
||
<button type="submit" class="mt-3 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Add Device</button>
|
||
</form>
|
||
{{else}}
|
||
<p class="text-sm text-gray-500 dark:text-gray-400">Add a room first to create devices.</p>
|
||
{{end}}
|
||
{{end}}
|
||
</section>
|
||
{{end}}
|
||
|
||
{{define "occupants"}}
|
||
<section id="occupants" class="mb-8">
|
||
<h2 class="text-xl font-semibold mb-3">Occupants</h2>
|
||
{{if not .Profile}}
|
||
<p class="text-gray-500 dark:text-gray-400">Create a profile first.</p>
|
||
{{else}}
|
||
{{if .Occupants}}
|
||
<div class="overflow-hidden rounded-lg shadow dark:shadow-gray-700 mb-4">
|
||
<table>
|
||
<thead>
|
||
<tr class="dark:bg-gray-800">
|
||
<th>Room</th>
|
||
<th>Count</th>
|
||
<th>Activity</th>
|
||
<th>Vulnerable</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range .Occupants}}
|
||
<tr class="bg-white dark:bg-gray-800">
|
||
<td>{{.RoomID}}</td>
|
||
<td>{{.Count}}</td>
|
||
<td>{{.ActivityLevel}}</td>
|
||
<td>{{if .Vulnerable}}Yes{{else}}No{{end}}</td>
|
||
<td>
|
||
<form method="POST" action="/setup/occupants/{{.ID}}/delete" class="inline">
|
||
<button type="submit" class="text-red-600 dark:text-red-400 hover:underline text-sm" onclick="return confirm('Delete occupant?')">Delete</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}
|
||
<p class="text-gray-500 dark:text-gray-400 mb-4">No occupants yet.</p>
|
||
{{end}}
|
||
|
||
{{if .Rooms}}
|
||
<form method="POST" action="/setup/occupants/add" class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow dark:shadow-gray-700">
|
||
<h3 class="font-medium mb-3">Add Occupant</h3>
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Room</label>
|
||
<select name="room_id" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
{{range .Rooms}}<option value="{{.ID}}">{{.Name}}</option>{{end}}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Count</label>
|
||
<input type="number" name="count" value="1" min="1" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Activity Level</label>
|
||
<select name="activity_level" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
<option value="sedentary">Sedentary</option>
|
||
<option value="light">Light</option>
|
||
<option value="moderate">Moderate</option>
|
||
<option value="active">Active</option>
|
||
</select>
|
||
</div>
|
||
<div class="flex items-end">
|
||
<label class="flex items-center gap-2 pb-2">
|
||
<input type="checkbox" name="vulnerable" value="true" class="rounded border-gray-300 dark:border-gray-600">
|
||
<span class="text-sm text-gray-600 dark:text-gray-400">Vulnerable</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<button type="submit" class="mt-3 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Add Occupant</button>
|
||
</form>
|
||
{{else}}
|
||
<p class="text-sm text-gray-500 dark:text-gray-400">Add a room first to create occupants.</p>
|
||
{{end}}
|
||
{{end}}
|
||
</section>
|
||
{{end}}
|
||
|
||
{{define "ac_units"}}
|
||
<section id="ac" class="mb-8">
|
||
<h2 class="text-xl font-semibold mb-3">AC Units</h2>
|
||
{{if not .Profile}}
|
||
<p class="text-gray-500 dark:text-gray-400">Create a profile first.</p>
|
||
{{else}}
|
||
{{if .ACUnits}}
|
||
<div class="space-y-3 mb-4">
|
||
{{range .ACUnits}}
|
||
{{$acID := .ID}}
|
||
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow dark:shadow-gray-700">
|
||
<div class="flex justify-between items-start mb-2">
|
||
<div>
|
||
<span class="font-medium">{{.Name}}</span>
|
||
<span class="text-sm text-gray-500 dark:text-gray-400 ml-2">{{.ACType}} — {{printf "%.0f" .CapacityBTU}} BTU/h — EER {{printf "%.1f" .EfficiencyEER}}</span>
|
||
{{if .HasDehumidify}}<span class="text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 px-2 py-0.5 rounded ml-1">Dehumidify</span>{{end}}
|
||
</div>
|
||
<form method="POST" action="/setup/ac/{{.ID}}/delete" class="inline">
|
||
<button type="submit" class="text-red-600 dark:text-red-400 hover:underline text-sm" onclick="return confirm('Delete AC unit?')">Delete</button>
|
||
</form>
|
||
</div>
|
||
{{if $.Rooms}}
|
||
<form method="POST" action="/setup/ac/{{.ID}}/assign" class="flex flex-wrap items-center gap-2 text-sm">
|
||
<span class="text-gray-600 dark:text-gray-400">Assign to:</span>
|
||
<select name="room_id" class="px-2 py-1 border dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-sm">
|
||
{{range $.Rooms}}<option value="{{.ID}}">{{.Name}}</option>{{end}}
|
||
</select>
|
||
<button type="submit" class="px-2 py-1 bg-green-600 text-white rounded text-sm hover:bg-green-700">Assign</button>
|
||
</form>
|
||
{{if .AssignedRoomIDs}}
|
||
<div class="mt-2 flex flex-wrap gap-1">
|
||
{{range .AssignedRoomIDs}}
|
||
<form method="POST" action="/setup/ac/{{$acID}}/unassign" class="inline">
|
||
<input type="hidden" name="room_id" value="{{.RoomID}}">
|
||
<button type="submit" class="inline-flex items-center gap-1 px-2 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-sm">
|
||
{{.RoomName}} <span class="text-red-500">×</span>
|
||
</button>
|
||
</form>
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
{{else}}
|
||
<p class="text-gray-500 dark:text-gray-400 mb-4">No AC units yet.</p>
|
||
{{end}}
|
||
|
||
<form method="POST" action="/setup/ac/add" class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow dark:shadow-gray-700">
|
||
<h3 class="font-medium mb-3">Add AC Unit</h3>
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Name</label>
|
||
<input type="text" name="name" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Type</label>
|
||
<select name="ac_type" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
<option value="portable">Portable</option>
|
||
<option value="split">Split</option>
|
||
<option value="window">Window</option>
|
||
<option value="central">Central</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Capacity (BTU/h)</label>
|
||
<input type="number" step="any" name="capacity_btu" value="12000" required class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm text-gray-600 dark:text-gray-400 mb-1">Efficiency (EER)</label>
|
||
<input type="number" step="any" name="efficiency_eer" value="10.0" class="w-full px-3 py-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700">
|
||
</div>
|
||
<div class="flex items-end">
|
||
<label class="flex items-center gap-2 pb-2">
|
||
<input type="checkbox" name="has_dehumidify" value="true" class="rounded border-gray-300 dark:border-gray-600">
|
||
<span class="text-sm text-gray-600 dark:text-gray-400">Has Dehumidify</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<button type="submit" class="mt-3 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Add AC Unit</button>
|
||
</form>
|
||
{{end}}
|
||
</section>
|
||
{{end}}
|
||
|
||
{{define "toggles"}}
|
||
<section id="toggles" class="mb-8">
|
||
<h2 class="text-xl font-semibold mb-3">Scenario Toggles</h2>
|
||
{{if not .Profile}}
|
||
<p class="text-gray-500 dark:text-gray-400">Create a profile first.</p>
|
||
{{else}}
|
||
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow dark:shadow-gray-700">
|
||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||
{{range $name, $active := .Toggles}}
|
||
<form method="POST" action="/setup/toggles/set" class="flex items-center justify-between p-2 rounded border dark:border-gray-600">
|
||
<input type="hidden" name="name" value="{{$name}}">
|
||
<input type="hidden" name="active" value="{{if $active}}false{{else}}true{{end}}">
|
||
<span class="font-medium">{{$name}}</span>
|
||
<button type="submit" class="px-3 py-1 rounded text-sm text-white {{if $active}}bg-green-600 hover:bg-red-600{{else}}bg-gray-400 hover:bg-green-600{{end}}">
|
||
{{if $active}}ON{{else}}OFF{{end}}
|
||
</button>
|
||
</form>
|
||
{{end}}
|
||
<form method="POST" action="/setup/toggles/set" class="flex items-center gap-2 p-2 rounded border dark:border-gray-600">
|
||
<input type="text" name="name" placeholder="new toggle" required class="flex-1 px-2 py-1 border dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-sm">
|
||
<input type="hidden" name="active" value="true">
|
||
<button type="submit" class="px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700">Add</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
</section>
|
||
{{end}}
|
||
|
||
{{define "forecast"}}
|
||
<section id="forecast" class="mb-8">
|
||
<h2 class="text-xl font-semibold mb-3">Forecast</h2>
|
||
{{if not .Profile}}
|
||
<p class="text-gray-500 dark:text-gray-400">Create a profile first.</p>
|
||
{{else}}
|
||
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow dark:shadow-gray-700">
|
||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||
Last fetched: <strong>{{if .LastFetch}}{{.LastFetch}}{{else}}never{{end}}</strong>
|
||
</p>
|
||
<form method="POST" action="/setup/forecast/fetch">
|
||
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Fetch Forecast Now</button>
|
||
</form>
|
||
</div>
|
||
{{end}}
|
||
</section>
|
||
{{end}}
|