fix: added energy-management

This commit is contained in:
Willem Serruys
2026-08-19 18:35:47 +02:00
parent fece1603e7
commit 53fa112aa8
38 changed files with 6060 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
import { useEffect, useState, type FormEvent } from "react";
import { api, type ControlKind, type DeviceConfig } from "./api";
const emptyForm = {
entityId: "",
name: "",
kind: "switch" as ControlKind,
priority: 1,
minValue: 0,
maxValue: 1,
dwellSeconds: 300,
manualOverride: false,
onlyWhenSun: false,
fromTime: null as string | null,
toTime: null as string | null,
enableUrl: null as string | null,
disableUrl: null as string | null,
};
export function Devices() {
const [devices, setDevices] = useState<DeviceConfig[]>([]);
const [form, setForm] = useState(emptyForm);
const [error, setError] = useState<string | null>(null);
async function refresh() {
setDevices(await api.getDevices());
}
useEffect(() => {
refresh();
}, []);
async function handleCreate(e: FormEvent) {
e.preventDefault();
try {
await api.createDevice(form);
setForm(emptyForm);
await refresh();
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to create device");
}
}
async function toggleOverride(d: DeviceConfig) {
await api.updateDevice(d.id, { manualOverride: !d.manualOverride });
await refresh();
}
async function remove(d: DeviceConfig) {
if (!confirm(`Remove ${d.name}?`)) return;
await api.deleteDevice(d.id);
await refresh();
}
return (
<>
<div className="card">
<h2>Add device</h2>
<form onSubmit={handleCreate}>
<div className="form-grid">
<label>
Name
<input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
required
/>
</label>
<label>
Home Assistant entity ID
<input
value={form.entityId}
onChange={(e) => setForm({ ...form, entityId: e.target.value })}
placeholder="switch.ev_charger"
required
/>
</label>
<label>
Control type
<select
value={form.kind}
onChange={(e) =>
setForm({ ...form, kind: e.target.value as ControlKind })
}
>
<option value="switch">Switch (on/off)</option>
<option value="number">Number (set-point)</option>
</select>
</label>
<label>
Priority (lower = shed first)
<input
type="number"
value={form.priority}
onChange={(e) =>
setForm({ ...form, priority: Number(e.target.value) })
}
/>
</label>
{form.kind === "number" && (
<>
<label>
Min value (shed target)
<input
type="number"
value={form.minValue}
onChange={(e) =>
setForm({ ...form, minValue: Number(e.target.value) })
}
/>
</label>
<label>
Max value (normal)
<input
type="number"
value={form.maxValue}
onChange={(e) =>
setForm({ ...form, maxValue: Number(e.target.value) })
}
/>
</label>
</>
)}
<label>
Min dwell time (seconds)
<input
type="number"
value={form.dwellSeconds}
onChange={(e) =>
setForm({ ...form, dwellSeconds: Number(e.target.value) })
}
/>
</label>
<label>
Only enable if sun
<input
type="checkbox"
checked={form.onlyWhenSun}
onChange={(e) =>
setForm({ ...form, onlyWhenSun: e.target.checked })
}
/>
</label>
<label>
From (allowed window start)
<input
type="time"
value={form.fromTime ?? ""}
onChange={(e) =>
setForm({ ...form, fromTime: e.target.value || null })
}
/>
</label>
<label>
To (allowed window end)
<input
type="time"
value={form.toTime ?? ""}
onChange={(e) =>
setForm({ ...form, toTime: e.target.value || null })
}
/>
</label>
<label>
Custom enable endpoint (optional)
<input
type="url"
value={form.enableUrl ?? ""}
onChange={(e) =>
setForm({ ...form, enableUrl: e.target.value || null })
}
placeholder="https://example.local/device/on"
/>
</label>
<label>
Custom disable endpoint (optional)
<input
type="url"
value={form.disableUrl ?? ""}
onChange={(e) =>
setForm({ ...form, disableUrl: e.target.value || null })
}
placeholder="https://example.local/device/off"
/>
</label>
</div>
<p style={{ color: "var(--text-muted)", fontSize: 13 }}>
Set both custom endpoints to control this device over plain HTTP
(POST) instead of Home Assistant. The entity ID above is still used
as the device's unique key.
</p>
{error && (
<p style={{ color: "var(--status-critical)", fontSize: 13 }}>
{error}
</p>
)}
<button className="primary" type="submit">
Add device
</button>
</form>
</div>
<div className="card">
<h2>Configured devices</h2>
{devices.length === 0 ? (
<p style={{ color: "var(--text-muted)", fontSize: 13 }}>
No devices configured yet.
</p>
) : (
devices
.slice()
.sort((a, b) => a.priority - b.priority)
.map((d) => (
<div className="device-row" key={d.id}>
<div>
<div className="device-name">{d.name}</div>
<div className="device-meta">
{d.entityId} · {d.kind} · priority {d.priority} · dwell{" "}
{d.dwellSeconds}s{d.onlyWhenSun && " · sun only"}
{d.fromTime && d.toTime && ` · ${d.fromTime}${d.toTime}`}
{d.enableUrl && d.disableUrl && " · custom endpoints"}
<div>{d.enableUrl}</div>
<div>{d.disableUrl}</div>
</div>
</div>
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
{d.manualOverride && (
<span className="badge override">Manual</span>
)}
<button
className="secondary"
onClick={() => toggleOverride(d)}
>
{d.manualOverride ? "Resume auto" : "Manual override"}
</button>
<button className="secondary" onClick={() => remove(d)}>
Remove
</button>
</div>
</div>
))
)}
</div>
</>
);
}